import { lazy, Suspense } from "react"; import { barrierProtects, upcomingEncounterMechanic, useGameStore } from "../game/store"; import { HEALER_CLASSES } from "../game/healers"; import { BOSS_DEFINITIONS } from "../game/bossCatalog"; import { bossRoomFor } from "../game/bossRooms"; import { tankAuraProtects } from "../game/partyCombat"; import { BuffDraftPanel } from "./BuffDraftPanel"; import { DEFAULT_CONTROLLER_GLYPHS } from "../input/controllerGlyphs"; const GameScene = lazy(() => import("./GameScene").then((module) => ({ default: module.GameScene }))); function CompactParty() { const party = useGameStore((state) => state.party); const time = useGameStore((state) => state.time); const selected = useGameStore((state) => state.selectedMemberId); const barrier = useGameStore((state) => state.barrier); const tankAura = useGameStore((state) => state.partyCombat.tankAura); const partyPositions = useGameStore((state) => state.partyPositions); const selectMember = useGameStore((state) => state.selectMember); const mana = useGameStore((state) => state.mana); const maxMana = useGameStore((state) => state.maxMana); return (
{party.map((member) => { const health = (member.hp / member.maxHp) * 100; const shield = (member.absorb / member.maxHp) * 100; return ( ); })}
); } function CastingBar() { const healerClassId = useGameStore((state) => state.healerClassId); const abilityName = HEALER_CLASSES[healerClassId].abilities.mend.name; const activeCast = useGameStore((state) => state.activeCast); const time = useGameStore((state) => state.time); const party = useGameStore((state) => state.party); if (!activeCast) return null; const duration = activeCast.completesAt - activeCast.startedAt; const progress = Math.min(1, Math.max(0, (time - activeCast.startedAt) / duration)); const target = party.find((member) => member.id === activeCast.targetId); return (
{abilityName}{target?.name} {Math.max(0, activeCast.completesAt - time).toFixed(1)}s
); } function BossBar() { const boss = useGameStore((state) => state.boss); const additionalBosses = useGameStore((state) => state.additionalBosses); const phase = useGameStore((state) => state.phase); if (phase === "briefing") return null; const bosses = [boss, ...additionalBosses.map((entry) => entry.boss)]; return (
1 ? "is-multi" : ""} ${bosses.length === 3 ? "is-trio" : ""}`}> {bosses.map((entry) =>
Vault Beast{entry.name}{Math.ceil((entry.hp / entry.maxHp) * 100)}%
)}
); } function EncounterCallout() { const phase = useGameStore((state) => state.phase); const time = useGameStore((state) => state.time); const boss = useGameStore((state) => state.boss); const bossMotion = useGameStore((state) => state.bossMotion); const additionalBosses = useGameStore((state) => state.additionalBosses); if (phase !== "combat") return null; const mechanic = upcomingEncounterMechanic({ boss, bossMotion, additionalBosses, time }); return (
{mechanic.name} {mechanic.remaining.toFixed(1)}s
); } function PhaseOverlay() { const phase = useGameStore((state) => state.phase); const runMode = useGameStore((state) => state.runMode); const round = useGameStore((state) => state.round); const endlessMode = useGameStore((state) => state.endlessMode); const endlessBossKills = useGameStore((state) => state.endlessBossKills); const primaryBoss = useGameStore((state) => state.boss); const additionalBosses = useGameStore((state) => state.additionalBosses); if (phase === "intermission") return ; const bosses = [primaryBoss, ...additionalBosses.map((entry) => entry.boss)]; const definitions = bosses.map((boss) => BOSS_DEFINITIONS[boss.id]); const room = bossRoomFor(primaryBoss.id); const bossNames = bosses.map((boss) => boss.name).join(" & "); const showEndlessChoice = phase === "victory" && runMode === "rogue-trials" && round === 5 && !endlessMode; const endlessDefeat = phase === "defeat" && endlessMode; const briefingMode = runMode === "rogue-trials" ? bosses.length === 3 ? "Rogue Trials · Trio Finale" : "Rogue Trials · Dual Round" : bosses.length > 1 ? "Roguelike PVE · Dual Encounter" : definitions[0].trial; if (phase === "combat") return null; const title = phase === "briefing" ? room.name : phase === "victory" ? showEndlessChoice ? "Rogue Trials Cleared" : `${bossNames} Broken` : endlessDefeat ? "Endless Run Ended" : "Party Broken"; const eyebrow = phase === "briefing" ? `${briefingMode} · ${room.biome}` : phase === "victory" ? showEndlessChoice ? "Endless Path Unlocked" : "Encounter Complete" : endlessDefeat ? `${endlessBossKills} Endless Bosses Defeated` : "Encounter Failed"; const copy = phase === "briefing" ? definitions.map((boss) => boss.briefing).join(" ") : phase === "victory" ? showEndlessChoice ? "Leave with the clear, or continue against an unbroken chain of replacement bosses." : "Five entered. Five endured." : endlessDefeat ? `Run record: ${endlessBossKills} bosses defeated after the trio finale.` : definitions.map((boss) => boss.failure).join(" "); return (
{eyebrow}

{title}

{copy}

{phase === "briefing" ? "Begin from lower display" : showEndlessChoice ? "Choose Continue or Quit on lower display" : "Restart from lower display"}
); } function PauseOverlay({ onExit }: { onExit?: () => void }) { const paused = useGameStore((state) => state.paused); const selection = useGameStore((state) => state.pauseSelection); const setPaused = useGameStore((state) => state.setPaused); const setPauseSelection = useGameStore((state) => state.setPauseSelection); if (!paused) return null; const exit = () => { setPaused(false); onExit?.(); }; return (
Encounter suspended

Paused

Simulation, damage, and movement are stopped.

↑ / ↓ Choose {DEFAULT_CONTROLLER_GLYPHS.confirm} / ENTER Confirm
); } export function TopScreen({ onExit }: { onExit?: () => void }) { const phase = useGameStore((state) => state.phase); const bossCount = useGameStore((state) => state.additionalBosses.length + 1); const round = useGameStore((state) => state.round); const runMode = useGameStore((state) => state.runMode); const endlessMode = useGameStore((state) => state.endlessMode); const endlessBossKills = useGameStore((state) => state.endlessBossKills); const setPaused = useGameStore((state) => state.setPaused); return (
}>
{endlessMode ? `Endless · ${endlessBossKills} kills` : runMode !== "encounter" ? `Round ${round}` : "Objective"}{endlessMode ? "Defeat bosses · replacements incoming" : bossCount === 3 ? "Defeat trio · keep five alive" : bossCount === 2 ? "Defeat both · keep five alive" : "Keep all five alive"}
WASD Move Q / E Target 1–6 Cast
{onExit && }
); }