generated from nhcarrigan/template
fix: preserve all-time stats, achievements, and boss first-kill across prestige #47
@@ -205,10 +205,51 @@ const buildPostPrestigeState = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const freshState = initialGameState(currentState.player, characterName);
|
const freshState = initialGameState(currentState.player, characterName);
|
||||||
|
|
||||||
|
// 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 = {
|
const prestigeState: GameState = {
|
||||||
...freshState,
|
...freshState,
|
||||||
lastTickAt: Date.now(),
|
lastTickAt: Date.now(),
|
||||||
prestige: prestigeData,
|
|
||||||
|
/*
|
||||||
|
* 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
|
// Codex lore persists across prestiges — players keep their discovered entries
|
||||||
...currentState.codex === undefined
|
...currentState.codex === undefined
|
||||||
? {}
|
? {}
|
||||||
|
|||||||
@@ -13,14 +13,24 @@ import {
|
|||||||
} from "../../src/services/prestige.js";
|
} from "../../src/services/prestige.js";
|
||||||
import type { GameState } from "@elysium/types";
|
import type { GameState } from "@elysium/types";
|
||||||
|
|
||||||
const makePlayer = (totalGoldEarned: number) => ({
|
const makePlayer = (
|
||||||
discordId: "test_id",
|
totalGoldEarned: number,
|
||||||
username: "testuser",
|
lifetimeGoldEarned = 0,
|
||||||
discriminator: "0",
|
totalClicks = 0,
|
||||||
avatar: null,
|
) => ({
|
||||||
totalGoldEarned,
|
avatar: null,
|
||||||
totalClicks: 0,
|
characterName: "Tester",
|
||||||
characterName: "Tester",
|
discordId: "test_id",
|
||||||
|
discriminator: "0",
|
||||||
|
lifetimeAchievementsUnlocked: 0,
|
||||||
|
lifetimeAdventurersRecruited: 0,
|
||||||
|
lifetimeBossesDefeated: 0,
|
||||||
|
lifetimeClicks: 0,
|
||||||
|
lifetimeGoldEarned: lifetimeGoldEarned,
|
||||||
|
lifetimeQuestsCompleted: 0,
|
||||||
|
totalClicks: totalClicks,
|
||||||
|
totalGoldEarned: totalGoldEarned,
|
||||||
|
username: "testuser",
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMinimalState = (overrides: Partial<GameState> = {}): GameState =>
|
const makeMinimalState = (overrides: Partial<GameState> = {}): GameState =>
|
||||||
@@ -242,4 +252,85 @@ describe("buildPostPrestigeState", () => {
|
|||||||
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
expect(prestigeState.apotheosis).toEqual(apotheosis);
|
expect(prestigeState.apotheosis).toEqual(apotheosis);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("accumulates current-run gold into lifetime total", () => {
|
||||||
|
const state = makeMinimalState({
|
||||||
|
player: makePlayer(4_000_000, 1_000_000),
|
||||||
|
});
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeGoldEarned).toBe(5_000_000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accumulates current-run clicks into lifetime total", () => {
|
||||||
|
const state = makeMinimalState({
|
||||||
|
player: makePlayer(4_000_000, 0, 500),
|
||||||
|
});
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeClicks).toBe(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accumulates defeated bosses into lifetime total", () => {
|
||||||
|
const defeatedBoss = {
|
||||||
|
bountyRunestones: 0,
|
||||||
|
crystalReward: 0,
|
||||||
|
currentHp: 0,
|
||||||
|
damagePerSecond: 10,
|
||||||
|
description: "A boss",
|
||||||
|
equipmentRewards: [] as string[],
|
||||||
|
essenceReward: 0,
|
||||||
|
goldReward: 100,
|
||||||
|
id: "boss_1",
|
||||||
|
maxHp: 100,
|
||||||
|
name: "Boss One",
|
||||||
|
prestigeRequirement: 0,
|
||||||
|
status: "defeated" as const,
|
||||||
|
upgradeRewards: [] as string[],
|
||||||
|
zoneId: "zone_1",
|
||||||
|
};
|
||||||
|
const state = makeMinimalState({ bosses: [ defeatedBoss ] });
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeBossesDefeated).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accumulates completed quests into lifetime total", () => {
|
||||||
|
const quest = {
|
||||||
|
id: "q_1",
|
||||||
|
name: "A Quest",
|
||||||
|
description: "Do the thing",
|
||||||
|
status: "completed" as const,
|
||||||
|
zoneId: "zone_1",
|
||||||
|
};
|
||||||
|
const state = makeMinimalState({ quests: [ quest ] as GameState["quests"] });
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeQuestsCompleted).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accumulates recruited adventurers into lifetime total", () => {
|
||||||
|
const adventurer = {
|
||||||
|
combatPower: 10,
|
||||||
|
count: 5,
|
||||||
|
essencePerSecond: 0,
|
||||||
|
goldPerSecond: 1,
|
||||||
|
id: "adv_1",
|
||||||
|
level: 1,
|
||||||
|
unlocked: true,
|
||||||
|
};
|
||||||
|
const state = makeMinimalState({ adventurers: [ adventurer ] as GameState["adventurers"] });
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeAdventurersRecruited).toBe(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("accumulates unlocked achievements into lifetime total", () => {
|
||||||
|
const achievement = {
|
||||||
|
description: "Did a thing",
|
||||||
|
id: "ach_1",
|
||||||
|
name: "Achiever",
|
||||||
|
requirement: 1,
|
||||||
|
type: "totalClicks" as const,
|
||||||
|
unlockedAt: Date.now(),
|
||||||
|
};
|
||||||
|
const state = makeMinimalState({ achievements: [ achievement ] as GameState["achievements"] });
|
||||||
|
const { prestigeState } = buildPostPrestigeState(state, "Tester");
|
||||||
|
expect(prestigeState.player.lifetimeAchievementsUnlocked).toBe(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user