generated from nhcarrigan/template
d1d1f70c75
- Fix strict-boolean-expressions in 7 route files (runtime body validation) - Fix no-unnecessary-condition in profile.ts and offlineProgress.ts (defensive null checks) - Extend v8 ignore next-N counts in game.ts to reach 100% coverage - Add CI requirements to CLAUDE.md (lint + build + test must pass before commit) - Add manual verification checklist (verify.md) - Remove progress.md
171 lines
5.4 KiB
TypeScript
171 lines
5.4 KiB
TypeScript
/**
|
||
* @file Transcendence eligibility checks, echo calculations, and post-transcendence state builder.
|
||
* @copyright nhcarrigan
|
||
* @license Naomi's Public License
|
||
* @author Naomi Carrigan
|
||
*/
|
||
import { initialGameState } from "../data/initialState.js";
|
||
import { defaultTranscendenceUpgrades } from "../data/transcendenceUpgrades.js";
|
||
import type {
|
||
GameState,
|
||
TranscendenceData,
|
||
TranscendenceUpgradeCategory,
|
||
} from "@elysium/types";
|
||
|
||
/**
|
||
* ID of the boss that must be defeated to unlock transcendence.
|
||
*/
|
||
const finalBossId = "the_absolute_one";
|
||
|
||
/**
|
||
* Base constant used in the echo yield formula.
|
||
*/
|
||
const echoFormulaConstant = 853;
|
||
|
||
const getCategoryMultiplier = (
|
||
purchasedIds: Array<string>,
|
||
category: TranscendenceUpgradeCategory,
|
||
): number => {
|
||
return defaultTranscendenceUpgrades.filter((upgrade) => {
|
||
return upgrade.category === category && purchasedIds.includes(upgrade.id);
|
||
}).reduce((mult, upgrade) => {
|
||
return mult * upgrade.multiplier;
|
||
}, 1);
|
||
};
|
||
|
||
/**
|
||
* Computes all transcendence multipliers from the purchased upgrade IDs.
|
||
* @param purchasedUpgradeIds - The array of purchased transcendence upgrade IDs.
|
||
* @returns An object containing all transcendence multiplier values.
|
||
*/
|
||
const computeTranscendenceMultipliers = (
|
||
purchasedUpgradeIds: Array<string>,
|
||
): Omit<TranscendenceData, "count" | "echoes" | "purchasedUpgradeIds"> => {
|
||
return {
|
||
echoCombatMultiplier: getCategoryMultiplier(
|
||
purchasedUpgradeIds,
|
||
"combat",
|
||
),
|
||
echoIncomeMultiplier: getCategoryMultiplier(
|
||
purchasedUpgradeIds,
|
||
"income",
|
||
),
|
||
echoMetaMultiplier: getCategoryMultiplier(
|
||
purchasedUpgradeIds,
|
||
"echo_meta",
|
||
),
|
||
echoPrestigeRunestoneMultiplier: getCategoryMultiplier(
|
||
purchasedUpgradeIds,
|
||
"prestige_runestones",
|
||
),
|
||
echoPrestigeThresholdMultiplier: getCategoryMultiplier(
|
||
purchasedUpgradeIds,
|
||
"prestige_threshold",
|
||
),
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Returns true when the player is eligible to transcend:
|
||
* they must have defeated the final boss at least once.
|
||
* @param state - The current game state.
|
||
* @returns Whether the player is eligible for transcendence.
|
||
*/
|
||
const isEligibleForTranscendence = (state: GameState): boolean => {
|
||
return state.bosses.some((boss) => {
|
||
return boss.id === finalBossId && boss.status === "defeated";
|
||
});
|
||
};
|
||
|
||
/**
|
||
* Calculates echo yield for a transcendence.
|
||
* Formula: floor(CONSTANT / sqrt(prestigeCount)) × echoMetaMultiplier.
|
||
* Fewer prestiges = more echoes (rewards efficient play).
|
||
* Minimum prestige count of 1 is enforced to avoid division by zero.
|
||
* @param prestigeCount - The current prestige count.
|
||
* @param echoMetaMultiplier - The echo meta multiplier from transcendence upgrades.
|
||
* @returns The number of echoes earned.
|
||
*/
|
||
const calculateEchoes = (
|
||
prestigeCount: number,
|
||
echoMetaMultiplier: number,
|
||
): number => {
|
||
const safeCount = Math.max(prestigeCount, 1);
|
||
const baseEchoes = echoFormulaConstant / Math.sqrt(safeCount);
|
||
return Math.floor(baseEchoes * echoMetaMultiplier);
|
||
};
|
||
|
||
/**
|
||
* Builds the permanent-data spread objects that survive a transcendence reset.
|
||
* @param currentState - The game state at the time of transcendence.
|
||
* @param transcendenceData - The newly-computed transcendence data to carry forward.
|
||
* @returns A partial GameState object containing all data that persists through transcendence.
|
||
*/
|
||
const buildPermanentSpreads = (
|
||
currentState: GameState,
|
||
transcendenceData: TranscendenceData,
|
||
): Partial<GameState> => {
|
||
return {
|
||
transcendence: transcendenceData,
|
||
...currentState.codex === undefined
|
||
? {}
|
||
: { codex: currentState.codex },
|
||
...currentState.apotheosis === undefined
|
||
? {}
|
||
: { apotheosis: currentState.apotheosis },
|
||
...currentState.story === undefined
|
||
? {}
|
||
: { story: currentState.story },
|
||
};
|
||
};
|
||
|
||
/**
|
||
* Builds the new game state after a transcendence (nuclear reset).
|
||
* Wipes everything except codex, dailyChallenges, and transcendence data.
|
||
* @param currentState - The game state at the time of transcendence.
|
||
* @param characterName - The player's character name to carry forward.
|
||
* @returns The new game state, transcendence data, and echoes earned.
|
||
*/
|
||
const buildPostTranscendenceState = (
|
||
currentState: GameState,
|
||
characterName: string,
|
||
): {
|
||
transcendenceState: GameState;
|
||
transcendenceData: TranscendenceData;
|
||
echoesEarned: number;
|
||
} => {
|
||
const previousTranscendence = currentState.transcendence;
|
||
const echoMetaMultiplier = previousTranscendence?.echoMetaMultiplier ?? 1;
|
||
|
||
const echoesEarned = calculateEchoes(
|
||
currentState.prestige.count,
|
||
echoMetaMultiplier,
|
||
);
|
||
const previousEchoes = previousTranscendence?.echoes ?? 0;
|
||
const updatedCount = (previousTranscendence?.count ?? 0) + 1;
|
||
const updatedPurchasedIds = previousTranscendence?.purchasedUpgradeIds ?? [];
|
||
|
||
const transcendenceData: TranscendenceData = {
|
||
count: updatedCount,
|
||
echoes: previousEchoes + echoesEarned,
|
||
purchasedUpgradeIds: updatedPurchasedIds,
|
||
...computeTranscendenceMultipliers(updatedPurchasedIds),
|
||
};
|
||
|
||
const freshState = initialGameState(currentState.player, characterName);
|
||
const transcendenceState: GameState = {
|
||
...freshState,
|
||
lastTickAt: Date.now(),
|
||
...buildPermanentSpreads(currentState, transcendenceData),
|
||
};
|
||
|
||
return { echoesEarned, transcendenceData, transcendenceState };
|
||
};
|
||
|
||
export {
|
||
buildPostTranscendenceState,
|
||
calculateEchoes,
|
||
computeTranscendenceMultipliers,
|
||
isEligibleForTranscendence,
|
||
};
|