Files
healer-man/src/GameRuntime.tsx
T
2026-09-10 14:14:02 -04:00

130 lines
6.0 KiB
TypeScript

import { useEffect } from "react";
import "./ui/uiLayout.css";
import { DualDisplayFrame } from "./components/DualDisplayFrame";
import { installInput, releasePointerLock } from "./game/inputManager";
import { CombatBridge } from "./game/CombatBridge";
import { useCombatStore } from "./game/combatStore";
import { releaseDungeonGltfCache } from "./game/dungeonAssetCache";
import { clearDungeonRuntimeCaches } from "./game/dungeonRuntimeReset";
import { requireDungeonDefinition } from "./game/dungeonRegistry";
import { PartyBridge } from "./game/PartyBridge";
import { ManastormBridge } from "./game/ManastormBridge";
import { releaseActiveManastormStage } from "./game/manastormStageLoader";
import { lootCurrentManastormBoss } from "./game/manastormLoot";
import { usePartyStore } from "./game/partyStore";
import { useGameStore } from "./game/store";
import { GameScene } from "./scene/GameScene";
import { LocalCompanionPanel } from "./ui/CompanionPanel";
import { Hud } from "./ui/Hud";
import { GameplayPanels } from "./ui/GameplayPanels";
import { MapPanel } from "./ui/MapPanel";
import { PauseMenu } from "./ui/PauseMenu";
import { SceneErrorBoundary } from "./ui/SceneErrorBoundary";
import { GmWorldEditor } from "./ui/GmWorldEditor";
import { GmReviewCard } from "./ui/GmReviewCard";
import { OnlineSessionBridge } from "./game/OnlineSessionBridge";
import { DungeonRunCheckpointBridge } from "./game/DungeonRunCheckpointBridge";
import { DungeonEncounterEffectBridge } from "./game/DungeonEncounterEffectBridge";
import { interactWithActiveDungeonObject } from "./game/dungeonWorldInteraction";
import { DungeonInteractionPrompt } from "./ui/DungeonInteractionPrompt";
import { UiLayoutEditor } from "./ui/UiLayoutEditor";
import { EditableHudElement } from "./ui/EditableHudElement";
import { activeUiLayout, currentUiDevice } from "./game/uiLayoutStore";
import { actionKeyForEvent, desktopSlotForKey } from "./game/uiLayout";
function InputBridge() {
const overlay = useGameStore((state) => state.overlay);
const companionOpen = useGameStore((state) => state.companionOpen);
useEffect(() => installInput({
desktopActionBarsActive: () => currentUiDevice() === "pc" && activeUiLayout().wowBars,
handleCustomKey: (event) => {
const game = useGameStore.getState();
if (game.overlay === "ui-layout") return !["Tab", "Enter", "Space", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.code);
if (currentUiDevice() !== "pc" || game.overlay || game.companionOpen || useCombatStore.getState().pendingGroundTarget) return false;
const button = desktopSlotForKey(activeUiLayout(), actionKeyForEvent(event));
if (!button) return false;
if (!event.repeat && button.abilityId) useCombatStore.getState().castAbility(button.abilityId, game.playerPosition, Date.now());
game.setInputMode("keyboard");
return true;
},
togglePause: () => useGameStore.getState().togglePause(),
toggleMap: () => useGameStore.getState().toggleMap(),
toggleInventory: () => useGameStore.getState().toggleOverlay("inventory"),
toggleSpellbook: () => useGameStore.getState().toggleOverlay("spellbook"),
toggleTalents: () => useGameStore.getState().toggleOverlay("talents"),
lootActiveBoss: () => interactWithActiveDungeonObject() || lootCurrentManastormBoss(),
castActionBinding: (layer, control) => {
const game = useGameStore.getState();
useCombatStore.getState().castActionBinding(layer, control, game.playerPosition, Date.now());
},
cycleTarget: (direction) => {
const game = useGameStore.getState();
usePartyStore.getState().clearSelection();
useCombatStore.getState().cycleTarget(direction, game.playerPosition);
},
cyclePartyTarget: (direction) => {
useCombatStore.getState().clearTarget();
usePartyStore.getState().cycleMember(direction);
},
setPartyCommand: (command) => usePartyStore.getState().setCommand(command),
recallParty: () => usePartyStore.getState().recallParty(),
clearTarget: () => {
useCombatStore.getState().clearTarget();
usePartyStore.getState().clearSelection();
},
groundTargetActive: () => useCombatStore.getState().pendingGroundTarget !== null,
confirmGroundTarget: () => { useCombatStore.getState().confirmGroundTarget(); },
cancelGroundTarget: () => useCombatStore.getState().cancelGroundTarget(),
gameplayActionsBlocked: () => {
const game = useGameStore.getState();
return game.overlay !== null || game.companionOpen;
},
setCameraLookActive: (active) => useGameStore.getState().setCameraLookActive(active),
setInputMode: (mode) => useGameStore.getState().setInputMode(mode),
setActionLayer: (layer) => useGameStore.getState().setActionLayer(layer),
}), []);
useEffect(() => () => releasePointerLock(), []);
useEffect(() => {
if (overlay || companionOpen) releasePointerLock();
}, [companionOpen, overlay]);
return null;
}
export default function GameRuntime() {
useEffect(() => () => {
const activeDungeon = requireDungeonDefinition(useGameStore.getState().activeDungeonId);
clearDungeonRuntimeCaches();
releaseDungeonGltfCache(activeDungeon);
releaseActiveManastormStage();
}, []);
return (
<DualDisplayFrame
contextLabel="Tactical"
onActiveSurfaceChange={(surface) => useGameStore.getState().setCompanionOpen(surface === "bottom")}
top={
<main className="game-shell">
<InputBridge />
<OnlineSessionBridge />
<DungeonEncounterEffectBridge />
<DungeonRunCheckpointBridge />
<CombatBridge />
<PartyBridge />
<ManastormBridge />
<SceneErrorBoundary><GameScene /></SceneErrorBoundary>
<Hud />
<EditableHudElement id="interaction"><DungeonInteractionPrompt /></EditableHudElement>
<MapPanel />
<GameplayPanels />
<PauseMenu />
<UiLayoutEditor />
<GmReviewCard />
<GmWorldEditor />
</main>
}
bottom={<LocalCompanionPanel />}
/>
);
}