import { IWT2_BOSS_METADATA, type Iwt2BossId } from '../content/bosses' import { IWT2_CLASS_METADATA } from '../content/classes' import { createInitialIwt2ArenaState as createCoreIwt2ArenaState, tickIwt2Arena as tickCoreIwt2Arena, } from './arena' import type { Iwt2ArenaIndicator, Iwt2ArenaInput, Iwt2ArenaState as Iwt2CoreArenaState, Iwt2BossEntityState, Iwt2HostileAddState, Iwt2PartyEntityState, Iwt2RoguelikePressureState, } from './types' export type Iwt2ArenaEntityKind = 'player' | 'party' | 'boss' | 'projectile' | 'hostileAdd' export type Iwt2ArenaEntity = { id: string kind: Iwt2ArenaEntityKind icon: string color: string x: number y: number radius: number health: number maxHealth: number stunnedFor: number } export type Iwt2ArenaTelegraph = | { kind: 'charge' x: number y: number width: number height: number active: boolean } | { kind: 'slam' x: number y: number radius: number active: boolean } export type Iwt2ArenaState = Iwt2CoreArenaState & { arena: { width: number height: number } entities: Iwt2ArenaEntity[] telegraphs: Iwt2ArenaTelegraph[] } export function createInitialIwt2ArenaState( bossId?: Iwt2BossId, bossIds?: Iwt2BossId[], bossHealthScale?: number, partyDamageTakenScale?: number, roguelikePressure?: Iwt2RoguelikePressureState, ): Iwt2ArenaState { return decorateArenaState(createCoreIwt2ArenaState(bossId, bossIds, bossHealthScale, partyDamageTakenScale, roguelikePressure)) } export function tickIwt2Arena(state: Iwt2ArenaState, input: Iwt2ArenaInput, dt: number): Iwt2ArenaState { return decorateArenaState(tickCoreIwt2Arena(state, input, dt)) } export function decorateArenaState(state: Iwt2CoreArenaState): Iwt2ArenaState { return { ...state, arena: { ...state.bounds }, entities: [ ...state.party.map(toArenaEntity), ...state.hostileAdds.map(toHostileAddArenaEntity), ...state.bosses.map(toBossArenaEntity), ], telegraphs: createTelegraphs(state.indicators), } } function toHostileAddArenaEntity(add: Iwt2HostileAddState): Iwt2ArenaEntity { return { id: add.id, kind: 'hostileAdd', icon: 'v', color: '#f0b84f', x: add.position.x, y: add.position.y, radius: add.radius, health: add.health, maxHealth: add.maxHealth, stunnedFor: 0, } } function toArenaEntity(member: Iwt2PartyEntityState): Iwt2ArenaEntity { const metadata = IWT2_CLASS_METADATA[member.classId] return { id: member.id, kind: member.aiRole === 'player' ? 'player' : 'party', icon: metadata.icon, color: metadata.color, x: member.position.x, y: member.position.y, radius: member.radius, health: member.health, maxHealth: member.maxHealth, stunnedFor: Math.max(member.status.stunnedSeconds, member.status.knockedDownSeconds), } } function toBossArenaEntity(boss: Iwt2BossEntityState): Iwt2ArenaEntity { const metadata = IWT2_BOSS_METADATA[boss.bossId] return { id: boss.id, kind: 'boss', icon: metadata.icon, color: metadata.color, x: boss.position.x, y: boss.position.y, radius: boss.radius, health: boss.health, maxHealth: boss.maxHealth, stunnedFor: 0, } } function createTelegraphs(indicators: Iwt2ArenaIndicator[]): Iwt2ArenaTelegraph[] { return indicators.flatMap((indicator): Iwt2ArenaTelegraph[] => { if (indicator.kind === 'lane') { return [{ active: indicator.phase === 'active', height: indicator.width * 2, kind: 'charge' as const, width: Math.max(16, Math.hypot(indicator.end.x - indicator.start.x, indicator.end.y - indicator.start.y)), x: Math.min(indicator.start.x, indicator.end.x), y: Math.min(indicator.start.y, indicator.end.y) - indicator.width, }] } if (indicator.kind === 'circle') { return [{ active: indicator.phase === 'active', kind: 'slam' as const, radius: indicator.radius, x: indicator.position.x, y: indicator.position.y, }] } return [] }) }