generated from nhcarrigan/template
fix: preserve all-time stats, achievements, and boss first-kill across prestige (#47)
Resolves #37, resolves #38, and resolves #39 — three related bugs where prestige incorrectly reset data that should survive all prestige resets. ## Changes ### fix: preserve lifetime player stats across prestige (#37) After prestige, `GameState.player.lifetime*` fields were stale — they reflected values from *before* the current run. The Prisma Player record was incremented correctly, but the GameState JSON saved to the DB had old values, so the UI showed wrong all-time totals on reload. `buildPostPrestigeState` now computes the run-stat contributions (bosses defeated, quests completed, adventurers recruited, achievements unlocked, gold earned, clicks) and folds them into the fresh player object before writing the prestige state. ### fix: preserve achievements across prestige (#38) `buildPostPrestigeState` was reconstructing achievements from `defaultAchievements` (via `initialGameState`), resetting all unlocked achievements on every prestige. Achievements are now carried forward from `currentState.achievements` instead. ### fix: preserve boss first-kill state across prestige (#39) Added `bountyRunestonesClaimed?: boolean` to the `Boss` type. The boss challenge route now: - Only awards the first-kill bounty runestones if `bountyRunestonesClaimed !== true` - Sets `bountyRunestonesClaimed = true` on first defeat `buildPostPrestigeState` maps the fresh boss list and carries the `bountyRunestonesClaimed` flag forward from the current state, so the bounty is never re-awarded in subsequent prestige runs. The boss panel badge is also hidden for bosses whose bounty is already claimed. ## Test Coverage All three fixes include new tests covering the new behaviours. API coverage remains at 100%. ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #47 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #47.
This commit is contained in:
@@ -205,10 +205,69 @@ const buildPostPrestigeState = (
|
||||
};
|
||||
|
||||
const freshState = initialGameState(currentState.player, characterName);
|
||||
|
||||
/*
|
||||
* Preserve first-kill (bounty claimed) status across the prestige reset so
|
||||
* the one-time bounty is never re-awarded in subsequent runs.
|
||||
*/
|
||||
const bossesWithBountyClaimed = freshState.bosses.map((freshBoss) => {
|
||||
const currentBoss = currentState.bosses.find((candidate) => {
|
||||
return candidate.id === freshBoss.id;
|
||||
});
|
||||
if (currentBoss?.bountyRunestonesClaimed === true) {
|
||||
return { ...freshBoss, bountyRunestonesClaimed: true };
|
||||
}
|
||||
return freshBoss;
|
||||
});
|
||||
|
||||
// Compute current-run contributions to accumulate into lifetime totals
|
||||
const runBossesDefeated = currentState.bosses.filter((boss) => {
|
||||
return boss.status === "defeated";
|
||||
}).length;
|
||||
const runQuestsCompleted = currentState.quests.filter((quest) => {
|
||||
return quest.status === "completed";
|
||||
}).length;
|
||||
let runAdventurersRecruited = 0;
|
||||
for (const adventurer of currentState.adventurers) {
|
||||
runAdventurersRecruited = runAdventurersRecruited + adventurer.count;
|
||||
}
|
||||
const runAchievementsUnlocked = currentState.achievements.filter(
|
||||
(achievement) => {
|
||||
return achievement.unlockedAt !== null;
|
||||
},
|
||||
).length;
|
||||
|
||||
const prestigeState: GameState = {
|
||||
...freshState,
|
||||
lastTickAt: Date.now(),
|
||||
prestige: prestigeData,
|
||||
// Achievements are permanent — earned achievements survive all prestiges
|
||||
achievements: currentState.achievements,
|
||||
// Boss statuses reset for gameplay, but first-kill claimed flag is preserved
|
||||
bosses: bossesWithBountyClaimed,
|
||||
lastTickAt: Date.now(),
|
||||
|
||||
/*
|
||||
* Fold current-run totals into lifetime stats so the GameState reflects
|
||||
* the true all-time values immediately after prestige.
|
||||
*/
|
||||
player: {
|
||||
...freshState.player,
|
||||
lifetimeAchievementsUnlocked:
|
||||
freshState.player.lifetimeAchievementsUnlocked
|
||||
+ runAchievementsUnlocked,
|
||||
lifetimeAdventurersRecruited:
|
||||
freshState.player.lifetimeAdventurersRecruited
|
||||
+ runAdventurersRecruited,
|
||||
lifetimeBossesDefeated:
|
||||
freshState.player.lifetimeBossesDefeated + runBossesDefeated,
|
||||
lifetimeClicks:
|
||||
freshState.player.lifetimeClicks + currentState.player.totalClicks,
|
||||
lifetimeGoldEarned:
|
||||
freshState.player.lifetimeGoldEarned
|
||||
+ currentState.player.totalGoldEarned,
|
||||
lifetimeQuestsCompleted:
|
||||
freshState.player.lifetimeQuestsCompleted + runQuestsCompleted,
|
||||
},
|
||||
prestige: prestigeData,
|
||||
// Codex lore persists across prestiges — players keep their discovered entries
|
||||
...currentState.codex === undefined
|
||||
? {}
|
||||
|
||||
Reference in New Issue
Block a user