/** * @file Consecration service handling eligibility checks and post-consecration state building. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Function requires many steps */ /* eslint-disable stylistic/max-len -- Service logic requires long lines */ import { defaultConsecrationUpgrades } from "../data/goddessConsecrationUpgrades.js"; import { initialGoddessState } from "../data/initialState.js"; import type { ConsecrationData, GameState } from "@elysium/types"; /** * Base prayers threshold for the first consecration. */ const baseConsecrationThreshold = 50_000; /** * Divisor used in the divinity yield formula. */ const divinityYieldDivisor = 1000; /** * Calculates the prayers threshold required for the next consecration. * Formula: BASE * (count + 1)^2 * thresholdMultiplier. * @param consecrationCount - The number of consecrations completed so far. * @param thresholdMultiplier - An optional stardust-upgrade multiplier applied to the threshold. * @returns The prayers amount required to consecrate. */ const calculateConsecrationThreshold = ( consecrationCount: number, thresholdMultiplier = 1, ): number => { return ( baseConsecrationThreshold * Math.pow(consecrationCount + 1, 2) * thresholdMultiplier ); }; /** * Returns true if the player is eligible to consecrate: * the total prayers earned in the current run must meet the threshold. * @param state - The current game state. * @returns Whether the player is eligible for consecration. */ const isEligibleForConsecration = (state: GameState): boolean => { // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next 3 -- @preserve */ if (state.goddess === undefined) { return false; } const thresholdMultiplier = state.goddess.enlightenment.stardustConsecrationThresholdMultiplier; const threshold = calculateConsecrationThreshold( state.goddess.consecration.count, thresholdMultiplier, ); return state.goddess.totalPrayersEarned >= threshold; }; /** * Calculates the divinity yield from a consecration. * Formula: MAX(1, FLOOR(SQRT(totalPrayersEarned / divisor) * divinityMultiplier)). * @param totalPrayersEarned - Total prayers earned in the current consecration run. * @param divinityMultiplier - Multiplier from stardust upgrades applied to divinity yield. * @returns The divinity earned. */ const calculateDivinityYield = ( totalPrayersEarned: number, divinityMultiplier: number, ): number => { return Math.max( 1, Math.floor( Math.sqrt(totalPrayersEarned / divinityYieldDivisor) * divinityMultiplier, ), ); }; /** * Computes the consecration production multiplier from the count. * Each consecration adds 25% to the production multiplier. * @param count - The number of consecrations completed. * @returns The computed production multiplier as a number. */ const computeConsecrationProductionMultiplier = (count: number): number => { const bonus = count * 0.25; return 1 + bonus; }; const getCategoryMultiplier = ( purchasedUpgradeIds: Array, category: string, ): number => { return defaultConsecrationUpgrades.filter((upgrade) => { return ( upgrade.category === category && purchasedUpgradeIds.includes(upgrade.id) ); }).reduce((mult, upgrade) => { return mult * upgrade.multiplier; }, 1); }; /** * Computes all three divinity-upgrade multipliers from the purchased upgrade IDs. * @param purchasedUpgradeIds - The array of purchased consecration upgrade IDs. * @returns An object containing the three divinity multiplier values. */ const computeConsecrationDivinityMultipliers = ( purchasedUpgradeIds: Array, ): Pick< ConsecrationData, | "divinityCombatMultiplier" | "divinityDisciplesMultiplier" | "divinityPrayersMultiplier" > => { return { divinityCombatMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "combat"), divinityDisciplesMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "disciples"), divinityPrayersMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "prayers"), }; }; /** * Builds the updated goddess state after a consecration reset. * Resets the current run (bosses, quests, disciples, upgrades, zones, exploration crafting). * Preserves: equipment, achievements, consecration data (updated), enlightenment, lifetime stats, sacred materials. * @param state - The current game state before consecration. * @returns The divinity earned and the updated goddess state. */ const buildPostConsecrationState = ( state: GameState, ): { divinityEarned: number; updatedGoddess: NonNullable } => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Caller must ensure goddess exists const goddess = state.goddess as NonNullable; const divinityMultiplier = goddess.enlightenment.stardustConsecrationDivinityMultiplier; const divinityEarned = calculateDivinityYield( goddess.totalPrayersEarned, divinityMultiplier, ); const updatedCount = goddess.consecration.count + 1; const updatedDivinity = goddess.consecration.divinity + divinityEarned; const productionMultiplier = computeConsecrationProductionMultiplier(updatedCount); const updatedConsecration: ConsecrationData = { ...goddess.consecration, count: updatedCount, divinity: updatedDivinity, lastConsecratedAt: Date.now(), productionMultiplier: productionMultiplier, ...computeConsecrationDivinityMultipliers( goddess.consecration.purchasedUpgradeIds, ), }; const freshGoddess = initialGoddessState(); const updatedGoddess: NonNullable = { ...freshGoddess, achievements: goddess.achievements, bosses: freshGoddess.bosses.map((b) => { // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next 7 -- @preserve */ const existing = goddess.bosses.find((gb) => { return gb.id === b.id; }); return { ...b, bountyDivinityClaimed: existing?.bountyDivinityClaimed ?? false, }; }), consecration: updatedConsecration, enlightenment: goddess.enlightenment, equipment: goddess.equipment, exploration: { ...freshGoddess.exploration, materials: goddess.exploration.materials, }, lastTickAt: Date.now(), lifetimeBossesDefeated: goddess.lifetimeBossesDefeated, lifetimePrayersEarned: goddess.lifetimePrayersEarned + goddess.totalPrayersEarned, lifetimeQuestsCompleted: goddess.lifetimeQuestsCompleted, totalPrayersEarned: 0, }; return { divinityEarned, updatedGoddess }; }; export { buildPostConsecrationState, calculateConsecrationThreshold, calculateDivinityYield, computeConsecrationDivinityMultipliers, computeConsecrationProductionMultiplier, isEligibleForConsecration, };