feat(bandit): implement sly discard trigger

This commit is contained in:
2026-06-15 00:06:53 +09:00
parent 709e6f8f99
commit 05a06644cf
5 changed files with 345 additions and 125 deletions

View File

@@ -124,10 +124,81 @@ export function simulateCombat(data, rng, stats) {
if (drawPile.length === 0) break;
const card = drawPile.pop();
// 손패 10장 상한 — 초과 드로는 자동 버림 (Lua DrawCards 동기화)
if (hand.length >= 10) discard.push(card); else hand.push(card);
if (hand.length >= 10) {
discard.push(card);
triggerSly(card);
} else hand.push(card);
}
}
const aliveList = () => mob.filter((m) => m.alive);
function resolveCardEffects(id, c, costSpent, recordStats = true) {
const alive = aliveList();
let dmg = 0;
if (c.kind === 'Attack') {
if (alive.length && c.damage) {
const target = chooseTarget(alive, calcAttack(c.damage || 0, pStr, pWeak, 0));
if (c.weak) target.weak += c.weak;
if (c.vuln) target.vuln += c.vuln;
const hitN = c.hits || 1;
let totalNv = 0;
for (let h = 0; h < hitN; h++) totalNv += calcAttack(c.damage || 0, pStr, pWeak, 0);
dmg = totalNv;
if (c.aoe === true) {
for (const m2 of aliveList()) {
const d2 = m2.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
const r2 = applyDamage(m2.hp, m2.block, d2);
m2.hp = r2.hp; m2.block = r2.block;
if (m2.hp <= 0) m2.alive = false;
}
} else {
dmg = target.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
if (c.pierce === true) {
target.hp -= dmg;
if (target.hp < 0) target.hp = 0;
} else {
const r = applyDamage(target.hp, target.block, dmg);
target.hp = r.hp; target.block = r.block;
}
if (target.hp <= 0) target.alive = false;
}
}
if (c.block) pBlock += c.block;
} else if (c.kind === 'Power') {
if (c.powerEffect && recordStats) powers.push(id);
} else {
pBlock += c.block || 0;
if ((c.weak || c.vuln || c.poison) && alive.length) {
const target = chooseTarget(alive, 0);
if (c.weak) target.weak += c.weak;
if (c.vuln) target.vuln += c.vuln;
if (c.poison) target.poison += c.poison;
}
}
if (c.strength) pStr += c.strength;
if (c.selfVuln) pVuln += c.selfVuln;
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
if (c.draw) draw(c.draw);
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, c.block || 0);
}
function triggerSly(id) {
const c = cards[id];
if (!c?.sly) return;
resolveCardEffects(id, c, 0, false);
}
function discardHandCard(idx, trigger = true) {
const [id] = hand.splice(idx, 1);
if (!id) return;
discard.push(id);
if (trigger) triggerSly(id);
}
function applyDiscardEffects(c) {
if (c.discardAll) {
while (hand.length) discardHandCard(hand.length - 1, true);
} else if (c.discard) {
const n = Math.min(c.discard, hand.length);
for (let i = 0; i < n; i++) discardHandCard(hand.length - 1, true);
}
}
while (turns < MAX_TURNS) {
turns++;
@@ -149,59 +220,10 @@ export function simulateCombat(data, rng, stats) {
if (idx < 0) break;
const id = hand[idx], c = cards[id];
energy -= c.cost;
if (c.kind === 'Attack') {
const target = chooseTarget(alive, calcAttack(c.damage || 0, pStr, pWeak, 0));
// 카드 디버프는 피해보다 먼저 적용 — Lua PlayCard(즉시 부여) + 지연 데미지(0.35s) 동기화
if (c.weak) target.weak += c.weak;
if (c.vuln) target.vuln += c.vuln;
// 다단히트: 타격마다 힘·약화 적용 합산, 취약은 합산값에 1회 (Lua 동기화)
const hitN = c.hits || 1;
let totalNv = 0;
for (let h = 0; h < hitN; h++) totalNv += calcAttack(c.damage || 0, pStr, pWeak, 0);
let dmg = totalNv; // 통계 보고용 (aoe는 1대상 기준)
if (c.aoe === true) {
// 전체 공격 — 대상마다 취약/방어 개별 적용 (Lua PlayAoeFx 동기화)
for (const m2 of aliveList()) {
const d2 = m2.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
const r2 = applyDamage(m2.hp, m2.block, d2);
m2.hp = r2.hp; m2.block = r2.block;
if (m2.hp <= 0) m2.alive = false;
}
} else {
dmg = target.vuln > 0 ? Math.floor(totalNv * 1.5) : totalNv;
if (c.pierce === true) {
target.hp -= dmg; // 방어 무시
if (target.hp < 0) target.hp = 0;
} else {
const r = applyDamage(target.hp, target.block, dmg);
target.hp = r.hp; target.block = r.block;
}
if (target.hp <= 0) target.alive = false;
}
if (c.block) pBlock += c.block;
if (c.strength) pStr += c.strength;
if (c.selfVuln) pVuln += c.selfVuln;
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
if (stats) stats[id] = bump(stats[id], c.cost, dmg, c.block || 0);
} else if (c.kind === 'Power') {
if (c.powerEffect) powers.push(id);
if (stats) stats[id] = bump(stats[id], c.cost, 0, 0);
} else {
pBlock += c.block || 0;
if (c.strength) pStr += c.strength;
if (c.selfVuln) pVuln += c.selfVuln;
if (c.heal) pHp = Math.min(pHp + c.heal, PLAYER_HP);
if (c.weak || c.vuln || c.poison) {
const target = chooseTarget(alive, 0);
if (c.weak) target.weak += c.weak;
if (c.vuln) target.vuln += c.vuln;
if (c.poison) target.poison += c.poison;
}
if (stats) stats[id] = bump(stats[id], c.cost, 0, c.block || 0);
}
resolveCardEffects(id, c, c.cost);
hand.splice(idx, 1);
if (c.kind !== 'Power') discard.push(id); // 파워는 소멸 — Lua 동기화
if (c.draw) draw(c.draw);
if (c.kind !== 'Power') discard.push(id);
applyDiscardEffects(c);
if (aliveList().length === 0) return { win: true, turns, playerHpRemaining: pHp };
}
// 화상(endTurnDamage) — 손패에 있으면 턴 종료 시 피해 (Lua EndPlayerTurn 동기화)