import { lazy, Suspense, useEffect, useMemo, useState, type ReactNode } from "react"; import { BASE_CLASS_IDS, classById } from "./app/characterCatalog"; import { useShellStore } from "./app/shellStore"; import { isShelvedShellPhase, SHELVED_MODES_ENABLED } from "./app/releaseFeatures"; import { preloadAbilityCatalogsForClasses } from "./game/abilityCatalog"; import { useAuthoritativeDualScreenSync, useForcedThorDisplays } from "./platform/useThorDualScreen"; import { LoginScreen } from "./ui/LoginScreen"; import { OnlineGroupCoordinator } from "./app/OnlineGroupCoordinator"; import { ShelvedModeRoute } from "@shelvedModeRoutes"; import { preloadTalentGameplayResources } from "./ui/talentGameplayResources"; import { getLoadedGameRuntime, loadGameRuntime, preloadSharedGameplayResources, } from "./ui/gameplayResources"; import { useCompanionCooldownPolicyStore } from "./game/companionCooldownPolicyStore"; import { useCompanionSpecReadinessStore } from "./game/companionSpecReadinessStore"; import { usePartyBuffPolicyStore } from "./game/partyBuffPolicyStore"; const GameRuntime = lazy(loadGameRuntime); const CharacterCreateScreen = lazy(() => import("./ui/CharacterCreateScreen").then((module) => ({ default: module.CharacterCreateScreen }))); const CharacterSelectScreen = lazy(() => import("./ui/CharacterSelectScreen").then((module) => ({ default: module.CharacterSelectScreen }))); const ClassHallScreen = lazy(() => import("./ui/ClassHallScreen").then((module) => ({ default: module.ClassHallScreen }))); const DungeonSelectScreen = lazy(() => import("./ui/DungeonSelectScreen").then((module) => ({ default: module.DungeonSelectScreen }))); const MainMenuScreen = lazy(() => import("./ui/MainMenuScreen").then((module) => ({ default: module.MainMenuScreen }))); const DungeonSimulationAdminScreen = lazy(() => import("./ui/DungeonSimulationAdminScreen").then((module) => ({ default: module.DungeonSimulationAdminScreen }))); function ShellTransition() { return
HMOpening expeditionPreparing the next screen...
; } function RuntimeCatalogGate({ children, preloadGameplay }: { readonly children: ReactNode; readonly preloadGameplay: boolean }) { const character = useShellStore((state) => ( state.characters.find((candidate) => candidate.id === state.selectedCharacterId) ?? null )); const classIds = useMemo(() => { if (!character) return []; const definition = classById(character.classId); if (definition.mode === "conquest") return [...BASE_CLASS_IDS, character.classId]; return [character.classId, ...(character.secondaryClassId ? [character.secondaryClassId] : [])]; }, [character]); const talentClassId = character && !character.classId.startsWith("rom-") ? character.classId : null; const originalMediaEnabled = useShellStore((state) => state.combatPresentationSettings.originalMediaEnabled); const requestKey = classIds.length ? `${classIds.join(":")}|${preloadGameplay ? `game:${originalMediaEnabled}` : "shell"}` : ""; const [loadedKey, setLoadedKey] = useState(""); const [failedKey, setFailedKey] = useState(""); useEffect(() => { if (!requestKey) return; let active = true; setFailedKey(""); void Promise.all([ preloadAbilityCatalogsForClasses(classIds), preloadGameplay && talentClassId ? preloadTalentGameplayResources(talentClassId) : Promise.resolve(), preloadGameplay ? preloadSharedGameplayResources(originalMediaEnabled) : Promise.resolve(), ]).then(() => { if (active) setLoadedKey(requestKey); }).catch(() => { if (active) setFailedKey(requestKey); }); return () => { active = false; }; }, [classIds, originalMediaEnabled, preloadGameplay, requestKey, talentClassId]); if (!requestKey || loadedKey === requestKey) return children; if (failedKey === requestKey) { return
!Catalog unavailableReload the app to retry loading combat data.
; } return ; } export default function App() { useForcedThorDisplays(); useAuthoritativeDualScreenSync(); const phase = useShellStore((state) => state.phase); const session = useShellStore((state) => state.session); const PreloadedGameRuntime = getLoadedGameRuntime(); const refreshCooldownPolicy = useCompanionCooldownPolicyStore((state) => state.refresh); const refreshSpecReadiness = useCompanionSpecReadinessStore((state) => state.refresh); const refreshPartyBuffPolicy = usePartyBuffPolicyStore((state) => state.refresh); useEffect(() => { void refreshCooldownPolicy(session); }, [refreshCooldownPolicy, session]); useEffect(() => { void refreshSpecReadiness(session); }, [refreshSpecReadiness, session]); useEffect(() => { void refreshPartyBuffPolicy(session); }, [refreshPartyBuffPolicy, session]); useEffect(() => { if (!SHELVED_MODES_ENABLED && isShelvedShellPhase(phase)) { useShellStore.getState().returnToMainMenu(); useShellStore.getState().setNotice("That game mode is temporarily unavailable."); } }, [phase]); let screen = null; if (phase === "login") screen = ; else if (phase === "characters") screen = }>; else if (phase === "create-character") screen = }>; else if (phase === "main-menu") screen = }>; else if (phase === "class-hall") screen = }>; else if (phase === "dungeons") screen = }>; else if (phase === "gm-admin") screen = }>; else if (isShelvedShellPhase(phase)) { screen = }>; } else if (phase === "game") screen = PreloadedGameRuntime ? : }>; const requiresRuntimeCatalog = !["login", "characters", "create-character"].includes(phase); return <>{requiresRuntimeCatalog ? {screen} : screen}; }