feat(combat): support commander wound skill triggers in preBattle phase
- Update applyWound/applyVenom/applySmite to include commanders in target lists - Add 'wound' skill support for commanders with 'on_attack' trigger during preBattle - Implement wound stack capping for commanders (max = skill.value) - Extend preBattle event structure with woundFires array - Add animation and state restoration logic for preBattle wound applications This enables commander cards to apply wound stacks before combat begins, with proper stack limits and UI feedback.
This commit is contained in:
parent
5a5941024b
commit
b60fc16bb4
|
|
@ -91,8 +91,8 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
};
|
||||
applyWound(this.playerLanes, this.playerLanes, 'player');
|
||||
applyWound(this.opponentLanes, this.opponentLanes, 'opponent');
|
||||
applyWound([this.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||
applyWound([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||
}
|
||||
|
||||
// If the card has carapace and just took damage, apply armor gain (with cap).
|
||||
|
|
@ -132,8 +132,8 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
};
|
||||
applyVenom(this.playerLanes, this.playerLanes, 'player');
|
||||
applyVenom(this.opponentLanes, this.opponentLanes, 'opponent');
|
||||
applyVenom([this.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||
applyVenom([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||
}
|
||||
|
||||
// Apply smite damage to all cards (mirrors venom)
|
||||
|
|
@ -156,8 +156,8 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
};
|
||||
applySmite(this.playerLanes, this.playerLanes, 'player');
|
||||
applySmite(this.opponentLanes, this.opponentLanes, 'opponent');
|
||||
applySmite([this.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||
applySmite([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||
}
|
||||
|
||||
// Decrement burrow stacks at the end of each combat turn (postBattle phase).
|
||||
|
|
@ -549,6 +549,7 @@ export class CombatEngine {
|
|||
const overchargeFires = [];
|
||||
const fortifyFires = [];
|
||||
const strikeFires = [];
|
||||
const woundFires = [];
|
||||
const liveAllies = allies.filter(c => c.currentHP > 0);
|
||||
const liveEnemies = enemies.filter(c => c.currentHP > 0);
|
||||
for (const card of cards) {
|
||||
|
|
@ -557,14 +558,18 @@ export class CombatEngine {
|
|||
for (const s of card.skills) {
|
||||
// Commanders fire their strike/strike-all during preBattle instead of preAttack
|
||||
const isCommanderStrike = card.type === 'commander' && s.name === 'strike' && s.trigger === 'preAttack';
|
||||
if (s.trigger !== 'preBattle' && !isCommanderStrike) continue;
|
||||
// Commanders fire wound during preBattle (after wound stacks tick)
|
||||
const isCommanderWound = card.type === 'commander' && s.name === 'wound' && s.trigger === 'on_attack';
|
||||
if (s.trigger !== 'preBattle' && !isCommanderStrike && !isCommanderWound) continue;
|
||||
// For drain, resolve the card directly across as defender
|
||||
const cardIdx = cards.indexOf(card);
|
||||
const acrossCard = enemyLanes[cardIdx];
|
||||
const drainDefender = (s.name === 'drain' && acrossCard?.currentHP > 0) ? acrossCard : null;
|
||||
// For single-target commander wound, target the enemy commander
|
||||
const woundDefender = (isCommanderWound && !s.all && enemyCommander?.currentHP > 0) ? enemyCommander : null;
|
||||
// Pass laneCards + enemyLaneCards so positional skills (protect, enfeeble) work
|
||||
const ctx = { rng: this.rng, enemyCommander, laneCards: cards, enemyLaneCards: enemyLanes };
|
||||
this.skillProcessor.process(s, card, drainDefender, liveAllies, liveEnemies, ctx);
|
||||
this.skillProcessor.process(s, card, drainDefender || woundDefender, liveAllies, liveEnemies, ctx);
|
||||
if (s.name === 'rally' && ctx.rallyAllTargets) {
|
||||
for (const t of ctx.rallyAllTargets) {
|
||||
buffs.push({ skill: 'rally', source: card, target: t.target, amount: t.amount, isAll: true });
|
||||
|
|
@ -674,6 +679,13 @@ export class CombatEngine {
|
|||
fortifyFires.push({ skill: 'fortify', source: card, target: ctx.fortifyTarget, amount: ctx.fortifyAmount });
|
||||
this._log(`${card.name} fortify: +${ctx.fortifyAmount} ARM to self`);
|
||||
}
|
||||
if (s.name === 'wound' && ctx.woundAllTargets) {
|
||||
woundFires.push({ skill: 'wound', source: card, isAll: true, targets: ctx.woundAllTargets });
|
||||
this._log(`${card.name} wound all: applied stacks to ${ctx.woundAllTargets.length} enemies`);
|
||||
} else if (s.name === 'wound' && ctx.woundTarget) {
|
||||
woundFires.push({ skill: 'wound', source: card, target: ctx.woundTarget, stacks: ctx.woundStacks });
|
||||
this._log(`${card.name} wound: applied ${ctx.woundStacks} stacks to ${ctx.woundTarget.name}`);
|
||||
}
|
||||
if (s.name === 'strike' && ctx.strikeAllTargets) {
|
||||
const isPlayerCard = this.playerLanes.includes(card) || card === this.playerCommander;
|
||||
for (const t of ctx.strikeAllTargets) {
|
||||
|
|
@ -703,7 +715,7 @@ export class CombatEngine {
|
|||
}
|
||||
}
|
||||
}
|
||||
return { buffs, siegeFires, protectFires, enfeebeFires, jamFires, weakenFires, drainFires, healFires, overchargeFires, fortifyFires, strikeFires };
|
||||
return { buffs, siegeFires, protectFires, enfeebeFires, jamFires, weakenFires, drainFires, healFires, overchargeFires, fortifyFires, strikeFires, woundFires };
|
||||
}
|
||||
|
||||
// Emit the 8-step pre-battle sequence and process preBattle skills.
|
||||
|
|
@ -724,8 +736,8 @@ export class CombatEngine {
|
|||
const otherAllies = [otherCmd, ...otherLanes];
|
||||
|
||||
// Steps 1–2: commander defensive buffs (placeholder — no defensive skills yet)
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: firstSide, card: firstCmd, buffs: [], siegeFires: [], protectFires: [], overchargeFires: [], fortifyFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: otherSide, card: otherCmd, buffs: [], siegeFires: [], protectFires: [], overchargeFires: [], fortifyFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: firstSide, card: firstCmd, buffs: [], siegeFires: [], protectFires: [], overchargeFires: [], fortifyFires: [], woundFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: otherSide, card: otherCmd, buffs: [], siegeFires: [], protectFires: [], overchargeFires: [], fortifyFires: [], woundFires: [] });
|
||||
|
||||
// Steps 3–4: commander offensive buffs (pass actual enemy lanes so all-targeting skills work)
|
||||
const firstCmdFires = this._collectPreBattleFires([firstCmd], firstAllies, otherAllies, otherCmd, otherLanes);
|
||||
|
|
@ -734,8 +746,8 @@ export class CombatEngine {
|
|||
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: otherSide, card: otherCmd, ...otherCmdFires });
|
||||
|
||||
// Steps 5–6: lane defensive buffs (placeholder)
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: [], siegeFires: [], protectFires: [], enfeebeFires: [], jamFires: [], healFires: [], overchargeFires: [], fortifyFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: [], siegeFires: [], protectFires: [], enfeebeFires: [], jamFires: [], healFires: [], overchargeFires: [], fortifyFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: firstSide, cards: firstLanes, buffs: [], siegeFires: [], protectFires: [], enfeebeFires: [], jamFires: [], healFires: [], overchargeFires: [], fortifyFires: [], woundFires: [] });
|
||||
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'lanes', side: otherSide, cards: otherLanes, buffs: [], siegeFires: [], protectFires: [], enfeebeFires: [], jamFires: [], healFires: [], overchargeFires: [], fortifyFires: [], woundFires: [] });
|
||||
|
||||
// Steps 7–8: lane offensive buffs + all skill fires
|
||||
const firstLaneFires = this._collectPreBattleFires(firstLanes, firstAllies, otherAllies, otherCmd, otherLanes);
|
||||
|
|
|
|||
|
|
@ -84,15 +84,28 @@ export class SkillProcessor {
|
|||
if (targets.length === 0) return;
|
||||
const allTargets = [];
|
||||
for (const t of targets) {
|
||||
t.woundStacks = (t.woundStacks || 0) + canApply;
|
||||
allTargets.push({ target: t, stacks: canApply });
|
||||
const toApply = t.type === 'commander'
|
||||
? Math.min(canApply, Math.max(0, skill.value - (t.woundStacks || 0)))
|
||||
: canApply;
|
||||
if (toApply <= 0) continue;
|
||||
t.woundStacks = (t.woundStacks || 0) + toApply;
|
||||
allTargets.push({ target: t, stacks: toApply });
|
||||
}
|
||||
if (tracker) tracker.applied += canApply;
|
||||
if (context) context.woundAllTargets = allTargets;
|
||||
if (context && allTargets.length > 0) context.woundAllTargets = allTargets;
|
||||
return;
|
||||
}
|
||||
if (!defender || defender.currentHP <= 0) return;
|
||||
if (defender.burrowTurns > 0) return;
|
||||
if (defender.type === 'commander') {
|
||||
// Cap wound stacks on the opposing commander at skill.value
|
||||
const toApply = Math.min(canApply, Math.max(0, skill.value - (defender.woundStacks || 0)));
|
||||
if (toApply <= 0) return;
|
||||
defender.woundStacks = (defender.woundStacks || 0) + toApply;
|
||||
if (tracker) tracker.applied += toApply;
|
||||
if (context) { context.woundTarget = defender; context.woundStacks = toApply; }
|
||||
return;
|
||||
}
|
||||
defender.woundStacks = (defender.woundStacks || 0) + canApply;
|
||||
if (tracker) tracker.applied += canApply;
|
||||
if (context) { context.woundTarget = defender; context.woundStacks = canApply; }
|
||||
|
|
|
|||
|
|
@ -828,6 +828,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
this._restoreHealForDisplay(preBattleEvents);
|
||||
this._restoreOverchargeForDisplay(preBattleEvents);
|
||||
this._restoreFortifyForDisplay(preBattleEvents);
|
||||
this._restorePreBattleWoundFires(preBattleEvents);
|
||||
// Render the field with all newly deployed cards but pre-combat stats
|
||||
this._renderState();
|
||||
this._reapplyBuffs(preBattleEvents);
|
||||
|
|
@ -838,6 +839,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
this._reapplyHeal(preBattleEvents);
|
||||
this._reapplyOvercharge(preBattleEvents);
|
||||
this._reapplyFortify(preBattleEvents);
|
||||
this._reapplyPreBattleWoundFires(preBattleEvents);
|
||||
|
||||
const newPlayerCard = chosenCard
|
||||
? this.engine.getState().player.lanes.find(c => !oldPlayerIds.has(c.instanceId))
|
||||
|
|
@ -2752,8 +2754,10 @@ export class BattleScene extends Phaser.Scene {
|
|||
const hasOvercharge = event.overchargeFires?.length > 0;
|
||||
const hasFortify = event.fortifyFires?.length > 0;
|
||||
const hasStrike = event.strikeFires?.length > 0;
|
||||
const hasWound = event.woundFires?.length > 0;
|
||||
|
||||
if (hasBuffs || hasSiege || hasProtect || hasEnfeeble || hasJam || hasWeaken || hasDrain || hasHeal || hasOvercharge || hasFortify || hasStrike) {
|
||||
if (hasBuffs || hasSiege || hasProtect || hasEnfeeble || hasJam || hasWeaken || hasDrain || hasHeal || hasOvercharge || hasFortify || hasStrike || hasWound) {
|
||||
this._processPreBattleWoundFires(event.woundFires || [], () => {
|
||||
this._processDrainFires(event.drainFires || [], () => {
|
||||
this._processOverchargeFires(event.overchargeFires || [], () => {
|
||||
this._processBuffAnimations(event.buffs || [], () => {
|
||||
|
|
@ -2775,11 +2779,52 @@ export class BattleScene extends Phaser.Scene {
|
|||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
onComplete();
|
||||
}
|
||||
}
|
||||
|
||||
_processPreBattleWoundFires(fires, onComplete) {
|
||||
if (!fires || fires.length === 0) { onComplete(); return; }
|
||||
const next = (idx) => {
|
||||
if (idx >= fires.length) { onComplete(); return; }
|
||||
const fire = fires[idx];
|
||||
// Adapt preBattle fire structure (source/target/targets) to animation functions (attacker/target/targets)
|
||||
const adapted = { ...fire, attacker: fire.source };
|
||||
if (fire.isAll) {
|
||||
this._animateWoundAll(adapted, () => next(idx + 1));
|
||||
} else {
|
||||
this._animateWoundApply(adapted, () => next(idx + 1));
|
||||
}
|
||||
};
|
||||
next(0);
|
||||
}
|
||||
|
||||
_restorePreBattleWoundFires(preBattleEvents) {
|
||||
for (const event of preBattleEvents) {
|
||||
for (const fire of event.woundFires || []) {
|
||||
if (fire.isAll) {
|
||||
for (const t of fire.targets) t.target.woundStacks -= t.stacks;
|
||||
} else if (fire.target) {
|
||||
fire.target.woundStacks -= fire.stacks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_reapplyPreBattleWoundFires(preBattleEvents) {
|
||||
for (const event of preBattleEvents) {
|
||||
for (const fire of event.woundFires || []) {
|
||||
if (fire.isAll) {
|
||||
for (const t of fire.targets) t.target.woundStacks += t.stacks;
|
||||
} else if (fire.target) {
|
||||
fire.target.woundStacks += fire.stacks;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_processPreBattleStrikeFires(fires, onComplete) {
|
||||
const next = (idx) => {
|
||||
if (idx >= fires.length) { onComplete(); return; }
|
||||
|
|
|
|||
Loading…
Reference in New Issue