/** * @file Debug panel component with administrative tools for correcting player state. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Panel has multiple async handlers and conditional renders */ /* eslint-disable stylistic/max-len -- Debug descriptions require full explanatory text */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { ConfirmationModal } from "../ui/confirmationModal.js"; type ActiveModal = "force-unlocks" | "hard-reset" | "sync-new-content" | null; interface SyncNewContentResult { achievementsAdded: number | undefined; adventurersAdded: number | undefined; bossesAdded: number | undefined; bossRewardsPatched: number | undefined; equipmentAdded: number | undefined; explorationAreasAdded: number | undefined; questRewardsPatched: number | undefined; questsAdded: number | undefined; upgradesAdded: number | undefined; zonesAdded: number | undefined; } const safeNumber = (value: number | undefined): number => { return value ?? 0; }; /** * Builds a human-readable summary of what the sync-new-content operation added. * @param result - The counts returned by the operation. * @returns A message string describing what was added, or a confirmation nothing was needed. */ const buildSyncNewContentMessage = (result: SyncNewContentResult): string => { const entries: Array<[ number, string ]> = [ [ safeNumber(result.zonesAdded), "zone(s)" ], [ safeNumber(result.questsAdded), "quest(s)" ], [ safeNumber(result.questRewardsPatched), "quest reward(s) patched" ], [ safeNumber(result.bossesAdded), "boss(es)" ], [ safeNumber(result.bossRewardsPatched), "boss reward(s) patched" ], [ safeNumber(result.explorationAreasAdded), "exploration area(s)" ], [ safeNumber(result.adventurersAdded), "adventurer tier(s)" ], [ safeNumber(result.upgradesAdded), "upgrade(s)" ], [ safeNumber(result.equipmentAdded), "equipment item(s)" ], [ safeNumber(result.achievementsAdded), "achievement(s)" ], ]; const parts = entries. filter(([ count ]) => { return count > 0; }). map(([ count, label ]) => { return `${String(count)} ${label}`; }); if (parts.length === 0) { return "Your save is already up to date — no new content was found."; } const total = entries.reduce((sum, [ count ]) => { return sum + count; }, 0); return `Synced ${String(total)} item(s): ${parts.join(", ")}.`; }; interface ForceUnlocksResult { adventurersUnlocked: number | undefined; bossesUnlocked: number | undefined; equipmentUnlocked: number | undefined; explorationUnlocked: number | undefined; questsUnlocked: number | undefined; storyUnlocked: number | undefined; upgradesUnlocked: number | undefined; zonesUnlocked: number | undefined; } /** * Builds a human-readable summary of what the force-unlock operation corrected. * @param result - The counts returned by the force-unlock operation. * @returns A message string describing what was fixed, or a confirmation that nothing needed fixing. */ const buildForceUnlocksMessage = (result: ForceUnlocksResult): string => { const entries: Array<[ number, string ]> = [ [ safeNumber(result.zonesUnlocked), "zone(s)" ], [ safeNumber(result.questsUnlocked), "quest(s)" ], [ safeNumber(result.bossesUnlocked), "boss(es)" ], [ safeNumber(result.explorationUnlocked), "exploration area(s)" ], [ safeNumber(result.adventurersUnlocked), "adventurer tier(s)" ], [ safeNumber(result.upgradesUnlocked), "upgrade(s)" ], [ safeNumber(result.equipmentUnlocked), "equipment item(s)" ], [ safeNumber(result.storyUnlocked), "story chapter(s)" ], ]; const parts = entries. filter(([ count ]) => { return count > 0; }). map(([ count, label ]) => { return `${String(count)} ${label}`; }); if (parts.length === 0) { return "Everything looks correct — no missing unlocks were found."; } const total = entries.reduce((sum, [ count ]) => { return sum + count; }, 0); return `Fixed ${String(total)} unlock(s): ${parts.join(", ")}.`; }; /** * Renders the debug panel with tools for fixing stuck game state. * @returns The JSX element. */ const DebugPanel = (): JSX.Element => { const { forceUnlocks, debugHardReset, syncNewContent, isLoading } = useGame(); const [ activeModal, setActiveModal ] = useState(null); const [ forceUnlocksResult, setForceUnlocksResult ] = useState(null); const [ syncNewContentResult, setSyncNewContentResult ] = useState(null); function handleOpenForceUnlocks(): void { setForceUnlocksResult(null); setActiveModal("force-unlocks"); } function handleOpenSyncNewContent(): void { setSyncNewContentResult(null); setActiveModal("sync-new-content"); } function handleOpenHardReset(): void { setActiveModal("hard-reset"); } function handleCancel(): void { setActiveModal(null); } function handleConfirmForceUnlocks(): void { setActiveModal(null); void (async(): Promise => { const result = await forceUnlocks(); setForceUnlocksResult(buildForceUnlocksMessage(result)); })(); } function handleConfirmSyncNewContent(): void { setActiveModal(null); void (async(): Promise => { const result = await syncNewContent(); setSyncNewContentResult(buildSyncNewContentMessage(result)); })(); } function handleConfirmHardReset(): void { setActiveModal(null); void debugHardReset(); } return (

{"🔧 Debug Tools"}

{ "These tools are intended to fix broken game state. Use them with care — some operations are irreversible." }

{"🔓 Force Unlocks"}

{ "Scans your game state and unlocks any zones, quests, and bosses that you have earned but that are still incorrectly locked." }

{forceUnlocksResult !== null &&

{forceUnlocksResult}

}

{"🔄 Sync New Content"}

{ "If the game has been updated since your save was created, this will add any missing adventurers, quests, bosses, equipment, upgrades, and more to your save without affecting your existing progress." }

{syncNewContentResult !== null &&

{syncNewContentResult}

}

{"💀 Hard Reset"}

{ "Completely wipes all progress and resets your account to a brand-new state. This cannot be undone." }

{activeModal === "force-unlocks" && } {activeModal === "sync-new-content" && } {activeModal === "hard-reset" && }
); }; export { DebugPanel };