/** * @file Offline earnings calculator for gold and essence accrued while logged out. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Offline earnings calculation requires iterating all adventurers with multi-step math */ import type { GameState } from "@elysium/types"; /** * Maximum offline accrual cap: 8 hours. */ const maxOfflineSeconds = 8 * 60 * 60; /** * Calculates the gold and essence earned whilst the player was offline. * Capped at 8 hours to prevent exploit via system clock manipulation. * Applies the same multipliers as the client-side tick engine. * @param state - The current game state to calculate offline earnings from. * @param nowMs - The current timestamp in milliseconds. * @returns The gold, essence, and elapsed seconds earned offline. */ const calculateOfflineEarnings = ( state: GameState, nowMs: number, ): { offlineGold: number; offlineEssence: number; offlineSeconds: number } => { const elapsedSeconds = Math.min( (nowMs - state.lastTickAt) / 1000, maxOfflineSeconds, ); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Defensive check for runtime nullable fields const equipmentGoldMultiplier = (state.equipment ?? []). filter((item) => { return item.equipped; }). reduce((mult, item) => { return mult * (item.bonus.goldMultiplier ?? 1); }, 1); const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1; const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1; let goldPerSecond = 0; let essencePerSecond = 0; for (const adventurer of state.adventurers) { if (!adventurer.unlocked || adventurer.count === 0) { continue; } const upgradeMultiplier = state.upgrades. filter((upgrade) => { const isGlobal = upgrade.target === "global"; const isForAdventurer = upgrade.target === "adventurer" && upgrade.adventurerId === adventurer.id; const affectsAdventurer = isGlobal || isForAdventurer; return upgrade.purchased && affectsAdventurer; }). reduce((mult, upgrade) => { return mult * upgrade.multiplier; }, 1); const prestige = state.prestige.productionMultiplier; const goldContribution = adventurer.goldPerSecond * adventurer.count * upgradeMultiplier * prestige * runestonesIncome * equipmentGoldMultiplier; goldPerSecond = goldPerSecond + goldContribution; const essenceContribution = adventurer.essencePerSecond * adventurer.count * upgradeMultiplier * prestige * runestonesEssence; essencePerSecond = essencePerSecond + essenceContribution; } return { offlineEssence: essencePerSecond * elapsedSeconds, offlineGold: goldPerSecond * elapsedSeconds, offlineSeconds: elapsedSeconds, }; }; export { calculateOfflineEarnings };