diff --git a/IDEAS.md b/IDEAS.md deleted file mode 100644 index aa2af82..0000000 --- a/IDEAS.md +++ /dev/null @@ -1,49 +0,0 @@ -# Elysium — Content Ideas - -A running list of planned features and content additions. Strike through items as they're completed! - ---- - -## 🌟 New Systems - -- [x] **Offline earnings** — When returning to the game, earn a percentage of what you'd have earned offline (cap at ~8–12 hours). Upgradeable via the prestige shop to increase the % and the time cap. Essential for an idle game! - -- [x] **Second prestige layer (Transcendence)** — Unlocked after ~10 prestiges. Sacrifice all runestones for a new currency ("Echoes"?). Echoes are permanent account-wide currency that persist across prestiges. Has its own upgrade tree with truly game-changing bonuses. Gives endgame players a long-term goal. - -- [x] **Daily challenges** — Three rotating objectives each day (e.g. kill X boss, earn X gold this run, complete X quests). Reward bonus crystals. Encourages daily logins even when idling comfortably. - -- [x] **Boss first-kill bounties** — Defeating a boss for the very first time grants a one-time runestone bonus. Rewards exploration and makes conquering a new zone feel extra satisfying. - -- [x] **Auto-prestige toggle** — Unlockable via the prestige shop. Automatically prestiges the moment the threshold is reached. Late-game convenience that dedicated players will love. - ---- - -## 📦 Content Additions - -- [x] **Equipment set bonuses** — Group existing equipment into named sets (e.g. "Shadow Infiltrator"). Wearing 2/3/4 pieces of a set grants escalating bonuses. Adds strategic depth without requiring lots of new items. - -- [x] **The Codex / Lore Book** — Defeating bosses and completing quests unlocks lore entries about the world. Pure flavour, but gives the world depth and a collection mechanic. Show a ✨ notification when new lore unlocks. - -- [x] **Milestone prestige bonuses** — Every 5th prestige, earn a free prestige upgrade or a large runestone windfall. Gives players mini-goals within the prestige loop. - ---- - -## 📊 UI / Statistics - -- [x] **Statistics panel** — All-time totals: gold earned across all runs, total prestiges, bosses defeated, quests completed, time played. Idle game players love seeing big numbers about their big numbers. - -- [x] **Last cloud save date + Force cloud save button** — Display when the last cloud save occurred (always visible, e.g. in the ResourceBar). Include a manual "Force Save" button for peace of mind. - ---- - -## 💜 Priority Order (Suggested) - -1. ~~Offline earnings~~ ✅ -2. ~~Statistics panel~~ ✅ -3. ~~Daily challenges~~ ✅ -4. ~~Boss first-kill bounties~~ ✅ -5. ~~Milestone prestige bonuses~~ ✅ -6. ~~Equipment set bonuses~~ ✅ -7. ~~Auto-prestige toggle~~ ✅ -8. ~~The Codex / Lore Book~~ ✅ -9. ✅ Second prestige layer / Transcendence (big feature, save for later) diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 06c3d09..4ef3c70 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -3,6 +3,7 @@ import { Hono } from "hono"; import { cors } from "hono/cors"; import { logger } from "hono/logger"; import { aboutRouter } from "./routes/about.js"; +import { apotheosisRouter } from "./routes/apotheosis.js"; import { authRouter } from "./routes/auth.js"; import { bossRouter } from "./routes/boss.js"; import { gameRouter } from "./routes/game.js"; @@ -28,6 +29,7 @@ app.route("/game", gameRouter); app.route("/boss", bossRouter); app.route("/prestige", prestigeRouter); app.route("/transcendence", transcendenceRouter); +app.route("/apotheosis", apotheosisRouter); app.route("/profile", profileRouter); app.get("/health", (context) => context.json({ status: "ok" })); diff --git a/apps/api/src/routes/apotheosis.ts b/apps/api/src/routes/apotheosis.ts new file mode 100644 index 0000000..c845de2 --- /dev/null +++ b/apps/api/src/routes/apotheosis.ts @@ -0,0 +1,71 @@ +import type { ApotheosisRequest, GameState } from "@elysium/types"; +import { Hono } from "hono"; +import type { HonoEnv } from "../types/hono.js"; +import { prisma } from "../db/client.js"; +import { authMiddleware } from "../middleware/auth.js"; +import { + buildPostApotheosisState, + isEligibleForApotheosis, +} from "../services/apotheosis.js"; + +export const apotheosisRouter = new Hono(); + +apotheosisRouter.use("*", authMiddleware); + +apotheosisRouter.post("/", async (context) => { + const discordId = context.get("discordId") as string; + const body = await context.req.json(); + + const characterName = body.characterName?.trim(); + if (!characterName) { + return context.json({ error: "characterName is required" }, 400); + } + + const record = await prisma.gameState.findUnique({ where: { discordId } }); + if (!record) { + return context.json({ error: "No save found" }, 404); + } + + const state = record.state as unknown as GameState; + + if (!isEligibleForApotheosis(state)) { + return context.json( + { error: "Not eligible for Apotheosis — purchase all Transcendence upgrades first" }, + 400, + ); + } + + // Capture current-run stats before the nuclear reset + const runBossesDefeated = state.bosses.filter((b) => b.status === "defeated").length; + const runQuestsCompleted = state.quests.filter((q) => q.status === "completed").length; + const runAdventurersRecruited = state.adventurers.reduce((sum, a) => sum + a.count, 0); + const runAchievementsUnlocked = (state.achievements ?? []).filter((a) => a.unlockedAt !== null).length; + + const { newState, newApotheosisData } = buildPostApotheosisState(state, characterName); + + const now = Date.now(); + await prisma.gameState.update({ + where: { discordId }, + data: { state: newState as object, updatedAt: now }, + }); + + await prisma.player.update({ + where: { discordId }, + data: { + characterName, + // Reset current-run counters + totalGoldEarned: 0, + totalClicks: 0, + // Accumulate into lifetime totals + lifetimeGoldEarned: { increment: state.player.totalGoldEarned }, + lifetimeClicks: { increment: state.player.totalClicks }, + lifetimeBossesDefeated: { increment: runBossesDefeated }, + lifetimeQuestsCompleted: { increment: runQuestsCompleted }, + lifetimeAdventurersRecruited: { increment: runAdventurersRecruited }, + lifetimeAchievementsUnlocked: { increment: runAchievementsUnlocked }, + lastSavedAt: now, + }, + }); + + return context.json({ newApotheosisCount: newApotheosisData.count }); +}); diff --git a/apps/api/src/routes/game.ts b/apps/api/src/routes/game.ts index 25f9d6c..4d02c14 100644 --- a/apps/api/src/routes/game.ts +++ b/apps/api/src/routes/game.ts @@ -302,7 +302,14 @@ const validateAndSanitize = (incoming: GameState, previous: GameState): GameStat ? { transcendence: previous.transcendence } : {}; - return { ...incoming, resources, bosses, quests, achievements, prestige, ...transcendenceSpread }; + // Apotheosis count can only increase server-side — cap at the previous value. + const apotheosisSpread = incoming.apotheosis + ? { apotheosis: { count: Math.min(incoming.apotheosis.count, previous.apotheosis?.count ?? 0) } } + : previous.apotheosis + ? { apotheosis: previous.apotheosis } + : {}; + + return { ...incoming, resources, bosses, quests, achievements, prestige, ...transcendenceSpread, ...apotheosisSpread }; }; export const gameRouter = new Hono(); diff --git a/apps/api/src/services/apotheosis.ts b/apps/api/src/services/apotheosis.ts new file mode 100644 index 0000000..364d939 --- /dev/null +++ b/apps/api/src/services/apotheosis.ts @@ -0,0 +1,41 @@ +import type { ApotheosisData, GameState } from "@elysium/types"; +import { INITIAL_GAME_STATE } from "../data/initialState.js"; +import { DEFAULT_TRANSCENDENCE_UPGRADES } from "../data/transcendenceUpgrades.js"; + +/** Total number of echo upgrades — all must be purchased to unlock Apotheosis */ +const TOTAL_ECHO_UPGRADES = DEFAULT_TRANSCENDENCE_UPGRADES.length; + +/** + * Returns true when the player is eligible to achieve Apotheosis: + * all Transcendence echo upgrades must be purchased. + */ +export const isEligibleForApotheosis = (state: GameState): boolean => { + const purchasedIds = state.transcendence?.purchasedUpgradeIds ?? []; + return purchasedIds.length >= TOTAL_ECHO_UPGRADES && + DEFAULT_TRANSCENDENCE_UPGRADES.every((u) => purchasedIds.includes(u.id)); +}; + +/** + * Builds the new game state after Apotheosis — the ultimate nuclear reset. + * Wipes absolutely everything including prestige and transcendence. + * Only codex lore entries and the apotheosis count itself are preserved. + */ +export const buildPostApotheosisState = ( + currentState: GameState, + characterName: string, +): { newState: GameState; newApotheosisData: ApotheosisData } => { + const newCount = (currentState.apotheosis?.count ?? 0) + 1; + const newApotheosisData: ApotheosisData = { count: newCount }; + + const freshState = INITIAL_GAME_STATE(currentState.player, characterName); + const newState: GameState = { + ...freshState, + lastTickAt: Date.now(), + // Codex lore persists through all resets — players keep their discovered entries + ...(currentState.codex ? { codex: currentState.codex } : {}), + // Apotheosis data is eternal — never wiped by any reset + apotheosis: newApotheosisData, + }; + + return { newState, newApotheosisData }; +}; diff --git a/apps/api/src/services/prestige.ts b/apps/api/src/services/prestige.ts index f170400..6daa0d7 100644 --- a/apps/api/src/services/prestige.ts +++ b/apps/api/src/services/prestige.ts @@ -120,6 +120,8 @@ export const buildPostPrestigeState = ( ...(currentState.codex ? { codex: currentState.codex } : {}), // Transcendence data is permanent — never wiped by prestige ...(currentState.transcendence ? { transcendence: currentState.transcendence } : {}), + // Apotheosis data is eternal — never wiped by prestige + ...(currentState.apotheosis ? { apotheosis: currentState.apotheosis } : {}), }; return { newState, newPrestigeData, runestonesEarned, milestoneRunestones }; diff --git a/apps/api/src/services/transcendence.ts b/apps/api/src/services/transcendence.ts index cd36f49..e9ff9d4 100644 --- a/apps/api/src/services/transcendence.ts +++ b/apps/api/src/services/transcendence.ts @@ -78,6 +78,8 @@ export const buildPostTranscendenceState = ( ...(currentState.codex ? { codex: currentState.codex } : {}), // Transcendence data is permanent transcendence: newTranscendenceData, + // Apotheosis data is eternal — never wiped by transcendence + ...(currentState.apotheosis ? { apotheosis: currentState.apotheosis } : {}), }; return { newState, newTranscendenceData, echoesEarned }; diff --git a/apps/web/src/api/client.ts b/apps/web/src/api/client.ts index 93b3514..c6356c6 100644 --- a/apps/web/src/api/client.ts +++ b/apps/web/src/api/client.ts @@ -1,5 +1,7 @@ import type { AboutResponse, + ApotheosisRequest, + ApotheosisResponse, AuthResponse, BossChallengeRequest, BossChallengeResponse, @@ -109,6 +111,12 @@ export const buyEchoUpgrade = async ( body: JSON.stringify(body), }); +export const achieveApotheosis = async (body: ApotheosisRequest): Promise => + request("/apotheosis", { + method: "POST", + body: JSON.stringify(body), + }); + export const getPublicProfile = async ( discordId: string, ): Promise => diff --git a/apps/web/src/components/game/AboutPanel.tsx b/apps/web/src/components/game/AboutPanel.tsx index 468cc84..c32bfc7 100644 --- a/apps/web/src/components/game/AboutPanel.tsx +++ b/apps/web/src/components/game/AboutPanel.tsx @@ -63,6 +63,10 @@ const HOW_TO_PLAY = [ title: "🌌 Transcendence", body: "Transcendence is the ultimate prestige layer, unlocked by defeating The Absolute One (requires Prestige 90). Transcending performs a nuclear reset — wiping resources, prestige, runestones, upgrades, and equipment — but grants Echoes based on your prestige count (fewer prestiges = more Echoes). Echoes are permanent and survive all future resets. Spend them in the Echo Shop on lasting multipliers: passive income, combat power, prestige quality-of-life, and Echo meta upgrades that amplify future Echo yields.", }, + { + title: "✨ Apotheosis", + body: "Apotheosis is the final act — a complete dissolution of everything you have built, including your prestige and transcendence progress. It is unlocked once you have purchased every Transcendence upgrade. In exchange for this total reset, you receive the Apotheosis badge: pure bragging rights, a mark of reaching the absolute pinnacle of the game. Apotheosis can be achieved multiple times; each cycle requires purchasing all Transcendence upgrades again. Your Codex entries and lifetime profile statistics are always preserved.", + }, ]; const formatDate = (dateStr: string): string => diff --git a/apps/web/src/components/game/ApotheosisPanel.tsx b/apps/web/src/components/game/ApotheosisPanel.tsx new file mode 100644 index 0000000..6bc74da --- /dev/null +++ b/apps/web/src/components/game/ApotheosisPanel.tsx @@ -0,0 +1,106 @@ +import { useState } from "react"; +import { useGame } from "../../context/GameContext.js"; +import { TRANSCENDENCE_UPGRADES } from "../../data/transcendenceUpgrades.js"; + +const TOTAL_ECHO_UPGRADES = TRANSCENDENCE_UPGRADES.length; + +export const ApotheosisPanel = (): React.JSX.Element => { + const { state, apotheosis } = useGame(); + const [characterName, setCharacterName] = useState(""); + const [isPending, setIsPending] = useState(false); + const [result, setResult] = useState(null); + const [error, setError] = useState(null); + + if (!state) return

