fix: preserve unit sprites until their death animation plays in pre-resolved turns
Units doomed to die later in a pre-resolved enemy turn were being removed from the board prematurely during sync, before their death animation could play. Now: - syncUnits only prunes dead views on actual army elimination, not on mid-turn CO powers that would kill units later in the same turn - Added 'loaded'/'joined' event handlers to remove views when units merge into transports at the correct animated moment - setUnitVisibility skips views whose units are already gone from state, preserving mid-replay sprites until their event fires
This commit is contained in:
parent
85996ad02c
commit
07a16f295d
|
|
@ -553,6 +553,15 @@ export default class AdvanceWarsGame extends Phaser.Scene {
|
||||||
const v = run.view.unitViews.get(e.unitId);
|
const v = run.view.unitViews.get(e.unitId);
|
||||||
if (v) { v.c.destroy(); run.view.unitViews.delete(e.unitId); }
|
if (v) { v.c.destroy(); run.view.unitViews.delete(e.unitId); }
|
||||||
}
|
}
|
||||||
|
if (e.type === 'loaded' || e.type === 'joined') {
|
||||||
|
// unit merged into a transport / another unit — remove its now
|
||||||
|
// defunct board sprite once its move-in has been animated (no
|
||||||
|
// explosion). Reaped here rather than by a state-diff sync so it
|
||||||
|
// vanishes at this exact moment, not early (state is already the
|
||||||
|
// whole turn's final result — see syncUnits' prune note).
|
||||||
|
const v = run.view.unitViews.get(e.unitId);
|
||||||
|
if (v) { v.c.destroy(); run.view.unitViews.delete(e.unitId); }
|
||||||
|
}
|
||||||
if (e.type === 'powerFired') {
|
if (e.type === 'powerFired') {
|
||||||
await new Promise((resolve) => Screens.powerCutIn(this, this.rules, this.oppById, run.state.armies[e.army].co, e.army, resolve));
|
await new Promise((resolve) => Screens.powerCutIn(this, this.rules, this.oppById, run.state.armies[e.army].co, e.army, resolve));
|
||||||
run.hud.refresh(run.state, 0);
|
run.hud.refresh(run.state, 0);
|
||||||
|
|
@ -570,9 +579,16 @@ export default class AdvanceWarsGame extends Phaser.Scene {
|
||||||
// full-screen reveal, so those still do a full sync.
|
// full-screen reveal, so those still do a full sync.
|
||||||
const wideReveal = events.some((e) =>
|
const wideReveal = events.some((e) =>
|
||||||
['powerFired', 'healedAll', 'refreshed', 'tsunami', 'meteor', 'armyEliminated'].includes(e.type));
|
['powerFired', 'healedAll', 'refreshed', 'tsunami', 'meteor', 'armyEliminated'].includes(e.type));
|
||||||
|
// A wide effect snaps every unit to its (final) state, but only an
|
||||||
|
// actual army elimination may *prune* dead views here — otherwise a
|
||||||
|
// mid-turn CO power would reap units merely doomed to die later in this
|
||||||
|
// same pre-resolved turn, yanking them off the board before their own
|
||||||
|
// death animation plays. Non-elimination deaths are reaped by the
|
||||||
|
// 'destroyed'/'crashed' handlers above, at the moment they animate.
|
||||||
|
const eliminated = events.some((e) => e.type === 'armyEliminated');
|
||||||
const touchedIds = wideReveal ? null : new Set(events.flatMap((e) =>
|
const touchedIds = wideReveal ? null : new Set(events.flatMap((e) =>
|
||||||
[e.unitId, e.attackerId, e.defenderId, e.transportId, e.intoId, e.byId].filter((id) => id != null)));
|
[e.unitId, e.attackerId, e.defenderId, e.transportId, e.intoId, e.byId].filter((id) => id != null)));
|
||||||
run.view.syncUnits(touchedIds);
|
run.view.syncUnits(touchedIds, { prune: eliminated });
|
||||||
run.view.refreshFog(0);
|
run.view.refreshFog(0);
|
||||||
run.hud.refresh(run.state, 0);
|
run.hud.refresh(run.state, 0);
|
||||||
if (enemy && (moved || battles.length || capturing)) await this.wait(500);
|
if (enemy && (moved || battles.length || capturing)) await this.wait(500);
|
||||||
|
|
|
||||||
|
|
@ -442,7 +442,7 @@ export class AdvanceWarsMapView {
|
||||||
// Used by replayEvents to reveal a turn's units one action at a time
|
// Used by replayEvents to reveal a turn's units one action at a time
|
||||||
// instead of snapping everyone straight to the turn's final result. Pass
|
// instead of snapping everyone straight to the turn's final result. Pass
|
||||||
// nothing (or null) for a full sync — creates/updates/prunes everyone.
|
// nothing (or null) for a full sync — creates/updates/prunes everyone.
|
||||||
syncUnits(onlyIds = null) {
|
syncUnits(onlyIds = null, { prune = !onlyIds } = {}) {
|
||||||
const { state, rules, scene } = this;
|
const { state, rules, scene } = this;
|
||||||
const present = new Set();
|
const present = new Set();
|
||||||
for (const u of state.units) {
|
for (const u of state.units) {
|
||||||
|
|
@ -473,11 +473,12 @@ export class AdvanceWarsMapView {
|
||||||
}
|
}
|
||||||
if (justCreated || !onlyIds || onlyIds.has(u.id)) this.placeUnit(u, v);
|
if (justCreated || !onlyIds || onlyIds.has(u.id)) this.placeUnit(u, v);
|
||||||
}
|
}
|
||||||
// Stale-view cleanup only runs on a full sync — a scoped pass can't
|
// Stale-view cleanup only runs when explicitly pruning (a full sync, by
|
||||||
// tell a genuinely-dead unit from one whose own (not yet replayed)
|
// default) — a mid-replay pass can't tell a genuinely-dead unit from one
|
||||||
// death is later in this same turn's log; the 'destroyed'/'crashed'
|
// whose own (not yet replayed) death/load/join is later in this same
|
||||||
|
// pre-resolved turn's log; the 'destroyed'/'crashed'/'loaded'/'joined'
|
||||||
// event handlers already remove views at the right moment for that.
|
// event handlers already remove views at the right moment for that.
|
||||||
if (!onlyIds) {
|
if (prune) {
|
||||||
for (const [id, v] of [...this.unitViews]) {
|
for (const [id, v] of [...this.unitViews]) {
|
||||||
if (!present.has(id)) { v.c.destroy(); this.unitViews.delete(id); }
|
if (!present.has(id)) { v.c.destroy(); this.unitViews.delete(id); }
|
||||||
}
|
}
|
||||||
|
|
@ -507,7 +508,15 @@ export class AdvanceWarsMapView {
|
||||||
}
|
}
|
||||||
|
|
||||||
setUnitVisibility(visibleIds) {
|
setUnitVisibility(visibleIds) {
|
||||||
|
// Views whose unit is already gone from state are mid-replay: the enemy
|
||||||
|
// turn is resolved up front, so a unit that dies (or loads/joins) later
|
||||||
|
// this turn vanishes from state while its board sprite is deliberately
|
||||||
|
// held until its own event is replayed (see syncUnits / replayEvents).
|
||||||
|
// Never force-hide those here — leave them exactly as last shown, so a
|
||||||
|
// doomed unit stays put until its death animation actually plays.
|
||||||
|
const alive = new Set(this.state.units.map((u) => u.id));
|
||||||
for (const [id, v] of this.unitViews) {
|
for (const [id, v] of this.unitViews) {
|
||||||
|
if (!alive.has(id)) continue;
|
||||||
v.c.setVisible(visibleIds.has(id));
|
v.c.setVisible(visibleIds.has(id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue