feat: initial elysium idle game prototype

Sets up the full monorepo with pnpm workspaces. Includes shared types
package, Hono API with Discord OAuth/JWT auth, Prisma v6 + MongoDB
Atlas, and React + Vite frontend with game loop, five tabs, and
Discord-linked save/load.
This commit is contained in:
2026-03-06 11:26:19 -08:00
committed by Naomi Carrigan
parent c69e155de3
commit a3daed1683
64 changed files with 9011 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
import type { Adventurer } from "@elysium/types";
export const DEFAULT_ADVENTURERS: Adventurer[] = [
{
id: "peasant",
name: "Peasant",
class: "warrior",
level: 1,
goldPerSecond: 0.1,
essencePerSecond: 0,
count: 0,
unlocked: true,
},
{
id: "militia",
name: "Militia",
class: "warrior",
level: 2,
goldPerSecond: 0.5,
essencePerSecond: 0,
count: 0,
unlocked: false,
},
{
id: "apprentice",
name: "Apprentice Mage",
class: "mage",
level: 3,
goldPerSecond: 1.5,
essencePerSecond: 0.01,
count: 0,
unlocked: false,
},
{
id: "scout",
name: "Scout",
class: "rogue",
level: 4,
goldPerSecond: 4,
essencePerSecond: 0.02,
count: 0,
unlocked: false,
},
{
id: "acolyte",
name: "Acolyte",
class: "cleric",
level: 5,
goldPerSecond: 10,
essencePerSecond: 0.05,
count: 0,
unlocked: false,
},
{
id: "ranger",
name: "Ranger",
class: "ranger",
level: 6,
goldPerSecond: 25,
essencePerSecond: 0.1,
count: 0,
unlocked: false,
},
{
id: "knight",
name: "Knight",
class: "warrior",
level: 7,
goldPerSecond: 75,
essencePerSecond: 0.2,
count: 0,
unlocked: false,
},
{
id: "archmage",
name: "Archmage",
class: "mage",
level: 8,
goldPerSecond: 200,
essencePerSecond: 0.5,
count: 0,
unlocked: false,
},
{
id: "paladin",
name: "Paladin",
class: "paladin",
level: 9,
goldPerSecond: 600,
essencePerSecond: 1,
count: 0,
unlocked: false,
},
{
id: "dragon_rider",
name: "Dragon Rider",
class: "ranger",
level: 10,
goldPerSecond: 2000,
essencePerSecond: 3,
count: 0,
unlocked: false,
},
];
+64
View File
@@ -0,0 +1,64 @@
import type { Boss } from "@elysium/types";
export const DEFAULT_BOSSES: Boss[] = [
{
id: "troll_king",
name: "The Troll King",
description:
"Gruk the Immovable has terrorised the trade roads for decades. Merchants will pay handsomely for his head.",
status: "available",
maxHp: 1_000,
currentHp: 1_000,
damagePerSecond: 5,
goldReward: 10_000,
essenceReward: 25,
crystalReward: 0,
upgradeRewards: ["click_2"],
prestigeRequirement: 0,
},
{
id: "lich_queen",
name: "The Lich Queen",
description:
"Seraphina the Undying commands legions of undead from her bone throne. Her defeat will echo through history.",
status: "locked",
maxHp: 10_000,
currentHp: 10_000,
damagePerSecond: 20,
goldReward: 100_000,
essenceReward: 200,
crystalReward: 10,
upgradeRewards: ["global_2"],
prestigeRequirement: 0,
},
{
id: "elder_dragon",
name: "Elder Dragon Vaeltharox",
description:
"The eldest dragon in existence, older than the kingdom itself. Even his breath can level mountains.",
status: "locked",
maxHp: 100_000,
currentHp: 100_000,
damagePerSecond: 75,
goldReward: 1_000_000,
essenceReward: 1_000,
crystalReward: 50,
upgradeRewards: ["click_3"],
prestigeRequirement: 1,
},
{
id: "void_titan",
name: "The Void Titan",
description:
"A creature from beyond the veil of reality, drawn by the power your guild has accumulated. It must not be allowed to exist.",
status: "locked",
maxHp: 1_000_000,
currentHp: 1_000_000,
damagePerSecond: 250,
goldReward: 10_000_000,
essenceReward: 5_000,
crystalReward: 200,
upgradeRewards: [],
prestigeRequirement: 3,
},
];
+34
View File
@@ -0,0 +1,34 @@
import type { GameState, Player, PrestigeData } from "@elysium/types";
import { DEFAULT_ADVENTURERS } from "./adventurers.js";
import { DEFAULT_BOSSES } from "./bosses.js";
import { DEFAULT_QUESTS } from "./quests.js";
import { DEFAULT_UPGRADES } from "./upgrades.js";
export const INITIAL_PRESTIGE: PrestigeData = {
count: 0,
runestones: 0,
productionMultiplier: 1,
purchasedUpgradeIds: [],
};
export const INITIAL_GAME_STATE = (player: Player, characterName: string): GameState => ({
player: {
...player,
characterName,
totalGoldEarned: 0,
totalClicks: 0,
},
resources: {
gold: 0,
essence: 0,
crystals: 0,
runestones: 0,
},
adventurers: structuredClone(DEFAULT_ADVENTURERS),
upgrades: structuredClone(DEFAULT_UPGRADES),
quests: structuredClone(DEFAULT_QUESTS),
bosses: structuredClone(DEFAULT_BOSSES),
prestige: INITIAL_PRESTIGE,
baseClickPower: 1,
lastTickAt: Date.now(),
});
+63
View File
@@ -0,0 +1,63 @@
import type { Quest } from "@elysium/types";
export const DEFAULT_QUESTS: Quest[] = [
{
id: "first_steps",
name: "First Steps",
description: "Every legend begins somewhere. Send your first adventurer into the field.",
status: "available",
durationSeconds: 60,
rewards: [{ type: "gold", amount: 500 }],
prerequisiteIds: [],
},
{
id: "goblin_camp",
name: "Goblin Camp",
description: "Clear out a troublesome goblin camp to the east.",
status: "locked",
durationSeconds: 5 * 60,
rewards: [
{ type: "gold", amount: 2_000 },
{ type: "essence", amount: 5 },
],
prerequisiteIds: ["first_steps"],
},
{
id: "haunted_mine",
name: "The Haunted Mine",
description: "An abandoned mine is rich with crystal deposits — if you dare brave its ghosts.",
status: "locked",
durationSeconds: 15 * 60,
rewards: [
{ type: "crystals", amount: 10 },
{ type: "upgrade", targetId: "global_1" },
],
prerequisiteIds: ["goblin_camp"],
},
{
id: "ancient_ruins",
name: "Ancient Ruins",
description: "Scholars believe the ruins hold secrets of a forgotten civilisation.",
status: "locked",
durationSeconds: 30 * 60,
rewards: [
{ type: "essence", amount: 50 },
{ type: "upgrade", targetId: "click_2" },
],
prerequisiteIds: ["haunted_mine"],
},
{
id: "dragon_lair",
name: "Dragon's Lair",
description:
"The legendary lair of Pyraxis the Undying. Few who enter return — those who do are rich beyond imagining.",
status: "locked",
durationSeconds: 60 * 60,
rewards: [
{ type: "gold", amount: 500_000 },
{ type: "crystals", amount: 50 },
{ type: "adventurer", targetId: "dragon_rider" },
],
prerequisiteIds: ["ancient_ruins"],
},
];
+98
View File
@@ -0,0 +1,98 @@
import type { Upgrade } from "@elysium/types";
export const DEFAULT_UPGRADES: Upgrade[] = [
// Click upgrades
{
id: "click_1",
name: "Keen Eye",
description: "Your strikes find weak points. Doubles click power.",
target: "click",
multiplier: 2,
costGold: 100,
costEssence: 0,
purchased: false,
unlocked: true,
},
{
id: "click_2",
name: "Battle Hardened",
description: "Years of combat sharpen your instincts. Doubles click power again.",
target: "click",
multiplier: 2,
costGold: 1000,
costEssence: 0,
purchased: false,
unlocked: false,
},
{
id: "click_3",
name: "Legendary Weapon",
description: "A weapon of ancient power. Triples click power.",
target: "click",
multiplier: 3,
costGold: 50_000,
costEssence: 10,
purchased: false,
unlocked: false,
},
// Global upgrades
{
id: "global_1",
name: "Guild Charter",
description: "Formalising the guild structure increases all income by 25%.",
target: "global",
multiplier: 1.25,
costGold: 500,
costEssence: 0,
purchased: false,
unlocked: false,
},
{
id: "global_2",
name: "Merchant Alliance",
description: "Trade routes boost all income by 50%.",
target: "global",
multiplier: 1.5,
costGold: 10_000,
costEssence: 5,
purchased: false,
unlocked: false,
},
// Adventurer-specific upgrades
{
id: "peasant_1",
name: "Better Tools",
description: "Peasants work twice as hard with proper equipment.",
target: "adventurer",
adventurerId: "peasant",
multiplier: 2,
costGold: 200,
costEssence: 0,
purchased: false,
unlocked: false,
},
{
id: "militia_1",
name: "Militia Training",
description: "Formal training doubles militia effectiveness.",
target: "adventurer",
adventurerId: "militia",
multiplier: 2,
costGold: 1_000,
costEssence: 0,
purchased: false,
unlocked: false,
},
{
id: "mage_1",
name: "Arcane Tomes",
description: "Ancient books of magic double mage output.",
target: "adventurer",
adventurerId: "apprentice",
multiplier: 2,
costGold: 5_000,
costEssence: 2,
purchased: false,
unlocked: false,
},
];