1 Commits

Author SHA1 Message Date
5b7f7bb69f 도적 불가침 기능 추가 2026-06-21 15:28:27 +09:00
11 changed files with 65 additions and 10 deletions

File diff suppressed because one or more lines are too long

View File

@@ -1316,8 +1316,8 @@
"class": "bandit", "class": "bandit",
"rarity": "legend", "rarity": "legend",
"desc": "불가침을 2 얻습니다. 내 턴 종료 시 민첩을 1 잃습니다.", "desc": "불가침을 2 얻습니다. 내 턴 종료 시 민첩을 1 잃습니다.",
"powerEffect": "blockPerTurn", "intangible": 2,
"value": 8, "endTurnDexLoss": 1,
"image": "0946f69d84464df29b24b94c744c868d" "image": "0946f69d84464df29b24b94c744c868d"
} }
}, },

5
docs/intangible.md Normal file
View File

@@ -0,0 +1,5 @@
# 불가침
`intangible`는 카드를 사용할 때 플레이어에게 불가침 수치를 부여하는 공용 필드입니다. 불가침이 남아 있는 동안 받는 피해는 1로 줄어들고, 턴이 끝날 때 1씩 감소합니다.
`endTurnDexLoss`는 그 카드가 활성화된 동안 매 턴 종료 시 민첩을 잃게 만드는 공용 필드입니다. `WraithForm` 같은 카드가 이 조합을 사용합니다.

View File

