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.
|
After Width: | Height: | Size: 721 KiB |
|
After Width: | Height: | Size: 682 KiB |
|
After Width: | Height: | Size: 675 KiB |
|
After Width: | Height: | Size: 724 KiB |
|
After Width: | Height: | Size: 756 KiB |
|
After Width: | Height: | Size: 704 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 734 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 707 KiB |
|
Before Width: | Height: | Size: 6.6 MiB After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 3.4 MiB |
|
|
@ -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 });
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||