/** * @file Prestige panel component for ascending and purchasing runestone upgrades. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Complex component with many render paths */ /* eslint-disable complexity -- Many conditional render paths */ /* eslint-disable max-lines -- Large panel with prestige and shop tabs */ /* eslint-disable max-statements -- Prestige panel manages many local state variables */ import { useState, type JSX } from "react"; import { prestige } from "../../api/client.js"; import { useGame } from "../../context/gameContext.js"; import { PRESTIGE_UPGRADE_CATEGORY_LABELS, PRESTIGE_UPGRADES, } from "../../data/prestigeUpgrades.js"; import { computeProjectedRunestones, } from "../../engine/tick.js"; import { cdnImage } from "../../utils/cdn.js"; import { sendNotification } from "../../utils/notification.js"; import { playSound } from "../../utils/sound.js"; import type { PrestigeUpgradeCategory } from "@elysium/types"; const baseThreshold = 1_000_000; /** * Calculates the prestige threshold for a given prestige count. * Mirrors the server formula: BASE * (count + 1)^2. * @param prestigeCount - The current prestige count. * @returns The required gold to prestige. */ const calculateThreshold = (prestigeCount: number): number => { return baseThreshold * Math.pow(prestigeCount + 1, 2); }; /** * Calculates the production multiplier for a given prestige count. * @param prestigeCount - The number of times the player has prestiged. * @returns The compounding multiplier applied to all income sources. */ const calculateProductionMultiplier = (prestigeCount: number): number => { return Math.pow(1.15, prestigeCount); }; const categoryOrder: Array = [ "income", "click", "essence", "crystals", "runestones", "utility", ]; /** * Renders the prestige panel with ascension and runestone shop tabs. * @returns The JSX element. */ const PrestigePanel = (): JSX.Element => { const { state, reloadSilent, formatNumber, buyPrestigeUpgrade, enableNotifications, enableSounds, toggleAutoAdventurer, toggleAutoPrestige, triggerPrestigeToast, } = useGame(); const [ isPending, setIsPending ] = useState(false); const [ result, setResult ] = useState<{ runestones: number; count: number; milestoneRunestones: number; } | null>(null); const [ prestigeError, setPrestigeError ] = useState(null); const [ buyingId, setBuyingId ] = useState(null); const [ activeTab, setActiveTab ] = useState<"prestige" | "shop">("prestige"); if (state === null) { return (

{"Loading..."}

); } const { autoAdventurer, prestige: prestigeData, player } = state; const threshold = calculateThreshold(prestigeData.count); const isEligible = player.totalGoldEarned >= threshold; const runestonePreview = computeProjectedRunestones(state); const nextMultiplier = calculateProductionMultiplier(prestigeData.count + 1); async function handlePrestige(): Promise { setIsPending(true); setPrestigeError(null); try { const data = await prestige({}); setResult({ count: data.newPrestigeCount, milestoneRunestones: data.milestoneRunestones, runestones: data.runestones, }); triggerPrestigeToast(); if (enableSounds) { playSound("prestige"); } if (enableNotifications) { sendNotification( "⭐ Prestige!", `You've reached prestige level ${data.newPrestigeCount.toString()}!`, ); } await reloadSilent(); } catch (error_: unknown) { setPrestigeError( error_ instanceof Error ? error_.message : "Prestige failed", ); } finally { setIsPending(false); } } async function handleBuyUpgrade(upgradeId: string): Promise { setBuyingId(upgradeId); try { await buyPrestigeUpgrade(upgradeId); } finally { setBuyingId(null); } } const upgradesByCategory = categoryOrder.map((categoryId) => { const label = PRESTIGE_UPGRADE_CATEGORY_LABELS[categoryId] ?? categoryId; const upgrades = PRESTIGE_UPGRADES.filter((upgrade) => { return upgrade.category === categoryId; }); return { categoryId, label, upgrades }; }); function handlePrestigeClick(): void { void handlePrestige(); } function handleAutoAdventurerToggle(): void { toggleAutoAdventurer(); } function handleAutoPrestigeToggle(): void { toggleAutoPrestige(); } function handlePrestigeTabClick(): void { setActiveTab("prestige"); } function handleShopTabClick(): void { setActiveTab("shop"); } const progressRatio = player.totalGoldEarned / threshold; const progressPct = (progressRatio * 100).toFixed(1); return (

{"⭐ Prestige"}

{activeTab === "prestige" && <>

{"Prestige resets your progress but grants "} {"Runestones"} {"— permanent currency used for powerful upgrades."} {" Each prestige multiplies your global production by ×1.15"} {" (compounding each run)."}

{"Total gold this run: "} {formatNumber(player.totalGoldEarned)}

{"Required to prestige: "} {formatNumber(threshold)}

{"Prestige count: "} {prestigeData.count}

{"Current production multiplier: "} {"×"} {prestigeData.productionMultiplier.toFixed(2)}

{"After next prestige: "} {"×"} {nextMultiplier.toFixed(2)}

{"Runestones: "} {formatNumber(prestigeData.runestones)}

{isEligible ?

{"Runestones on prestige: "} {"+"} {formatNumber(runestonePreview)}

: null} {isEligible ? null :

{"Progress: "} {formatNumber(player.totalGoldEarned)} {" / "} {formatNumber(threshold)} {" ("} {progressPct} {"%"} {")"}

}
{isEligible ?

{"You are ready to prestige!"}

{prestigeError === null ? null :

{prestigeError}

} {result === null ? null :

{"Ascended to Prestige "} {result.count} {"! Earned "} {formatNumber(result.runestones)} {" Runestones."} {result.milestoneRunestones > 0 && <> {" 🎉 Milestone bonus: +"} {formatNumber(result.milestoneRunestones)} {" Runestones!"} }

}
:

{"Earn "} {formatNumber(threshold - player.totalGoldEarned)} {" more gold to unlock prestige."}

} } {activeTab === "shop" &&

{"Balance: "} {formatNumber(prestigeData.runestones)} {" Runestones"}

{upgradesByCategory.map(({ categoryId, label, upgrades }) => { return (

{label}

{upgrades.map((upgrade) => { const purchased = prestigeData.purchasedUpgradeIds.includes( upgrade.id, ); const canAfford = prestigeData.runestones >= upgrade.runestonesCost; const isLoading = buyingId === upgrade.id; const isAutoAdventurerToggle = upgrade.id === "auto_adventurer" && purchased; const autoAdventurerEnabled = autoAdventurer ?? false; const isAutoPrestigeToggle = upgrade.id === "auto_prestige" && purchased; const autoPrestigeEnabled = prestigeData.autoPrestigeEnabled ?? false; function handleBuyClick(): void { void handleBuyUpgrade(upgrade.id); } return (
{upgrade.name}

{upgrade.name}

{upgrade.description}

{purchased ? "✅ Purchased" : `🔮 ${formatNumber(upgrade.runestonesCost)} Runestones`}

{isAutoAdventurerToggle ? : null} {isAutoPrestigeToggle ? : null} {purchased ? null : }
); })}
); })}
}
); }; export { PrestigePanel };