diff --git a/apps/api/src/data/achievements.ts b/apps/api/src/data/achievements.ts index 241cd0b..b87b30c 100644 --- a/apps/api/src/data/achievements.ts +++ b/apps/api/src/data/achievements.ts @@ -1,6 +1,7 @@ import type { Achievement } from "@elysium/types"; export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ + // Click milestones { id: "first_click", name: "First Strike", @@ -28,6 +29,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 100 }, unlockedAt: null, }, + { + id: "click_legend", + name: "Click Legend", + description: "Click the Guild Hall 10,000 times.", + icon: "π©οΈ", + condition: { type: "totalClicks", amount: 10_000 }, + reward: { crystals: 300 }, + unlockedAt: null, + }, + // Gold milestones { id: "first_gold", name: "First Gold", @@ -64,6 +75,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 500 }, unlockedAt: null, }, + { + id: "trillionaire", + name: "Trillionaire", + description: "Earn 1,000,000,000,000 gold in total.", + icon: "π", + condition: { type: "totalGoldEarned", amount: 1_000_000_000_000 }, + reward: { crystals: 2_000 }, + unlockedAt: null, + }, + // Quest milestones { id: "first_quest", name: "Adventurous Spirit", @@ -82,6 +103,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 50 }, unlockedAt: null, }, + { + id: "quest_master", + name: "Quest Master", + description: "Complete 15 quests.", + icon: "πΊοΈ", + condition: { type: "questsCompleted", amount: 15 }, + reward: { crystals: 200 }, + unlockedAt: null, + }, + // Boss milestones { id: "boss_slayer", name: "Boss Slayer", @@ -92,14 +123,33 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ unlockedAt: null, }, { - id: "legendary_hunter", - name: "Legendary Hunter", - description: "Defeat all four bosses.", - icon: "π", - condition: { type: "bossesDefeated", amount: 4 }, - reward: { crystals: 200 }, + id: "boss_veteran", + name: "Boss Veteran", + description: "Defeat 5 bosses.", + icon: "π‘οΈ", + condition: { type: "bossesDefeated", amount: 5 }, + reward: { crystals: 150 }, unlockedAt: null, }, + { + id: "legendary_hunter", + name: "Legendary Hunter", + description: "Defeat 10 bosses.", + icon: "π", + condition: { type: "bossesDefeated", amount: 10 }, + reward: { crystals: 500 }, + unlockedAt: null, + }, + { + id: "devourer_slayer", + name: "World Saver", + description: "Defeat all 18 bosses, including the Devourer of Worlds.", + icon: "π", + condition: { type: "bossesDefeated", amount: 18 }, + reward: { crystals: 2_000 }, + unlockedAt: null, + }, + // Adventurer milestones { id: "guild_master", name: "Guild Master", @@ -118,6 +168,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 200 }, unlockedAt: null, }, + { + id: "army_legend", + name: "Legendary Commander", + description: "Recruit a total of 5,000 adventurers.", + icon: "βοΈ", + condition: { type: "adventurerTotal", amount: 5_000 }, + reward: { crystals: 750 }, + unlockedAt: null, + }, + // Prestige milestones { id: "first_prestige", name: "Born Again", @@ -127,6 +187,7 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 100 }, unlockedAt: null, }, + // Collection milestones { id: "collector", name: "Collector", @@ -136,4 +197,13 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [ reward: { crystals: 10 }, unlockedAt: null, }, + { + id: "arsenal", + name: "Arsenal", + description: "Own 12 pieces of equipment.", + icon: "ποΈ", + condition: { type: "equipmentOwned", amount: 12 }, + reward: { crystals: 200 }, + unlockedAt: null, + }, ]; diff --git a/apps/api/src/data/adventurers.ts b/apps/api/src/data/adventurers.ts index f90373d..150e522 100644 --- a/apps/api/src/data/adventurers.ts +++ b/apps/api/src/data/adventurers.ts @@ -111,4 +111,59 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [ count: 0, unlocked: false, }, + { + id: "shadow_assassin", + name: "Shadow Assassin", + class: "rogue", + level: 11, + goldPerSecond: 5_000, + essencePerSecond: 6, + combatPower: 18_000, + count: 0, + unlocked: false, + }, + { + id: "arcane_scholar", + name: "Arcane Scholar", + class: "mage", + level: 12, + goldPerSecond: 14_000, + essencePerSecond: 15, + combatPower: 45_000, + count: 0, + unlocked: false, + }, + { + id: "void_walker", + name: "Void Walker", + class: "rogue", + level: 13, + goldPerSecond: 40_000, + essencePerSecond: 35, + combatPower: 130_000, + count: 0, + unlocked: false, + }, + { + id: "celestial_guard", + name: "Celestial Guard", + class: "paladin", + level: 14, + goldPerSecond: 120_000, + essencePerSecond: 100, + combatPower: 400_000, + count: 0, + unlocked: false, + }, + { + id: "divine_champion", + name: "Divine Champion", + class: "warrior", + level: 15, + goldPerSecond: 400_000, + essencePerSecond: 300, + combatPower: 1_200_000, + count: 0, + unlocked: false, + }, ]; diff --git a/apps/api/src/data/bosses.ts b/apps/api/src/data/bosses.ts index a5a2989..feed1f1 100644 --- a/apps/api/src/data/bosses.ts +++ b/apps/api/src/data/bosses.ts @@ -1,6 +1,7 @@ import type { Boss } from "@elysium/types"; export const DEFAULT_BOSSES: Boss[] = [ + // ββ Verdant Vale ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "troll_king", name: "The Troll King", @@ -35,38 +36,281 @@ export const DEFAULT_BOSSES: Boss[] = [ prestigeRequirement: 0, zoneId: "verdant_vale", }, + { + id: "forest_giant", + name: "The Forest Giant", + description: + "An ancient colossus of bark and stone who has slumbered beneath the Vale for centuries. Its awakening spells disaster for every settlement in the region.", + status: "locked", + maxHp: 35_000, + currentHp: 35_000, + damagePerSecond: 40, + goldReward: 350_000, + essenceReward: 400, + crystalReward: 20, + upgradeRewards: ["archmage_1"], + equipmentRewards: ["hide_armour", "rune_stone"], + prestigeRequirement: 0, + zoneId: "verdant_vale", + }, + // ββ Shattered Ruins βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "stone_golem", + name: "The Stone Golem", + description: + "A guardian construct from the fallen civilisation, still faithfully protecting the ruins of a city long since turned to dust.", + status: "locked", + maxHp: 60_000, + currentHp: 60_000, + damagePerSecond: 60, + goldReward: 600_000, + essenceReward: 600, + crystalReward: 25, + upgradeRewards: ["paladin_1"], + equipmentRewards: [], + prestigeRequirement: 0, + zoneId: "shattered_ruins", + }, + { + id: "bone_colossus", + name: "The Bone Colossus", + description: + "Forged from the skeletons of a thousand fallen warriors by the Lich Queen's disciples. Its hollow eye sockets blaze with the same sorcery that animated them.", + status: "locked", + maxHp: 200_000, + currentHp: 200_000, + damagePerSecond: 120, + goldReward: 2_000_000, + essenceReward: 1_500, + crystalReward: 60, + upgradeRewards: ["essence_guild"], + equipmentRewards: ["frost_rune"], + prestigeRequirement: 0, + zoneId: "shattered_ruins", + }, { 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, + maxHp: 500_000, + currentHp: 500_000, + damagePerSecond: 200, + goldReward: 5_000_000, + essenceReward: 3_000, + crystalReward: 100, upgradeRewards: ["click_3"], equipmentRewards: ["vorpal_sword", "dragon_scale"], prestigeRequirement: 1, zoneId: "shattered_ruins", }, + // ββ Shadow Marshes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "swamp_witch", + name: "Morgantha the Swamp Witch", + description: + "She has hexed villages for three centuries from her hut on the black water. Her curse-weaving is second to none β but so is the bounty on her head.", + status: "locked", + maxHp: 80_000, + currentHp: 80_000, + damagePerSecond: 80, + goldReward: 800_000, + essenceReward: 800, + crystalReward: 30, + upgradeRewards: ["shadow_assassin_1"], + equipmentRewards: [], + prestigeRequirement: 0, + zoneId: "shadow_marshes", + }, + { + id: "plague_lord", + name: "The Plague Lord", + description: + "A bloated, rotting horror that spreads pestilence wherever it walks. Entire kingdoms have fallen to the disease it carries. Yours will not.", + status: "locked", + maxHp: 300_000, + currentHp: 300_000, + damagePerSecond: 180, + goldReward: 3_000_000, + essenceReward: 2_000, + crystalReward: 80, + upgradeRewards: ["grand_council"], + equipmentRewards: ["runestone_amulet"], + prestigeRequirement: 0, + zoneId: "shadow_marshes", + }, + { + id: "mud_kraken", + name: "The Mud Kraken", + description: + "An eldritch leviathan that lurks in the deepest part of the marshes, older than the gods themselves. Its tentacles have dragged ships β and armies β into the mire.", + status: "locked", + maxHp: 800_000, + currentHp: 800_000, + damagePerSecond: 350, + goldReward: 8_000_000, + essenceReward: 4_000, + crystalReward: 150, + upgradeRewards: ["arcane_scholar_1"], + equipmentRewards: ["crystal_shard"], + prestigeRequirement: 1, + zoneId: "shadow_marshes", + }, + // ββ Frozen Peaks ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "frost_wyrm", + name: "The Frost Wyrm", + description: + "A serpentine dragon of pure ice who has hunted the tundra for aeons. Its breath flash-freezes anything it touches, and it has never known defeat.", + status: "locked", + maxHp: 500_000, + currentHp: 500_000, + damagePerSecond: 220, + goldReward: 5_000_000, + essenceReward: 3_500, + crystalReward: 100, + upgradeRewards: ["dragon_rider_1"], + equipmentRewards: [], + prestigeRequirement: 1, + zoneId: "frozen_peaks", + }, + { + id: "ice_queen", + name: "The Ice Queen", + description: + "A sorceress who made a pact with the winter itself and was transformed into something no longer mortal. She rules the Frozen Peaks from a palace of living ice.", + status: "locked", + maxHp: 1_500_000, + currentHp: 1_500_000, + damagePerSecond: 500, + goldReward: 15_000_000, + essenceReward: 8_000, + crystalReward: 250, + upgradeRewards: ["void_walker_1"], + equipmentRewards: ["frost_crystal"], + prestigeRequirement: 2, + zoneId: "frozen_peaks", + }, { 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, + maxHp: 5_000_000, + currentHp: 5_000_000, + damagePerSecond: 1_200, + goldReward: 50_000_000, + essenceReward: 20_000, + crystalReward: 500, upgradeRewards: [], equipmentRewards: ["philosophers_stone"], prestigeRequirement: 3, zoneId: "frozen_peaks", }, + // ββ Volcanic Depths βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "fire_elemental", + name: "The Ancient Fire Elemental", + description: + "Born from the first volcanic eruption the world ever knew. It exists purely to consume, and your guild looks like the finest kindling it has seen in millennia.", + status: "locked", + maxHp: 1_000_000, + currentHp: 1_000_000, + damagePerSecond: 400, + goldReward: 10_000_000, + essenceReward: 6_000, + crystalReward: 150, + upgradeRewards: ["celestial_guard_1"], + equipmentRewards: ["flame_lance"], + prestigeRequirement: 2, + zoneId: "volcanic_depths", + }, + { + id: "magma_titan", + name: "The Magma Titan", + description: + "Half-giant, half-living volcano, this colossus was created by the fire elementals to guard their greatest forge. Every strike from its fists sends shockwaves through the earth.", + status: "locked", + maxHp: 4_000_000, + currentHp: 4_000_000, + damagePerSecond: 1_000, + goldReward: 40_000_000, + essenceReward: 15_000, + crystalReward: 400, + upgradeRewards: ["crystal_resonance"], + equipmentRewards: ["volcanic_plate"], + prestigeRequirement: 3, + zoneId: "volcanic_depths", + }, + { + id: "phoenix_lord", + name: "The Phoenix Lord", + description: + "The apex predator of the volcanic chain β a being of pure flame that has died and reborn itself more times than recorded history. This time, it will not rise again.", + status: "locked", + maxHp: 12_000_000, + currentHp: 12_000_000, + damagePerSecond: 2_500, + goldReward: 120_000_000, + essenceReward: 40_000, + crystalReward: 800, + upgradeRewards: ["crystal_mastery"], + equipmentRewards: ["eternal_flame"], + prestigeRequirement: 4, + zoneId: "volcanic_depths", + }, + // ββ Astral Void βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "astral_wraith", + name: "The Astral Wraith", + description: + "A being of pure psychic energy who has haunted the space between stars since before the world was formed. It feeds on consciousness itself.", + status: "locked", + maxHp: 20_000_000, + currentHp: 20_000_000, + damagePerSecond: 4_000, + goldReward: 200_000_000, + essenceReward: 60_000, + crystalReward: 1_000, + upgradeRewards: ["divine_champion_1"], + equipmentRewards: ["astral_robe"], + prestigeRequirement: 4, + zoneId: "astral_void", + }, + { + id: "cosmic_horror", + name: "The Cosmic Horror", + description: + "A god-thing from before the age of mortals. Its true form cannot be perceived without madness β what you see is a mercy granted by the universe itself.", + status: "locked", + maxHp: 75_000_000, + currentHp: 75_000_000, + damagePerSecond: 10_000, + goldReward: 750_000_000, + essenceReward: 150_000, + crystalReward: 2_500, + upgradeRewards: ["crystal_focus"], + equipmentRewards: ["celestial_blade"], + prestigeRequirement: 5, + zoneId: "astral_void", + }, + { + id: "the_devourer", + name: "The Devourer of Worlds", + description: + "The end. The hunger at the heart of existence that has unmade countless realities before this one. Your guild stands between it and everything that has ever lived.", + status: "locked", + maxHp: 300_000_000, + currentHp: 300_000_000, + damagePerSecond: 30_000, + goldReward: 3_000_000_000, + essenceReward: 500_000, + crystalReward: 10_000, + upgradeRewards: [], + equipmentRewards: ["infinity_gem"], + prestigeRequirement: 6, + zoneId: "astral_void", + }, ]; diff --git a/apps/api/src/data/equipment.ts b/apps/api/src/data/equipment.ts index cc99ae3..2c7c07c 100644 --- a/apps/api/src/data/equipment.ts +++ b/apps/api/src/data/equipment.ts @@ -1,7 +1,7 @@ import type { Equipment } from "@elysium/types"; export const DEFAULT_EQUIPMENT: Equipment[] = [ - // Weapons β drop from bosses; common starts owned + // ββ Weapons βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "rusty_sword", name: "Rusty Sword", @@ -32,6 +32,27 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "shadow_dagger", + name: "Shadow Dagger", + description: "Forged in the Shadow Marshes from condensed darkness. It strikes before it is seen.", + type: "weapon", + rarity: "epic", + bonus: { combatMultiplier: 1.65 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 500, crystals: 0 }, + }, + { + id: "flame_lance", + name: "Flame Lance", + description: "A spear tipped with a shard of the Primordial Forge's eternal fire.", + type: "weapon", + rarity: "epic", + bonus: { combatMultiplier: 1.7 }, + owned: false, + equipped: false, + }, { id: "vorpal_sword", name: "Vorpal Sword", @@ -42,7 +63,39 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, - // Armour β drop from bosses; common starts owned + { + id: "soul_reaper", + name: "Soul Reaper", + description: "A scythe that harvests not flesh but essence itself. Every swing drains the will to resist.", + type: "weapon", + rarity: "legendary", + bonus: { combatMultiplier: 2.5 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 0, crystals: 300 }, + }, + { + id: "celestial_blade", + name: "Celestial Blade", + description: "Forged from the heart of a dying star by the Cosmic Horror itself. Its edge exists in three realities simultaneously.", + type: "weapon", + rarity: "legendary", + bonus: { combatMultiplier: 3.0 }, + owned: false, + equipped: false, + }, + { + id: "void_edge", + name: "Void Edge", + description: "A blade made of compressed nothingness. It does not cut β it simply unmakes.", + type: "weapon", + rarity: "legendary", + bonus: { combatMultiplier: 2.75 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 2_000, crystals: 500 }, + }, + // ββ Armour ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "leather_armour", name: "Leather Armour", @@ -63,6 +116,16 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "hide_armour", + name: "Giant's Hide Armour", + description: "Cured hide from a Forest Giant, worked into armour that radiates primal authority.", + type: "armour", + rarity: "rare", + bonus: { goldMultiplier: 1.35 }, + owned: false, + equipped: false, + }, { id: "plate_armour", name: "Plate Armour", @@ -73,6 +136,27 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "void_shroud", + name: "Void Shroud", + description: "A cloak woven from the fabric of the Shadow Marshes itself. Wealth flows to those hidden from sight.", + type: "armour", + rarity: "epic", + bonus: { goldMultiplier: 1.75 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 400, crystals: 0 }, + }, + { + id: "volcanic_plate", + name: "Volcanic Plate", + description: "Armour quenched in magma that hardened into something neither metal nor stone. Burns with inner heat.", + type: "armour", + rarity: "epic", + bonus: { goldMultiplier: 1.65, combatMultiplier: 1.15 }, + owned: false, + equipped: false, + }, { id: "dragon_scale", name: "Dragon Scale Armour", @@ -83,7 +167,28 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, - // Trinkets β drop from bosses; common starts owned + { + id: "titan_aegis", + name: "Titan's Aegis", + description: "A shield-armour hybrid blessed by the celestials. Its bearer becomes a fortress.", + type: "armour", + rarity: "legendary", + bonus: { goldMultiplier: 2.5 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 0, crystals: 250 }, + }, + { + id: "astral_robe", + name: "Astral Robe", + description: "Woven from threads of pure starlight harvested by the Astral Wraith. Income flows like cosmic energy.", + type: "armour", + rarity: "legendary", + bonus: { goldMultiplier: 2.25 }, + owned: false, + equipped: false, + }, + // ββ Trinkets ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "lucky_coin", name: "Lucky Coin", @@ -104,6 +209,16 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "frost_rune", + name: "Frost Rune", + description: "A rune carved from bone-ice by the Bone Colossus. It amplifies strikes with cold precision.", + type: "trinket", + rarity: "rare", + bonus: { clickMultiplier: 1.3 }, + owned: false, + equipped: false, + }, { id: "arcane_orb", name: "Arcane Orb", @@ -114,6 +229,47 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "runestone_amulet", + name: "Runestone Amulet", + description: "An amulet carved from ancient runestones found in the plague ruins. Its inscriptions hum with forgotten power.", + type: "trinket", + rarity: "epic", + bonus: { clickMultiplier: 1.45, goldMultiplier: 1.15 }, + owned: false, + equipped: false, + }, + { + id: "crystal_shard", + name: "Crystal Shard", + description: "A fragment of the Mud Kraken's crystallised essence. Focuses raw power into devastating strikes.", + type: "trinket", + rarity: "epic", + bonus: { clickMultiplier: 1.55, goldMultiplier: 1.1 }, + owned: false, + equipped: false, + }, + { + id: "void_compass", + name: "Void Compass", + description: "A compass that points not north but toward the greatest concentration of power β wherever that may be.", + type: "trinket", + rarity: "epic", + bonus: { clickMultiplier: 1.6 }, + owned: false, + equipped: false, + cost: { gold: 0, essence: 350, crystals: 0 }, + }, + { + id: "frost_crystal", + name: "Frost Crystal", + description: "A perfectly formed crystal harvested from the Ice Queen's throne room. Cold enough to burn.", + type: "trinket", + rarity: "legendary", + bonus: { clickMultiplier: 2.0, goldMultiplier: 1.2 }, + owned: false, + equipped: false, + }, { id: "philosophers_stone", name: "Philosopher's Stone", @@ -124,4 +280,24 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [ owned: false, equipped: false, }, + { + id: "eternal_flame", + name: "Eternal Flame", + description: "A flame that has never been extinguished, sealed in crystal by the Phoenix Lord. It burns with the power of rebirth.", + type: "trinket", + rarity: "legendary", + bonus: { clickMultiplier: 2.25, goldMultiplier: 1.15 }, + owned: false, + equipped: false, + }, + { + id: "infinity_gem", + name: "Infinity Gem", + description: "A gem that contains a universe within it. Those who hold it become more than mortal.", + type: "trinket", + rarity: "legendary", + bonus: { clickMultiplier: 2.5, goldMultiplier: 1.3, combatMultiplier: 1.25 }, + owned: false, + equipped: false, + }, ]; diff --git a/apps/api/src/data/quests.ts b/apps/api/src/data/quests.ts index 2f0706d..27933ce 100644 --- a/apps/api/src/data/quests.ts +++ b/apps/api/src/data/quests.ts @@ -1,6 +1,7 @@ import type { Quest } from "@elysium/types"; export const DEFAULT_QUESTS: Quest[] = [ + // ββ Verdant Vale ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "first_steps", name: "First Steps", @@ -37,21 +38,6 @@ export const DEFAULT_QUESTS: Quest[] = [ prerequisiteIds: ["goblin_camp"], zoneId: "verdant_vale", }, - { - 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"], - zoneId: "verdant_vale", - }, { id: "ancient_ruins", name: "Ancient Ruins", @@ -65,6 +51,68 @@ export const DEFAULT_QUESTS: Quest[] = [ prerequisiteIds: ["haunted_mine"], zoneId: "verdant_vale", }, + // ββ Shattered Ruins βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + 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: [], + zoneId: "shattered_ruins", + }, + { + id: "crumbling_fortress", + name: "The Crumbling Fortress", + description: + "An ancient fortress still garrisoned by constructs who don't know the war ended. Clear it out and claim its vaults.", + status: "locked", + durationSeconds: 45 * 60, + rewards: [ + { type: "gold", amount: 80_000 }, + { type: "essence", amount: 120 }, + { type: "upgrade", targetId: "scout_1" }, + ], + prerequisiteIds: ["necromancer_tower"], + zoneId: "shattered_ruins", + }, + { + id: "cursed_library", + name: "The Cursed Library", + description: + "A vast library sealed for centuries whose contents have warped and grown hostile. The knowledge within is priceless.", + status: "locked", + durationSeconds: 60 * 60, + rewards: [ + { type: "essence", amount: 300 }, + { type: "crystals", amount: 30 }, + { type: "upgrade", targetId: "mage_1" }, + ], + prerequisiteIds: ["crumbling_fortress"], + zoneId: "shattered_ruins", + }, + { + 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: 90 * 60, + rewards: [ + { type: "gold", amount: 500_000 }, + { type: "crystals", amount: 50 }, + { type: "adventurer", targetId: "dragon_rider" }, + ], + prerequisiteIds: ["cursed_library"], + zoneId: "shattered_ruins", + }, + // ββ Shadow Marshes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "shadow_mere", name: "The Shadow Mere", @@ -74,26 +122,56 @@ export const DEFAULT_QUESTS: Quest[] = [ durationSeconds: 45 * 60, rewards: [ { type: "essence", amount: 150 }, - { type: "upgrade", targetId: "scout_1" }, + { type: "upgrade", targetId: "militia_1" }, ], - prerequisiteIds: ["ancient_ruins"], - zoneId: "shattered_ruins", + prerequisiteIds: [], + zoneId: "shadow_marshes", }, { - id: "dragon_lair", - name: "Dragon's Lair", + id: "witch_coven", + name: "The Witch Coven", description: - "The legendary lair of Pyraxis the Undying. Few who enter return β those who do are rich beyond imagining.", + "Deep in the marshes, a coven of swamp witches performs rites that twist the very land. Their power must be broken.", status: "locked", - durationSeconds: 60 * 60, + durationSeconds: 90 * 60, rewards: [ - { type: "gold", amount: 500_000 }, - { type: "crystals", amount: 50 }, - { type: "adventurer", targetId: "dragon_rider" }, + { type: "essence", amount: 500 }, + { type: "adventurer", targetId: "shadow_assassin" }, ], - prerequisiteIds: ["ancient_ruins"], - zoneId: "shattered_ruins", + prerequisiteIds: ["shadow_mere"], + zoneId: "shadow_marshes", }, + { + id: "sunken_temple", + name: "The Sunken Temple", + description: + "An ancient temple half-submerged in black water, its altars still humming with the power of a god long since departed.", + status: "locked", + durationSeconds: 2 * 60 * 60, + rewards: [ + { type: "gold", amount: 2_000_000 }, + { type: "crystals", amount: 75 }, + { type: "upgrade", targetId: "knight_1" }, + ], + prerequisiteIds: ["witch_coven"], + zoneId: "shadow_marshes", + }, + { + id: "plague_ruins", + name: "The Plague Ruins", + description: + "A city that died overnight, its streets still thick with something no healer can identify. Treasures lie unclaimed among the bones.", + status: "locked", + durationSeconds: 3 * 60 * 60, + rewards: [ + { type: "gold", amount: 8_000_000 }, + { type: "essence", amount: 2_000 }, + { type: "crystals", amount: 150 }, + ], + prerequisiteIds: ["sunken_temple"], + zoneId: "shadow_marshes", + }, + // ββ Frozen Peaks ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "frozen_wastes", name: "The Frozen Wastes", @@ -102,13 +180,104 @@ export const DEFAULT_QUESTS: Quest[] = [ status: "locked", durationSeconds: 2 * 60 * 60, rewards: [ - { type: "gold", amount: 2_000_000 }, - { type: "crystals", amount: 150 }, + { type: "gold", amount: 5_000_000 }, + { type: "crystals", amount: 100 }, { type: "upgrade", targetId: "global_3" }, ], - prerequisiteIds: ["dragon_lair"], + prerequisiteIds: [], zoneId: "frozen_peaks", }, + { + id: "ice_caves", + name: "The Ice Caves", + description: + "A labyrinthine network of crystal caverns that descend for miles. The cold here is a presence, not just a temperature.", + status: "locked", + durationSeconds: 3 * 60 * 60, + rewards: [ + { type: "essence", amount: 5_000 }, + { type: "crystals", amount: 200 }, + { type: "adventurer", targetId: "arcane_scholar" }, + ], + prerequisiteIds: ["frozen_wastes"], + zoneId: "frozen_peaks", + }, + { + id: "storm_citadel", + name: "The Storm Citadel", + description: + "A fortress suspended in a permanent blizzard, built by a mage who wanted to be left alone β and succeeded for three hundred years.", + status: "locked", + durationSeconds: 5 * 60 * 60, + rewards: [ + { type: "gold", amount: 30_000_000 }, + { type: "essence", amount: 10_000 }, + { type: "upgrade", targetId: "peasant_1" }, + ], + prerequisiteIds: ["ice_caves"], + zoneId: "frozen_peaks", + }, + // ββ Volcanic Depths βββββββββββββββββββββββββββββββββββββββββββββββββββββββ + { + id: "lava_flows", + name: "The Lava Flows", + description: + "A river of molten rock that flows without end through the volcanic tunnels. Something valuable gleams in the depths.", + status: "locked", + durationSeconds: 3 * 60 * 60, + rewards: [ + { type: "gold", amount: 15_000_000 }, + { type: "essence", amount: 4_000 }, + ], + prerequisiteIds: [], + zoneId: "volcanic_depths", + }, + { + id: "fire_temple", + name: "The Temple of the Flame", + description: + "A vast shrine where fire elementals perform rituals that shake the mountains. Whatever they worship, it has answered.", + status: "locked", + durationSeconds: 5 * 60 * 60, + rewards: [ + { type: "essence", amount: 12_000 }, + { type: "crystals", amount: 300 }, + { type: "adventurer", targetId: "void_walker" }, + ], + prerequisiteIds: ["lava_flows"], + zoneId: "volcanic_depths", + }, + { + id: "magma_caverns", + name: "The Magma Caverns", + description: + "Kilometres of tunnels filled with rivers of fire and creatures born from the earth's core. The heat alone should kill you. Somehow, it won't.", + status: "locked", + durationSeconds: 7 * 60 * 60, + rewards: [ + { type: "gold", amount: 100_000_000 }, + { type: "essence", amount: 25_000 }, + { type: "crystals", amount: 600 }, + ], + prerequisiteIds: ["fire_temple"], + zoneId: "volcanic_depths", + }, + { + id: "the_forge", + name: "The Primordial Forge", + description: + "The oldest forge in existence, where the fire elementals crafted weapons for gods. Its secrets could revolutionise your guild's arsenal.", + status: "locked", + durationSeconds: 10 * 60 * 60, + rewards: [ + { type: "gold", amount: 500_000_000 }, + { type: "essence", amount: 80_000 }, + { type: "adventurer", targetId: "celestial_guard" }, + ], + prerequisiteIds: ["magma_caverns"], + zoneId: "volcanic_depths", + }, + // ββ Astral Void βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "void_rift", name: "Void Rift", @@ -119,9 +288,53 @@ export const DEFAULT_QUESTS: Quest[] = [ rewards: [ { type: "crystals", amount: 500 }, { type: "essence", amount: 5_000 }, - { type: "upgrade", targetId: "knight_1" }, ], - prerequisiteIds: ["frozen_wastes"], - zoneId: "frozen_peaks", + prerequisiteIds: [], + zoneId: "astral_void", + }, + { + id: "star_graveyard", + name: "The Star Graveyard", + description: + "A field of dead stars, each one larger than a planet, each one cold and silent where once they burned with the light of creation.", + status: "locked", + durationSeconds: 8 * 60 * 60, + rewards: [ + { type: "gold", amount: 1_000_000_000 }, + { type: "essence", amount: 100_000 }, + { type: "crystals", amount: 1_000 }, + ], + prerequisiteIds: ["void_rift"], + zoneId: "astral_void", + }, + { + id: "between_worlds", + name: "Between Worlds", + description: + "The space between realities, where the rules that govern your world do not apply. Time is meaningless here. Power is everything.", + status: "locked", + durationSeconds: 12 * 60 * 60, + rewards: [ + { type: "essence", amount: 250_000 }, + { type: "crystals", amount: 2_000 }, + { type: "adventurer", targetId: "divine_champion" }, + ], + prerequisiteIds: ["star_graveyard"], + zoneId: "astral_void", + }, + { + id: "the_end", + name: "The End of All Things", + description: + "There is nothing beyond this point. Only the greatest guild in the history of all existence could reach here β and you have.", + status: "locked", + durationSeconds: 24 * 60 * 60, + rewards: [ + { type: "gold", amount: 10_000_000_000 }, + { type: "essence", amount: 1_000_000 }, + { type: "crystals", amount: 10_000 }, + ], + prerequisiteIds: ["between_worlds"], + zoneId: "astral_void", }, ]; diff --git a/apps/api/src/data/upgrades.ts b/apps/api/src/data/upgrades.ts index b977d50..b665e03 100644 --- a/apps/api/src/data/upgrades.ts +++ b/apps/api/src/data/upgrades.ts @@ -1,7 +1,7 @@ import type { Upgrade } from "@elysium/types"; export const DEFAULT_UPGRADES: Upgrade[] = [ - // Click upgrades + // ββ Click upgrades ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "click_1", name: "Keen Eye", @@ -10,6 +10,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 100, costEssence: 0, + costCrystals: 0, purchased: false, unlocked: true, }, @@ -19,8 +20,9 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ description: "Years of combat sharpen your instincts. Doubles click power again.", target: "click", multiplier: 2, - costGold: 1000, + costGold: 1_000, costEssence: 0, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -32,10 +34,23 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 3, costGold: 50_000, costEssence: 10, + costCrystals: 0, purchased: false, unlocked: false, }, - // Global upgrades + { + id: "crystal_focus", + name: "Crystal Focus", + description: "Channel crystallised power into every strike. Doubles click power.", + target: "click", + multiplier: 2, + costGold: 0, + costEssence: 0, + costCrystals: 100, + purchased: false, + unlocked: true, + }, + // ββ Global gold upgrades ββββββββββββββββββββββββββββββββββββββββββββββββββ { id: "global_1", name: "Guild Charter", @@ -44,6 +59,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 1.25, costGold: 500, costEssence: 0, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -55,6 +71,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 1.5, costGold: 10_000, costEssence: 5, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -66,10 +83,59 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 1_000_000, costEssence: 100, + costCrystals: 0, purchased: false, unlocked: false, }, - // Adventurer-specific upgrades + { + id: "essence_guild", + name: "Essence Guild", + description: "Forge partnerships with mage guilds across the realm. All income +50%.", + target: "global", + multiplier: 1.5, + costGold: 50_000, + costEssence: 50, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "grand_council", + name: "Grand Council", + description: "A council of the realm's greatest minds organises your operations. All income doubled.", + target: "global", + multiplier: 2, + costGold: 500_000, + costEssence: 250, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "crystal_resonance", + name: "Crystal Resonance", + description: "Align crystalline frequencies across your guild. All income +50%.", + target: "global", + multiplier: 1.5, + costGold: 0, + costEssence: 0, + costCrystals: 250, + purchased: false, + unlocked: false, + }, + { + id: "crystal_mastery", + name: "Crystal Mastery", + description: "Master the art of crystal amplification. All income doubled.", + target: "global", + multiplier: 2, + costGold: 0, + costEssence: 0, + costCrystals: 600, + purchased: false, + unlocked: false, + }, + // ββ Adventurer-specific upgrades ββββββββββββββββββββββββββββββββββββββββββ { id: "peasant_1", name: "Better Tools", @@ -79,6 +145,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 200, costEssence: 0, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -91,6 +158,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 1_000, costEssence: 0, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -103,6 +171,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 5_000, costEssence: 2, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -115,6 +184,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 8_000, costEssence: 3, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -127,6 +197,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 15_000, costEssence: 5, + costCrystals: 0, purchased: false, unlocked: false, }, @@ -139,6 +210,111 @@ export const DEFAULT_UPGRADES: Upgrade[] = [ multiplier: 2, costGold: 50_000, costEssence: 10, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "archmage_1", + name: "Leyline Binding", + description: "Tap into the world's leylines to double archmage output.", + target: "adventurer", + adventurerId: "archmage", + multiplier: 2, + costGold: 100_000, + costEssence: 75, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "paladin_1", + name: "Holy Vanguard", + description: "Divine blessings from the gods themselves double paladin output.", + target: "adventurer", + adventurerId: "paladin", + multiplier: 2, + costGold: 200_000, + costEssence: 150, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "dragon_rider_1", + name: "Bond of Wings", + description: "The unbreakable bond between rider and dragon doubles their combined output.", + target: "adventurer", + adventurerId: "dragon_rider", + multiplier: 2, + costGold: 500_000, + costEssence: 200, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "shadow_assassin_1", + name: "Shadow Arts", + description: "Mastery of the shadow arts doubles assassin effectiveness.", + target: "adventurer", + adventurerId: "shadow_assassin", + multiplier: 2, + costGold: 0, + costEssence: 50, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "arcane_scholar_1", + name: "Ancient Tomes", + description: "Access to forbidden libraries doubles scholar output.", + target: "adventurer", + adventurerId: "arcane_scholar", + multiplier: 2, + costGold: 0, + costEssence: 150, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "void_walker_1", + name: "Void Step", + description: "Walking through the void itself doubles the output of your void walkers.", + target: "adventurer", + adventurerId: "void_walker", + multiplier: 2, + costGold: 0, + costEssence: 300, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "celestial_guard_1", + name: "Divine Ward", + description: "A blessing from the celestials themselves doubles guard output.", + target: "adventurer", + adventurerId: "celestial_guard", + multiplier: 2, + costGold: 0, + costEssence: 750, + costCrystals: 0, + purchased: false, + unlocked: false, + }, + { + id: "divine_champion_1", + name: "Champion's Oath", + description: "An unbreakable oath to the divine doubles champion output.", + target: "adventurer", + adventurerId: "divine_champion", + multiplier: 2, + costGold: 0, + costEssence: 2_000, + costCrystals: 0, purchased: false, unlocked: false, }, diff --git a/apps/api/src/data/zones.ts b/apps/api/src/data/zones.ts index 343b386..1d0c745 100644 --- a/apps/api/src/data/zones.ts +++ b/apps/api/src/data/zones.ts @@ -19,6 +19,15 @@ export const DEFAULT_ZONES: Zone[] = [ status: "locked", unlockBossId: "lich_queen", }, + { + id: "shadow_marshes", + name: "The Shadow Marshes", + description: + "A vast, fog-choked wetland where the sun never fully rises. Dark magic seeps from the earth itself, and things far older than the kingdom lurk beneath the murky waters.", + emoji: "π", + status: "locked", + unlockBossId: "troll_king", + }, { id: "frozen_peaks", name: "The Frozen Peaks", @@ -28,4 +37,22 @@ export const DEFAULT_ZONES: Zone[] = [ status: "locked", unlockBossId: "elder_dragon", }, + { + id: "volcanic_depths", + name: "The Volcanic Depths", + description: + "A chain of active volcanoes whose caverns plunge deep into the earth's molten heart. Legendary forges burn here, tended by fire elementals who serve no master β yet.", + emoji: "π", + status: "locked", + unlockBossId: "bone_colossus", + }, + { + id: "astral_void", + name: "The Astral Void", + description: + "Beyond the veil of the mortal world lies a realm of pure possibility and absolute terror. Stars are born and die here in moments, and the beings that call this place home have never known mortality.", + emoji: "π", + status: "locked", + unlockBossId: "void_titan", + }, ]; diff --git a/apps/api/src/routes/boss.ts b/apps/api/src/routes/boss.ts index 26dfab7..c85b985 100644 --- a/apps/api/src/routes/boss.ts +++ b/apps/api/src/routes/boss.ts @@ -146,16 +146,24 @@ bossRouter.post("/challenge", async (context) => { } } - const bossIndex = state.bosses.findIndex((b) => b.id === body.bossId); - const nextBoss = state.bosses[bossIndex + 1]; - if (nextBoss && nextBoss.prestigeRequirement <= state.prestige.count) { - nextBoss.status = "available"; + // Unlock next boss in the same zone (zone-based sequential progression) + const zoneBosses = state.bosses.filter((b) => b.zoneId === boss.zoneId); + const zoneIndex = zoneBosses.findIndex((b) => b.id === body.bossId); + const nextZoneBoss = zoneBosses[zoneIndex + 1]; + if (nextZoneBoss && nextZoneBoss.prestigeRequirement <= state.prestige.count) { + const nextBossInState = state.bosses.find((b) => b.id === nextZoneBoss.id); + if (nextBossInState) nextBossInState.status = "available"; } - // Unlock any zone whose unlock condition is this boss + // Unlock any zone whose unlock condition is this boss, and activate its first boss for (const zone of (state.zones ?? [])) { if (zone.unlockBossId === body.bossId) { zone.status = "unlocked"; + const newZoneBosses = state.bosses.filter((b) => b.zoneId === zone.id); + const firstNewBoss = newZoneBosses[0]; + if (firstNewBoss && firstNewBoss.prestigeRequirement <= state.prestige.count) { + firstNewBoss.status = "available"; + } } } diff --git a/apps/api/src/routes/game.ts b/apps/api/src/routes/game.ts index 5708f2e..28b0be0 100644 --- a/apps/api/src/routes/game.ts +++ b/apps/api/src/routes/game.ts @@ -69,7 +69,7 @@ gameRouter.get("/load", async (context) => { } } - // Backfill new quests and upgrades from defaults (add missing ones) + // Backfill new quests, upgrades, zones, and bosses from defaults (add missing ones) const { DEFAULT_QUESTS } = await import("../data/quests.js"); const { DEFAULT_UPGRADES } = await import("../data/upgrades.js"); const { DEFAULT_ZONES } = await import("../data/zones.js"); @@ -82,10 +82,14 @@ gameRouter.get("/load", async (context) => { } } - // Backfill zoneId on quests that predate the field + // Sync zoneId on quests to match current defaults for (const quest of state.quests) { + const defaults = DEFAULT_QUESTS.find((d) => d.id === quest.id); + if (defaults && quest.zoneId !== defaults.zoneId) { + quest.zoneId = defaults.zoneId; + needsBackfill = true; + } if (!quest.zoneId) { - const defaults = DEFAULT_QUESTS.find((d) => d.id === quest.id); quest.zoneId = defaults?.zoneId ?? "verdant_vale"; needsBackfill = true; } @@ -98,7 +102,23 @@ gameRouter.get("/load", async (context) => { } } - // Backfill zones on saves that predate the feature + // Backfill costCrystals on upgrades that predate the field + for (const upgrade of state.upgrades) { + if (upgrade.costCrystals == null) { + upgrade.costCrystals = 0; + needsBackfill = true; + } + } + + // Merge new adventurers from defaults + for (const defaultAdventurer of DEFAULT_ADVENTURERS) { + if (!state.adventurers.some((a) => a.id === defaultAdventurer.id)) { + state.adventurers.push(structuredClone(defaultAdventurer)); + needsBackfill = true; + } + } + + // Backfill zones if (!Array.isArray(state.zones) || state.zones.length === 0) { state.zones = structuredClone(DEFAULT_ZONES); // Infer unlock state from defeated bosses @@ -111,6 +131,22 @@ gameRouter.get("/load", async (context) => { } } needsBackfill = true; + } else { + // Merge new zones from defaults + for (const defaultZone of DEFAULT_ZONES) { + if (!state.zones.some((z) => z.id === defaultZone.id)) { + const newZone = structuredClone(defaultZone); + // Infer unlock state from defeated bosses + if (newZone.unlockBossId != null) { + const unlockBoss = state.bosses.find((b) => b.id === newZone.unlockBossId); + if (unlockBoss?.status === "defeated") { + newZone.status = "unlocked"; + } + } + state.zones.push(newZone); + needsBackfill = true; + } + } } // Backfill zoneId on bosses that predate the field @@ -122,6 +158,23 @@ gameRouter.get("/load", async (context) => { } } + // Merge new bosses from defaults (new zones' bosses) + for (const defaultBoss of DEFAULT_BOSSES) { + if (!state.bosses.some((b) => b.id === defaultBoss.id)) { + const newBoss = structuredClone(defaultBoss); + // If the zone for this boss is already unlocked, make the first boss in that zone available + const zone = state.zones.find((z) => z.id === newBoss.zoneId); + if (zone?.status === "unlocked") { + const zoneBossesInState = state.bosses.filter((b) => b.zoneId === newBoss.zoneId); + if (zoneBossesInState.length === 0 && newBoss.status === "locked") { + newBoss.status = "available"; + } + } + state.bosses.push(newBoss); + needsBackfill = true; + } + } + const now = Date.now(); const { offlineGold, offlineSeconds } = calculateOfflineGold(state, now); diff --git a/apps/web/src/components/game/EquipmentPanel.tsx b/apps/web/src/components/game/EquipmentPanel.tsx index 134c1f0..a75665d 100644 --- a/apps/web/src/components/game/EquipmentPanel.tsx +++ b/apps/web/src/components/game/EquipmentPanel.tsx @@ -32,10 +32,25 @@ const bonusDescription = (item: Equipment): string => { interface EquipmentCardProps { item: Equipment; + gold: number; + essence: number; + crystals: number; } -const EquipmentCard = ({ item }: EquipmentCardProps): React.JSX.Element => { - const { equipItem } = useGame(); +const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => { + const parts: string[] = []; + if (cost.gold > 0) parts.push(`πͺ ${cost.gold.toLocaleString()}`); + if (cost.essence > 0) parts.push(`β¨ ${cost.essence.toLocaleString()}`); + if (cost.crystals > 0) parts.push(`π ${cost.crystals.toLocaleString()}`); + return parts.join(" "); +}; + +const EquipmentCard = ({ item, gold, essence, crystals }: EquipmentCardProps): React.JSX.Element => { + const { equipItem, buyEquipment } = useGame(); + + const canAfford = item.cost + ? gold >= item.cost.gold && essence >= item.cost.essence && crystals >= item.cost.crystals + : false; return (
{item.description}
{bonusDescription(item)}
+ {!item.owned && item.cost && ( +{costLabel(item.cost)}
+ )}