feat: add Righteous faction assets and enhance Sanctify animations

- Add new audio and image assets for the Righteous faction:
  - FX: sanctify.mp3
  - Cards: Archangel, Commander (x3), Inquisitor, Judicator, Prophet, Purifier, Seraph, Templar
  - UI: right_01.png, right_02.png, right_03.png
- Update battle backgrounds to include the 'righteous' theme.
- Fix CombatEngine to pass actual enemy lanes to commander offensive buff collection, ensuring all-targeting skills function correctly.
- Refactor Sanctify animations in BattleScene:
  - Introduce `_doSanctifySpriteTo` to animate a projectile flying from source to target.
  - Animate sprite 35 flying in, shaking on impact, swapping to sprite 36, and fading out.
  - Apply the new animation logic to both single-target and multi-target Sanctify effects.
- Update attack spritesheet to support the new animation frames.
This commit is contained in:
Brian Fertig 2026-03-21 16:37:39 -06:00
parent 77bbef1e5b
commit 71d291f814
18 changed files with 77 additions and 42 deletions

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 675 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 756 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 734 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 MiB

After

Width:  |  Height:  |  Size: 6.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 MiB

View File

@ -685,9 +685,9 @@ export class CombatEngine {
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: firstSide, card: firstCmd, buffs: [], siegeFires: [], protectFires: [], sanctifyFires: [], overchargeFires: [], fortifyFires: [] });
this.events.push({ type: 'preBattle', phase: 'defensive', target: 'commander', side: otherSide, card: otherCmd, buffs: [], siegeFires: [], protectFires: [], sanctifyFires: [], overchargeFires: [], fortifyFires: [] });
// Steps 3–4: commander offensive buffs
const firstCmdFires = this._collectPreBattleFires([firstCmd], firstAllies, otherAllies, otherCmd, [otherCmd]);
const otherCmdFires = this._collectPreBattleFires([otherCmd], otherAllies, firstAllies, firstCmd, [firstCmd]);
// 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 otherCmdFires = this._collectPreBattleFires([otherCmd], otherAllies, firstAllies, firstCmd, firstLanes);
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: firstSide, card: firstCmd, ...firstCmdFires });
this.events.push({ type: 'preBattle', phase: 'offensive', target: 'commander', side: otherSide, card: otherCmd, ...otherCmdFires });

View File

