Implement hacked Berserk and Flurry skills with animations
- CombatEngine: - Handle hacked `berserk` by adding `hackSkill.value` to attacker's current attack and logging the event. - Handle hacked `flurry` by injecting a temporary passive skill (+1 extra attack) that is removed after the attack resolves. - BattleScene: - Include `smiteTick` in pre-battle event filtering. - Add `smiteApply` and `smiteApplyAll` to on-attack-all event tracking. - Increase smite projectile size (sprite 38) from 80x80 to 180x180. - Enhance smite damage animation: pulse sprite 39 twice over 1s, synchronize with HP loss, and enforce minimum timing before proceeding or playing death/hive-link animations. - Add visual and text feedback for `berserk` (animate attack gain) and `flurry` (status text) in fire animations.
This commit is contained in:
parent
82f871abfb
commit
ff4a7eebf0
|
|
@ -919,6 +919,15 @@ export class CombatEngine {
|
|||
if (copied.name === 'molt' && ctx2.moltHeal > 0) {
|
||||
hackFires.push({ skill: 'molt', card: pending.attacker, heal: ctx2.moltHeal, armorLost: ctx2.moltArmorLost, isHacked: true });
|
||||
}
|
||||
if (copied.name === 'berserk') {
|
||||
pending.attacker.currentAttack += hackSkill.value;
|
||||
hackFires.push({ skill: 'berserk', card: pending.attacker, gain: hackSkill.value, isHacked: true });
|
||||
}
|
||||
if (copied.name === 'flurry') {
|
||||
// Cap hacked flurry at 1 extra attack; inject a temporary skill so _performAttack picks it up
|
||||
pending.attacker.skills.push({ name: 'flurry', trigger: 'passive', value: 1, _hackedSkill: true });
|
||||
hackFires.push({ skill: 'flurry', card: pending.attacker, isHacked: true });
|
||||
}
|
||||
}
|
||||
if (hackFires.length > 0) {
|
||||
preAttackFires.push({ skill: 'hack', source: pending.attacker, copiedFrom: pending.target, fires: hackFires });
|
||||
|
|
@ -972,6 +981,9 @@ export class CombatEngine {
|
|||
pending.alliedLanes, pending.enemyLanes, pending.side
|
||||
);
|
||||
|
||||
// Remove any skills temporarily injected by hack (e.g. hacked flurry)
|
||||
pending.attacker.skills = pending.attacker.skills.filter(s => !s._hackedSkill);
|
||||
|
||||
// postAttack event — fires after the attack animation resolves.
|
||||
// Only references cards still alive so skill handlers don't need to guard.
|
||||
// pierceRestores: armor that was temporarily reduced by pierce — restore if target survived.
|
||||
|
|
|
|||
|
|
@ -315,7 +315,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
// Deploy + pre-attack phases (NO damage applied yet)
|
||||
// On player-first turns, beginCommit also deploys the AI card
|
||||
const commitEvents = this.engine.beginCommit(chosenCard);
|
||||
const preBattleEvents = commitEvents.filter(e => e.type === 'preBattle' || e.type === 'venomTick' || e.type === 'carapace' || e.type === 'molt' || e.type === 'hive_link');
|
||||
const preBattleEvents = commitEvents.filter(e => e.type === 'preBattle' || e.type === 'venomTick' || e.type === 'smiteTick' || e.type === 'carapace' || e.type === 'molt' || e.type === 'hive_link');
|
||||
|
||||
// Build preBattle hive_link map for venom kills: dead card instanceId → hive_link event
|
||||
this._preBattleHiveLinks = {};
|
||||
|
|
@ -424,7 +424,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
} else if (currentRound) {
|
||||
if (e.type === 'berserk') currentRound.berserkEvent = e;
|
||||
else if (e.type === 'counter') currentRound.counterEvent = e;
|
||||
else if (['ruptureAll', 'weakenAll', 'venomApply', 'venomApplyAll', 'carapace'].includes(e.type))
|
||||
else if (['ruptureAll', 'weakenAll', 'venomApply', 'venomApplyAll', 'smiteApply', 'smiteApplyAll', 'carapace'].includes(e.type))
|
||||
currentRound.onAttackAllEvents.push(e);
|
||||
}
|
||||
}
|
||||
|
|
@ -3927,7 +3927,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
// Shared helper: fly sprite 38 from source to target, burst on arrival, call cb
|
||||
_doSmiteCastTo(sourceObj, targetObj, onArrival) {
|
||||
const projectile = this.add.sprite(sourceObj.x, sourceObj.y, 'attacks', 38)
|
||||
.setDisplaySize(80, 80)
|
||||
.setDisplaySize(180, 180)
|
||||
.setDepth(30);
|
||||
this.tweens.add({
|
||||
targets: projectile,
|
||||
|
|
@ -3981,6 +3981,7 @@ export class BattleScene extends Phaser.Scene {
|
|||
this.statusText.setText(`${event.card.name} suffers ${event.damage} smite damage!`);
|
||||
this.sound.play('sfx_smite_damage', { volume: 0.8 });
|
||||
|
||||
// Sprite 39 pulses twice (250ms × 4 = 1000ms total) over the card
|
||||
const effectSprite = this.add.sprite(obj.x, obj.y, 'attacks', 39)
|
||||
.setDisplaySize(140, 140)
|
||||
.setDepth(30)
|
||||
|
|
@ -3995,24 +3996,38 @@ export class BattleScene extends Phaser.Scene {
|
|||
onComplete: () => { if (effectSprite.scene) effectSprite.destroy(); }
|
||||
});
|
||||
|
||||
if (event.damage > 0 && obj.animateHPLoss) {
|
||||
obj.animateHPLoss(event.damage, () => {
|
||||
if (event.killed) {
|
||||
const hlEvent = this._preBattleHiveLinks?.[event.card.instanceId];
|
||||
if (hlEvent) delete this._preBattleHiveLinks[event.card.instanceId];
|
||||
// Latch: wait for both HP animation AND the 1s minimum before continuing
|
||||
let spriteHeld = true;
|
||||
let hpDone = false;
|
||||
const tryFinish = () => {
|
||||
if (!spriteHeld && hpDone) {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
}
|
||||
};
|
||||
this.time.delayedCall(1000, () => { spriteHeld = false; tryFinish(); });
|
||||
|
||||
const afterHP = () => {
|
||||
if (event.killed) {
|
||||
const hlEvent = this._preBattleHiveLinks?.[event.card.instanceId];
|
||||
if (hlEvent) delete this._preBattleHiveLinks[event.card.instanceId];
|
||||
// For kills wait out the full 1s, then play death
|
||||
this.time.delayedCall(1000, () => {
|
||||
this._playCardDeath(obj, event.card,
|
||||
hlEvent ? () => this._animateHiveLink(hlEvent, onComplete) : onComplete
|
||||
);
|
||||
} else {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
hpDone = true;
|
||||
tryFinish();
|
||||
}
|
||||
};
|
||||
|
||||
if (event.damage > 0 && obj.animateHPLoss) {
|
||||
obj.animateHPLoss(event.damage, afterHP);
|
||||
} else {
|
||||
this.time.delayedCall(550, () => {
|
||||
if (obj.scene) obj.refresh();
|
||||
onComplete();
|
||||
});
|
||||
hpDone = true;
|
||||
tryFinish();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4332,6 +4347,17 @@ export class BattleScene extends Phaser.Scene {
|
|||
else if (fire.skill === 'smite') this._animateSmiteApply(fire, cb);
|
||||
else if (fire.skill === 'smiteAll') this._animateSmiteApplyAll(fire, cb);
|
||||
else if (fire.skill === 'molt') this._animateMolt(fire, cb);
|
||||
else if (fire.skill === 'berserk') {
|
||||
const obj = this.cardObjects.get(fire.card.instanceId) || this.commanderObjects?.get(fire.card.instanceId);
|
||||
if (obj?.scene) {
|
||||
this.statusText.setText(`${fire.card.name} goes berserk! +${fire.gain} ATK`);
|
||||
obj.animateBerserkGain(fire.gain, () => { if (obj.scene) obj.refresh(); cb(); });
|
||||
} else cb();
|
||||
}
|
||||
else if (fire.skill === 'flurry') {
|
||||
this.statusText.setText(`${fire.card.name} gains flurry! +1 extra attack`);
|
||||
this.time.delayedCall(600, cb);
|
||||
}
|
||||
else cb();
|
||||
};
|
||||
nextFire(0);
|
||||
|
|
|
|||
Loading…
Reference in New Issue