From 790d35420fe5fc8f20320755ef52e026a3563845 Mon Sep 17 00:00:00 2001 From: Hikari Date: Mon, 23 Mar 2026 18:45:14 -0700 Subject: [PATCH] fix: patch quest and boss rewards on sync to restore unlock conditions --- apps/api/src/routes/debug.ts | 61 +++++++++++++++++++++ apps/web/src/components/game/debugPanel.tsx | 6 +- apps/web/src/context/gameContext.tsx | 6 ++ packages/types/src/interfaces/api.ts | 10 ++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/apps/api/src/routes/debug.ts b/apps/api/src/routes/debug.ts index 68ba744..e2e4ab5 100644 --- a/apps/api/src/routes/debug.ts +++ b/apps/api/src/routes/debug.ts @@ -566,6 +566,63 @@ const injectMissingExplorationAreas = (state: GameState): number => { return added; }; +/** + * Patches rewards on existing quests whose reward lists have grown since the + * save was created (e.g. A new upgrade added as a reward to an old quest). + * @param state - The player's current game state (mutated in place). + * @returns The total number of individual rewards that were added. + */ +const patchQuestRewards = (state: GameState): number => { + const defaultQuestMap = new Map(defaultQuests.map((quest) => { + return [ quest.id, quest ] as const; + })); + let added = 0; + for (const savedQuest of state.quests) { + const defaultQuest = defaultQuestMap.get(savedQuest.id); + if (defaultQuest === undefined) { + continue; + } + const existingKeys = new Set(savedQuest.rewards.map((reward) => { + return `${reward.type}:${String(reward.targetId ?? reward.amount ?? "")}`; + })); + for (const reward of defaultQuest.rewards) { + const key = `${reward.type}:${String(reward.targetId ?? reward.amount ?? "")}`; + if (!existingKeys.has(key)) { + savedQuest.rewards.push(structuredClone(reward)); + added = added + 1; + } + } + } + return added; +}; + +/** + * Patches upgradeRewards on existing bosses whose reward lists have grown + * since the save was created. + * @param state - The player's current game state (mutated in place). + * @returns The total number of upgrade reward IDs that were added. + */ +const patchBossUpgradeRewards = (state: GameState): number => { + const defaultBossMap = new Map(defaultBosses.map((boss) => { + return [ boss.id, boss ] as const; + })); + let added = 0; + for (const savedBoss of state.bosses) { + const defaultBoss = defaultBossMap.get(savedBoss.id); + if (defaultBoss === undefined) { + continue; + } + const existingIds = new Set(savedBoss.upgradeRewards); + for (const upgradeId of defaultBoss.upgradeRewards) { + if (!existingIds.has(upgradeId)) { + savedBoss.upgradeRewards.push(upgradeId); + added = added + 1; + } + } + } + return added; +}; + /* eslint-disable stylistic/max-len -- Long function call lines cannot be shortened without losing alignment */ /** * Syncs a player's save with the current game data, injecting any content @@ -579,8 +636,10 @@ const syncNewContent = ( achievementsAdded: number; adventurersAdded: number; bossesAdded: number; + bossRewardsPatched: number; equipmentAdded: number; explorationAreasAdded: number; + questRewardsPatched: number; questsAdded: number; upgradesAdded: number; zonesAdded: number; @@ -588,9 +647,11 @@ const syncNewContent = ( return { achievementsAdded: injectMissingEntries(state.achievements, defaultAchievements), adventurersAdded: injectMissingEntries(state.adventurers, defaultAdventurers), + bossRewardsPatched: patchBossUpgradeRewards(state), bossesAdded: injectMissingEntries(state.bosses, defaultBosses), equipmentAdded: injectMissingEntries(state.equipment, defaultEquipment), explorationAreasAdded: injectMissingExplorationAreas(state), + questRewardsPatched: patchQuestRewards(state), questsAdded: injectMissingEntries(state.quests, defaultQuests), upgradesAdded: injectMissingEntries(state.upgrades, defaultUpgrades), zonesAdded: injectMissingEntries(state.zones, defaultZones), diff --git a/apps/web/src/components/game/debugPanel.tsx b/apps/web/src/components/game/debugPanel.tsx index 7643418..32f04bd 100644 --- a/apps/web/src/components/game/debugPanel.tsx +++ b/apps/web/src/components/game/debugPanel.tsx @@ -16,8 +16,10 @@ interface SyncNewContentResult { achievementsAdded: number; adventurersAdded: number; bossesAdded: number; + bossRewardsPatched: number; equipmentAdded: number; explorationAreasAdded: number; + questRewardsPatched: number; questsAdded: number; upgradesAdded: number; zonesAdded: number; @@ -32,7 +34,9 @@ const buildSyncNewContentMessage = (result: SyncNewContentResult): string => { const entries: Array<[ number, string ]> = [ [ result.zonesAdded, "zone(s)" ], [ result.questsAdded, "quest(s)" ], + [ result.questRewardsPatched, "quest reward(s) patched" ], [ result.bossesAdded, "boss(es)" ], + [ result.bossRewardsPatched, "boss reward(s) patched" ], [ result.explorationAreasAdded, "exploration area(s)" ], [ result.adventurersAdded, "adventurer tier(s)" ], [ result.upgradesAdded, "upgrade(s)" ], @@ -52,7 +56,7 @@ const buildSyncNewContentMessage = (result: SyncNewContentResult): string => { const total = entries.reduce((sum, [ count ]) => { return sum + count; }, 0); - return `Added ${String(total)} new item(s) to your save: ${parts.join(", ")}.`; + return `Synced ${String(total)} item(s): ${parts.join(", ")}.`; }; interface ForceUnlocksResult { diff --git a/apps/web/src/context/gameContext.tsx b/apps/web/src/context/gameContext.tsx index 4d43328..5887e28 100644 --- a/apps/web/src/context/gameContext.tsx +++ b/apps/web/src/context/gameContext.tsx @@ -583,8 +583,10 @@ interface GameContextValue { achievementsAdded: number; adventurersAdded: number; bossesAdded: number; + bossRewardsPatched: number; equipmentAdded: number; explorationAreasAdded: number; + questRewardsPatched: number; questsAdded: number; upgradesAdded: number; zonesAdded: number; @@ -2178,9 +2180,11 @@ export const GameProvider = ({ return { achievementsAdded: data.achievementsAdded, adventurersAdded: data.adventurersAdded, + bossRewardsPatched: data.bossRewardsPatched, bossesAdded: data.bossesAdded, equipmentAdded: data.equipmentAdded, explorationAreasAdded: data.explorationAreasAdded, + questRewardsPatched: data.questRewardsPatched, questsAdded: data.questsAdded, upgradesAdded: data.upgradesAdded, zonesAdded: data.zonesAdded, @@ -2194,9 +2198,11 @@ export const GameProvider = ({ return { achievementsAdded: 0, adventurersAdded: 0, + bossRewardsPatched: 0, bossesAdded: 0, equipmentAdded: 0, explorationAreasAdded: 0, + questRewardsPatched: 0, questsAdded: 0, upgradesAdded: 0, zonesAdded: 0, diff --git a/packages/types/src/interfaces/api.ts b/packages/types/src/interfaces/api.ts index fb4074d..70fd596 100644 --- a/packages/types/src/interfaces/api.ts +++ b/packages/types/src/interfaces/api.ts @@ -468,6 +468,11 @@ interface SyncNewContentResponse { */ upgradesAdded: number; + /** + * Number of rewards patched onto existing quests. + */ + questRewardsPatched: number; + /** * Number of quests added to the save. */ @@ -478,6 +483,11 @@ interface SyncNewContentResponse { */ bossesAdded: number; + /** + * Number of upgrade reward IDs patched onto existing bosses. + */ + bossRewardsPatched: number; + /** * Number of equipment items added to the save. */