feat: add equipment, achievements, and visual polish

- Equipment system: 12 items across weapon/armour/trinket slots with
  common/rare/epic/legendary rarities; starter commons auto-equipped,
  higher tiers drop from boss victories
- Achievement system: 15 milestones with typed conditions; checked
  each tick and crystal rewards applied automatically
- Achievement toast: slide-in notification, auto-dismisses after 4s
- Floating click text: +X gold floats on each manual click
- Expanded quests (9 total) and upgrades (12 total)
- Upgrade panel now shows locked upgrades so players can see their
  progression path
- formatNumber utility (K/M/B/T) used consistently across all panels
- Backfill logic for existing saves to add new content gracefully
- types package now emits .d.ts declarations
This commit is contained in:
2026-03-06 13:27:48 -08:00
committed by Naomi Carrigan
parent a3daed1683
commit e9e0df31fd
33 changed files with 2066 additions and 133 deletions
+139
View File
@@ -0,0 +1,139 @@
import type { Achievement } from "@elysium/types";
export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
{
id: "first_click",
name: "First Strike",
description: "Click the Guild Hall for the first time.",
icon: "👆",
condition: { type: "totalClicks", amount: 1 },
reward: { crystals: 5 },
unlockedAt: null,
},
{
id: "click_enthusiast",
name: "Click Enthusiast",
description: "Click the Guild Hall 100 times.",
icon: "🖱️",
condition: { type: "totalClicks", amount: 100 },
reward: { crystals: 25 },
unlockedAt: null,
},
{
id: "click_master",
name: "Click Master",
description: "Click the Guild Hall 1,000 times.",
icon: "⚡",
condition: { type: "totalClicks", amount: 1_000 },
reward: { crystals: 100 },
unlockedAt: null,
},
{
id: "first_gold",
name: "First Gold",
description: "Earn your first 100 gold.",
icon: "🪙",
condition: { type: "totalGoldEarned", amount: 100 },
reward: { crystals: 5 },
unlockedAt: null,
},
{
id: "wealthy",
name: "Wealthy",
description: "Earn 10,000 gold in total.",
icon: "💰",
condition: { type: "totalGoldEarned", amount: 10_000 },
reward: { crystals: 25 },
unlockedAt: null,
},
{
id: "rich",
name: "Rich",
description: "Earn 1,000,000 gold in total.",
icon: "👑",
condition: { type: "totalGoldEarned", amount: 1_000_000 },
reward: { crystals: 100 },
unlockedAt: null,
},
{
id: "billionaire",
name: "Billionaire",
description: "Earn 1,000,000,000 gold in total.",
icon: "🏦",
condition: { type: "totalGoldEarned", amount: 1_000_000_000 },
reward: { crystals: 500 },
unlockedAt: null,
},
{
id: "first_quest",
name: "Adventurous Spirit",
description: "Complete your first quest.",
icon: "📜",
condition: { type: "questsCompleted", amount: 1 },
reward: { crystals: 10 },
unlockedAt: null,
},
{
id: "quest_veteran",
name: "Quest Veteran",
description: "Complete 5 quests.",
icon: "📚",
condition: { type: "questsCompleted", amount: 5 },
reward: { crystals: 50 },
unlockedAt: null,
},
{
id: "boss_slayer",
name: "Boss Slayer",
description: "Defeat your first boss.",
icon: "⚔️",
condition: { type: "bossesDefeated", amount: 1 },
reward: { crystals: 25 },
unlockedAt: null,
},
{
id: "legendary_hunter",
name: "Legendary Hunter",
description: "Defeat all four bosses.",
icon: "🏆",
condition: { type: "bossesDefeated", amount: 4 },
reward: { crystals: 200 },
unlockedAt: null,
},
{
id: "guild_master",
name: "Guild Master",
description: "Recruit a total of 50 adventurers.",
icon: "🏰",
condition: { type: "adventurerTotal", amount: 50 },
reward: { crystals: 50 },
unlockedAt: null,
},
{
id: "army_commander",
name: "Army Commander",
description: "Recruit a total of 500 adventurers.",
icon: "🛡️",
condition: { type: "adventurerTotal", amount: 500 },
reward: { crystals: 200 },
unlockedAt: null,
},
{
id: "first_prestige",
name: "Born Again",
description: "Prestige for the first time.",
icon: "⭐",
condition: { type: "prestigeCount", amount: 1 },
reward: { crystals: 100 },
unlockedAt: null,
},
{
id: "collector",
name: "Collector",
description: "Acquire your first piece of boss-dropped equipment.",
icon: "🎒",
condition: { type: "equipmentOwned", amount: 4 },
reward: { crystals: 10 },
unlockedAt: null,
},
];
+10
View File
@@ -8,6 +8,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 1,
goldPerSecond: 0.1,
essencePerSecond: 0,
combatPower: 1,
count: 0,
unlocked: true,
},
@@ -18,6 +19,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 2,
goldPerSecond: 0.5,
essencePerSecond: 0,
combatPower: 3,
count: 0,
unlocked: false,
},
@@ -28,6 +30,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 3,
goldPerSecond: 1.5,
essencePerSecond: 0.01,
combatPower: 8,
count: 0,
unlocked: false,
},
@@ -38,6 +41,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 4,
goldPerSecond: 4,
essencePerSecond: 0.02,
combatPower: 20,
count: 0,
unlocked: false,
},
@@ -48,6 +52,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 5,
goldPerSecond: 10,
essencePerSecond: 0.05,
combatPower: 50,
count: 0,
unlocked: false,
},
@@ -58,6 +63,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 6,
goldPerSecond: 25,
essencePerSecond: 0.1,
combatPower: 120,
count: 0,
unlocked: false,
},
@@ -68,6 +74,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 7,
goldPerSecond: 75,
essencePerSecond: 0.2,
combatPower: 300,
count: 0,
unlocked: false,
},
@@ -78,6 +85,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 8,
goldPerSecond: 200,
essencePerSecond: 0.5,
combatPower: 800,
count: 0,
unlocked: false,
},
@@ -88,6 +96,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 9,
goldPerSecond: 600,
essencePerSecond: 1,
combatPower: 2000,
count: 0,
unlocked: false,
},
@@ -98,6 +107,7 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
level: 10,
goldPerSecond: 2000,
essencePerSecond: 3,
combatPower: 6000,
count: 0,
unlocked: false,
},
+4
View File
@@ -14,6 +14,7 @@ export const DEFAULT_BOSSES: Boss[] = [
essenceReward: 25,
crystalReward: 0,
upgradeRewards: ["click_2"],
equipmentRewards: ["iron_sword", "chainmail", "mages_focus"],
prestigeRequirement: 0,
},
{
@@ -29,6 +30,7 @@ export const DEFAULT_BOSSES: Boss[] = [
essenceReward: 200,
crystalReward: 10,
upgradeRewards: ["global_2"],
equipmentRewards: ["enchanted_blade", "plate_armour", "arcane_orb"],
prestigeRequirement: 0,
},
{
@@ -44,6 +46,7 @@ export const DEFAULT_BOSSES: Boss[] = [
essenceReward: 1_000,
crystalReward: 50,
upgradeRewards: ["click_3"],
equipmentRewards: ["vorpal_sword", "dragon_scale"],
prestigeRequirement: 1,
},
{
@@ -59,6 +62,7 @@ export const DEFAULT_BOSSES: Boss[] = [
essenceReward: 5_000,
crystalReward: 200,
upgradeRewards: [],
equipmentRewards: ["philosophers_stone"],
prestigeRequirement: 3,
},
];
+127
View File
@@ -0,0 +1,127 @@
import type { Equipment } from "@elysium/types";
export const DEFAULT_EQUIPMENT: Equipment[] = [
// Weapons — drop from bosses; common starts owned
{
id: "rusty_sword",
name: "Rusty Sword",
description: "A battered blade, but still sharp enough to draw blood.",
type: "weapon",
rarity: "common",
bonus: { combatMultiplier: 1.1 },
owned: true,
equipped: true,
},
{
id: "iron_sword",
name: "Iron Sword",
description: "A sturdy weapon issued to veterans of the guild.",
type: "weapon",
rarity: "rare",
bonus: { combatMultiplier: 1.25 },
owned: false,
equipped: false,
},
{
id: "enchanted_blade",
name: "Enchanted Blade",
description: "A sword imbued with ancient magic that makes every strike count.",
type: "weapon",
rarity: "epic",
bonus: { combatMultiplier: 1.5 },
owned: false,
equipped: false,
},
{
id: "vorpal_sword",
name: "Vorpal Sword",
description: "A legendary blade that severs even the strongest bonds.",
type: "weapon",
rarity: "legendary",
bonus: { combatMultiplier: 2.0 },
owned: false,
equipped: false,
},
// Armour — drop from bosses; common starts owned
{
id: "leather_armour",
name: "Leather Armour",
description: "Simple protection that keeps your adventurers moving efficiently.",
type: "armour",
rarity: "common",
bonus: { goldMultiplier: 1.1 },
owned: true,
equipped: true,
},
{
id: "chainmail",
name: "Chainmail",
description: "Interlocked rings that guard against most mundane threats.",
type: "armour",
rarity: "rare",
bonus: { goldMultiplier: 1.25 },
owned: false,
equipped: false,
},
{
id: "plate_armour",
name: "Plate Armour",
description: "Full plate protection that inspires confidence — and gold.",
type: "armour",
rarity: "epic",
bonus: { goldMultiplier: 1.5 },
owned: false,
equipped: false,
},
{
id: "dragon_scale",
name: "Dragon Scale Armour",
description: "Armour forged from the scales of a defeated elder dragon.",
type: "armour",
rarity: "legendary",
bonus: { goldMultiplier: 2.0 },
owned: false,
equipped: false,
},
// Trinkets — drop from bosses; common starts owned
{
id: "lucky_coin",
name: "Lucky Coin",
description: "A coin that always lands on the side you need.",
type: "trinket",
rarity: "common",
bonus: { clickMultiplier: 1.1 },
owned: true,
equipped: true,
},
{
id: "mages_focus",
name: "Mage's Focus",
description: "A crystal lens that sharpens magical precision.",
type: "trinket",
rarity: "rare",
bonus: { clickMultiplier: 1.25 },
owned: false,
equipped: false,
},
{
id: "arcane_orb",
name: "Arcane Orb",
description: "An orb humming with concentrated arcane energy.",
type: "trinket",
rarity: "epic",
bonus: { clickMultiplier: 1.5 },
owned: false,
equipped: false,
},
{
id: "philosophers_stone",
name: "Philosopher's Stone",
description: "The legendary stone that grants mastery over gold and combat alike.",
type: "trinket",
rarity: "legendary",
bonus: { clickMultiplier: 2.0, goldMultiplier: 1.25 },
owned: false,
equipped: false,
},
];
+4
View File
@@ -1,6 +1,8 @@
import type { GameState, Player, PrestigeData } from "@elysium/types";
import { DEFAULT_ACHIEVEMENTS } from "./achievements.js";
import { DEFAULT_ADVENTURERS } from "./adventurers.js";
import { DEFAULT_BOSSES } from "./bosses.js";
import { DEFAULT_EQUIPMENT } from "./equipment.js";
import { DEFAULT_QUESTS } from "./quests.js";
import { DEFAULT_UPGRADES } from "./upgrades.js";
@@ -28,6 +30,8 @@ export const INITIAL_GAME_STATE = (player: Player, characterName: string): GameS
upgrades: structuredClone(DEFAULT_UPGRADES),
quests: structuredClone(DEFAULT_QUESTS),
bosses: structuredClone(DEFAULT_BOSSES),
equipment: structuredClone(DEFAULT_EQUIPMENT),
achievements: structuredClone(DEFAULT_ACHIEVEMENTS),
prestige: INITIAL_PRESTIGE,
baseClickPower: 1,
lastTickAt: Date.now(),
+55
View File
@@ -34,6 +34,20 @@ export const DEFAULT_QUESTS: Quest[] = [
],
prerequisiteIds: ["goblin_camp"],
},
{
id: "necromancer_tower",
name: "Necromancer's Tower",
description:
"A rogue necromancer has raised an army of skeletons near the city. Silence him before the dead overrun us.",
status: "locked",
durationSeconds: 25 * 60,
rewards: [
{ type: "gold", amount: 15_000 },
{ type: "essence", amount: 20 },
{ type: "upgrade", targetId: "cleric_1" },
],
prerequisiteIds: ["haunted_mine"],
},
{
id: "ancient_ruins",
name: "Ancient Ruins",
@@ -46,6 +60,19 @@ export const DEFAULT_QUESTS: Quest[] = [
],
prerequisiteIds: ["haunted_mine"],
},
{
id: "shadow_mere",
name: "The Shadow Mere",
description:
"A cursed lake shrouded in permanent twilight. Strange energies pulse beneath its surface.",
status: "locked",
durationSeconds: 45 * 60,
rewards: [
{ type: "essence", amount: 150 },
{ type: "upgrade", targetId: "scout_1" },
],
prerequisiteIds: ["ancient_ruins"],
},
{
id: "dragon_lair",
name: "Dragon's Lair",
@@ -60,4 +87,32 @@ export const DEFAULT_QUESTS: Quest[] = [
],
prerequisiteIds: ["ancient_ruins"],
},
{
id: "frozen_wastes",
name: "The Frozen Wastes",
description:
"A tundra at the edge of the world, home to creatures that have never seen the sun. Rumours speak of artefacts buried in the permafrost.",
status: "locked",
durationSeconds: 2 * 60 * 60,
rewards: [
{ type: "gold", amount: 2_000_000 },
{ type: "crystals", amount: 150 },
{ type: "upgrade", targetId: "global_3" },
],
prerequisiteIds: ["dragon_lair"],
},
{
id: "void_rift",
name: "Void Rift",
description:
"A tear in reality itself. What lies beyond defies description — but the power within is unlike anything of this world.",
status: "locked",
durationSeconds: 4 * 60 * 60,
rewards: [
{ type: "crystals", amount: 500 },
{ type: "essence", amount: 5_000 },
{ type: "upgrade", targetId: "knight_1" },
],
prerequisiteIds: ["frozen_wastes"],
},
];
+47
View File
@@ -58,6 +58,17 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
purchased: false,
unlocked: false,
},
{
id: "global_3",
name: "Royal Patronage",
description: "The king himself backs your guild. All income doubled.",
target: "global",
multiplier: 2,
costGold: 1_000_000,
costEssence: 100,
purchased: false,
unlocked: false,
},
// Adventurer-specific upgrades
{
id: "peasant_1",
@@ -95,4 +106,40 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
purchased: false,
unlocked: false,
},
{
id: "cleric_1",
name: "Holy Rites",
description: "Sacred ceremonies double the output of your clerics.",
target: "adventurer",
adventurerId: "acolyte",
multiplier: 2,
costGold: 8_000,
costEssence: 3,
purchased: false,
unlocked: false,
},
{
id: "scout_1",
name: "Stealth Training",
description: "Advanced scouting techniques double ranger effectiveness.",
target: "adventurer",
adventurerId: "ranger",
multiplier: 2,
costGold: 15_000,
costEssence: 5,
purchased: false,
unlocked: false,
},
{
id: "knight_1",
name: "Tempered Steel",
description: "Superior forging techniques double the output of your knights.",
target: "adventurer",
adventurerId: "knight",
multiplier: 2,
costGold: 50_000,
costEssence: 10,
purchased: false,
unlocked: false,
},
];