Files
healer-man/src/GameRuntime.tsx
T
2026-09-13 11:07:56 -04:00

137 lines
6.4 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 { connectDpsMeter } from "./game/dpsMeterStore";
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 { 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 { OnlineSessionBridge } from "./game/OnlineSessionBridge";
import { DungeonRunCheckpointBridge } from "./game/DungeonRunCheckpointBridge";
import { DungeonRunBridge } from "./game/DungeonRunBridge";
import { DungeonRunResults } from "./ui/DungeonRunResults";
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 { useUiLayoutStore } from "./game/uiLayoutStore";
import { profileInputBindings } from "./game/keybindPreferences";
import { KeybindsPanel } from "./ui/KeybindsPanel";
import { lootShelvedBoss, releaseShelvedGameRuntime, ShelvedGameBridges } from "@shelvedGameRuntime";
function InputBridge() {
const overlay = useGameStore((state) => state.overlay);
const companionOpen = useGameStore((state) => state.companionOpen);
useEffect(() => installInput({
desktopActionBarsActive: () => currentUiDevice() === "pc" && activeUiLayout().wowBars,
inputSuspended: () => ["ui-layout", "keybinds", "dungeon-results"].includes(useGameStore.getState().overlay ?? ""),
getBindings: (device) => {
const preferences = useUiLayoutStore.getState().preferences;
const bindings = profileInputBindings(preferences, device);
for (let b = activeUiLayout().barCount; b < 5; b++) for (let s = 0; s < 12; s++) bindings[`bar:${b}:${s}`] = null;
return bindings;
},
castDesktopSlot: (bar, slot) => {
const game = useGameStore.getState(); const button = activeUiLayout().bars[bar]?.slots[slot];
if (button?.abilityId) useCombatStore.getState().castAbility(button.abilityId, game.playerPosition, Date.now());
},
togglePanel: (panel) => useGameStore.getState().toggleOverlay(panel),
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() || lootShelvedBoss(),
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(connectDpsMeter, []);
const editingControls = useGameStore((state) => state.overlay === "keybinds" || state.overlay === "ui-layout");
useEffect(() => () => {
const activeDungeon = requireDungeonDefinition(useGameStore.getState().activeDungeonId);
clearDungeonRuntimeCaches();
releaseDungeonGltfCache(activeDungeon);
releaseShelvedGameRuntime();
}, []);
return (
<DualDisplayFrame
contextLabel="Tactical"
companionDisabled={editingControls}
onActiveSurfaceChange={(surface) => useGameStore.getState().setCompanionOpen(surface === "bottom")}
top={
<main className="game-shell">
<InputBridge />
<OnlineSessionBridge />
<DungeonEncounterEffectBridge />
<DungeonRunCheckpointBridge />
<CombatBridge />
<PartyBridge />
<DungeonRunBridge />
<ShelvedGameBridges />
<SceneErrorBoundary><GameScene /></SceneErrorBoundary>
<Hud />
<EditableHudElement id="interaction"><DungeonInteractionPrompt /></EditableHudElement>
<MapPanel />
<GameplayPanels />
<PauseMenu />
<DungeonRunResults />
<UiLayoutEditor />
<KeybindsPanel />
</main>
}
bottom={<LocalCompanionPanel />}
/>
);
}