/** * @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, 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, ): Omit => { 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 => { 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, };