Files
La-Fabrik/src/components/ui/debug/GameStateDebugPanel.tsx
T
Tom Boullay d1665891f4 feat(repair): filter debug sub-state options by current mission
Pylon-only mission steps (approaching/arrived/npc-return/narrator-outro)
no longer appear in the GameStateDebugPanel sub-state dropdown for the
ebike or farm missions, which use the shorter
locked/waiting/inspected/fragmented/scanning/repairing/reassembling/done
flow.
2026-06-02 21:59:54 +02:00

199 lines
6.0 KiB
TypeScript

import { RotateCcw, StepBack, StepForward } from "lucide-react";
import {
GAME_STEPS,
isGameStep,
MAIN_GAME_STATES,
} from "@/data/game/gameStateConfig";
import {
getMissionStepsFor,
isMissionStep,
} from "@/data/gameplay/repairMissionState";
import { useGameStore } from "@/managers/stores/useGameStore";
import type { MainGameState } from "@/types/game";
function toPascalCase(value: string): string {
return value
.split(/[-_\s]+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join("");
}
export function GameStateDebugPanel(): React.JSX.Element {
const mainState = useGameStore((state) => state.mainState);
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
const pylonStep = useGameStore((state) => state.pylon.currentStep);
const farmStep = useGameStore((state) => state.farm.currentStep);
const detail = useGameStore((state) => {
switch (state.mainState) {
case "intro":
return state.intro.currentStep;
case "ebike":
return state.ebike.currentStep;
case "pylon":
return state.pylon.currentStep;
case "farm":
return state.farm.currentStep;
case "outro":
return state.outro.hasStarted ? "started" : "waiting";
}
});
const setMainState = useGameStore((state) => state.setMainState);
const setIntroStep = useGameStore((state) => state.setIntroStep);
const setEbikeState = useGameStore((state) => state.setEbikeState);
const setPylonState = useGameStore((state) => state.setPylonState);
const setFarmState = useGameStore((state) => state.setFarmState);
const setOutroState = useGameStore((state) => state.setOutroState);
const advanceGameState = useGameStore((state) => state.advanceGameState);
const rewindGameState = useGameStore((state) => state.rewindGameState);
const resetGame = useGameStore((state) => state.resetGame);
const subStateOptions =
mainState === "intro"
? GAME_STEPS
: mainState === "outro"
? ["waiting", "started"]
: mainState === "ebike" || mainState === "pylon" || mainState === "farm"
? getMissionStepsFor(mainState)
: [];
function setSubState(nextSubState: string): void {
if (mainState === "intro") {
if (isGameStep(nextSubState)) {
setIntroStep(nextSubState);
}
return;
}
if (mainState === "outro") {
setOutroState({ hasStarted: nextSubState === "started" });
return;
}
if (!isMissionStep(nextSubState)) return;
if (mainState === "ebike") {
setEbikeState({ currentStep: nextSubState });
return;
}
if (mainState === "pylon") {
setPylonState({ currentStep: nextSubState });
return;
}
if (mainState === "farm") {
setFarmState({ currentStep: nextSubState });
return;
}
}
function setDebugMainState(nextMainState: MainGameState): void {
setMainState(nextMainState);
if (
nextMainState === "pylon" ||
nextMainState === "farm" ||
nextMainState === "outro"
) {
setEbikeState({ currentStep: "done", isRepaired: true });
}
if (nextMainState === "farm" || nextMainState === "outro") {
setPylonState({ currentStep: "done", isPowered: true });
}
if (nextMainState === "outro") {
setFarmState({ currentStep: "done", irrigationFixed: true });
}
if (nextMainState === "ebike" && ebikeStep === "locked") {
setEbikeState({ currentStep: "waiting" });
return;
}
if (nextMainState === "pylon" && pylonStep === "locked") {
setPylonState({ currentStep: "waiting" });
return;
}
if (nextMainState === "farm" && farmStep === "locked") {
setFarmState({ currentStep: "waiting" });
}
}
return (
<section
className="game-state-debug-panel debug-overlay-section"
aria-label="Game state debug panel"
>
<div className="game-state-debug-panel__header">
<h3>Game State</h3>
</div>
<div className="game-state-debug-panel__switch-group">
<div className="game-state-debug-panel__switch-heading">
<span>Main state</span>
<strong>{toPascalCase(mainState)}</strong>
</div>
<div
className="game-state-debug-panel__states"
aria-label="Main states"
role="group"
>
{MAIN_GAME_STATES.map((state) => (
<button
key={state}
aria-pressed={state === mainState}
className={state === mainState ? "is-active" : undefined}
type="button"
onClick={() => setDebugMainState(state)}
>
{toPascalCase(state)}
</button>
))}
</div>
</div>
<div className="game-state-debug-panel__switch-group">
<div className="game-state-debug-panel__switch-heading">
<span>Sub state</span>
<strong>{toPascalCase(detail)}</strong>
</div>
<div
className="game-state-debug-panel__states"
aria-label="Sub states"
role="group"
>
{subStateOptions.map((subState) => (
<button
key={subState}
aria-pressed={subState === detail}
className={subState === detail ? "is-active" : undefined}
type="button"
onClick={() => setSubState(subState)}
>
{toPascalCase(subState)}
</button>
))}
</div>
</div>
<div className="game-state-debug-panel__actions">
<button type="button" onClick={rewindGameState}>
<StepBack aria-hidden="true" size={14} />
Previous step
</button>
<button type="button" onClick={advanceGameState}>
<StepForward aria-hidden="true" size={14} />
Next step
</button>
<button type="button" onClick={resetGame}>
<RotateCcw aria-hidden="true" size={14} />
Reset
</button>
</div>
</section>
);
}