generated from nhcarrigan/template
feat: major content expansion with essence and crystal sinks
- 6 zones (up from 3): Shadow Marshes, Volcanic Depths, Astral Void - 18 bosses (up from 4): 3 per zone with zone-based sequential unlock - 24 quests (up from 9): 3-4 per zone, reorganised by zone - 15 adventurer tiers (up from 10): Shadow Assassin through Divine Champion - 28 equipment pieces (up from 12): boss drops + purchasable with essence/crystals - 24 upgrades (up from 13): crystal-cost upgrades + new adventurer upgrades - 22 achievements (up from 14): new milestones for bosses, quests, gold, armies - Purchasable equipment system: buy items directly with essence or crystals - Crystal-cost upgrades: spend crystals on global and click power boosts - Zone-based boss progression: defeating a zone's last boss unlocks the next zone
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import type { Achievement } from "@elysium/types";
|
import type { Achievement } from "@elysium/types";
|
||||||
|
|
||||||
export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
||||||
|
// Click milestones
|
||||||
{
|
{
|
||||||
id: "first_click",
|
id: "first_click",
|
||||||
name: "First Strike",
|
name: "First Strike",
|
||||||
@@ -28,6 +29,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 100 },
|
reward: { crystals: 100 },
|
||||||
unlockedAt: null,
|
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",
|
id: "first_gold",
|
||||||
name: "First Gold",
|
name: "First Gold",
|
||||||
@@ -64,6 +75,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 500 },
|
reward: { crystals: 500 },
|
||||||
unlockedAt: null,
|
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",
|
id: "first_quest",
|
||||||
name: "Adventurous Spirit",
|
name: "Adventurous Spirit",
|
||||||
@@ -82,6 +103,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 50 },
|
reward: { crystals: 50 },
|
||||||
unlockedAt: null,
|
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",
|
id: "boss_slayer",
|
||||||
name: "Boss Slayer",
|
name: "Boss Slayer",
|
||||||
@@ -92,14 +123,33 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
unlockedAt: null,
|
unlockedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "legendary_hunter",
|
id: "boss_veteran",
|
||||||
name: "Legendary Hunter",
|
name: "Boss Veteran",
|
||||||
description: "Defeat all four bosses.",
|
description: "Defeat 5 bosses.",
|
||||||
icon: "🏆",
|
icon: "🗡️",
|
||||||
condition: { type: "bossesDefeated", amount: 4 },
|
condition: { type: "bossesDefeated", amount: 5 },
|
||||||
reward: { crystals: 200 },
|
reward: { crystals: 150 },
|
||||||
unlockedAt: null,
|
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",
|
id: "guild_master",
|
||||||
name: "Guild Master",
|
name: "Guild Master",
|
||||||
@@ -118,6 +168,16 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 200 },
|
reward: { crystals: 200 },
|
||||||
unlockedAt: null,
|
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",
|
id: "first_prestige",
|
||||||
name: "Born Again",
|
name: "Born Again",
|
||||||
@@ -127,6 +187,7 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 100 },
|
reward: { crystals: 100 },
|
||||||
unlockedAt: null,
|
unlockedAt: null,
|
||||||
},
|
},
|
||||||
|
// Collection milestones
|
||||||
{
|
{
|
||||||
id: "collector",
|
id: "collector",
|
||||||
name: "Collector",
|
name: "Collector",
|
||||||
@@ -136,4 +197,13 @@ export const DEFAULT_ACHIEVEMENTS: Achievement[] = [
|
|||||||
reward: { crystals: 10 },
|
reward: { crystals: 10 },
|
||||||
unlockedAt: null,
|
unlockedAt: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "arsenal",
|
||||||
|
name: "Arsenal",
|
||||||
|
description: "Own 12 pieces of equipment.",
|
||||||
|
icon: "🗃️",
|
||||||
|
condition: { type: "equipmentOwned", amount: 12 },
|
||||||
|
reward: { crystals: 200 },
|
||||||
|
unlockedAt: null,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -111,4 +111,59 @@ export const DEFAULT_ADVENTURERS: Adventurer[] = [
|
|||||||
count: 0,
|
count: 0,
|
||||||
unlocked: false,
|
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,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
+256
-12
@@ -1,6 +1,7 @@
|
|||||||
import type { Boss } from "@elysium/types";
|
import type { Boss } from "@elysium/types";
|
||||||
|
|
||||||
export const DEFAULT_BOSSES: Boss[] = [
|
export const DEFAULT_BOSSES: Boss[] = [
|
||||||
|
// ── Verdant Vale ──────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: "troll_king",
|
id: "troll_king",
|
||||||
name: "The Troll King",
|
name: "The Troll King",
|
||||||
@@ -35,38 +36,281 @@ export const DEFAULT_BOSSES: Boss[] = [
|
|||||||
prestigeRequirement: 0,
|
prestigeRequirement: 0,
|
||||||
zoneId: "verdant_vale",
|
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",
|
id: "elder_dragon",
|
||||||
name: "Elder Dragon Vaeltharox",
|
name: "Elder Dragon Vaeltharox",
|
||||||
description:
|
description:
|
||||||
"The eldest dragon in existence, older than the kingdom itself. Even his breath can level mountains.",
|
"The eldest dragon in existence, older than the kingdom itself. Even his breath can level mountains.",
|
||||||
status: "locked",
|
status: "locked",
|
||||||
maxHp: 100_000,
|
maxHp: 500_000,
|
||||||
currentHp: 100_000,
|
currentHp: 500_000,
|
||||||
damagePerSecond: 75,
|
damagePerSecond: 200,
|
||||||
goldReward: 1_000_000,
|
goldReward: 5_000_000,
|
||||||
essenceReward: 1_000,
|
essenceReward: 3_000,
|
||||||
crystalReward: 50,
|
crystalReward: 100,
|
||||||
upgradeRewards: ["click_3"],
|
upgradeRewards: ["click_3"],
|
||||||
equipmentRewards: ["vorpal_sword", "dragon_scale"],
|
equipmentRewards: ["vorpal_sword", "dragon_scale"],
|
||||||
prestigeRequirement: 1,
|
prestigeRequirement: 1,
|
||||||
zoneId: "shattered_ruins",
|
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",
|
id: "void_titan",
|
||||||
name: "The Void Titan",
|
name: "The Void Titan",
|
||||||
description:
|
description:
|
||||||
"A creature from beyond the veil of reality, drawn by the power your guild has accumulated. It must not be allowed to exist.",
|
"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",
|
status: "locked",
|
||||||
maxHp: 1_000_000,
|
maxHp: 5_000_000,
|
||||||
currentHp: 1_000_000,
|
currentHp: 5_000_000,
|
||||||
damagePerSecond: 250,
|
damagePerSecond: 1_200,
|
||||||
goldReward: 10_000_000,
|
goldReward: 50_000_000,
|
||||||
essenceReward: 5_000,
|
essenceReward: 20_000,
|
||||||
crystalReward: 200,
|
crystalReward: 500,
|
||||||
upgradeRewards: [],
|
upgradeRewards: [],
|
||||||
equipmentRewards: ["philosophers_stone"],
|
equipmentRewards: ["philosophers_stone"],
|
||||||
prestigeRequirement: 3,
|
prestigeRequirement: 3,
|
||||||
zoneId: "frozen_peaks",
|
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",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Equipment } from "@elysium/types";
|
import type { Equipment } from "@elysium/types";
|
||||||
|
|
||||||
export const DEFAULT_EQUIPMENT: Equipment[] = [
|
export const DEFAULT_EQUIPMENT: Equipment[] = [
|
||||||
// Weapons — drop from bosses; common starts owned
|
// ── Weapons ───────────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: "rusty_sword",
|
id: "rusty_sword",
|
||||||
name: "Rusty Sword",
|
name: "Rusty Sword",
|
||||||
@@ -32,6 +32,27 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "vorpal_sword",
|
||||||
name: "Vorpal Sword",
|
name: "Vorpal Sword",
|
||||||
@@ -42,7 +63,39 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "leather_armour",
|
||||||
name: "Leather Armour",
|
name: "Leather Armour",
|
||||||
@@ -63,6 +116,16 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "plate_armour",
|
||||||
name: "Plate Armour",
|
name: "Plate Armour",
|
||||||
@@ -73,6 +136,27 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "dragon_scale",
|
||||||
name: "Dragon Scale Armour",
|
name: "Dragon Scale Armour",
|
||||||
@@ -83,7 +167,28 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "lucky_coin",
|
||||||
name: "Lucky Coin",
|
name: "Lucky Coin",
|
||||||
@@ -104,6 +209,16 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "arcane_orb",
|
||||||
name: "Arcane Orb",
|
name: "Arcane Orb",
|
||||||
@@ -114,6 +229,47 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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",
|
id: "philosophers_stone",
|
||||||
name: "Philosopher's Stone",
|
name: "Philosopher's Stone",
|
||||||
@@ -124,4 +280,24 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
|
|||||||
owned: false,
|
owned: false,
|
||||||
equipped: 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,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
+246
-33
@@ -1,6 +1,7 @@
|
|||||||
import type { Quest } from "@elysium/types";
|
import type { Quest } from "@elysium/types";
|
||||||
|
|
||||||
export const DEFAULT_QUESTS: Quest[] = [
|
export const DEFAULT_QUESTS: Quest[] = [
|
||||||
|
// ── Verdant Vale ──────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: "first_steps",
|
id: "first_steps",
|
||||||
name: "First Steps",
|
name: "First Steps",
|
||||||
@@ -37,21 +38,6 @@ export const DEFAULT_QUESTS: Quest[] = [
|
|||||||
prerequisiteIds: ["goblin_camp"],
|
prerequisiteIds: ["goblin_camp"],
|
||||||
zoneId: "verdant_vale",
|
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",
|
id: "ancient_ruins",
|
||||||
name: "Ancient Ruins",
|
name: "Ancient Ruins",
|
||||||
@@ -65,6 +51,68 @@ export const DEFAULT_QUESTS: Quest[] = [
|
|||||||
prerequisiteIds: ["haunted_mine"],
|
prerequisiteIds: ["haunted_mine"],
|
||||||
zoneId: "verdant_vale",
|
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",
|
id: "shadow_mere",
|
||||||
name: "The Shadow Mere",
|
name: "The Shadow Mere",
|
||||||
@@ -74,26 +122,56 @@ export const DEFAULT_QUESTS: Quest[] = [
|
|||||||
durationSeconds: 45 * 60,
|
durationSeconds: 45 * 60,
|
||||||
rewards: [
|
rewards: [
|
||||||
{ type: "essence", amount: 150 },
|
{ type: "essence", amount: 150 },
|
||||||
{ type: "upgrade", targetId: "scout_1" },
|
{ type: "upgrade", targetId: "militia_1" },
|
||||||
],
|
],
|
||||||
prerequisiteIds: ["ancient_ruins"],
|
prerequisiteIds: [],
|
||||||
zoneId: "shattered_ruins",
|
zoneId: "shadow_marshes",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "dragon_lair",
|
id: "witch_coven",
|
||||||
name: "Dragon's Lair",
|
name: "The Witch Coven",
|
||||||
description:
|
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",
|
status: "locked",
|
||||||
durationSeconds: 60 * 60,
|
durationSeconds: 90 * 60,
|
||||||
rewards: [
|
rewards: [
|
||||||
{ type: "gold", amount: 500_000 },
|
{ type: "essence", amount: 500 },
|
||||||
{ type: "crystals", amount: 50 },
|
{ type: "adventurer", targetId: "shadow_assassin" },
|
||||||
{ type: "adventurer", targetId: "dragon_rider" },
|
|
||||||
],
|
],
|
||||||
prerequisiteIds: ["ancient_ruins"],
|
prerequisiteIds: ["shadow_mere"],
|
||||||
zoneId: "shattered_ruins",
|
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",
|
id: "frozen_wastes",
|
||||||
name: "The Frozen Wastes",
|
name: "The Frozen Wastes",
|
||||||
@@ -102,13 +180,104 @@ export const DEFAULT_QUESTS: Quest[] = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
durationSeconds: 2 * 60 * 60,
|
durationSeconds: 2 * 60 * 60,
|
||||||
rewards: [
|
rewards: [
|
||||||
{ type: "gold", amount: 2_000_000 },
|
{ type: "gold", amount: 5_000_000 },
|
||||||
{ type: "crystals", amount: 150 },
|
{ type: "crystals", amount: 100 },
|
||||||
{ type: "upgrade", targetId: "global_3" },
|
{ type: "upgrade", targetId: "global_3" },
|
||||||
],
|
],
|
||||||
prerequisiteIds: ["dragon_lair"],
|
prerequisiteIds: [],
|
||||||
zoneId: "frozen_peaks",
|
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",
|
id: "void_rift",
|
||||||
name: "Void Rift",
|
name: "Void Rift",
|
||||||
@@ -119,9 +288,53 @@ export const DEFAULT_QUESTS: Quest[] = [
|
|||||||
rewards: [
|
rewards: [
|
||||||
{ type: "crystals", amount: 500 },
|
{ type: "crystals", amount: 500 },
|
||||||
{ type: "essence", amount: 5_000 },
|
{ type: "essence", amount: 5_000 },
|
||||||
{ type: "upgrade", targetId: "knight_1" },
|
|
||||||
],
|
],
|
||||||
prerequisiteIds: ["frozen_wastes"],
|
prerequisiteIds: [],
|
||||||
zoneId: "frozen_peaks",
|
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",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Upgrade } from "@elysium/types";
|
import type { Upgrade } from "@elysium/types";
|
||||||
|
|
||||||
export const DEFAULT_UPGRADES: Upgrade[] = [
|
export const DEFAULT_UPGRADES: Upgrade[] = [
|
||||||
// Click upgrades
|
// ── Click upgrades ────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: "click_1",
|
id: "click_1",
|
||||||
name: "Keen Eye",
|
name: "Keen Eye",
|
||||||
@@ -10,6 +10,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 100,
|
costGold: 100,
|
||||||
costEssence: 0,
|
costEssence: 0,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: true,
|
unlocked: true,
|
||||||
},
|
},
|
||||||
@@ -19,8 +20,9 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
description: "Years of combat sharpen your instincts. Doubles click power again.",
|
description: "Years of combat sharpen your instincts. Doubles click power again.",
|
||||||
target: "click",
|
target: "click",
|
||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 1000,
|
costGold: 1_000,
|
||||||
costEssence: 0,
|
costEssence: 0,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -32,10 +34,23 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 3,
|
multiplier: 3,
|
||||||
costGold: 50_000,
|
costGold: 50_000,
|
||||||
costEssence: 10,
|
costEssence: 10,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: 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",
|
id: "global_1",
|
||||||
name: "Guild Charter",
|
name: "Guild Charter",
|
||||||
@@ -44,6 +59,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 1.25,
|
multiplier: 1.25,
|
||||||
costGold: 500,
|
costGold: 500,
|
||||||
costEssence: 0,
|
costEssence: 0,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -55,6 +71,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 1.5,
|
multiplier: 1.5,
|
||||||
costGold: 10_000,
|
costGold: 10_000,
|
||||||
costEssence: 5,
|
costEssence: 5,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -66,10 +83,59 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 1_000_000,
|
costGold: 1_000_000,
|
||||||
costEssence: 100,
|
costEssence: 100,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: 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",
|
id: "peasant_1",
|
||||||
name: "Better Tools",
|
name: "Better Tools",
|
||||||
@@ -79,6 +145,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 200,
|
costGold: 200,
|
||||||
costEssence: 0,
|
costEssence: 0,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -91,6 +158,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 1_000,
|
costGold: 1_000,
|
||||||
costEssence: 0,
|
costEssence: 0,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -103,6 +171,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 5_000,
|
costGold: 5_000,
|
||||||
costEssence: 2,
|
costEssence: 2,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -115,6 +184,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 8_000,
|
costGold: 8_000,
|
||||||
costEssence: 3,
|
costEssence: 3,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -127,6 +197,7 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 15_000,
|
costGold: 15_000,
|
||||||
costEssence: 5,
|
costEssence: 5,
|
||||||
|
costCrystals: 0,
|
||||||
purchased: false,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
@@ -139,6 +210,111 @@ export const DEFAULT_UPGRADES: Upgrade[] = [
|
|||||||
multiplier: 2,
|
multiplier: 2,
|
||||||
costGold: 50_000,
|
costGold: 50_000,
|
||||||
costEssence: 10,
|
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,
|
purchased: false,
|
||||||
unlocked: false,
|
unlocked: false,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -19,6 +19,15 @@ export const DEFAULT_ZONES: Zone[] = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
unlockBossId: "lich_queen",
|
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",
|
id: "frozen_peaks",
|
||||||
name: "The Frozen Peaks",
|
name: "The Frozen Peaks",
|
||||||
@@ -28,4 +37,22 @@ export const DEFAULT_ZONES: Zone[] = [
|
|||||||
status: "locked",
|
status: "locked",
|
||||||
unlockBossId: "elder_dragon",
|
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",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -146,16 +146,24 @@ bossRouter.post("/challenge", async (context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bossIndex = state.bosses.findIndex((b) => b.id === body.bossId);
|
// Unlock next boss in the same zone (zone-based sequential progression)
|
||||||
const nextBoss = state.bosses[bossIndex + 1];
|
const zoneBosses = state.bosses.filter((b) => b.zoneId === boss.zoneId);
|
||||||
if (nextBoss && nextBoss.prestigeRequirement <= state.prestige.count) {
|
const zoneIndex = zoneBosses.findIndex((b) => b.id === body.bossId);
|
||||||
nextBoss.status = "available";
|
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 ?? [])) {
|
for (const zone of (state.zones ?? [])) {
|
||||||
if (zone.unlockBossId === body.bossId) {
|
if (zone.unlockBossId === body.bossId) {
|
||||||
zone.status = "unlocked";
|
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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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_QUESTS } = await import("../data/quests.js");
|
||||||
const { DEFAULT_UPGRADES } = await import("../data/upgrades.js");
|
const { DEFAULT_UPGRADES } = await import("../data/upgrades.js");
|
||||||
const { DEFAULT_ZONES } = await import("../data/zones.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) {
|
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) {
|
if (!quest.zoneId) {
|
||||||
const defaults = DEFAULT_QUESTS.find((d) => d.id === quest.id);
|
|
||||||
quest.zoneId = defaults?.zoneId ?? "verdant_vale";
|
quest.zoneId = defaults?.zoneId ?? "verdant_vale";
|
||||||
needsBackfill = true;
|
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) {
|
if (!Array.isArray(state.zones) || state.zones.length === 0) {
|
||||||
state.zones = structuredClone(DEFAULT_ZONES);
|
state.zones = structuredClone(DEFAULT_ZONES);
|
||||||
// Infer unlock state from defeated bosses
|
// Infer unlock state from defeated bosses
|
||||||
@@ -111,6 +131,22 @@ gameRouter.get("/load", async (context) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
needsBackfill = true;
|
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
|
// 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 now = Date.now();
|
||||||
|
|
||||||
const { offlineGold, offlineSeconds } = calculateOfflineGold(state, now);
|
const { offlineGold, offlineSeconds } = calculateOfflineGold(state, now);
|
||||||
|
|||||||
@@ -32,10 +32,25 @@ const bonusDescription = (item: Equipment): string => {
|
|||||||
|
|
||||||
interface EquipmentCardProps {
|
interface EquipmentCardProps {
|
||||||
item: Equipment;
|
item: Equipment;
|
||||||
|
gold: number;
|
||||||
|
essence: number;
|
||||||
|
crystals: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const EquipmentCard = ({ item }: EquipmentCardProps): React.JSX.Element => {
|
const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => {
|
||||||
const { equipItem } = useGame();
|
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 (
|
return (
|
||||||
<div className={`equipment-card rarity-${item.rarity} ${item.equipped ? "equipped" : ""} ${!item.owned ? "not-owned" : ""}`}>
|
<div className={`equipment-card rarity-${item.rarity} ${item.equipped ? "equipped" : ""} ${!item.owned ? "not-owned" : ""}`}>
|
||||||
@@ -47,9 +62,22 @@ const EquipmentCard = ({ item }: EquipmentCardProps): React.JSX.Element => {
|
|||||||
</div>
|
</div>
|
||||||
<p className="equipment-description">{item.description}</p>
|
<p className="equipment-description">{item.description}</p>
|
||||||
<p className="equipment-bonus">{bonusDescription(item)}</p>
|
<p className="equipment-bonus">{bonusDescription(item)}</p>
|
||||||
|
{!item.owned && item.cost && (
|
||||||
|
<p className="equipment-cost">{costLabel(item.cost)}</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="equipment-action">
|
<div className="equipment-action">
|
||||||
{!item.owned && <span className="equipment-locked">🔒 Not yet obtained</span>}
|
{!item.owned && !item.cost && <span className="equipment-locked">🔒 Boss drop</span>}
|
||||||
|
{!item.owned && item.cost && (
|
||||||
|
<button
|
||||||
|
className="equip-button"
|
||||||
|
disabled={!canAfford}
|
||||||
|
onClick={() => { buyEquipment(item.id); }}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{canAfford ? "Purchase" : "Can't afford"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
{item.owned && item.equipped && <span className="equipment-equipped-badge">✓ Equipped</span>}
|
{item.owned && item.equipped && <span className="equipment-equipped-badge">✓ Equipped</span>}
|
||||||
{item.owned && !item.equipped && (
|
{item.owned && !item.equipped && (
|
||||||
<button
|
<button
|
||||||
@@ -104,7 +132,13 @@ export const EquipmentPanel = (): React.JSX.Element => {
|
|||||||
<h3 className="slot-heading">{SLOT_LABEL[slotType]}</h3>
|
<h3 className="slot-heading">{SLOT_LABEL[slotType]}</h3>
|
||||||
<div className="equipment-list">
|
<div className="equipment-list">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<EquipmentCard key={item.id} item={item} />
|
<EquipmentCard
|
||||||
|
key={item.id}
|
||||||
|
item={item}
|
||||||
|
gold={state.resources.gold}
|
||||||
|
essence={state.resources.essence}
|
||||||
|
crystals={state.resources.crystals}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
{items.length === 0 && (
|
{items.length === 0 && (
|
||||||
<p className="empty-zone">No items to show in this slot.</p>
|
<p className="empty-zone">No items to show in this slot.</p>
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ interface UpgradeCardProps {
|
|||||||
upgrade: Upgrade;
|
upgrade: Upgrade;
|
||||||
currentGold: number;
|
currentGold: number;
|
||||||
currentEssence: number;
|
currentEssence: number;
|
||||||
|
currentCrystals: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UpgradeCard = ({ upgrade, currentGold, currentEssence }: UpgradeCardProps): React.JSX.Element => {
|
const UpgradeCard = ({ upgrade, currentGold, currentEssence, currentCrystals }: UpgradeCardProps): React.JSX.Element => {
|
||||||
const { buyUpgrade } = useGame();
|
const { buyUpgrade } = useGame();
|
||||||
const canAfford =
|
const canAfford =
|
||||||
currentGold >= upgrade.costGold && currentEssence >= upgrade.costEssence;
|
currentGold >= upgrade.costGold &&
|
||||||
|
currentEssence >= upgrade.costEssence &&
|
||||||
|
currentCrystals >= (upgrade.costCrystals ?? 0);
|
||||||
|
|
||||||
if (!upgrade.unlocked) {
|
if (!upgrade.unlocked) {
|
||||||
return (
|
return (
|
||||||
@@ -25,6 +28,7 @@ const UpgradeCard = ({ upgrade, currentGold, currentEssence }: UpgradeCardProps)
|
|||||||
<div className="upgrade-cost">
|
<div className="upgrade-cost">
|
||||||
{upgrade.costGold > 0 && <span>🪙 {upgrade.costGold.toLocaleString()}</span>}
|
{upgrade.costGold > 0 && <span>🪙 {upgrade.costGold.toLocaleString()}</span>}
|
||||||
{upgrade.costEssence > 0 && <span>✨ {upgrade.costEssence.toLocaleString()}</span>}
|
{upgrade.costEssence > 0 && <span>✨ {upgrade.costEssence.toLocaleString()}</span>}
|
||||||
|
{(upgrade.costCrystals ?? 0) > 0 && <span>💎 {upgrade.costCrystals?.toLocaleString()}</span>}
|
||||||
</div>
|
</div>
|
||||||
<span className="upgrade-locked-label">Locked</span>
|
<span className="upgrade-locked-label">Locked</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,6 +54,7 @@ const UpgradeCard = ({ upgrade, currentGold, currentEssence }: UpgradeCardProps)
|
|||||||
<div className="upgrade-cost">
|
<div className="upgrade-cost">
|
||||||
{upgrade.costGold > 0 && <span>🪙 {upgrade.costGold.toLocaleString()}</span>}
|
{upgrade.costGold > 0 && <span>🪙 {upgrade.costGold.toLocaleString()}</span>}
|
||||||
{upgrade.costEssence > 0 && <span>✨ {upgrade.costEssence.toLocaleString()}</span>}
|
{upgrade.costEssence > 0 && <span>✨ {upgrade.costEssence.toLocaleString()}</span>}
|
||||||
|
{(upgrade.costCrystals ?? 0) > 0 && <span>💎 {upgrade.costCrystals?.toLocaleString()}</span>}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
className="buy-button"
|
className="buy-button"
|
||||||
@@ -94,6 +99,7 @@ export const UpgradePanel = (): React.JSX.Element => {
|
|||||||
upgrade={upgrade}
|
upgrade={upgrade}
|
||||||
currentGold={state.resources.gold}
|
currentGold={state.resources.gold}
|
||||||
currentEssence={state.resources.essence}
|
currentEssence={state.resources.essence}
|
||||||
|
currentCrystals={state.resources.crystals}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{purchased.map((upgrade) => (
|
{purchased.map((upgrade) => (
|
||||||
@@ -102,6 +108,7 @@ export const UpgradePanel = (): React.JSX.Element => {
|
|||||||
upgrade={upgrade}
|
upgrade={upgrade}
|
||||||
currentGold={state.resources.gold}
|
currentGold={state.resources.gold}
|
||||||
currentEssence={state.resources.essence}
|
currentEssence={state.resources.essence}
|
||||||
|
currentCrystals={state.resources.crystals}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{showLocked && locked.map((upgrade) => (
|
{showLocked && locked.map((upgrade) => (
|
||||||
@@ -110,6 +117,7 @@ export const UpgradePanel = (): React.JSX.Element => {
|
|||||||
upgrade={upgrade}
|
upgrade={upgrade}
|
||||||
currentGold={state.resources.gold}
|
currentGold={state.resources.gold}
|
||||||
currentEssence={state.resources.essence}
|
currentEssence={state.resources.essence}
|
||||||
|
currentCrystals={state.resources.crystals}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ interface GameContextValue {
|
|||||||
buyAdventurer: (adventurerId: string) => void;
|
buyAdventurer: (adventurerId: string) => void;
|
||||||
/** Buy an upgrade */
|
/** Buy an upgrade */
|
||||||
buyUpgrade: (upgradeId: string) => void;
|
buyUpgrade: (upgradeId: string) => void;
|
||||||
|
/** Purchase a buyable equipment item */
|
||||||
|
buyEquipment: (equipmentId: string) => void;
|
||||||
/** Start a quest */
|
/** Start a quest */
|
||||||
startQuest: (questId: string) => void;
|
startQuest: (questId: string) => void;
|
||||||
/** Challenge a boss — runs full server-side simulation */
|
/** Challenge a boss — runs full server-side simulation */
|
||||||
@@ -175,6 +177,7 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React
|
|||||||
if (!upgrade || !upgrade.unlocked || upgrade.purchased) return prev;
|
if (!upgrade || !upgrade.unlocked || upgrade.purchased) return prev;
|
||||||
if (prev.resources.gold < upgrade.costGold) return prev;
|
if (prev.resources.gold < upgrade.costGold) return prev;
|
||||||
if (prev.resources.essence < upgrade.costEssence) return prev;
|
if (prev.resources.essence < upgrade.costEssence) return prev;
|
||||||
|
if (prev.resources.crystals < (upgrade.costCrystals ?? 0)) return prev;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
@@ -182,6 +185,7 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React
|
|||||||
...prev.resources,
|
...prev.resources,
|
||||||
gold: prev.resources.gold - upgrade.costGold,
|
gold: prev.resources.gold - upgrade.costGold,
|
||||||
essence: prev.resources.essence - upgrade.costEssence,
|
essence: prev.resources.essence - upgrade.costEssence,
|
||||||
|
crystals: prev.resources.crystals - (upgrade.costCrystals ?? 0),
|
||||||
},
|
},
|
||||||
upgrades: prev.upgrades.map((u) =>
|
upgrades: prev.upgrades.map((u) =>
|
||||||
u.id === upgradeId ? { ...u, purchased: true } : u,
|
u.id === upgradeId ? { ...u, purchased: true } : u,
|
||||||
@@ -225,6 +229,37 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const buyEquipment = useCallback((equipmentId: string) => {
|
||||||
|
setState((prev) => {
|
||||||
|
if (!prev) return prev;
|
||||||
|
const item = (prev.equipment ?? []).find((e) => e.id === equipmentId);
|
||||||
|
if (!item || item.owned || !item.cost) return prev;
|
||||||
|
|
||||||
|
const { gold, essence, crystals } = item.cost;
|
||||||
|
if (prev.resources.gold < gold) return prev;
|
||||||
|
if (prev.resources.essence < essence) return prev;
|
||||||
|
if (prev.resources.crystals < crystals) return prev;
|
||||||
|
|
||||||
|
const slotAlreadyEquipped = (prev.equipment ?? []).some(
|
||||||
|
(e) => e.type === item.type && e.equipped,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
resources: {
|
||||||
|
...prev.resources,
|
||||||
|
gold: prev.resources.gold - gold,
|
||||||
|
essence: prev.resources.essence - essence,
|
||||||
|
crystals: prev.resources.crystals - crystals,
|
||||||
|
},
|
||||||
|
equipment: (prev.equipment ?? []).map((e) => {
|
||||||
|
if (e.id === equipmentId) return { ...e, owned: true, equipped: !slotAlreadyEquipped };
|
||||||
|
return e;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const challengeBoss = useCallback(async (bossId: string) => {
|
const challengeBoss = useCallback(async (bossId: string) => {
|
||||||
if (!stateRef.current) return;
|
if (!stateRef.current) return;
|
||||||
const boss = stateRef.current.bosses.find((b) => b.id === bossId);
|
const boss = stateRef.current.bosses.find((b) => b.id === bossId);
|
||||||
@@ -238,21 +273,33 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React
|
|||||||
if (!prev) return prev;
|
if (!prev) return prev;
|
||||||
|
|
||||||
if (result.won) {
|
if (result.won) {
|
||||||
const bossIndex = prev.bosses.findIndex((b) => b.id === bossId);
|
const defeatedBoss = prev.bosses.find((b) => b.id === bossId);
|
||||||
|
const zoneBosses = prev.bosses.filter((b) => b.zoneId === defeatedBoss?.zoneId);
|
||||||
|
const zoneIdx = zoneBosses.findIndex((b) => b.id === bossId);
|
||||||
|
const nextZoneBossId = zoneBosses[zoneIdx + 1]?.id;
|
||||||
|
|
||||||
|
// Find newly unlocked zones and their first bosses
|
||||||
|
const newlyUnlockedZones = (prev.zones ?? []).filter((z) => z.unlockBossId === bossId && z.status === "locked");
|
||||||
|
const newZoneFirstBossIds = newlyUnlockedZones.map((z) => {
|
||||||
|
const firstBoss = prev.bosses.find((b) => b.zoneId === z.id);
|
||||||
|
return firstBoss?.id;
|
||||||
|
}).filter(Boolean);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
bosses: prev.bosses.map((b, idx) => {
|
bosses: prev.bosses.map((b) => {
|
||||||
if (b.id === bossId) {
|
if (b.id === bossId) return { ...b, status: "defeated" as const, currentHp: 0 };
|
||||||
return { ...b, status: "defeated" as const, currentHp: 0 };
|
if (b.id === nextZoneBossId && b.prestigeRequirement <= prev.prestige.count) {
|
||||||
|
return { ...b, status: "available" as const };
|
||||||
}
|
}
|
||||||
if (
|
if (newZoneFirstBossIds.includes(b.id) && b.prestigeRequirement <= prev.prestige.count) {
|
||||||
idx === bossIndex + 1 &&
|
|
||||||
b.prestigeRequirement <= prev.prestige.count
|
|
||||||
) {
|
|
||||||
return { ...b, status: "available" as const };
|
return { ...b, status: "available" as const };
|
||||||
}
|
}
|
||||||
return b;
|
return b;
|
||||||
}),
|
}),
|
||||||
|
zones: (prev.zones ?? []).map((z) =>
|
||||||
|
z.unlockBossId === bossId ? { ...z, status: "unlocked" as const } : z,
|
||||||
|
),
|
||||||
resources: result.rewards
|
resources: result.rewards
|
||||||
? {
|
? {
|
||||||
...prev.resources,
|
...prev.resources,
|
||||||
@@ -332,6 +379,7 @@ export const GameProvider = ({ children }: { children: React.ReactNode }): React
|
|||||||
handleClick,
|
handleClick,
|
||||||
buyAdventurer,
|
buyAdventurer,
|
||||||
buyUpgrade,
|
buyUpgrade,
|
||||||
|
buyEquipment,
|
||||||
startQuest,
|
startQuest,
|
||||||
challengeBoss,
|
challengeBoss,
|
||||||
equipItem,
|
equipItem,
|
||||||
|
|||||||
@@ -937,6 +937,12 @@ body {
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.equipment-cost {
|
||||||
|
color: var(--colour-essence);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
.equipment-equipped-badge {
|
.equipment-equipped-badge {
|
||||||
color: var(--colour-success);
|
color: var(--colour-success);
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
|
|||||||
@@ -22,4 +22,6 @@ export interface Equipment {
|
|||||||
owned: boolean;
|
owned: boolean;
|
||||||
/** Whether this item is currently equipped (only one per type can be equipped) */
|
/** Whether this item is currently equipped (only one per type can be equipped) */
|
||||||
equipped: boolean;
|
equipped: boolean;
|
||||||
|
/** If set, this item can be purchased directly rather than obtained via boss drops */
|
||||||
|
cost?: { gold: number; essence: number; crystals: number };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface Upgrade {
|
|||||||
multiplier: number;
|
multiplier: number;
|
||||||
costGold: number;
|
costGold: number;
|
||||||
costEssence: number;
|
costEssence: number;
|
||||||
|
costCrystals: number;
|
||||||
purchased: boolean;
|
purchased: boolean;
|
||||||
unlocked: boolean;
|
unlocked: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user