Loading...

; + + const purchasedIds = state.transcendence?.purchasedUpgradeIds ?? []; + const purchasedCount = TRANSCENDENCE_UPGRADES.filter((u) => purchasedIds.includes(u.id)).length; + const isEligible = purchasedCount >= TOTAL_ECHO_UPGRADES; + const apotheosisCount = state.apotheosis?.count ?? 0; + + const handleApotheosis = async (): Promise => { + if (!characterName.trim()) return; + setIsPending(true); + setError(null); + try { + const data = await apotheosis(characterName.trim()); + setResult(data.newApotheosisCount); + } catch (err) { + setError(err instanceof Error ? err.message : "Apotheosis failed"); + } finally { + setIsPending(false); + } + }; + + return ( +
+

✨ Apotheosis

+ +

+ Apotheosis is the final act — a complete dissolution of everything you have built. + Prestige, Transcendence, Echoes, upgrades, equipment, resources: all of it returns + to nothing. In exchange, you receive only one thing: +

+

+ The ✨ Apotheosis badge. Proof that you have done it all. +

+

+ Apotheosis can be achieved multiple times. Each cycle requires you to purchase + every Transcendence upgrade again before the next Apotheosis becomes available. + There is no mechanical benefit — only the knowledge that you have reached the + pinnacle, dissolved it, and climbed back up. +

+ + {apotheosisCount > 0 && ( +
+ You have achieved Apotheosis {apotheosisCount} time{apotheosisCount === 1 ? "" : "s"}. +
+ )} + +
+

+ Transcendence upgrades purchased:{" "} + {purchasedCount} / {TOTAL_ECHO_UPGRADES} +

+ {!isEligible && ( +

+ 🔒 Purchase all {TOTAL_ECHO_UPGRADES} Transcendence upgrades to unlock Apotheosis. + ({TOTAL_ECHO_UPGRADES - purchasedCount} remaining) +

+ )} + {isEligible && ( +

✅ All Transcendence upgrades purchased. You are ready.

+ )} +
+ + {isEligible && ( +
+

This action is permanent and irreversible. Choose your name for the next cycle:

+ { setCharacterName(e.target.value); }} + placeholder="Character name..." + type="text" + value={characterName} + /> + + {error &&

{error}

} + {result !== null && ( +

+ Apotheosis achieved. This is cycle {result}. + The infinite loop continues. +

+ )} +
+ )} +
+ ); +}; diff --git a/apps/web/src/components/game/GameLayout.tsx b/apps/web/src/components/game/GameLayout.tsx index ced4a59..072ece5 100644 --- a/apps/web/src/components/game/GameLayout.tsx +++ b/apps/web/src/components/game/GameLayout.tsx @@ -14,13 +14,14 @@ import { EditProfileModal } from "./EditProfileModal.js"; import { EquipmentPanel } from "./EquipmentPanel.js"; import { OfflineModal } from "./OfflineModal.js"; import { PrestigePanel } from "./PrestigePanel.js"; +import { ApotheosisPanel } from "./ApotheosisPanel.js"; import { TranscendencePanel } from "./TranscendencePanel.js"; import { QuestPanel } from "./QuestPanel.js"; import { StatisticsPanel } from "./StatisticsPanel.js"; import { UpgradePanel } from "./UpgradePanel.js"; import { DailyChallengePanel } from "./DailyChallengePanel.js"; -type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige" | "transcendence" | "statistics" | "daily" | "codex" | "about"; +type Tab = "adventurers" | "upgrades" | "quests" | "bosses" | "equipment" | "achievements" | "prestige" | "transcendence" | "apotheosis" | "statistics" | "daily" | "codex" | "about"; const BASE_TABS: { id: Tab; label: string }[] = [ { id: "adventurers", label: "⚔️ Adventurers" }, @@ -31,6 +32,7 @@ const BASE_TABS: { id: Tab; label: string }[] = [ { id: "achievements", label: "🏆 Achievements" }, { id: "prestige", label: "⭐ Prestige" }, { id: "transcendence", label: "🌌 Transcendence" }, + { id: "apotheosis", label: "✨ Apotheosis" }, { id: "statistics", label: "📊 Statistics" }, { id: "daily", label: "📅 Daily" }, { id: "codex", label: "📖 Codex" }, @@ -69,6 +71,7 @@ export const GameLayout = (): React.JSX.Element => { runestones={state.prestige.runestones} prestigeCount={state.prestige.count} transcendenceCount={state.transcendence?.count ?? 0} + apotheosisCount={state.apotheosis?.count ?? 0} profileUrl={profileUrl} onEditProfile={() => { setEditingProfile(true); }} lastSavedAt={lastSavedAt} @@ -116,6 +119,7 @@ export const GameLayout = (): React.JSX.Element => { {activeTab === "achievements" && } {activeTab === "prestige" && } {activeTab === "transcendence" && } + {activeTab === "apotheosis" && } {activeTab === "statistics" && } {activeTab === "daily" && } {activeTab === "codex" && } diff --git a/apps/web/src/components/ui/ResourceBar.tsx b/apps/web/src/components/ui/ResourceBar.tsx index 0fb56ab..3b4279e 100644 --- a/apps/web/src/components/ui/ResourceBar.tsx +++ b/apps/web/src/components/ui/ResourceBar.tsx @@ -7,6 +7,7 @@ interface ResourceBarProps { runestones: number; prestigeCount: number; transcendenceCount: number; + apotheosisCount: number; profileUrl: string; onEditProfile: () => void; lastSavedAt: number | null; @@ -31,6 +32,7 @@ export const ResourceBar = ({ runestones, prestigeCount, transcendenceCount, + apotheosisCount, profileUrl, onEditProfile, lastSavedAt, @@ -65,6 +67,11 @@ export const ResourceBar = ({ {formatNumber(runestones)} Runestones + {apotheosisCount > 0 && ( +
+ ✨ Apotheosis {apotheosisCount} +
+ )} {transcendenceCount > 0 && (
🌌 Transcendence {transcendenceCount} diff --git a/apps/web/src/context/GameContext.tsx b/apps/web/src/context/GameContext.tsx index e506821..9eb8792 100644 --- a/apps/web/src/context/GameContext.tsx +++ b/apps/web/src/context/GameContext.tsx @@ -8,6 +8,7 @@ import { useState, } from "react"; import { + achieveApotheosis as achieveApotheosisApi, buyEchoUpgrade as buyEchoUpgradeApi, buyPrestigeUpgrade as buyPrestigeUpgradeApi, challengeBoss as challengeBossApi, @@ -87,6 +88,8 @@ interface GameContextValue { transcend: (characterName: string) => Promise<{ echoes: number; newTranscendenceCount: number }>; /** Buy an echo upgrade from the transcendence shop */ buyEchoUpgrade: (upgradeId: string) => Promise; + /** Achieve Apotheosis — the ultimate nuclear reset, bragging rights only */ + apotheosis: (characterName: string) => Promise<{ newApotheosisCount: number }>; } const GameContext = createContext(null); @@ -543,6 +546,12 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React return result; }, [reload]); + const apotheosis = useCallback(async (characterName: string) => { + const result = await achieveApotheosisApi({ characterName }); + await reload(); + return result; + }, [reload]); + const buyEchoUpgrade = useCallback(async (upgradeId: string) => { try { const result = await buyEchoUpgradeApi({ upgradeId }); @@ -751,6 +760,7 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React dismissCodexEntry, transcend, buyEchoUpgrade, + apotheosis, }} > {children} diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 30770b3..b84dff5 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -2441,3 +2441,84 @@ body { cursor: not-allowed; opacity: 0.4; } + +/* ── Apotheosis ─────────────────────────────────────────────────────────── */ + +.apotheosis-badge { + background: linear-gradient(135deg, #78350f, #d97706, #fbbf24); + border-radius: 999px; + color: #1c1917; + font-size: 0.85rem; + font-weight: 700; + padding: 0.25rem 0.75rem; +} + +.apotheosis-panel .apotheosis-intro { + color: var(--colour-text-muted); + font-size: 0.95rem; + margin-bottom: 0.75rem; +} + +.apotheosis-reward { + background: linear-gradient(135deg, rgba(120, 53, 15, 0.2), rgba(217, 119, 6, 0.2)); + border: 1px solid #d97706; + border-radius: var(--radius); + font-size: 1rem; + margin: 1rem 0; + padding: 0.75rem 1rem; + text-align: center; +} + +.apotheosis-count { + background: var(--colour-surface); + border: 1px solid #d97706; + border-radius: var(--radius); + margin: 0.75rem 0; + padding: 0.75rem 1rem; + text-align: center; +} + +.apotheosis-status { + background: var(--colour-surface); + border: 1px solid #d97706; + border-radius: var(--radius); + margin: 1rem 0; + padding: 1rem; +} + +.apotheosis-status p { + margin: 0.25rem 0; +} + +.apotheosis-missing { + color: var(--colour-text-muted); + font-size: 0.9rem; +} + +.apotheosis-ready { + color: #fbbf24; + font-weight: 600; +} + +.apotheosis-button { + background: linear-gradient(135deg, #78350f, #d97706); + border: none; + border-radius: var(--radius); + color: #fff; + cursor: pointer; + font-size: 1rem; + font-weight: 700; + margin-top: 0.5rem; + padding: 0.75rem 2rem; + transition: opacity 0.2s; + width: 100%; +} + +.apotheosis-button:hover:not(:disabled) { + opacity: 0.85; +} + +.apotheosis-button:disabled { + cursor: not-allowed; + opacity: 0.4; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 36cdae7..9bd3361 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,3 +1,4 @@ +export type { ApotheosisData } from "./interfaces/Apotheosis.js"; export type { CodexEntry, CodexState } from "./interfaces/Codex.js"; export type { Achievement, @@ -9,6 +10,8 @@ export type { Adventurer, AdventurerClass } from "./interfaces/Adventurer.js"; export type { AboutResponse, ApiError, + ApotheosisRequest, + ApotheosisResponse, AuthResponse, BossChallengeRequest, BossChallengeResponse, diff --git a/packages/types/src/interfaces/Api.ts b/packages/types/src/interfaces/Api.ts index b4bce79..eb2c226 100644 --- a/packages/types/src/interfaces/Api.ts +++ b/packages/types/src/interfaces/Api.ts @@ -150,6 +150,14 @@ export interface BuyEchoUpgradeResponse { echoMetaMultiplier: number; } +export interface ApotheosisRequest { + characterName: string; +} + +export interface ApotheosisResponse { + newApotheosisCount: number; +} + export interface ApiError { error: string; } diff --git a/packages/types/src/interfaces/Apotheosis.ts b/packages/types/src/interfaces/Apotheosis.ts new file mode 100644 index 0000000..6d462b8 --- /dev/null +++ b/packages/types/src/interfaces/Apotheosis.ts @@ -0,0 +1,4 @@ +export interface ApotheosisData { + /** Number of times the player has achieved Apotheosis */ + count: number; +} diff --git a/packages/types/src/interfaces/GameState.ts b/packages/types/src/interfaces/GameState.ts index f0de0b0..5dc0538 100644 --- a/packages/types/src/interfaces/GameState.ts +++ b/packages/types/src/interfaces/GameState.ts @@ -1,6 +1,7 @@ import type { Achievement } from "./Achievement.js"; import type { Adventurer } from "./Adventurer.js"; import type { Boss } from "./Boss.js"; +import type { ApotheosisData } from "./Apotheosis.js"; import type { CodexState } from "./Codex.js"; import type { DailyChallengeState } from "./DailyChallenge.js"; import type { TranscendenceData } from "./Transcendence.js"; @@ -33,4 +34,6 @@ export interface GameState { codex?: CodexState; /** Transcendence (second prestige layer) state — optional for backwards compatibility */ transcendence?: TranscendenceData; + /** Apotheosis (third prestige layer) state — optional for backwards compatibility */ + apotheosis?: ApotheosisData; }