fix: adventurer unlocks not applied by force-unlock tool #93

Merged
naomi merged 3 commits from fix/force-unlock-adventurers into main 2026-03-20 10:28:18 -07:00
4 changed files with 61 additions and 4 deletions
Showing only changes of commit 715ccd3fc7 - Show all commits
+48 -3
View File
@@ -257,6 +257,37 @@ const applyBossUnlocks = (state: GameState): number => {
return count; 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<string>();
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. * Makes available any exploration areas whose parent zone is now unlocked.
* @param state - The player's current game state (mutated directly). * @param state - The player's current game state (mutated directly).
@@ -301,6 +332,7 @@ const applyExplorationUnlocks = (state: GameState): number => {
const applyForceUnlocks = ( const applyForceUnlocks = (
state: GameState, state: GameState,
): { ): {
adventurersUnlocked: number;
bossesUnlocked: number; bossesUnlocked: number;
explorationUnlocked: number; explorationUnlocked: number;
questsUnlocked: number; questsUnlocked: number;
@@ -310,7 +342,14 @@ const applyForceUnlocks = (
const questsUnlocked = applyQuestUnlocks(state); const questsUnlocked = applyQuestUnlocks(state);
const bossesUnlocked = applyBossUnlocks(state); const bossesUnlocked = applyBossUnlocks(state);
const explorationUnlocked = applyExplorationUnlocks(state); const explorationUnlocked = applyExplorationUnlocks(state);
return { bossesUnlocked, explorationUnlocked, questsUnlocked, zonesUnlocked }; const adventurersUnlocked = applyAdventurerUnlocks(state);
return {
adventurersUnlocked,
bossesUnlocked,
explorationUnlocked,
questsUnlocked,
zonesUnlocked,
};
}; };
const debugRouter = new Hono<HonoEnvironment>(); const debugRouter = new Hono<HonoEnvironment>();
@@ -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 */ /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Prisma stores state as JSON object */
const state = gameStateRecord.state as unknown as GameState; const state = gameStateRecord.state as unknown as GameState;
const { bossesUnlocked, explorationUnlocked, questsUnlocked, zonesUnlocked } const {
= applyForceUnlocks(state); adventurersUnlocked,
bossesUnlocked,
explorationUnlocked,
questsUnlocked,
zonesUnlocked,
} = applyForceUnlocks(state);
const updatedAt = Date.now(); const updatedAt = Date.now();
await prisma.gameState.update({ await prisma.gameState.update({
@@ -347,6 +391,7 @@ debugRouter.post("/force-unlocks", async(context) => {
: computeHmac(JSON.stringify(state), secret); : computeHmac(JSON.stringify(state), secret);
return context.json({ return context.json({
adventurersUnlocked,
bossesUnlocked, bossesUnlocked,
explorationUnlocked, explorationUnlocked,
questsUnlocked, questsUnlocked,
+5 -1
View File
@@ -51,11 +51,15 @@ const DebugPanel = (): JSX.Element => {
if (result.explorationUnlocked > 0) { if (result.explorationUnlocked > 0) {
parts.push(`${String(result.explorationUnlocked)} exploration area(s)`); parts.push(`${String(result.explorationUnlocked)} exploration area(s)`);
} }
if (result.adventurersUnlocked > 0) {
parts.push(`${String(result.adventurersUnlocked)} adventurer tier(s)`);
}
const total const total
= result.zonesUnlocked = result.zonesUnlocked
+ result.questsUnlocked + result.questsUnlocked
+ result.bossesUnlocked + result.bossesUnlocked
+ result.explorationUnlocked; + result.explorationUnlocked
+ result.adventurersUnlocked;
const message const message
= parts.length === 0 = parts.length === 0
? "Everything looks correct — no missing unlocks were found." ? "Everything looks correct — no missing unlocks were found."
+3
View File
@@ -558,6 +558,7 @@ interface GameContextValue {
* @returns Counts of what was corrected. * @returns Counts of what was corrected.
*/ */
forceUnlocks: ()=> Promise<{ forceUnlocks: ()=> Promise<{
adventurersUnlocked: number;
bossesUnlocked: number; bossesUnlocked: number;
explorationUnlocked: number; explorationUnlocked: number;
questsUnlocked: number; questsUnlocked: number;
@@ -2104,6 +2105,7 @@ export const GameProvider = ({
localStorage.setItem("elysium_save_signature", data.signature); localStorage.setItem("elysium_save_signature", data.signature);
} }
return { return {
adventurersUnlocked: data.adventurersUnlocked,
bossesUnlocked: data.bossesUnlocked, bossesUnlocked: data.bossesUnlocked,
explorationUnlocked: data.explorationUnlocked, explorationUnlocked: data.explorationUnlocked,
questsUnlocked: data.questsUnlocked, questsUnlocked: data.questsUnlocked,
@@ -2116,6 +2118,7 @@ export const GameProvider = ({
: "Failed to force unlocks", : "Failed to force unlocks",
); );
return { return {
adventurersUnlocked: 0,
bossesUnlocked: 0, bossesUnlocked: 0,
explorationUnlocked: 0, explorationUnlocked: 0,
questsUnlocked: 0, questsUnlocked: 0,
+5
View File
@@ -425,6 +425,11 @@ interface ForceUnlocksResponse {
*/ */
explorationUnlocked: number; 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. * HMAC-SHA256 signature of the corrected state for anti-cheat chain continuity.
*/ */