diff --git a/web/src/components/BattlePhase.tsx b/web/src/components/BattlePhase.tsx index 0182865..8b0d5e7 100644 --- a/web/src/components/BattlePhase.tsx +++ b/web/src/components/BattlePhase.tsx @@ -30,6 +30,9 @@ interface SideVis { pending: Card[] // foods revealed (or prepped) waiting for a pet unit: UnitVis | null setAside: Card[] // fainted pets kept beside the arena with a pending effect + // Ids of cards (set-aside pets or spent food perks) released this step: kept + // in their arrays for one beat so they can animate out, cleared next event. + leaving: string[] } // Milliseconds each event type stays on screen during playback. @@ -57,10 +60,24 @@ const appleCount = (foods: Card[]) => foods.filter((f) => f.food === 'apple').le // seat's visual state. Units that died in the last applied event are still // present with dying=true so they can animate out. function replay(events: BattleEvent[], stackSizes: number[], upto: number): SideVis[] { - const sides: SideVis[] = stackSizes.map((n) => ({ stack: n, pending: [], unit: null, setAside: [] })) + const sides: SideVis[] = stackSizes.map((n) => ({ + stack: n, + pending: [], + unit: null, + setAside: [], + leaving: [], + })) for (let k = 0; k < upto && k < events.length; k++) { for (const s of sides) { if (s.unit?.dying) s.unit = null // clear last step's casualties + if (s.leaving.length) { + // Drop cards that finished animating out last step. + const gone = new Set(s.leaving) + s.setAside = s.setAside.filter((c) => !gone.has(c.id)) + if (s.unit) s.unit.foods = s.unit.foods.filter((c) => !gone.has(c.id)) + s.pending = s.pending.filter((c) => !gone.has(c.id)) + s.leaving = [] + } } const ev = events[k] switch (ev.type) { @@ -152,12 +169,10 @@ function replay(events: BattleEvent[], stackSizes: number[], upto: number): Side break case 'release': { // A spent card leaves the play area: a set-aside pet (Turtle) or a - // used-up food perk (Melon), which lives in the pet's food fan. - const s = sides[ev.seat!] + // used-up food perk (Melon), which lives in the pet's food fan. Flag it + // rather than removing it, so it animates out before unmounting. const id = ev.card?.id - s.setAside = s.setAside.filter((c) => c.id !== id) - if (s.unit) s.unit.foods = s.unit.foods.filter((c) => c.id !== id) - s.pending = s.pending.filter((c) => c.id !== id) + if (id) sides[ev.seat!].leaving.push(id) break } } @@ -327,7 +342,12 @@ export function BattlePhase({ view, send, step, setStep }: Props) { const setAsideEl = s.setAside.length > 0 && (