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) {
|
if (copied.name === 'molt' && ctx2.moltHeal > 0) {
|
||||||
hackFires.push({ skill: 'molt', card: pending.attacker, heal: ctx2.moltHeal, armorLost: ctx2.moltArmorLost, isHacked: true });
|
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) {
|
if (hackFires.length > 0) {
|
||||||
preAttackFires.push({ skill: 'hack', source: pending.attacker, copiedFrom: pending.target, fires: hackFires });
|
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
|
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.
|
// postAttack event — fires after the attack animation resolves.
|
||||||
// Only references cards still alive so skill handlers don't need to guard.
|
// 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.
|
// 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)
|
// Deploy + pre-attack phases (NO damage applied yet)
|
||||||
// On player-first turns, beginCommit also deploys the AI card
|
// On player-first turns, beginCommit also deploys the AI card
|
||||||
const commitEvents = this.engine.beginCommit(chosenCard);
|
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
|
// Build preBattle hive_link map for venom kills: dead card instanceId → hive_link event
|
||||||
this._preBattleHiveLinks = {};
|
this._preBattleHiveLinks = {};
|
||||||
|
|
@ -424,7 +424,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
} else if (currentRound) {
|
} else if (currentRound) {
|
||||||
if (e.type === 'berserk') currentRound.berserkEvent = e;
|
if (e.type === 'berserk') currentRound.berserkEvent = e;
|
||||||
else if (e.type === 'counter') currentRound.counterEvent = 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);
|
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
|
// Shared helper: fly sprite 38 from source to target, burst on arrival, call cb
|
||||||
_doSmiteCastTo(sourceObj, targetObj, onArrival) {
|
_doSmiteCastTo(sourceObj, targetObj, onArrival) {
|
||||||
const projectile = this.add.sprite(sourceObj.x, sourceObj.y, 'attacks', 38)
|
const projectile = this.add.sprite(sourceObj.x, sourceObj.y, 'attacks', 38)
|
||||||
.setDisplaySize(80, 80)
|
.setDisplaySize(180, 180)
|
||||||
.setDepth(30);
|
.setDepth(30);
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
targets: projectile,
|
targets: projectile,
|
||||||
|
|
@ -3981,6 +3981,7 @@ export class BattleScene extends Phaser.Scene {
|
||||||
this.statusText.setText(`${event.card.name} suffers ${event.damage} smite damage!`);
|
this.statusText.setText(`${event.card.name} suffers ${event.damage} smite damage!`);
|
||||||
this.sound.play('sfx_smite_damage', { volume: 0.8 });
|
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)
|
const effectSprite = this.add.sprite(obj.x, obj.y, 'attacks', 39)
|
||||||
.setDisplaySize(140, 140)
|
.setDisplaySize(140, 140)
|
||||||
.setDepth(30)
|
.setDepth(30)
|
||||||
|
|
@ -3995,24 +3996,38 @@ export class BattleScene extends Phaser.Scene {
|
||||||
onComplete: () => { if (effectSprite.scene) effectSprite.destroy(); }
|
onComplete: () => { if (effectSprite.scene) effectSprite.destroy(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (event.damage > 0 && obj.animateHPLoss) {
|
// Latch: wait for both HP animation AND the 1s minimum before continuing
|
||||||
obj.animateHPLoss(event.damage, () => {
|
let spriteHeld = true;
|
||||||
if (event.killed) {
|
let hpDone = false;
|
||||||
const hlEvent = this._preBattleHiveLinks?.[event.card.instanceId];
|
const tryFinish = () => {
|
||||||
if (hlEvent) delete this._preBattleHiveLinks[event.card.instanceId];
|
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,
|
this._playCardDeath(obj, event.card,
|
||||||
hlEvent ? () => this._animateHiveLink(hlEvent, onComplete) : onComplete
|
hlEvent ? () => this._animateHiveLink(hlEvent, onComplete) : onComplete
|
||||||
);
|
);
|
||||||
} else {
|
});
|
||||||
if (obj.scene) obj.refresh();
|
} else {
|
||||||
onComplete();
|
hpDone = true;
|
||||||
}
|
tryFinish();
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (event.damage > 0 && obj.animateHPLoss) {
|
||||||
|
obj.animateHPLoss(event.damage, afterHP);
|
||||||
} else {
|
} else {
|
||||||
this.time.delayedCall(550, () => {
|
hpDone = true;
|
||||||
if (obj.scene) obj.refresh();
|
tryFinish();
|
||||||
onComplete();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4332,6 +4347,17 @@ export class BattleScene extends Phaser.Scene {
|
||||||
else if (fire.skill === 'smite') this._animateSmiteApply(fire, cb);
|
else if (fire.skill === 'smite') this._animateSmiteApply(fire, cb);
|
||||||
else if (fire.skill === 'smiteAll') this._animateSmiteApplyAll(fire, cb);
|
else if (fire.skill === 'smiteAll') this._animateSmiteApplyAll(fire, cb);
|
||||||
else if (fire.skill === 'molt') this._animateMolt(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();
|
else cb();
|
||||||
};
|
};
|
||||||
nextFire(0);
|
nextFire(0);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue