import type { GameState } from "@elysium/types"; const MAX_OFFLINE_SECONDS = 8 * 60 * 60; // 8 hours /** * 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. */ export const calculateOfflineEarnings = ( state: GameState, nowMs: number, ): { offlineGold: number; offlineEssence: number; offlineSeconds: number } => { const elapsedSeconds = Math.min( (nowMs - state.lastTickAt) / 1000, MAX_OFFLINE_SECONDS, ); const equipmentGoldMultiplier = (state.equipment ?? []) .filter((e) => e.equipped) .reduce((mult, e) => mult * (e.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( (u) => u.purchased && (u.target === "global" || (u.target === "adventurer" && u.adventurerId === adventurer.id)), ) .reduce((mult, u) => mult * u.multiplier, 1); const prestige = state.prestige.productionMultiplier; goldPerSecond += adventurer.goldPerSecond * adventurer.count * upgradeMultiplier * prestige * runestonesIncome * equipmentGoldMultiplier; essencePerSecond += adventurer.essencePerSecond * adventurer.count * upgradeMultiplier * prestige * runestonesEssence; } return { offlineGold: goldPerSecond * elapsedSeconds, offlineEssence: essencePerSecond * elapsedSeconds, offlineSeconds: elapsedSeconds, }; };