@@ -148,7 +148,7 @@ export function simulateCombat(data, rng, stats) {
const exhaust = []; const exhaust = [];
let hand = []; let hand = [];
let pHp = PLAYER_HP, pBlock = 0; let pHp = PLAYER_HP, pBlock = 0;
let pStr = 0, pDex = 0, pThorns = 0, pWeak = 0, pVuln = 0; let pStr = 0, pDex = 0, pThorns = 0, pWeak = 0, pVuln = 0, pIntangible = 0;
let blockGainMultiplier = 1; let blockGainMultiplier = 1;
let handCostZeroThisTurn = false; let handCostZeroThisTurn = false;
let drawDisabledThisTurn = false; let drawDisabledThisTurn = false;
@@ -338,6 +338,7 @@ export function simulateCombat(data, rng, stats) {
if (c.selfVuln) pVuln += c.selfVuln; if (c.selfVuln) pVuln += c.selfVuln;
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP); if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
if (c.gainEnergy) energy += c.gainEnergy; if (c.gainEnergy) energy += c.gainEnergy;
if (c.intangible) pIntangible += c.intangible;
queueNextTurnEffects(c); queueNextTurnEffects(c);
let drawnCards = []; let drawnCards = [];
if (c.draw) drawnCards = drawnCards.concat(draw(c.draw)); if (c.draw) drawnCards = drawnCards.concat(draw(c.draw));
@@ -476,6 +477,14 @@ export function simulateCombat(data, rng, stats) {
else discard.push(hid); else discard.push(hid);
} }
hand = kept; hand = kept;
for (const pid of powers) {
const pc = cards[pid];
if (pc?.endTurnDexLoss) {
pDex -= pc.endTurnDexLoss;
if (pDex < 0) pDex = 0;
}
}
if (pIntangible > 0) pIntangible--;
if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 }; if (pHp <= 0) return { win: false, turns, playerHpRemaining: 0 };
// 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전) // 플레이어 디버프 감소 — Lua EndPlayerTurn 동기화 (적 행동 전)
if (pWeak > 0) pWeak--; if (pWeak > 0) pWeak--;
@@ -495,7 +504,9 @@ export function simulateCombat(data, rng, stats) {
if (it.kind === 'Attack') { if (it.kind === 'Attack') {
const atk = calcAttack(it.value, m.str, m.weak, pVuln); const atk = calcAttack(it.value, m.str, m.weak, pVuln);
const beforeHp = pHp; const beforeHp = pHp;
const r = applyDamage(pHp, pBlock, atk); pHp = r.hp; pBlock = r.block; let incoming = atk;
if (pIntangible > 0 && incoming > 1) incoming = 1;
const r = applyDamage(pHp, pBlock, incoming); pHp = r.hp; pBlock = r.block;
if (beforeHp > pHp && pThorns > 0) { if (beforeHp > pHp && pThorns > 0) {
m.hp -= pThorns; m.hp -= pThorns;
if (m.hp <= 0) m.alive = false; if (m.hp <= 0) m.alive = false;

View File

@@ -823,6 +823,19 @@ test("simulateCombat: cardPlayedRandomDamage hits a random enemy on card play",
assert.equal(r.win, true); assert.equal(r.win, true);
}); });
test("simulateCombat: intangible cards reduce incoming damage and persist across turns", () => {
const data = {
cards: {
Wraith: { name: "WraithForm", cost: 3, kind: "Power", intangible: 2, endTurnDexLoss: 1, innate: true },
Strike: { name: "Strike", cost: 1, kind: "Attack", damage: 1 },
},
starterDeck: ["Wraith", "Strike"],
monsters: [{ name: "Dummy", maxHp: 1, intents: [{ kind: "Attack", value: 10 }] }],
};
const r = simulateCombat(data, () => 0.999999);
assert.equal(r.win, true);
});
test("simulateCombat: useAllEnergy skewer consumes all energy for damage", () => { test("simulateCombat: useAllEnergy skewer consumes all energy for damage", () => {
const data = { const data = {
cards: { cards: {

View File

@@ -415,6 +415,9 @@ if self.PlayerBlock > 0 then
self.PlayerBlock = self.PlayerBlock - absorbed self.PlayerBlock = self.PlayerBlock - absorbed
dmg = dmg - absorbed dmg = dmg - absorbed
end end
if dmg > 0 and self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 and dmg > 1 then
dmg = 1
end
if dmg > 0 then if dmg > 0 then
self.PlayerHp = self.PlayerHp - dmg self.PlayerHp = self.PlayerHp - dmg
local reflect = self.PlayerThorns or 0 local reflect = self.PlayerThorns or 0

View File

@@ -426,6 +426,19 @@ for i = 1, #self.Hand do
\tend \tend
end end
self.Hand = kept self.Hand = kept
if self.PlayerPowers ~= nil then
for i = 1, #self.PlayerPowers do
local pc = self.Cards[self.PlayerPowers[i]]
if pc ~= nil and pc.endTurnDexLoss ~= nil and pc.endTurnDexLoss > 0 then
self.PlayerDex = self.PlayerDex - pc.endTurnDexLoss
if self.PlayerDex < 0 then self.PlayerDex = 0 end
end
end
end
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
self.PlayerIntangible = self.PlayerIntangible - 1
if self.PlayerIntangible < 0 then self.PlayerIntangible = 0 end
end
if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end if self.PlayerWeak > 0 then self.PlayerWeak = self.PlayerWeak - 1 end
if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end if self.PlayerVuln > 0 then self.PlayerVuln = self.PlayerVuln - 1 end
self:RenderHand(false) self:RenderHand(false)

View File

@@ -459,6 +459,9 @@ end
if c.gainEnergy ~= nil and c.gainEnergy ~= 0 then if c.gainEnergy ~= nil and c.gainEnergy ~= 0 then
self.Energy = self.Energy + c.gainEnergy self.Energy = self.Energy + c.gainEnergy
end end
if c.intangible ~= nil and c.intangible > 0 then
self.PlayerIntangible = (self.PlayerIntangible or 0) + c.intangible
end
self:QueueNextTurnEffects(c) self:QueueNextTurnEffects(c)
if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil or c.xWeakPerEnergy ~= nil then if c.weak ~= nil or c.vuln ~= nil or c.poison ~= nil or c.xWeakPerEnergy ~= nil then
local tm = self.Monsters[self.TargetIndex] local tm = self.Monsters[self.TargetIndex]

View File

@@ -68,6 +68,10 @@ self:SetHpBar("/ui/RunUIGroup/CombatHud/PlayerPanel/HpBarFill", self.PlayerHp, s
self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0) self:SetEntityEnabled("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge", self.PlayerBlock > 0)
self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock)) self:SetText("/ui/RunUIGroup/CombatHud/PlayerPanel/BlockBadge/Value", string.format("%d", self.PlayerBlock))
local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0) local pb = self:BuffsLabel(self.PlayerStr, self.PlayerWeak, self.PlayerVuln, 0)
if self.PlayerIntangible ~= nil and self.PlayerIntangible > 0 then
if pb ~= "" then pb = pb .. " " end
pb = pb .. "불가침" .. tostring(self.PlayerIntangible)
end
if self.PlayerDex ~= nil and self.PlayerDex > 0 then if self.PlayerDex ~= nil and self.PlayerDex > 0 then
if pb ~= "" then pb = pb .. " " end if pb ~= "" then pb = pb .. " " end
pb = pb .. "민첩+" .. tostring(self.PlayerDex) pb = pb .. "민첩+" .. tostring(self.PlayerDex)

View File

@@ -78,6 +78,7 @@ self.PlayerDex = 0
self.PlayerThorns = 0 self.PlayerThorns = 0
self.PlayerWeak = 0 self.PlayerWeak = 0
self.PlayerVuln = 0 self.PlayerVuln = 0
self.PlayerIntangible = 0
self.PlayerPowers = {} self.PlayerPowers = {}
self.FightAttackCount = 0 self.FightAttackCount = 0
self.TurnAttackCardsPlayed = 0 self.TurnAttackCardsPlayed = 0

View File

@@ -166,6 +166,8 @@ function luaCardsTable(cards) {
if (c.damagePerTurn != null) fields.push(`damagePerTurn = ${c.damagePerTurn}`); if (c.damagePerTurn != null) fields.push(`damagePerTurn = ${c.damagePerTurn}`);
if (c.cardPlayedDamage != null) fields.push(`cardPlayedDamage = ${c.cardPlayedDamage}`); if (c.cardPlayedDamage != null) fields.push(`cardPlayedDamage = ${c.cardPlayedDamage}`);
if (c.cardPlayedRandomDamage != null) fields.push(`cardPlayedRandomDamage = ${c.cardPlayedRandomDamage}`); if (c.cardPlayedRandomDamage != null) fields.push(`cardPlayedRandomDamage = ${c.cardPlayedRandomDamage}`);
if (c.intangible != null) fields.push(`intangible = ${c.intangible}`);
if (c.endTurnDexLoss != null) fields.push(`endTurnDexLoss = ${c.endTurnDexLoss}`);
if (c.poisonPerTurn != null) fields.push(`poisonPerTurn = ${c.poisonPerTurn}`); if (c.poisonPerTurn != null) fields.push(`poisonPerTurn = ${c.poisonPerTurn}`);
if (c.attackPoison != null) fields.push(`attackPoison = ${c.attackPoison}`); if (c.attackPoison != null) fields.push(`attackPoison = ${c.attackPoison}`);
if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${c.otherHandAtLeast}`); if (c.otherHandAtLeast != null) fields.push(`otherHandAtLeast = ${c.otherHandAtLeast}`);