This reverts commit 17200d47ec, reversing
changes made to 95d6155086.
This commit is contained in:
2026-06-30 00:49:30 +09:00
parent 17200d47ec
commit 5e2fd5db22
7 changed files with 58 additions and 129 deletions

View File

@@ -55,8 +55,7 @@ export function calcAttack(base, str, weak, vulnOnTarget) {
}
export function calcEnemyAttack(base, str, weak, vulnOnTarget, strengthLoss = 0) {
// Lua EnemyActStep 동기화: 힘 손실은 (value+str) 전체에서 차감(음수 힘 허용), 최종 calcAttack이 0 클램프.
return calcAttack(base, str - strengthLoss, weak, vulnOnTarget);
return calcAttack(base, Math.max(0, str - strengthLoss), weak, vulnOnTarget);
}
// 방어 우선 차감 후 hp 적용 → { hp, block }
@@ -343,7 +342,7 @@ export function simulateCombat(data, rng, stats) {
if (c.damagePerDiscardedThisTurn) base += turnDiscardedCards * c.damagePerDiscardedThisTurn;
if (c.damagePerSkillInHand) base += countOtherHandSkills(id) * c.damagePerSkillInHand;
if (c.damagePerCardDrawnThisCombat) base += cardsDrawnThisCombat * c.damagePerCardDrawnThisCombat;
if (c.kind === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus;
if (c.class === 'Attack' && turnCardsPlayedThisTurn === 0 && c.firstCardDamageBonus) base += c.firstCardDamageBonus;
if (c.class === 'shiv') {
if (powerFieldTotal('shivDamageBonus') > 0) base += powerFieldTotal('shivDamageBonus');
if (!shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) base += powerFieldTotal('firstShivDamageBonus');
@@ -423,9 +422,6 @@ export function simulateCombat(data, rng, stats) {
const hitN = (c.hits || 1) + bonusHits;
let useAoe = c.aoe === true;
if (c.class === 'shiv' && shivAoeThisCombat === true) useAoe = true;
if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) {
shivFirstDamageBonusUsed = true;
}
const perHit = calcAttack(baseDamage || 0, pStr, pWeak, 0) * turnAttackMultiplier;
const dealToTarget = (target, amount) => {
if (!target || !target.alive) return { killed: false, dealt: 0 };
@@ -519,6 +515,9 @@ export function simulateCombat(data, rng, stats) {
}
}
}
if (c.class === 'shiv' && !shivFirstDamageBonusUsed && powerFieldTotal('firstShivDamageBonus') > 0) {
shivFirstDamageBonusUsed = true;
}
}
}
if (c.strength) pStr += c.strength;
@@ -565,7 +564,7 @@ export function simulateCombat(data, rng, stats) {
}
}
if (c.blockPerDamageDealtThisTurn && c.blockPerDamageDealtThisTurn > 0 && c.kind !== 'Power') {
blockGained += addBlock(Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn));
blockGained += Math.max(0, damageDealtThisTurn * c.blockPerDamageDealtThisTurn);
}
if (recordStats && stats) stats[id] = bump(stats[id], costSpent, dmg, blockGained);
}
@@ -659,7 +658,7 @@ export function simulateCombat(data, rng, stats) {
const baseCost = c.cost || 0;
const combatReduction = combatCardCostReduction[id] || 0;
const cost = handCostZeroThisTurn === true ? 0 : (c.useAllEnergy === true ? energy : (skillFree ? 0 : (c.kind === 'Skill' ? Math.max(0, baseCost - skillCostReductionThisTurn) : baseCost)));
const finalCost = c.useAllEnergy === true ? cost : Math.max(0, cost - combatReduction);
const finalCost = Math.max(0, cost - combatReduction);
energy -= finalCost;
resolveCardEffects(id, c, finalCost);
const playedBlock = powerFieldTotal('cardPlayedBlock');
@@ -722,7 +721,7 @@ export function simulateCombat(data, rng, stats) {
const it = m.intents.length ? m.intents[Math.floor(rng() * m.intents.length)] : null;
if (it) {
if (it.kind === 'Attack') {
const atk = calcEnemyAttack(it.value, m.str, m.weak, pVuln, enemyStrengthLossThisTurn);
const atk = calcAttack(it.value, Math.max(0, m.str - enemyStrengthLossThisTurn), m.weak, pVuln);
const beforeHp = pHp;
let incoming = atk;
if (pIntangible > 0 && incoming > 1) incoming = 1;

View File

@@ -262,19 +262,6 @@ test('simulateCombat: 카드 취약 부여가 같은 카드 피해에 선적용
assert.equal(r.turns, 1);
});
test('simulateCombat: firstCardDamageBonus가 턴 첫 카드에 적용 (kind===Attack, Lua 동기화)', () => {
// ChargedBlow처럼 class=warrior·kind=Attack인 카드의 첫-카드 보너스.
// 게이트가 class==="Attack"이면 영구 false라 미발동(버그) → 5뎀/2턴.
// kind==="Attack"이면 5+2=7 → 1턴 처치.
const data = {
cards: { CB: { name: '차지블로우', cost: 3, kind: 'Attack', class: 'warrior', damage: 5, firstCardDamageBonus: 2 } },
starterDeck: ['CB', 'CB', 'CB', 'CB', 'CB'],
monsters: [{ name: '적', maxHp: 7, intents: [{ kind: 'Defend', value: 0 }] }],
};
const r = simulateCombat(data, mulberry32(1));
assert.equal(r.turns, 1);
});
test('simulateCombat: Power(매턴 힘) 누적', () => {
const data = {
cards: {
@@ -895,44 +882,6 @@ test("calcEnemyAttack: enemyStrengthLossThisTurn reduces enemy attack damage", (
assert.equal(calcEnemyAttack(10, 6, 0, 0, 0), 16);
});
test("calcEnemyAttack: 힘 손실이 base 아래로 공격을 낮춘다 (음수 힘, Lua 동기화)", () => {
// 적 str=0, loss=6 → 힘 -6 → 10-6=4. JS가 str을 0에서 클램프하면 10(버그). Lua는 전체에서 차감.
assert.equal(calcEnemyAttack(10, 0, 0, 0, 6), 4);
assert.equal(calcEnemyAttack(10, 3, 0, 0, 6), 7);
assert.equal(calcEnemyAttack(5, 0, 0, 0, 6), 0); // 5-6=-1 → 0 클램프
});
test('simulateCombat: firstShivDamageBonus는 턴당 첫 Shiv에만 적용 (Lua 동기화)', () => {
// PhantomBlades(firstShivDamageBonus 9) 활성. 턴당 3 Shiv 사용(에너지3·cost1).
// 정답(첫 Shiv만 +9): 턴1 = 10+1+1=12 → 13HP에 1 남김 → 2턴.
// 버그(모든 Shiv +9): 턴1 = 10*3=30 → 1턴.
const data = {
cards: {
PhantomBlades: { name: '환영검', cost: 0, kind: 'Power', firstShivDamageBonus: 9 },
Shiv: { name: '시브', cost: 1, kind: 'Attack', class: 'shiv', damage: 1 },
},
starterDeck: ['PhantomBlades', 'Shiv', 'Shiv', 'Shiv', 'Shiv'],
monsters: [{ name: '적', maxHp: 13, intents: [{ kind: 'Attack', value: 0 }] }],
};
const r = simulateCombat(data, mulberry32(1));
assert.equal(r.turns, 2);
});
test('simulateCombat: blockPerDamageDealtThisTurn이 실제 방어를 부여 (Lua 동기화)', () => {
// 매턴 Hit(5뎀) → Guard(준 피해만큼 방어 5) → 적 공격 5 상쇄.
// 수정(실제 방어): 무한 생존 → 무승부. 버그(방어 미부여): 매턴 5피해 → 사망.
const data = {
cards: {
Hit: { name: '타격', cost: 2, kind: 'Attack', damage: 5 },
Guard: { name: '대비', cost: 1, kind: 'Skill', blockPerDamageDealtThisTurn: 1 },
},
starterDeck: ['Hit', 'Guard'],
monsters: [{ name: '적', maxHp: 9999, intents: [{ kind: 'Attack', value: 5 }] }],
};
const r = simulateCombat(data, mulberry32(1));
assert.equal(r.draw, true);
});
test("simulateCombat: repeatOnKill repeats an attack until no kill occurs", () => {
const shared = {
cards: {