Add pets for tiers 4-6.
This commit is contained in:
@@ -18,19 +18,27 @@ interface UnitVis {
|
||||
|
||||
interface SideVis {
|
||||
stack: number
|
||||
pending: Card[] // revealed foods waiting for a pet
|
||||
pending: Card[] // foods revealed (or prepped) waiting for a pet
|
||||
unit: UnitVis | null
|
||||
}
|
||||
|
||||
// Milliseconds each event type stays on screen during playback.
|
||||
const EVENT_MS: Record<BattleEvent['type'], number> = {
|
||||
prep: 700,
|
||||
reveal: 800,
|
||||
summon: 1000,
|
||||
mill: 700,
|
||||
rock: 1200,
|
||||
clash: 1400,
|
||||
shield: 900,
|
||||
strip: 1100,
|
||||
steal: 1100,
|
||||
eat: 1000,
|
||||
heal: 900,
|
||||
}
|
||||
|
||||
const appleCount = (foods: Card[]) => foods.filter((f) => f.food === 'apple').length
|
||||
|
||||
// replay applies the first `upto` events to fresh stacks and returns each
|
||||
// seat's visual state. Units that died in the last applied event are still
|
||||
// present with dying=true so they can animate out.
|
||||
@@ -42,6 +50,9 @@ function replay(events: BattleEvent[], stackSizes: number[], upto: number): Side
|
||||
}
|
||||
const ev = events[k]
|
||||
switch (ev.type) {
|
||||
case 'prep':
|
||||
sides[ev.seat!].pending.push(ev.card!)
|
||||
break
|
||||
case 'reveal': {
|
||||
const s = sides[ev.seat!]
|
||||
s.stack--
|
||||
@@ -52,7 +63,7 @@ function replay(events: BattleEvent[], stackSizes: number[], upto: number): Side
|
||||
s.unit = {
|
||||
card,
|
||||
foods: s.pending,
|
||||
bonus: s.pending.filter((f) => f.food === 'apple').length,
|
||||
bonus: ev.bonus ?? appleCount(s.pending),
|
||||
damage: 0,
|
||||
dying: false,
|
||||
}
|
||||
@@ -63,6 +74,9 @@ function replay(events: BattleEvent[], stackSizes: number[], upto: number): Side
|
||||
case 'summon':
|
||||
sides[ev.seat!].stack++
|
||||
break
|
||||
case 'mill':
|
||||
sides[ev.seat!].stack--
|
||||
break
|
||||
case 'rock': {
|
||||
const u = sides[ev.target!].unit
|
||||
if (u) {
|
||||
@@ -78,16 +92,81 @@ function replay(events: BattleEvent[], stackSizes: number[], upto: number): Side
|
||||
if (ev.died?.[seat]) s.unit.dying = true
|
||||
})
|
||||
break
|
||||
case 'strip': {
|
||||
const u = sides[ev.target!].unit
|
||||
if (u) {
|
||||
u.bonus -= appleCount(u.foods)
|
||||
u.foods = []
|
||||
if (ev.targetDied) u.dying = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'steal': {
|
||||
const from = sides[ev.target!].unit
|
||||
const to = sides[ev.seat!].unit
|
||||
const n = ev.count ?? 0
|
||||
if (from && to) {
|
||||
let moved = 0
|
||||
from.foods = from.foods.filter((f) => {
|
||||
if (f.food === 'apple' && moved < n) {
|
||||
moved++
|
||||
to.foods.push(f)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
from.bonus -= moved
|
||||
to.bonus += moved
|
||||
if (ev.targetDied) from.dying = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'eat': {
|
||||
const u = sides[ev.seat!].unit
|
||||
if (u) u.bonus = ev.bonus ?? u.bonus
|
||||
break
|
||||
}
|
||||
case 'heal': {
|
||||
const u = sides[ev.seat!].unit
|
||||
if (u) u.damage = ev.damageAfter ?? u.damage
|
||||
break
|
||||
}
|
||||
case 'shield':
|
||||
break // pure animation; no state change
|
||||
}
|
||||
}
|
||||
return sides
|
||||
}
|
||||
|
||||
// unitPop decides the floating effect text over a seat's pet for the event
|
||||
// currently playing. Null = nothing.
|
||||
function unitPop(ev: BattleEvent | null, seat: number, events: BattleEvent[], step: number): string | null {
|
||||
if (!ev) return null
|
||||
switch (ev.type) {
|
||||
case 'rock':
|
||||
if (ev.target !== seat) return null
|
||||
return ev.roll === 0 ? 'miss!' : `−${ev.roll}`
|
||||
case 'clash': {
|
||||
const taken = clashDamageTaken(events, step - 1, seat)
|
||||
return taken > 0 ? `−${taken}` : null
|
||||
}
|
||||
case 'shield':
|
||||
return ev.seat === seat ? '🛡️' : null
|
||||
case 'eat':
|
||||
return ev.seat === seat ? '🍎' : null
|
||||
case 'heal':
|
||||
return ev.seat === seat ? '💚 +1' : null
|
||||
case 'strip':
|
||||
return ev.target === seat ? '💨' : null
|
||||
case 'steal':
|
||||
if (ev.seat === seat) return `+🍎×${ev.count}`
|
||||
if (ev.target === seat) return `−🍎×${ev.count}`
|
||||
return null
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// BattlePhase plays back the battle log: cards flip off each deck, rocks
|
||||
// fly, pets clash, the fallen fade out, then the round result lands.
|
||||
export function BattlePhase({ view, send }: Props) {
|
||||
@@ -122,9 +201,10 @@ export function BattlePhase({ view, send }: Props) {
|
||||
const clashing = !done && lastEvent?.type === 'clash' && s.unit && !s.unit.dying
|
||||
const clashDying = lastEvent?.type === 'clash' && s.unit?.dying
|
||||
const rockVictim = lastEvent?.type === 'rock' && lastEvent.target === seat
|
||||
const eating = lastEvent?.type === 'eat' && lastEvent.seat === seat
|
||||
const summoning = !done && lastEvent?.type === 'summon' && lastEvent.seat === seat
|
||||
const milling = !done && lastEvent?.type === 'mill' && lastEvent.seat === seat
|
||||
const revealing = !done && lastEvent?.type === 'reveal' && lastEvent.seat === seat
|
||||
const pop = done ? null : unitPop(lastEvent, seat, events, step)
|
||||
|
||||
const stackEl = (
|
||||
<div className="stackpile">
|
||||
@@ -140,6 +220,11 @@ export function BattlePhase({ view, send }: Props) {
|
||||
<CardView card={lastEvent.card} size="sm" />
|
||||
</div>
|
||||
)}
|
||||
{milling && lastEvent?.card && (
|
||||
<div className="summon-pop mill-pop">
|
||||
<CardView card={lastEvent.card} size="sm" dead />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -182,16 +267,11 @@ export function BattlePhase({ view, send }: Props) {
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{(lastEvent?.type === 'clash' || rockVictim) && !done && (
|
||||
<div className="damage-pop">
|
||||
{lastEvent?.type === 'rock'
|
||||
? lastEvent.roll === 0
|
||||
? 'miss!'
|
||||
: `−${lastEvent.roll}`
|
||||
: `−${clashDamageTaken(events, step - 1, seat)}`}
|
||||
{pop && (
|
||||
<div key={`pop-${step}`} className={pop.startsWith('−') ? 'damage-pop' : 'fx-pop'}>
|
||||
{pop}
|
||||
</div>
|
||||
)}
|
||||
{eating && <div className="eat-pop">🍎 +1</div>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -284,7 +364,7 @@ function clashDamageTaken(events: BattleEvent[], idx: number, seat: number): num
|
||||
for (let k = idx - 1; k >= 0; k--) {
|
||||
const e = events[k]
|
||||
if (e.type === 'reveal' && e.seat === seat && e.card?.kind === 'pet') break
|
||||
if (e.type === 'rock' && e.target === seat) {
|
||||
if ((e.type === 'rock' || e.type === 'heal') && (e.target ?? e.seat) === seat) {
|
||||
before = e.damageAfter ?? 0
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user