/** * @file Awakening service handling eligibility checks and post-awakening state building. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable stylistic/max-len -- Service logic requires long lines */ import { initialVampireState } from "../data/initialState.js"; import { defaultVampireAwakeningUpgrades } from "../data/vampireAwakeningUpgrades.js"; import type { AwakeningData, GameState } from "@elysium/types"; /** * The ID of the final vampire boss whose defeat triggers eligibility for awakening. */ const finalVampireBossId = "eternal_darkness"; const getCategoryMultiplier = ( purchasedIds: Array, category: string, ): number => { return defaultVampireAwakeningUpgrades.filter((upgrade) => { return upgrade.category === category && purchasedIds.includes(upgrade.id); }).reduce((mult, upgrade) => { return mult * upgrade.multiplier; }, 1); }; /** * Computes all five soul shard multipliers from the purchased awakening upgrade IDs. * @param purchasedUpgradeIds - The array of purchased awakening upgrade IDs. * @returns An object containing all five soul shard multiplier values. */ const computeAwakeningMultipliers = ( purchasedUpgradeIds: Array, ): Omit => { return { soulShardsBloodMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "blood"), soulShardsCombatMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "combat"), soulShardsMetaMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "soulshards_meta"), soulShardsSiringIchorMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "siring_ichor"), soulShardsSiringThresholdMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "siring_threshold"), }; }; /** * Returns true if the player is eligible to awaken: * the final vampire boss must have been defeated. * @param state - The current game state. * @returns Whether the player is eligible for awakening. */ const isEligibleForAwakening = (state: GameState): boolean => { // eslint-disable-next-line capitalized-comments -- v8 ignore /* v8 ignore next 3 -- @preserve */ if (state.vampire === undefined) { return false; } return state.vampire.bosses.some((boss) => { return boss.id === finalVampireBossId && boss.status === "defeated"; }); }; /** * Calculates the soul shards yield from an awakening. * Formula: MAX(1, FLOOR(SQRT(siringCount) * metaMultiplier)). * @param siringCount - The number of sirings completed. * @param metaMultiplier - Multiplier from soul shard meta upgrades applied to yield. * @returns The soul shards earned. */ const calculateSoulShardsYield = ( siringCount: number, metaMultiplier: number, ): number => { return Math.max(1, Math.floor(Math.sqrt(siringCount) * metaMultiplier)); }; /** * Builds the updated vampire state after an awakening reset. * Resets the current run including siring data (bosses, quests, thralls, upgrades, zones, siring data). * Preserves: equipment, achievements, awakening data (updated), eternal sovereignty, lifetime stats. * @param state - The current game state before awakening. * @returns The soul shards earned and the updated vampire state. */ const buildPostAwakeningState = ( state: GameState, ): { soulShardsEarned: number; updatedVampire: NonNullable } => { /* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Caller must ensure vampire exists */ const vampire = state.vampire as NonNullable; const metaMultiplier = vampire.awakening.soulShardsMetaMultiplier; const soulShardsEarned = calculateSoulShardsYield(vampire.siring.count, metaMultiplier); const updatedCount = vampire.awakening.count + 1; const updatedSoulShards = vampire.awakening.soulShards + soulShardsEarned; const updatedPurchasedIds = vampire.awakening.purchasedUpgradeIds; const updatedMultipliers = computeAwakeningMultipliers(updatedPurchasedIds); const updatedAwakening: AwakeningData = { count: updatedCount, purchasedUpgradeIds: updatedPurchasedIds, soulShards: updatedSoulShards, ...updatedMultipliers, }; const freshVampire = initialVampireState(); const updatedVampire: NonNullable = { ...freshVampire, achievements: vampire.achievements, awakening: updatedAwakening, bosses: freshVampire.bosses.map((b) => { const existing = vampire.bosses.find((vb) => { return vb.id === b.id; }); return { ...b, bountyIchorClaimed: existing?.bountyIchorClaimed ?? false, }; }), equipment: vampire.equipment, eternalSovereignty: vampire.eternalSovereignty, lastTickAt: Date.now(), lifetimeBloodEarned: vampire.lifetimeBloodEarned, lifetimeBossesDefeated: vampire.lifetimeBossesDefeated, lifetimeQuestsCompleted: vampire.lifetimeQuestsCompleted, totalBloodEarned: 0, }; return { soulShardsEarned, updatedVampire }; }; export { buildPostAwakeningState, calculateSoulShardsYield, computeAwakeningMultipliers, isEligibleForAwakening, };