generated from nhcarrigan/template
0c7a5f50fc
Closes #125
263 lines
9.4 KiB
TypeScript
263 lines
9.4 KiB
TypeScript
/**
|
|
* @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<ActiveModal>(null);
|
|
const [ forceUnlocksResult, setForceUnlocksResult ] = useState<string | null>(null);
|
|
const [ syncNewContentResult, setSyncNewContentResult ] = useState<string | null>(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<void> => {
|
|
const result = await forceUnlocks();
|
|
setForceUnlocksResult(buildForceUnlocksMessage(result));
|
|
})();
|
|
}
|
|
|
|
function handleConfirmSyncNewContent(): void {
|
|
setActiveModal(null);
|
|
void (async(): Promise<void> => {
|
|
const result = await syncNewContent();
|
|
setSyncNewContentResult(buildSyncNewContentMessage(result));
|
|
})();
|
|
}
|
|
|
|
function handleConfirmHardReset(): void {
|
|
setActiveModal(null);
|
|
void debugHardReset();
|
|
}
|
|
|
|
return (
|
|
<div className="panel">
|
|
<h2>{"🔧 Debug Tools"}</h2>
|
|
<p className="panel-description">
|
|
{
|
|
"These tools are intended to fix broken game state. Use them with care — some operations are irreversible."
|
|
}
|
|
</p>
|
|
|
|
<div className="debug-actions">
|
|
<div className="debug-action-card">
|
|
<h3>{"🔓 Force Unlocks"}</h3>
|
|
<p>
|
|
{
|
|
"Scans your game state and unlocks any zones, quests, and bosses that you have earned but that are still incorrectly locked."
|
|
}
|
|
</p>
|
|
<button
|
|
className="action-button"
|
|
disabled={isLoading}
|
|
onClick={handleOpenForceUnlocks}
|
|
type="button"
|
|
>
|
|
{"Force Unlocks"}
|
|
</button>
|
|
{forceUnlocksResult !== null
|
|
&& <p className="debug-result-message">{forceUnlocksResult}</p>
|
|
}
|
|
</div>
|
|
|
|
<div className="debug-action-card">
|
|
<h3>{"🔄 Sync New Content"}</h3>
|
|
<p>
|
|
{
|
|
"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."
|
|
}
|
|
</p>
|
|
<button
|
|
className="action-button"
|
|
disabled={isLoading}
|
|
onClick={handleOpenSyncNewContent}
|
|
type="button"
|
|
>
|
|
{"Sync New Content"}
|
|
</button>
|
|
{syncNewContentResult !== null
|
|
&& <p className="debug-result-message">{syncNewContentResult}</p>
|
|
}
|
|
</div>
|
|
|
|
<div className="debug-action-card">
|
|
<h3>{"💀 Hard Reset"}</h3>
|
|
<p>
|
|
{
|
|
"Completely wipes all progress and resets your account to a brand-new state. This cannot be undone."
|
|
}
|
|
</p>
|
|
<button
|
|
className="action-button action-button-danger"
|
|
disabled={isLoading}
|
|
onClick={handleOpenHardReset}
|
|
type="button"
|
|
>
|
|
{"Hard Reset"}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{activeModal === "force-unlocks"
|
|
&& <ConfirmationModal
|
|
confirmLabel="Yes, Force Unlocks"
|
|
description="This will scan your save data and grant access to any zones, quests, and bosses that you have already earned but are incorrectly locked. This operation is safe and non-destructive."
|
|
isLoading={isLoading}
|
|
onCancel={handleCancel}
|
|
onConfirm={handleConfirmForceUnlocks}
|
|
title="Force Unlocks"
|
|
/>
|
|
}
|
|
|
|
{activeModal === "sync-new-content"
|
|
&& <ConfirmationModal
|
|
confirmLabel="Yes, Sync New Content"
|
|
description="This will scan for any adventurers, quests, bosses, equipment, upgrades, achievements, and zones added to the game after your save was created, and add them to your save. This operation is safe and non-destructive — your existing progress will not be affected."
|
|
isLoading={isLoading}
|
|
onCancel={handleCancel}
|
|
onConfirm={handleConfirmSyncNewContent}
|
|
title="Sync New Content"
|
|
/>
|
|
}
|
|
|
|
{activeModal === "hard-reset"
|
|
&& <ConfirmationModal
|
|
confirmLabel="Yes, Wipe Everything"
|
|
description="This will permanently delete all of your current progress — gold, adventurers, upgrades, bosses, quests, and zones — and reset your account to a brand-new state. Lifetime stats are preserved, but everything else will be gone forever."
|
|
isLoading={isLoading}
|
|
onCancel={handleCancel}
|
|
onConfirm={handleConfirmHardReset}
|
|
title="⚠️ Hard Reset — This Cannot Be Undone"
|
|
/>
|
|
}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export { DebugPanel };
|