@ -9,7 +9,8 @@ const BATTLE_BACKGROUNDS = {
imperial: ['imperial_01', 'imperial_02', 'imperial_03'],
raider: ['raider_01', 'raider_02', 'raider_03'],
bloodthirsty: ['bloodthirsty_01', 'bloodthirsty_02', 'bloodthirsty_03'],
xeno: ['xeno_01', 'xeno_02', 'xeno_03']
xeno: ['xeno_01', 'xeno_02', 'xeno_03'],
righteous: ['right_01', 'right_02', 'right_03']
};
// ── Battle music playlist ─────────────────────────────────────────────────────
@ -1837,7 +1838,7 @@ export class BattleScene extends Phaser.Scene {
if (hlEvent) delete this._pendingHiveLinks[fire.target.instanceId];
this._playCardDeath(targetObj, fire.target,
hlEvent ? () => this._animateHiveLink(hlEvent, () => animateNext(idx + 1, targetObj.x, targetObj.y))
: () => animateNext(idx + 1, targetObj.x, targetObj.y)
: () => animateNext(idx + 1, targetObj.x, targetObj.y)
);
} else if (fire.carapaceGain > 0 && targetObj.scene) {
this._animateCarapaceEffect(targetObj, fire.carapaceGain, () => {
@ -4072,30 +4073,19 @@ export class BattleScene extends Phaser.Scene {
}
_animateSanctifySingle(fire, onComplete) {
const obj = this.cardObjects.get(fire.target.instanceId) || this.commanderObjects?.get(fire.target.instanceId);
if (!obj?.scene) { onComplete(); return; }
const sourceObj = this.cardObjects.get(fire.source.instanceId) || this.commanderObjects?.get(fire.source.instanceId);
const targetObj = this.cardObjects.get(fire.target.instanceId) || this.commanderObjects?.get(fire.target.instanceId);
if (!targetObj?.scene) { onComplete(); return; }
const effects = fire.cleansed.map(c => c.effect).join(', ');
this.statusText.setText(`${fire.source.name} sanctifies self! Cleansed: ${effects}`);
if (this.sound.get('sfx_sanctify')) this.sound.play('sfx_sanctify', { volume: 0.8 });
// White flash effect
const effectSprite = this.add.sprite(obj.x, obj.y, 'attacks', 34)
.setDisplaySize(140, 140)
.setDepth(30)
.setAlpha(0.9)
.setTint(0xffffff);
this.tweens.add({
targets: effectSprite,
scaleX: { from: 0.8, to: 1.5 },
scaleY: { from: 0.8, to: 1.5 },
alpha: { from: 0.9, to: 0 },
duration: 400,
onComplete: () => {
if (effectSprite.scene) effectSprite.destroy();
if (obj.scene) obj.refresh();
onComplete();
}
const startX = sourceObj?.scene ? sourceObj.x : targetObj.x;
const startY = sourceObj?.scene ? sourceObj.y : targetObj.y;
this._doSanctifySpriteTo(startX, startY, targetObj, () => {
if (targetObj.scene) targetObj.refresh();
onComplete();
});
}
@ -4104,33 +4094,78 @@ export class BattleScene extends Phaser.Scene {
this.statusText.setText(`${fire.source.name} sanctifies all allies!`);
if (this.sound.get('sfx_sanctify')) this.sound.play('sfx_sanctify', { volume: 0.8 });
const sourceObj = this.cardObjects.get(fire.source.instanceId) || this.commanderObjects?.get(fire.source.instanceId);
const startX = sourceObj?.scene ? sourceObj.x : 0;
const startY = sourceObj?.scene ? sourceObj.y : 0;
let remaining = fire.targets.length;
const done = () => { if (--remaining <= 0) onComplete(); };
for (const t of fire.targets) {
const obj = this.cardObjects.get(t.target.instanceId) || this.commanderObjects?.get(t.target.instanceId);
if (!obj?.scene) { done(); continue; }
const targetObj = this.cardObjects.get(t.target.instanceId) || this.commanderObjects?.get(t.target.instanceId);
if (!targetObj?.scene) { done(); continue; }
const effectSprite = this.add.sprite(obj.x, obj.y, 'attacks', 34)
.setDisplaySize(140, 140)
.setDepth(30)
.setAlpha(0.9)
.setTint(0xffffff);
this.tweens.add({
targets: effectSprite,
scaleX: { from: 0.8, to: 1.5 },
scaleY: { from: 0.8, to: 1.5 },
alpha: { from: 0.9, to: 0 },
duration: 400,
onComplete: () => {
if (effectSprite.scene) effectSprite.destroy();
if (obj.scene) obj.refresh();
done();
}
this._doSanctifySpriteTo(startX, startY, targetObj, () => {
if (targetObj.scene) targetObj.refresh();
done();
});
}
}
// Fly sprite 35 from (startX, startY) to targetObj, shake it, swap to sprite 36, fade out.
_doSanctifySpriteTo(startX, startY, targetObj, onComplete) {
const sprite = this.add.sprite(startX, startY, 'attacks', 35)
.setDisplaySize(100, 100)
.setDepth(30)
.setAlpha(1);
const flyDuration = 350;
this.tweens.add({
targets: sprite,
x: targetObj.x,
y: targetObj.y,
duration: flyDuration,
ease: 'Quad.easeIn',
onComplete: () => {
if (!sprite.scene) { onComplete(); return; }
// Shake: rapid left-right oscillation
const shakeAmp = 6;
const shakeDuration = 60;
this.tweens.add({
targets: sprite,
x: { from: targetObj.x - shakeAmp, to: targetObj.x + shakeAmp },
duration: shakeDuration,
yoyo: true,
repeat: 3,
ease: 'Sine.easeInOut',
onComplete: () => {
if (!sprite.scene) { onComplete(); return; }
// Snap back to centre, swap to sprite 36
sprite.setPosition(targetObj.x, targetObj.y);
sprite.setFrame(36);
// Fade out sprite 36
this.tweens.add({
targets: sprite,
alpha: 0,
scaleX: { from: 1, to: 1.4 },
scaleY: { from: 1, to: 1.4 },
duration: 350,
ease: 'Power2',
onComplete: () => {
if (sprite.scene) sprite.destroy();
onComplete();
}
});
}
});
}
});
}
// ── Overcharge animations ────────────────────────────────────────────────
_processOverchargeFires(fires, onComplete) {