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.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||||
applyWound(this.opponentLanes, this.opponentLanes, 'opponent');
|
applyWound([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the card has carapace and just took damage, apply armor gain (with cap).
|
// 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.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||||
applyVenom(this.opponentLanes, this.opponentLanes, 'opponent');
|
applyVenom([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply smite damage to all cards (mirrors venom)
|
// Apply smite damage to all cards (mirrors venom)
|
||||||
|
|
@ -156,8 +156,8 @@ export class CombatEngine {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
applySmite(this.playerLanes, this.playerLanes, 'player');
|
applySmite([this.playerCommander, ...this.playerLanes], this.playerLanes, 'player');
|
||||||
applySmite(this.opponentLanes, this.opponentLanes, 'opponent');
|
applySmite([this.opponentCommander, ...this.opponentLanes], this.opponentLanes, 'opponent');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrement burrow stacks at the end of each combat turn (postBattle phase).
|
// Decrement burrow stacks at the end of each combat turn (postBattle phase).
|
||||||
|
|
@ -549,6 +549,7 @@ export class CombatEngine {
|
||||||
const overchargeFires = [];
|
const overchargeFires = [];
|
||||||
const fortifyFires = [];
|
const fortifyFires = [];
|
||||||
const strikeFires = [];
|
const strikeFires = [];
|
||||||
|
const woundFires = [];
|
||||||
const liveAllies = allies.filter(c => c.currentHP > 0);
|
const liveAllies = allies.filter(c => c.currentHP > 0);
|
||||||
const liveEnemies = enemies.filter(c => c.currentHP > 0);
|
const liveEnemies = enemies.filter(c => c.currentHP > 0);
|
||||||
for (const card of cards) {
|
for (const card of cards) {
|
||||||
|
|
@ -557,14 +558,18 @@ export class CombatEngine {
|
||||||
for (const s of card.skills) {
|
for (const s of card.skills) {
|
||||||
// Commanders fire their strike/strike-all during preBattle instead of preAttack
|
// Commanders fire their strike/strike-all during preBattle instead of preAttack
|
||||||
const isCommanderStrike = card.type === 'commander' && s.name === 'strike' && s.trigger === '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
|
// For drain, resolve the card directly across as defender
|
||||||
const cardIdx = cards.indexOf(card);
|
const cardIdx = cards.indexOf(card);
|
||||||
const acrossCard = enemyLanes[cardIdx];
|
const acrossCard = enemyLanes[cardIdx];
|
||||||
const drainDefender = (s.name === 'drain' && acrossCard?.currentHP > 0) ? acrossCard : null;
|
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
|
// Pass laneCards + enemyLaneCards so positional skills (protect, enfeeble) work
|
||||||
const ctx = { rng: this.rng, enemyCommander, laneCards: cards, enemyLaneCards: enemyLanes };
|
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) {
|
if (s.name === 'rally' && ctx.rallyAllTargets) {
|
||||||
for (const t of ctx.rallyAllTargets) {
|
for (const t of ctx.rallyAllTargets) {
|
||||||
buffs.push({ skill: 'rally', source: card, target: t.target, amount: t.amount, isAll: true });
|
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 });
|
fortifyFires.push({ skill: 'fortify', source: card, target: ctx.fortifyTarget, amount: ctx.fortifyAmount });
|
||||||
this._log(`${card.name} fortify: +${ctx.fortifyAmount} ARM to self`);
|
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) {
|
if (s.name === 'strike' && ctx.strikeAllTargets) {
|
||||||
const isPlayerCard = this.playerLanes.includes(card) || card === this.playerCommander;
|
const isPlayerCard = this.playerLanes.includes(card) || card === this.playerCommander;
|
||||||
for (const t of ctx.strikeAllTargets) {
|
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.
|
// Emit the 8-step pre-battle sequence and process preBattle skills.
|
||||||
|
|
@ -724,8 +736,8 @@ export class CombatEngine {
|
||||||
const otherAllies = [otherCmd, ...otherLanes];
|
const otherAllies = [otherCmd, ...otherLanes];
|
||||||
|
|
||||||
// Steps 1–2: commander defensive buffs (placeholder — no defensive skills yet)
|
// 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: 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: [] });
|
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)
|
// Steps 3–4: commander offensive buffs (pass actual enemy lanes so all-targeting skills work)
|
||||||
const firstCmdFires = this._collectPreBattleFires([firstCmd], firstAllies, otherAllies, otherCmd, otherLanes);
|
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 });
|
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: otherSide, card: otherCmd, ...otherCmdFires });
|
||||||
|
|
||||||
// Steps 5–6: lane defensive buffs (placeholder)
|
// 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: 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: [] });
|
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
|
// Steps 7–8: lane offensive buffs + all skill fires
|
||||||
const firstLaneFires = this._collectPreBattleFires(firstLanes, firstAllies, otherAllies, otherCmd, otherLanes);
|
const firstLaneFires = this._collectPreBattleFires(firstLanes, firstAllies, otherAllies, otherCmd, otherLanes);
|
||||||
|
|
|
||||||
|
|
@ -84,15 +84,28 @@ export class SkillProcessor {
|
||||||
if (targets.length === 0) return;
|
if (targets.length === 0) return;
|
||||||
const allTargets = [];
|
const allTargets = [];
|
||||||
for (const t of targets) {
|
for (const t of targets) {
|
||||||
t.woundStacks = (t.woundStacks || 0) + canApply;
|
const toApply = t.type === 'commander'
|
||||||
allTargets.push({ target: t, stacks: canApply });
|
? 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 (tracker) tracker.applied += canApply;
|
||||||
if (context) context.woundAllTargets = allTargets;
|
if (context && allTargets.length > 0) context.woundAllTargets = allTargets;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!defender || defender.currentHP <= 0) return;
|
if (!defender || defender.currentHP <= 0) return;
|
||||||
if (defender.burrowTurns > 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;
|
defender.woundStacks = (defender.woundStacks || 0) + canApply;
|
||||||
if (tracker) tracker.applied += canApply;
|
if (tracker) tracker.applied += canApply;
|
||||||
if (context) { context.woundTarget = defender; context.woundStacks = canApply; }
|
if (context) { context.woundTarget = defender; context.woundStacks = canApply; }
|
||||||
|
|
|
||||||
|
|
@ -828,6 +828,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
this._restoreHealForDisplay(preBattleEvents);
|
this._restoreHealForDisplay(preBattleEvents);
|
||||||
this._restoreOverchargeForDisplay(preBattleEvents);
|
this._restoreOverchargeForDisplay(preBattleEvents);
|
||||||
this._restoreFortifyForDisplay(preBattleEvents);
|
this._restoreFortifyForDisplay(preBattleEvents);
|
||||||
|
this._restorePreBattleWoundFires(preBattleEvents);
|
||||||
// Render the field with all newly deployed cards but pre-combat stats
|
// Render the field with all newly deployed cards but pre-combat stats
|
||||||
this._renderState();
|
this._renderState();
|
||||||
this._reapplyBuffs(preBattleEvents);
|
this._reapplyBuffs(preBattleEvents);
|
||||||
|
|
@ -838,6 +839,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
this._reapplyHeal(preBattleEvents);
|
this._reapplyHeal(preBattleEvents);
|
||||||
this._reapplyOvercharge(preBattleEvents);
|
this._reapplyOvercharge(preBattleEvents);
|
||||||
this._reapplyFortify(preBattleEvents);
|
this._reapplyFortify(preBattleEvents);
|
||||||
|
this._reapplyPreBattleWoundFires(preBattleEvents);
|
||||||
|
|
||||||
const newPlayerCard = chosenCard
|
const newPlayerCard = chosenCard
|
||||||
? this.engine.getState().player.lanes.find(c => !oldPlayerIds.has(c.instanceId))
|
? 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 hasOvercharge = event.overchargeFires?.length > 0;
|
||||||
const hasFortify = event.fortifyFires?.length > 0;
|
const hasFortify = event.fortifyFires?.length > 0;
|
||||||
const hasStrike = event.strikeFires?.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._processDrainFires(event.drainFires || [], () => {
|
||||||
this._processOverchargeFires(event.overchargeFires || [], () => {
|
this._processOverchargeFires(event.overchargeFires || [], () => {
|
||||||
this._processBuffAnimations(event.buffs || [], () => {
|
this._processBuffAnimations(event.buffs || [], () => {
|
||||||
|
|
@ -2775,11 +2779,52 @@ export class BattleScene extends Phaser.Scene {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
onComplete();
|
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) {
|
_processPreBattleStrikeFires(fires, onComplete) {
|
||||||
const next = (idx) => {
|
const next = (idx) => {
|
||||||
if (idx >= fires.length) { onComplete(); return; }
|
if (idx >= fires.length) { onComplete(); return; }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue