diff --git a/apps/api/src/routes/debug.ts b/apps/api/src/routes/debug.ts index 3cc345e..ecf482e 100644 --- a/apps/api/src/routes/debug.ts +++ b/apps/api/src/routes/debug.ts @@ -257,6 +257,37 @@ const applyBossUnlocks = (state: GameState): number => { return count; }; +/** + * Unlocks any adventurer tiers that were granted as rewards for completed quests + * but are still locked in the player's state. + * @param state - The player's current game state (mutated directly). + * @returns The number of adventurer tiers that were unlocked. + */ +const applyAdventurerUnlocks = (state: GameState): number => { + let count = 0; + const earnedAdventurerIds = new Set(); + + for (const quest of state.quests) { + if (quest.status !== "completed") { + continue; + } + for (const reward of quest.rewards) { + if (reward.type === "adventurer" && reward.targetId !== undefined) { + earnedAdventurerIds.add(reward.targetId); + } + } + } + + for (const adventurer of state.adventurers) { + if (!adventurer.unlocked && earnedAdventurerIds.has(adventurer.id)) { + adventurer.unlocked = true; + count = count + 1; + } + } + + return count; +}; + /** * Makes available any exploration areas whose parent zone is now unlocked. * @param state - The player's current game state (mutated directly). @@ -301,6 +332,7 @@ const applyExplorationUnlocks = (state: GameState): number => { const applyForceUnlocks = ( state: GameState, ): { + adventurersUnlocked: number; bossesUnlocked: number; explorationUnlocked: number; questsUnlocked: number; @@ -310,7 +342,14 @@ const applyForceUnlocks = ( const questsUnlocked = applyQuestUnlocks(state); const bossesUnlocked = applyBossUnlocks(state); const explorationUnlocked = applyExplorationUnlocks(state); - return { bossesUnlocked, explorationUnlocked, questsUnlocked, zonesUnlocked }; + const adventurersUnlocked = applyAdventurerUnlocks(state); + return { + adventurersUnlocked, + bossesUnlocked, + explorationUnlocked, + questsUnlocked, + zonesUnlocked, + }; }; const debugRouter = new Hono(); @@ -330,8 +369,13 @@ debugRouter.post("/force-unlocks", async(context) => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma stores state as JSON object */ const state = gameStateRecord.state as unknown as GameState; - const { bossesUnlocked, explorationUnlocked, questsUnlocked, zonesUnlocked } - = applyForceUnlocks(state); + const { + adventurersUnlocked, + bossesUnlocked, + explorationUnlocked, + questsUnlocked, + zonesUnlocked, + } = applyForceUnlocks(state); const updatedAt = Date.now(); await prisma.gameState.update({ @@ -347,6 +391,7 @@ debugRouter.post("/force-unlocks", async(context) => { : computeHmac(JSON.stringify(state), secret); return context.json({ + adventurersUnlocked, bossesUnlocked, explorationUnlocked, questsUnlocked, diff --git a/apps/web/src/components/game/debugPanel.tsx b/apps/web/src/components/game/debugPanel.tsx index 0abc231..8bfc410 100644 --- a/apps/web/src/components/game/debugPanel.tsx +++ b/apps/web/src/components/game/debugPanel.tsx @@ -51,11 +51,15 @@ const DebugPanel = (): JSX.Element => { if (result.explorationUnlocked > 0) { parts.push(`${String(result.explorationUnlocked)} exploration area(s)`); } + if (result.adventurersUnlocked > 0) { + parts.push(`${String(result.adventurersUnlocked)} adventurer tier(s)`); + } const total = result.zonesUnlocked + result.questsUnlocked + result.bossesUnlocked - + result.explorationUnlocked; + + result.explorationUnlocked + + result.adventurersUnlocked; const message = parts.length === 0 ? "Everything looks correct — no missing unlocks were found." diff --git a/apps/web/src/context/gameContext.tsx b/apps/web/src/context/gameContext.tsx index a33b9d1..b65db14 100644 --- a/apps/web/src/context/gameContext.tsx +++ b/apps/web/src/context/gameContext.tsx @@ -558,6 +558,7 @@ interface GameContextValue { * @returns Counts of what was corrected. */ forceUnlocks: ()=> Promise<{ + adventurersUnlocked: number; bossesUnlocked: number; explorationUnlocked: number; questsUnlocked: number; @@ -2104,6 +2105,7 @@ export const GameProvider = ({ localStorage.setItem("elysium_save_signature", data.signature); } return { + adventurersUnlocked: data.adventurersUnlocked, bossesUnlocked: data.bossesUnlocked, explorationUnlocked: data.explorationUnlocked, questsUnlocked: data.questsUnlocked, @@ -2116,6 +2118,7 @@ export const GameProvider = ({ : "Failed to force unlocks", ); return { + adventurersUnlocked: 0, bossesUnlocked: 0, explorationUnlocked: 0, questsUnlocked: 0, diff --git a/packages/types/src/interfaces/api.ts b/packages/types/src/interfaces/api.ts index 8da988e..ef5e7f0 100644 --- a/packages/types/src/interfaces/api.ts +++ b/packages/types/src/interfaces/api.ts @@ -425,6 +425,11 @@ interface ForceUnlocksResponse { */ explorationUnlocked: number; + /** + * Number of adventurer tiers that were unlocked by this operation. + */ + adventurersUnlocked: number; + /** * HMAC-SHA256 signature of the corrected state for anti-cheat chain continuity. */