feat: add equipment set bonuses and boss bounty runestones

- Define EquipmentSet type + computeSetBonuses utility in packages/types
- Add setId field to Equipment type and assign sets to 27 equipment items
- Create 9 named equipment sets (Iron Vanguard → Eternal Throne) with 2pc/3pc bonuses
- Apply set combat multiplier in boss route
- Apply set gold/click multipliers in tick engine and click handler
- Include set bonuses in anti-cheat delta validation
- Show active set bonus strip + set badge per card in EquipmentPanel
- Add boss first-kill bounty runestones (scaling 1–10 per boss tier)
- Update AboutPanel and IDEAS.md
This commit is contained in:
2026-03-06 23:56:45 -08:00
committed by Naomi Carrigan
parent 48bf74e713
commit 078ae50e69
19 changed files with 488 additions and 20 deletions
+5
View File
@@ -0,0 +1,5 @@
# Elysium Project Notes
## About Page
The About page (`apps/web/src/components/game/AboutPanel.tsx`) contains a **How to Play** guide that should be kept up to date as new features are added to the game. When implementing new game systems, zones, mechanics, or significant UI features, update the `HOW_TO_PLAY` array in `AboutPanel.tsx` to include a description of the new feature.
+2 -2
View File
@@ -20,7 +20,7 @@ A running list of planned features and content additions. Strike through items a
## 📦 Content Additions ## 📦 Content Additions
- [ ] **Equipment set bonuses** — Group existing equipment into named sets (e.g. "Shadow Infiltrator"). Wearing 2/3/4 pieces of a set grants escalating bonuses. Adds strategic depth without requiring lots of new items. - [x] **Equipment set bonuses** — Group existing equipment into named sets (e.g. "Shadow Infiltrator"). Wearing 2/3/4 pieces of a set grants escalating bonuses. Adds strategic depth without requiring lots of new items.
- [ ] **The Codex / Lore Book** — Defeating bosses and completing quests unlocks lore entries about the world. Pure flavour, but gives the world depth and a collection mechanic. Show a ✨ notification when new lore unlocks. - [ ] **The Codex / Lore Book** — Defeating bosses and completing quests unlocks lore entries about the world. Pure flavour, but gives the world depth and a collection mechanic. Show a ✨ notification when new lore unlocks.
@@ -43,7 +43,7 @@ A running list of planned features and content additions. Strike through items a
3. ~~Daily challenges~~ 3. ~~Daily challenges~~
4. ~~Boss first-kill bounties~~ 4. ~~Boss first-kill bounties~~
5. ~~Milestone prestige bonuses~~ 5. ~~Milestone prestige bonuses~~
6. Equipment set bonuses (medium effort) 6. ~~Equipment set bonuses~~
7. Auto-prestige toggle (prestige shop upgrade) 7. Auto-prestige toggle (prestige shop upgrade)
8. The Codex / Lore Book (flavour, lower priority) 8. The Codex / Lore Book (flavour, lower priority)
9. Second prestige layer / Transcendence (big feature, save for later) 9. Second prestige layer / Transcendence (big feature, save for later)
+72
View File
@@ -18,6 +18,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["iron_sword", "chainmail", "mages_focus"], equipmentRewards: ["iron_sword", "chainmail", "mages_focus"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "verdant_vale", zoneId: "verdant_vale",
bountyRunestones: 1,
}, },
{ {
id: "lich_queen", id: "lich_queen",
@@ -35,6 +36,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["enchanted_blade", "plate_armour", "arcane_orb"], equipmentRewards: ["enchanted_blade", "plate_armour", "arcane_orb"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "verdant_vale", zoneId: "verdant_vale",
bountyRunestones: 2,
}, },
{ {
id: "forest_giant", id: "forest_giant",
@@ -52,6 +54,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["hide_armour"], equipmentRewards: ["hide_armour"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "verdant_vale", zoneId: "verdant_vale",
bountyRunestones: 3,
}, },
// ── Shattered Ruins ─────────────────────────────────────────────────────── // ── Shattered Ruins ───────────────────────────────────────────────────────
{ {
@@ -70,6 +73,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shattered_ruins", zoneId: "shattered_ruins",
bountyRunestones: 3,
}, },
{ {
id: "bone_colossus", id: "bone_colossus",
@@ -87,6 +91,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["frost_rune"], equipmentRewards: ["frost_rune"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shattered_ruins", zoneId: "shattered_ruins",
bountyRunestones: 5,
}, },
{ {
id: "elder_dragon", id: "elder_dragon",
@@ -104,6 +109,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["vorpal_sword", "dragon_scale"], equipmentRewards: ["vorpal_sword", "dragon_scale"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shattered_ruins", zoneId: "shattered_ruins",
bountyRunestones: 7,
}, },
// ── Shadow Marshes ──────────────────────────────────────────────────────── // ── Shadow Marshes ────────────────────────────────────────────────────────
{ {
@@ -122,6 +128,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shadow_marshes", zoneId: "shadow_marshes",
bountyRunestones: 5,
}, },
{ {
id: "plague_lord", id: "plague_lord",
@@ -139,6 +146,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["runestone_amulet"], equipmentRewards: ["runestone_amulet"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shadow_marshes", zoneId: "shadow_marshes",
bountyRunestones: 8,
}, },
{ {
id: "mud_kraken", id: "mud_kraken",
@@ -156,6 +164,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["crystal_shard"], equipmentRewards: ["crystal_shard"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "shadow_marshes", zoneId: "shadow_marshes",
bountyRunestones: 10,
}, },
// ── Frozen Peaks ────────────────────────────────────────────────────────── // ── Frozen Peaks ──────────────────────────────────────────────────────────
{ {
@@ -174,6 +183,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "frozen_peaks", zoneId: "frozen_peaks",
bountyRunestones: 8,
}, },
{ {
id: "ice_queen", id: "ice_queen",
@@ -191,6 +201,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["frost_crystal"], equipmentRewards: ["frost_crystal"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "frozen_peaks", zoneId: "frozen_peaks",
bountyRunestones: 12,
}, },
{ {
id: "void_titan", id: "void_titan",
@@ -208,6 +219,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["philosophers_stone"], equipmentRewards: ["philosophers_stone"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "frozen_peaks", zoneId: "frozen_peaks",
bountyRunestones: 15,
}, },
// ── Volcanic Depths ─────────────────────────────────────────────────────── // ── Volcanic Depths ───────────────────────────────────────────────────────
{ {
@@ -226,6 +238,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["flame_lance"], equipmentRewards: ["flame_lance"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "volcanic_depths", zoneId: "volcanic_depths",
bountyRunestones: 12,
}, },
{ {
id: "magma_titan", id: "magma_titan",
@@ -243,6 +256,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["volcanic_plate"], equipmentRewards: ["volcanic_plate"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "volcanic_depths", zoneId: "volcanic_depths",
bountyRunestones: 18,
}, },
{ {
id: "phoenix_lord", id: "phoenix_lord",
@@ -260,6 +274,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["eternal_flame"], equipmentRewards: ["eternal_flame"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "volcanic_depths", zoneId: "volcanic_depths",
bountyRunestones: 25,
}, },
// ── Astral Void (original) ──────────────────────────────────────────────── // ── Astral Void (original) ────────────────────────────────────────────────
{ {
@@ -278,6 +293,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["astral_robe"], equipmentRewards: ["astral_robe"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "astral_void", zoneId: "astral_void",
bountyRunestones: 20,
}, },
{ {
id: "cosmic_horror", id: "cosmic_horror",
@@ -295,6 +311,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["celestial_blade"], equipmentRewards: ["celestial_blade"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "astral_void", zoneId: "astral_void",
bountyRunestones: 30,
}, },
{ {
id: "the_devourer", id: "the_devourer",
@@ -312,6 +329,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["infinity_gem"], equipmentRewards: ["infinity_gem"],
prestigeRequirement: 0, prestigeRequirement: 0,
zoneId: "astral_void", zoneId: "astral_void",
bountyRunestones: 40,
}, },
// ── Celestial Reaches ───────────────────────────────────────────────────── // ── Celestial Reaches ─────────────────────────────────────────────────────
{ {
@@ -330,6 +348,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["seraph_wing"], equipmentRewards: ["seraph_wing"],
prestigeRequirement: 6, prestigeRequirement: 6,
zoneId: "celestial_reaches", zoneId: "celestial_reaches",
bountyRunestones: 30,
}, },
{ {
id: "fallen_archangel", id: "fallen_archangel",
@@ -347,6 +366,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["angels_halo"], equipmentRewards: ["angels_halo"],
prestigeRequirement: 7, prestigeRequirement: 7,
zoneId: "celestial_reaches", zoneId: "celestial_reaches",
bountyRunestones: 40,
}, },
{ {
id: "divine_judge", id: "divine_judge",
@@ -364,6 +384,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 8, prestigeRequirement: 8,
zoneId: "celestial_reaches", zoneId: "celestial_reaches",
bountyRunestones: 50,
}, },
{ {
id: "celestial_titan", id: "celestial_titan",
@@ -381,6 +402,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["celestial_armour"], equipmentRewards: ["celestial_armour"],
prestigeRequirement: 9, prestigeRequirement: 9,
zoneId: "celestial_reaches", zoneId: "celestial_reaches",
bountyRunestones: 60,
}, },
{ {
id: "the_first_light", id: "the_first_light",
@@ -398,6 +420,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["divine_edge", "heaven_mantle"], equipmentRewards: ["divine_edge", "heaven_mantle"],
prestigeRequirement: 10, prestigeRequirement: 10,
zoneId: "celestial_reaches", zoneId: "celestial_reaches",
bountyRunestones: 75,
}, },
// ── Abyssal Trench ──────────────────────────────────────────────────────── // ── Abyssal Trench ────────────────────────────────────────────────────────
{ {
@@ -416,6 +439,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["depth_blade"], equipmentRewards: ["depth_blade"],
prestigeRequirement: 9, prestigeRequirement: 9,
zoneId: "abyssal_trench", zoneId: "abyssal_trench",
bountyRunestones: 40,
}, },
{ {
id: "kraken_elder", id: "kraken_elder",
@@ -433,6 +457,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["leviathan_eye"], equipmentRewards: ["leviathan_eye"],
prestigeRequirement: 10, prestigeRequirement: 10,
zoneId: "abyssal_trench", zoneId: "abyssal_trench",
bountyRunestones: 55,
}, },
{ {
id: "abyssal_colossus", id: "abyssal_colossus",
@@ -450,6 +475,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["pressure_plate"], equipmentRewards: ["pressure_plate"],
prestigeRequirement: 11, prestigeRequirement: 11,
zoneId: "abyssal_trench", zoneId: "abyssal_trench",
bountyRunestones: 70,
}, },
{ {
id: "the_deep_one", id: "the_deep_one",
@@ -467,6 +493,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 12, prestigeRequirement: 12,
zoneId: "abyssal_trench", zoneId: "abyssal_trench",
bountyRunestones: 85,
}, },
{ {
id: "elder_abomination", id: "elder_abomination",
@@ -484,6 +511,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["abyssal_edge", "abyss_shroud"], equipmentRewards: ["abyssal_edge", "abyss_shroud"],
prestigeRequirement: 13, prestigeRequirement: 13,
zoneId: "abyssal_trench", zoneId: "abyssal_trench",
bountyRunestones: 100,
}, },
// ── Infernal Court ──────────────────────────────────────────────────────── // ── Infernal Court ────────────────────────────────────────────────────────
{ {
@@ -502,6 +530,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["demon_hide"], equipmentRewards: ["demon_hide"],
prestigeRequirement: 12, prestigeRequirement: 12,
zoneId: "infernal_court", zoneId: "infernal_court",
bountyRunestones: 55,
}, },
{ {
id: "hellfire_titan", id: "hellfire_titan",
@@ -519,6 +548,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["hellfire_edge"], equipmentRewards: ["hellfire_edge"],
prestigeRequirement: 13, prestigeRequirement: 13,
zoneId: "infernal_court", zoneId: "infernal_court",
bountyRunestones: 70,
}, },
{ {
id: "lord_of_sin", id: "lord_of_sin",
@@ -536,6 +566,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["soul_gem"], equipmentRewards: ["soul_gem"],
prestigeRequirement: 14, prestigeRequirement: 14,
zoneId: "infernal_court", zoneId: "infernal_court",
bountyRunestones: 90,
}, },
{ {
id: "infernal_sovereign", id: "infernal_sovereign",
@@ -553,6 +584,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 15, prestigeRequirement: 15,
zoneId: "infernal_court", zoneId: "infernal_court",
bountyRunestones: 110,
}, },
{ {
id: "the_fallen", id: "the_fallen",
@@ -570,6 +602,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["infernal_edge", "sinslayer_aegis"], equipmentRewards: ["infernal_edge", "sinslayer_aegis"],
prestigeRequirement: 16, prestigeRequirement: 16,
zoneId: "infernal_court", zoneId: "infernal_court",
bountyRunestones: 135,
}, },
// ── Crystalline Spire ───────────────────────────────────────────────────── // ── Crystalline Spire ─────────────────────────────────────────────────────
{ {
@@ -588,6 +621,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["prism_blade"], equipmentRewards: ["prism_blade"],
prestigeRequirement: 15, prestigeRequirement: 15,
zoneId: "crystalline_spire", zoneId: "crystalline_spire",
bountyRunestones: 70,
}, },
{ {
id: "crystal_drake", id: "crystal_drake",
@@ -605,6 +639,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 16, prestigeRequirement: 16,
zoneId: "crystalline_spire", zoneId: "crystalline_spire",
bountyRunestones: 90,
}, },
{ {
id: "the_faceted", id: "the_faceted",
@@ -622,6 +657,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["faceted_armour"], equipmentRewards: ["faceted_armour"],
prestigeRequirement: 17, prestigeRequirement: 17,
zoneId: "crystalline_spire", zoneId: "crystalline_spire",
bountyRunestones: 115,
}, },
{ {
id: "diamond_colossus", id: "diamond_colossus",
@@ -639,6 +675,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["prism_eye"], equipmentRewards: ["prism_eye"],
prestigeRequirement: 18, prestigeRequirement: 18,
zoneId: "crystalline_spire", zoneId: "crystalline_spire",
bountyRunestones: 140,
}, },
{ {
id: "crystal_sovereign", id: "crystal_sovereign",
@@ -656,6 +693,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["crystal_sovereign_blade", "diamond_plate"], equipmentRewards: ["crystal_sovereign_blade", "diamond_plate"],
prestigeRequirement: 19, prestigeRequirement: 19,
zoneId: "crystalline_spire", zoneId: "crystalline_spire",
bountyRunestones: 175,
}, },
// ── Void Sanctum ────────────────────────────────────────────────────────── // ── Void Sanctum ──────────────────────────────────────────────────────────
{ {
@@ -674,6 +712,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["void_annihilator"], equipmentRewards: ["void_annihilator"],
prestigeRequirement: 18, prestigeRequirement: 18,
zoneId: "void_sanctum", zoneId: "void_sanctum",
bountyRunestones: 90,
}, },
{ {
id: "eternal_shade", id: "eternal_shade",
@@ -691,6 +730,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["eternal_shroud"], equipmentRewards: ["eternal_shroud"],
prestigeRequirement: 19, prestigeRequirement: 19,
zoneId: "void_sanctum", zoneId: "void_sanctum",
bountyRunestones: 115,
}, },
{ {
id: "the_unmaker", id: "the_unmaker",
@@ -708,6 +748,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 20, prestigeRequirement: 20,
zoneId: "void_sanctum", zoneId: "void_sanctum",
bountyRunestones: 145,
}, },
{ {
id: "void_progenitor", id: "void_progenitor",
@@ -725,6 +766,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["void_heart_gem"], equipmentRewards: ["void_heart_gem"],
prestigeRequirement: 21, prestigeRequirement: 21,
zoneId: "void_sanctum", zoneId: "void_sanctum",
bountyRunestones: 180,
}, },
{ {
id: "void_emperor", id: "void_emperor",
@@ -742,6 +784,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["sanctum_breaker", "void_emperor_plate"], equipmentRewards: ["sanctum_breaker", "void_emperor_plate"],
prestigeRequirement: 22, prestigeRequirement: 22,
zoneId: "void_sanctum", zoneId: "void_sanctum",
bountyRunestones: 225,
}, },
// ── Eternal Throne ──────────────────────────────────────────────────────── // ── Eternal Throne ────────────────────────────────────────────────────────
{ {
@@ -760,6 +803,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["eternal_armour"], equipmentRewards: ["eternal_armour"],
prestigeRequirement: 21, prestigeRequirement: 21,
zoneId: "eternal_throne", zoneId: "eternal_throne",
bountyRunestones: 115,
}, },
{ {
id: "eternal_knight", id: "eternal_knight",
@@ -777,6 +821,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["throne_blade"], equipmentRewards: ["throne_blade"],
prestigeRequirement: 22, prestigeRequirement: 22,
zoneId: "eternal_throne", zoneId: "eternal_throne",
bountyRunestones: 150,
}, },
{ {
id: "the_undying", id: "the_undying",
@@ -794,6 +839,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 23, prestigeRequirement: 23,
zoneId: "eternal_throne", zoneId: "eternal_throne",
bountyRunestones: 190,
}, },
{ {
id: "apex_sovereign", id: "apex_sovereign",
@@ -811,6 +857,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 24, prestigeRequirement: 24,
zoneId: "eternal_throne", zoneId: "eternal_throne",
bountyRunestones: 235,
}, },
{ {
id: "the_apex", id: "the_apex",
@@ -828,6 +875,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["apex_sword", "apex_plate", "eternity_stone"], equipmentRewards: ["apex_sword", "apex_plate", "eternity_stone"],
prestigeRequirement: 25, prestigeRequirement: 25,
zoneId: "eternal_throne", zoneId: "eternal_throne",
bountyRunestones: 295,
}, },
// ── Primordial Chaos ────────────────────────────────────────────────────── // ── Primordial Chaos ──────────────────────────────────────────────────────
{ {
@@ -846,6 +894,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 26, prestigeRequirement: 26,
zoneId: "primordial_chaos", zoneId: "primordial_chaos",
bountyRunestones: 150,
}, },
{ {
id: "creation_engine", id: "creation_engine",
@@ -863,6 +912,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 27, prestigeRequirement: 27,
zoneId: "primordial_chaos", zoneId: "primordial_chaos",
bountyRunestones: 200,
}, },
{ {
id: "entropy_avatar", id: "entropy_avatar",
@@ -880,6 +930,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 29, prestigeRequirement: 29,
zoneId: "primordial_chaos", zoneId: "primordial_chaos",
bountyRunestones: 265,
}, },
{ {
id: "primordial_titan", id: "primordial_titan",
@@ -897,6 +948,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["chaos_mantle", "titan_core"], equipmentRewards: ["chaos_mantle", "titan_core"],
prestigeRequirement: 31, prestigeRequirement: 31,
zoneId: "primordial_chaos", zoneId: "primordial_chaos",
bountyRunestones: 350,
}, },
// ── Infinite Expanse ────────────────────────────────────────────────────── // ── Infinite Expanse ──────────────────────────────────────────────────────
{ {
@@ -915,6 +967,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 33, prestigeRequirement: 33,
zoneId: "infinite_expanse", zoneId: "infinite_expanse",
bountyRunestones: 200,
}, },
{ {
id: "horizon_beast", id: "horizon_beast",
@@ -932,6 +985,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 35, prestigeRequirement: 35,
zoneId: "infinite_expanse", zoneId: "infinite_expanse",
bountyRunestones: 265,
}, },
{ {
id: "infinity_construct", id: "infinity_construct",
@@ -949,6 +1003,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 37, prestigeRequirement: 37,
zoneId: "infinite_expanse", zoneId: "infinite_expanse",
bountyRunestones: 350,
}, },
{ {
id: "expanse_sovereign", id: "expanse_sovereign",
@@ -966,6 +1021,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["expanse_blade", "void_armour_mk2"], equipmentRewards: ["expanse_blade", "void_armour_mk2"],
prestigeRequirement: 39, prestigeRequirement: 39,
zoneId: "infinite_expanse", zoneId: "infinite_expanse",
bountyRunestones: 465,
}, },
// ── Reality Forge ───────────────────────────────────────────────────────── // ── Reality Forge ─────────────────────────────────────────────────────────
{ {
@@ -984,6 +1040,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 41, prestigeRequirement: 41,
zoneId: "reality_forge", zoneId: "reality_forge",
bountyRunestones: 265,
}, },
{ {
id: "reality_shaper", id: "reality_shaper",
@@ -1001,6 +1058,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 44, prestigeRequirement: 44,
zoneId: "reality_forge", zoneId: "reality_forge",
bountyRunestones: 350,
}, },
{ {
id: "creation_prime", id: "creation_prime",
@@ -1018,6 +1076,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 47, prestigeRequirement: 47,
zoneId: "reality_forge", zoneId: "reality_forge",
bountyRunestones: 465,
}, },
{ {
id: "reality_architect", id: "reality_architect",
@@ -1035,6 +1094,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["cosmos_blade", "reality_plate"], equipmentRewards: ["cosmos_blade", "reality_plate"],
prestigeRequirement: 49, prestigeRequirement: 49,
zoneId: "reality_forge", zoneId: "reality_forge",
bountyRunestones: 615,
}, },
// ── Cosmic Maelstrom ────────────────────────────────────────────────────── // ── Cosmic Maelstrom ──────────────────────────────────────────────────────
{ {
@@ -1053,6 +1113,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 51, prestigeRequirement: 51,
zoneId: "cosmic_maelstrom", zoneId: "cosmic_maelstrom",
bountyRunestones: 350,
}, },
{ {
id: "force_prime", id: "force_prime",
@@ -1070,6 +1131,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 54, prestigeRequirement: 54,
zoneId: "cosmic_maelstrom", zoneId: "cosmic_maelstrom",
bountyRunestones: 465,
}, },
{ {
id: "maelstrom_god", id: "maelstrom_god",
@@ -1087,6 +1149,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 57, prestigeRequirement: 57,
zoneId: "cosmic_maelstrom", zoneId: "cosmic_maelstrom",
bountyRunestones: 615,
}, },
{ {
id: "cosmic_annihilator", id: "cosmic_annihilator",
@@ -1104,6 +1167,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["maelstrom_edge", "cosmic_plate"], equipmentRewards: ["maelstrom_edge", "cosmic_plate"],
prestigeRequirement: 59, prestigeRequirement: 59,
zoneId: "cosmic_maelstrom", zoneId: "cosmic_maelstrom",
bountyRunestones: 815,
}, },
// ── Primeval Sanctum ────────────────────────────────────────────────────── // ── Primeval Sanctum ──────────────────────────────────────────────────────
{ {
@@ -1122,6 +1186,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 61, prestigeRequirement: 61,
zoneId: "primeval_sanctum", zoneId: "primeval_sanctum",
bountyRunestones: 465,
}, },
{ {
id: "time_elder", id: "time_elder",
@@ -1139,6 +1204,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 65, prestigeRequirement: 65,
zoneId: "primeval_sanctum", zoneId: "primeval_sanctum",
bountyRunestones: 615,
}, },
{ {
id: "origin_beast", id: "origin_beast",
@@ -1156,6 +1222,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 69, prestigeRequirement: 69,
zoneId: "primeval_sanctum", zoneId: "primeval_sanctum",
bountyRunestones: 815,
}, },
{ {
id: "primeval_god", id: "primeval_god",
@@ -1173,6 +1240,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["primeval_blade", "ancient_aegis"], equipmentRewards: ["primeval_blade", "ancient_aegis"],
prestigeRequirement: 74, prestigeRequirement: 74,
zoneId: "primeval_sanctum", zoneId: "primeval_sanctum",
bountyRunestones: 1080,
}, },
// ── The Absolute ────────────────────────────────────────────────────────── // ── The Absolute ──────────────────────────────────────────────────────────
{ {
@@ -1191,6 +1259,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 76, prestigeRequirement: 76,
zoneId: "the_absolute", zoneId: "the_absolute",
bountyRunestones: 615,
}, },
{ {
id: "void_convergence", id: "void_convergence",
@@ -1208,6 +1277,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 79, prestigeRequirement: 79,
zoneId: "the_absolute", zoneId: "the_absolute",
bountyRunestones: 815,
}, },
{ {
id: "eternal_end", id: "eternal_end",
@@ -1225,6 +1295,7 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: [], equipmentRewards: [],
prestigeRequirement: 83, prestigeRequirement: 83,
zoneId: "the_absolute", zoneId: "the_absolute",
bountyRunestones: 1080,
}, },
{ {
id: "the_absolute_one", id: "the_absolute_one",
@@ -1242,5 +1313,6 @@ export const DEFAULT_BOSSES: Boss[] = [
equipmentRewards: ["absolute_blade", "eternity_plate", "omniversal_core"], equipmentRewards: ["absolute_blade", "eternity_plate", "omniversal_core"],
prestigeRequirement: 90, prestigeRequirement: 90,
zoneId: "the_absolute", zoneId: "the_absolute",
bountyRunestones: 1430,
}, },
]; ];
+27
View File
@@ -21,6 +21,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 1.25 }, bonus: { combatMultiplier: 1.25 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "iron_vanguard",
}, },
{ {
id: "enchanted_blade", id: "enchanted_blade",
@@ -42,6 +43,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
owned: false, owned: false,
equipped: false, equipped: false,
cost: { gold: 0, essence: 500, crystals: 0 }, cost: { gold: 0, essence: 500, crystals: 0 },
setId: "shadow_infiltrator",
}, },
{ {
id: "flame_lance", id: "flame_lance",
@@ -52,6 +54,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 1.7 }, bonus: { combatMultiplier: 1.7 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "volcanic_forger",
}, },
{ {
id: "vorpal_sword", id: "vorpal_sword",
@@ -115,6 +118,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 1.25 }, bonus: { goldMultiplier: 1.25 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "iron_vanguard",
}, },
{ {
id: "hide_armour", id: "hide_armour",
@@ -146,6 +150,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
owned: false, owned: false,
equipped: false, equipped: false,
cost: { gold: 0, essence: 400, crystals: 0 }, cost: { gold: 0, essence: 400, crystals: 0 },
setId: "shadow_infiltrator",
}, },
{ {
id: "volcanic_plate", id: "volcanic_plate",
@@ -156,6 +161,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 1.65, combatMultiplier: 1.15 }, bonus: { goldMultiplier: 1.65, combatMultiplier: 1.15 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "volcanic_forger",
}, },
{ {
id: "dragon_scale", id: "dragon_scale",
@@ -208,6 +214,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 1.25 }, bonus: { clickMultiplier: 1.25 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "iron_vanguard",
}, },
{ {
id: "frost_rune", id: "frost_rune",
@@ -248,6 +255,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 1.55, goldMultiplier: 1.1 }, bonus: { clickMultiplier: 1.55, goldMultiplier: 1.1 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "volcanic_forger",
}, },
{ {
id: "void_compass", id: "void_compass",
@@ -259,6 +267,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
owned: false, owned: false,
equipped: false, equipped: false,
cost: { gold: 0, essence: 350, crystals: 0 }, cost: { gold: 0, essence: 350, crystals: 0 },
setId: "shadow_infiltrator",
}, },
{ {
id: "frost_crystal", id: "frost_crystal",
@@ -310,6 +319,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 3.5 }, bonus: { combatMultiplier: 3.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "celestial_guardian",
}, },
{ {
id: "angels_halo", id: "angels_halo",
@@ -320,6 +330,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 2.75, goldMultiplier: 1.3 }, bonus: { clickMultiplier: 2.75, goldMultiplier: 1.3 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "celestial_guardian",
}, },
{ {
id: "celestial_armour", id: "celestial_armour",
@@ -330,6 +341,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 2.75 }, bonus: { goldMultiplier: 2.75 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "celestial_guardian",
}, },
{ {
id: "divine_edge", id: "divine_edge",
@@ -361,6 +373,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 4.5 }, bonus: { combatMultiplier: 4.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "abyssal_predator",
}, },
{ {
id: "leviathan_eye", id: "leviathan_eye",
@@ -371,6 +384,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 3.0, goldMultiplier: 1.35 }, bonus: { clickMultiplier: 3.0, goldMultiplier: 1.35 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "abyssal_predator",
}, },
{ {
id: "pressure_plate", id: "pressure_plate",
@@ -381,6 +395,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 3.25 }, bonus: { goldMultiplier: 3.25 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "abyssal_predator",
}, },
{ {
id: "abyssal_edge", id: "abyssal_edge",
@@ -412,6 +427,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 3.75 }, bonus: { goldMultiplier: 3.75 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "infernal_conqueror",
}, },
{ {
id: "hellfire_edge", id: "hellfire_edge",
@@ -422,6 +438,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 5.5 }, bonus: { combatMultiplier: 5.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "infernal_conqueror",
}, },
{ {
id: "soul_gem", id: "soul_gem",
@@ -432,6 +449,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 3.25, goldMultiplier: 1.4 }, bonus: { clickMultiplier: 3.25, goldMultiplier: 1.4 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "infernal_conqueror",
}, },
{ {
id: "infernal_edge", id: "infernal_edge",
@@ -463,6 +481,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 6.5 }, bonus: { combatMultiplier: 6.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "crystal_domain",
}, },
{ {
id: "faceted_armour", id: "faceted_armour",
@@ -473,6 +492,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 4.5 }, bonus: { goldMultiplier: 4.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "crystal_domain",
}, },
{ {
id: "prism_eye", id: "prism_eye",
@@ -483,6 +503,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 3.5, goldMultiplier: 1.5 }, bonus: { clickMultiplier: 3.5, goldMultiplier: 1.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "crystal_domain",
}, },
{ {
id: "crystal_sovereign_blade", id: "crystal_sovereign_blade",
@@ -514,6 +535,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 8.0 }, bonus: { combatMultiplier: 8.0 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "void_emperor",
}, },
{ {
id: "eternal_shroud", id: "eternal_shroud",
@@ -524,6 +546,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 5.5 }, bonus: { goldMultiplier: 5.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "void_emperor",
}, },
{ {
id: "void_heart_gem", id: "void_heart_gem",
@@ -534,6 +557,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 4.0, goldMultiplier: 1.6 }, bonus: { clickMultiplier: 4.0, goldMultiplier: 1.6 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "void_emperor",
}, },
{ {
id: "sanctum_breaker", id: "sanctum_breaker",
@@ -565,6 +589,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { goldMultiplier: 7.0 }, bonus: { goldMultiplier: 7.0 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "eternal_throne",
}, },
{ {
id: "throne_blade", id: "throne_blade",
@@ -575,6 +600,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { combatMultiplier: 10.0 }, bonus: { combatMultiplier: 10.0 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "eternal_throne",
}, },
{ {
id: "apex_sword", id: "apex_sword",
@@ -605,6 +631,7 @@ export const DEFAULT_EQUIPMENT: Equipment[] = [
bonus: { clickMultiplier: 5.0, goldMultiplier: 2.0, combatMultiplier: 1.5 }, bonus: { clickMultiplier: 5.0, goldMultiplier: 2.0, combatMultiplier: 1.5 },
owned: false, owned: false,
equipped: false, equipped: false,
setId: "eternal_throne",
}, },
// ── Purchasable endgame sinks ───────────────────────────────────────────── // ── Purchasable endgame sinks ─────────────────────────────────────────────
{ {
+94
View File
@@ -0,0 +1,94 @@
import type { EquipmentSet } from "@elysium/types";
export const DEFAULT_EQUIPMENT_SETS: EquipmentSet[] = [
{
id: "iron_vanguard",
name: "Iron Vanguard",
description: "The armaments of a seasoned guild soldier — proven steel, reliable gold.",
pieces: ["iron_sword", "chainmail", "mages_focus"],
bonuses: {
2: { goldMultiplier: 1.1 },
3: { combatMultiplier: 1.1 },
},
},
{
id: "shadow_infiltrator",
name: "Shadow Infiltrator",
description: "Gear forged from the Shadow Marshes themselves — unseen, unstoppable.",
pieces: ["shadow_dagger", "void_shroud", "void_compass"],
bonuses: {
2: { goldMultiplier: 1.15 },
3: { clickMultiplier: 1.2 },
},
},
{
id: "volcanic_forger",
name: "Volcanic Forger",
description: "Weapons and armour tempered in the depths of the Volcanic Reaches.",
pieces: ["flame_lance", "volcanic_plate", "crystal_shard"],
bonuses: {
2: { combatMultiplier: 1.15 },
3: { goldMultiplier: 1.15 },
},
},
{
id: "celestial_guardian",
name: "Celestial Guardian",
description: "Relics of the Celestial Reaches — divine power made manifest.",
pieces: ["seraph_wing", "celestial_armour", "angels_halo"],
bonuses: {
2: { combatMultiplier: 1.2 },
3: { goldMultiplier: 1.2 },
},
},
{
id: "abyssal_predator",
name: "Abyssal Predator",
description: "Trophies reclaimed from the deepest trenches of the Abyssal Reaches.",
pieces: ["depth_blade", "pressure_plate", "leviathan_eye"],
bonuses: {
2: { goldMultiplier: 1.2 },
3: { clickMultiplier: 1.25 },
},
},
{
id: "infernal_conqueror",
name: "Infernal Conqueror",
description: "Forged in the heart of the Infernal Court from the essence of the defeated.",
pieces: ["hellfire_edge", "demon_hide", "soul_gem"],
bonuses: {
2: { combatMultiplier: 1.25 },
3: { goldMultiplier: 1.25 },
},
},
{
id: "crystal_domain",
name: "Crystal Domain",
description: "Instruments of the Crystalline Spire — reality refracted into absolute efficiency.",
pieces: ["prism_blade", "faceted_armour", "prism_eye"],
bonuses: {
2: { clickMultiplier: 1.25 },
3: { goldMultiplier: 1.25 },
},
},
{
id: "void_emperor",
name: "Void Emperor",
description: "The regalia of the Void Sanctum's lord — power carved from absolute nothingness.",
pieces: ["void_annihilator", "eternal_shroud", "void_heart_gem"],
bonuses: {
2: { goldMultiplier: 1.3 },
3: { combatMultiplier: 1.3 },
},
},
{
id: "eternal_throne",
name: "Eternal Throne",
description: "The armaments of the Eternal Throne — weapons and armour that have endured all of time.",
pieces: ["throne_blade", "eternal_armour", "eternity_stone"],
bonuses: {
2: { combatMultiplier: 1.35, goldMultiplier: 1.25 },
3: { clickMultiplier: 1.35 },
},
},
];
+15 -2
View File
@@ -1,10 +1,14 @@
import type { BossChallengeResponse, GameState } from "@elysium/types"; import type { BossChallengeResponse, GameState } from "@elysium/types";
import { computeSetBonuses } from "@elysium/types";
import { Hono } from "hono"; import { Hono } from "hono";
import type { HonoEnv } from "../types/hono.js";
import { prisma } from "../db/client.js"; import { prisma } from "../db/client.js";
import { DEFAULT_BOSSES } from "../data/bosses.js";
import { DEFAULT_EQUIPMENT_SETS } from "../data/equipmentSets.js";
import { authMiddleware } from "../middleware/auth.js"; import { authMiddleware } from "../middleware/auth.js";
import { updateChallengeProgress } from "../services/dailyChallenges.js"; import { updateChallengeProgress } from "../services/dailyChallenges.js";
export const bossRouter = new Hono(); export const bossRouter = new Hono<HonoEnv>();
bossRouter.use("*", authMiddleware); bossRouter.use("*", authMiddleware);
@@ -25,6 +29,9 @@ const calculatePartyStats = (
.filter((e) => e.equipped && e.bonus.combatMultiplier != null) .filter((e) => e.equipped && e.bonus.combatMultiplier != null)
.reduce((mult, e) => mult * (e.bonus.combatMultiplier ?? 1), 1); .reduce((mult, e) => mult * (e.bonus.combatMultiplier ?? 1), 1);
const equippedItemIds = (state.equipment ?? []).filter((e) => e.equipped).map((e) => e.id);
const setCombatMultiplier = computeSetBonuses(equippedItemIds, DEFAULT_EQUIPMENT_SETS).combatMultiplier;
let partyDPS = 0; let partyDPS = 0;
let partyMaxHp = 0; let partyMaxHp = 0;
@@ -52,7 +59,7 @@ const calculatePartyStats = (
partyMaxHp += adventurer.level * 50 * adventurer.count; partyMaxHp += adventurer.level * 50 * adventurer.count;
} }
partyDPS *= equipmentCombatMultiplier; partyDPS *= equipmentCombatMultiplier * setCombatMultiplier;
return { partyDPS, partyMaxHp }; return { partyDPS, partyMaxHp };
}; };
@@ -185,12 +192,18 @@ bossRouter.post("/challenge", async (context) => {
state.resources.crystals += crystalsAwarded; state.resources.crystals += crystalsAwarded;
} }
// First-kill bounty — look up authoritative bounty from static data
const staticBoss = DEFAULT_BOSSES.find((b) => b.id === body.bossId);
const bountyRunestones = staticBoss?.bountyRunestones ?? 0;
state.prestige.runestones += bountyRunestones;
rewards = { rewards = {
gold: boss.goldReward, gold: boss.goldReward,
essence: boss.essenceReward, essence: boss.essenceReward,
crystals: boss.crystalReward, crystals: boss.crystalReward,
upgradeIds: boss.upgradeRewards, upgradeIds: boss.upgradeRewards,
equipmentIds: equipmentRewards, equipmentIds: equipmentRewards,
bountyRunestones,
}; };
} else { } else {
bossHpAtBattleEnd = Math.max( bossHpAtBattleEnd = Math.max(
+16 -7
View File
@@ -1,11 +1,14 @@
import type { GameState, SaveRequest } from "@elysium/types"; import type { GameState, SaveRequest } from "@elysium/types";
import { computeSetBonuses } from "@elysium/types";
import { createHmac } from "node:crypto"; import { createHmac } from "node:crypto";
import { Hono } from "hono"; import { Hono } from "hono";
import type { HonoEnv } from "../types/hono.js";
import { prisma } from "../db/client.js"; import { prisma } from "../db/client.js";
import { DEFAULT_ACHIEVEMENTS } from "../data/achievements.js"; import { DEFAULT_ACHIEVEMENTS } from "../data/achievements.js";
import { DEFAULT_ADVENTURERS } from "../data/adventurers.js"; import { DEFAULT_ADVENTURERS } from "../data/adventurers.js";
import { DEFAULT_BOSSES } from "../data/bosses.js"; import { DEFAULT_BOSSES } from "../data/bosses.js";
import { DEFAULT_EQUIPMENT } from "../data/equipment.js"; import { DEFAULT_EQUIPMENT } from "../data/equipment.js";
import { DEFAULT_EQUIPMENT_SETS } from "../data/equipmentSets.js";
import { DEFAULT_QUESTS } from "../data/quests.js"; import { DEFAULT_QUESTS } from "../data/quests.js";
import { authMiddleware } from "../middleware/auth.js"; import { authMiddleware } from "../middleware/auth.js";
import { getOrResetDailyChallenges } from "../services/dailyChallenges.js"; import { getOrResetDailyChallenges } from "../services/dailyChallenges.js";
@@ -44,6 +47,8 @@ const computeMaxPassiveIncome = (
(mult, e) => mult * (e.bonus.goldMultiplier ?? 1), (mult, e) => mult * (e.bonus.goldMultiplier ?? 1),
1, 1,
); );
const equippedItemIds = equippedItems.map((e) => e.id);
const setGoldMultiplier = computeSetBonuses(equippedItemIds, DEFAULT_EQUIPMENT_SETS).goldMultiplier;
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1; const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1; const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
@@ -70,7 +75,8 @@ const computeMaxPassiveIncome = (
upgradeMultiplier * upgradeMultiplier *
prestige * prestige *
runestonesIncome * runestonesIncome *
equipmentGoldMultiplier; equipmentGoldMultiplier *
setGoldMultiplier;
essencePerSecond += essencePerSecond +=
adventurer.essencePerSecond * adventurer.essencePerSecond *
@@ -93,9 +99,11 @@ const computeMaxClickGoldPerSecond = (state: GameState): number => {
.filter((u) => u.purchased && u.target === "click") .filter((u) => u.purchased && u.target === "click")
.reduce((mult, u) => mult * u.multiplier, 1); .reduce((mult, u) => mult * u.multiplier, 1);
const equipmentClickMultiplier = (state.equipment ?? []) const equippedItems = (state.equipment ?? []).filter((e) => e.equipped);
.filter((e) => e.equipped && e.bonus.clickMultiplier != null) const equipmentClickMultiplier = equippedItems
.filter((e) => e.bonus.clickMultiplier != null)
.reduce((mult, e) => mult * (e.bonus.clickMultiplier ?? 1), 1); .reduce((mult, e) => mult * (e.bonus.clickMultiplier ?? 1), 1);
const setClickMultiplier = computeSetBonuses(equippedItems.map((e) => e.id), DEFAULT_EQUIPMENT_SETS).clickMultiplier;
const runestonesClick = state.prestige.runestonesClickMultiplier ?? 1; const runestonesClick = state.prestige.runestonesClickMultiplier ?? 1;
@@ -104,7 +112,8 @@ const computeMaxClickGoldPerSecond = (state: GameState): number => {
clickMultiplier * clickMultiplier *
state.prestige.productionMultiplier * state.prestige.productionMultiplier *
runestonesClick * runestonesClick *
equipmentClickMultiplier; equipmentClickMultiplier *
setClickMultiplier;
return clickPower * CLICK_BUFFER_CPS; return clickPower * CLICK_BUFFER_CPS;
}; };
@@ -284,7 +293,7 @@ const validateAndSanitize = (incoming: GameState, previous: GameState): GameStat
return { ...incoming, resources, bosses, quests, achievements, prestige }; return { ...incoming, resources, bosses, quests, achievements, prestige };
}; };
export const gameRouter = new Hono(); export const gameRouter = new Hono<HonoEnv>();
gameRouter.use("*", authMiddleware); gameRouter.use("*", authMiddleware);
@@ -610,8 +619,8 @@ gameRouter.post("/save", async (context) => {
await prisma.gameState.upsert({ await prisma.gameState.upsert({
where: { discordId }, where: { discordId },
create: { discordId, state: stateToSave, updatedAt: now }, create: { discordId, state: stateToSave as unknown as never, updatedAt: now },
update: { state: stateToSave, updatedAt: now }, update: { state: stateToSave as unknown as never, updatedAt: now },
}); });
const signature = secret ? computeHmac(JSON.stringify(stateToSave), secret) : undefined; const signature = secret ? computeHmac(JSON.stringify(stateToSave), secret) : undefined;
+2 -2
View File
@@ -28,8 +28,8 @@ const HOW_TO_PLAY = [
body: "New zones unlock when you defeat the final boss AND complete the final quest of the previous zone. Each zone contains new bosses and quests with progressively greater rewards.", body: "New zones unlock when you defeat the final boss AND complete the final quest of the previous zone. Each zone contains new bosses and quests with progressively greater rewards.",
}, },
{ {
title: "🗡️ Equipment", title: "🗡️ Equipment & Sets",
body: "Earn equipment from boss drops and quest rewards. Each piece of equipment provides bonuses to gold income, click power, or adventurer output. Rarer equipment provides stronger bonuses.", body: "Earn equipment from boss drops and quest rewards. Each piece provides bonuses to gold income, click power, or combat. Rarer equipment provides stronger bonuses. Equip matching set pieces (2 or 3 of a named set) to unlock escalating set bonuses shown at the top of the Equipment panel.",
}, },
{ {
title: "⭐ Prestige", title: "⭐ Prestige",
@@ -133,6 +133,9 @@ export const BattleModal = ({
{result.rewards.crystals > 0 && ( {result.rewards.crystals > 0 && (
<span>💎 {formatNumber(result.rewards.crystals)} crystals</span> <span>💎 {formatNumber(result.rewards.crystals)} crystals</span>
)} )}
{result.rewards.bountyRunestones > 0 && (
<span className="battle-bounty">🔮 {formatNumber(result.rewards.bountyRunestones)} runestones (first kill!)</span>
)}
</div> </div>
)} )}
</> </>
@@ -70,6 +70,9 @@ const BossCard = ({
{(boss.equipmentRewards ?? []).length > 0 && ( {(boss.equipmentRewards ?? []).length > 0 && (
<span>🗡 {boss.equipmentRewards.length} Equipment</span> <span>🗡 {boss.equipmentRewards.length} Equipment</span>
)} )}
{boss.status !== "defeated" && boss.bountyRunestones > 0 && (
<span className="boss-bounty">🔮 {boss.bountyRunestones} (first kill)</span>
)}
</div> </div>
{(boss.status === "available" || boss.status === "in_progress") && ( {(boss.status === "available" || boss.status === "in_progress") && (
@@ -1,6 +1,7 @@
import type { Equipment, EquipmentType } from "@elysium/types"; import type { Equipment, EquipmentType } from "@elysium/types";
import { useState } from "react"; import { useState } from "react";
import { useGame } from "../../context/GameContext.js"; import { useGame } from "../../context/GameContext.js";
import { EQUIPMENT_SETS } from "../../data/equipmentSets.js";
import { LockToggle } from "../ui/LockToggle.js"; import { LockToggle } from "../ui/LockToggle.js";
const RARITY_LABEL: Record<string, string> = { const RARITY_LABEL: Record<string, string> = {
@@ -36,6 +37,7 @@ interface EquipmentCardProps {
essence: number; essence: number;
crystals: number; crystals: number;
dropBossName?: string | undefined; dropBossName?: string | undefined;
setName?: string | undefined;
} }
const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => { const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => {
@@ -46,7 +48,7 @@ const costLabel = (cost: { gold: number; essence: number; crystals: number }): s
return parts.join(" "); return parts.join(" ");
}; };
const EquipmentCard = ({ item, gold, essence, crystals, dropBossName }: EquipmentCardProps): React.JSX.Element => { const EquipmentCard = ({ item, gold, essence, crystals, dropBossName, setName }: EquipmentCardProps): React.JSX.Element => {
const { equipItem, buyEquipment } = useGame(); const { equipItem, buyEquipment } = useGame();
const canAfford = item.cost const canAfford = item.cost
@@ -63,6 +65,7 @@ const EquipmentCard = ({ item, gold, essence, crystals, dropBossName }: Equipmen
</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>
{setName && <span className="equipment-set-badge">🔗 {setName}</span>}
{!item.owned && item.cost && ( {!item.owned && item.cost && (
<p className="equipment-cost">{costLabel(item.cost)}</p> <p className="equipment-cost">{costLabel(item.cost)}</p>
)} )}
@@ -121,6 +124,31 @@ export const EquipmentPanel = (): React.JSX.Element => {
} }
} }
// Build set name lookup for card badges
const setNameById = new Map<string, string>(
EQUIPMENT_SETS.map((s) => [s.id, s.name]),
);
// Compute active set bonuses for the summary strip
const equippedItemIds = equipment.filter((e) => e.equipped).map((e) => e.id);
const activeSets = EQUIPMENT_SETS.map((set) => {
const count = set.pieces.filter((id) => equippedItemIds.includes(id)).length;
return { set, count };
}).filter(({ count }) => count >= 2);
const setBonusDescription = (set: typeof EQUIPMENT_SETS[number], count: number): string => {
const parts: string[] = [];
for (const threshold of [2, 3] as const) {
if (count >= threshold) {
const bonus = set.bonuses[threshold];
if (bonus.goldMultiplier) parts.push(`+${Math.round((bonus.goldMultiplier - 1) * 100)}% Gold/s (${threshold}pc)`);
if (bonus.combatMultiplier) parts.push(`+${Math.round((bonus.combatMultiplier - 1) * 100)}% Combat (${threshold}pc)`);
if (bonus.clickMultiplier) parts.push(`+${Math.round((bonus.clickMultiplier - 1) * 100)}% Click (${threshold}pc)`);
}
}
return parts.join(", ");
};
return ( return (
<section className="panel equipment-panel"> <section className="panel equipment-panel">
<div className="panel-header"> <div className="panel-header">
@@ -132,9 +160,21 @@ export const EquipmentPanel = (): React.JSX.Element => {
/> />
</div> </div>
<p className="equipment-intro"> <p className="equipment-intro">
Equipment drops from bosses and grants passive bonuses. Only one item per slot can be equipped at a time. Equipment drops from bosses and grants passive bonuses. Only one item per slot can be equipped at a time. Equip matching set pieces for bonus effects!
</p> </p>
{activeSets.length > 0 && (
<div className="active-sets">
<h3 className="active-sets-heading"> Active Set Bonuses</h3>
{activeSets.map(({ set, count }) => (
<div key={set.id} className="active-set-row">
<span className="active-set-name">{set.name} ({count}/{set.pieces.length})</span>
<span className="active-set-bonus">{setBonusDescription(set, count)}</span>
</div>
))}
</div>
)}
{SLOT_ORDER.map((slotType) => { {SLOT_ORDER.map((slotType) => {
const items = equipment.filter( const items = equipment.filter(
(e) => e.type === slotType && (showLocked || e.owned), (e) => e.type === slotType && (showLocked || e.owned),
@@ -151,6 +191,7 @@ export const EquipmentPanel = (): React.JSX.Element => {
essence={state.resources.essence} essence={state.resources.essence}
crystals={state.resources.crystals} crystals={state.resources.crystals}
dropBossName={equipmentDropSources.get(item.id)} dropBossName={equipmentDropSources.get(item.id)}
setName={item.setId ? setNameById.get(item.setId) : undefined}
/> />
))} ))}
{items.length === 0 && ( {items.length === 0 && (
+94
View File
@@ -0,0 +1,94 @@
import type { EquipmentSet } from "@elysium/types";
export const EQUIPMENT_SETS: EquipmentSet[] = [
{
id: "iron_vanguard",
name: "Iron Vanguard",
description: "The armaments of a seasoned guild soldier — proven steel, reliable gold.",
pieces: ["iron_sword", "chainmail", "mages_focus"],
bonuses: {
2: { goldMultiplier: 1.1 },
3: { combatMultiplier: 1.1 },
},
},
{
id: "shadow_infiltrator",
name: "Shadow Infiltrator",
description: "Gear forged from the Shadow Marshes themselves — unseen, unstoppable.",
pieces: ["shadow_dagger", "void_shroud", "void_compass"],
bonuses: {
2: { goldMultiplier: 1.15 },
3: { clickMultiplier: 1.2 },
},
},
{
id: "volcanic_forger",
name: "Volcanic Forger",
description: "Weapons and armour tempered in the depths of the Volcanic Reaches.",
pieces: ["flame_lance", "volcanic_plate", "crystal_shard"],
bonuses: {
2: { combatMultiplier: 1.15 },
3: { goldMultiplier: 1.15 },
},
},
{
id: "celestial_guardian",
name: "Celestial Guardian",
description: "Relics of the Celestial Reaches — divine power made manifest.",
pieces: ["seraph_wing", "celestial_armour", "angels_halo"],
bonuses: {
2: { combatMultiplier: 1.2 },
3: { goldMultiplier: 1.2 },
},
},
{
id: "abyssal_predator",
name: "Abyssal Predator",
description: "Trophies reclaimed from the deepest trenches of the Abyssal Reaches.",
pieces: ["depth_blade", "pressure_plate", "leviathan_eye"],
bonuses: {
2: { goldMultiplier: 1.2 },
3: { clickMultiplier: 1.25 },
},
},
{
id: "infernal_conqueror",
name: "Infernal Conqueror",
description: "Forged in the heart of the Infernal Court from the essence of the defeated.",
pieces: ["hellfire_edge", "demon_hide", "soul_gem"],
bonuses: {
2: { combatMultiplier: 1.25 },
3: { goldMultiplier: 1.25 },
},
},
{
id: "crystal_domain",
name: "Crystal Domain",
description: "Instruments of the Crystalline Spire — reality refracted into absolute efficiency.",
pieces: ["prism_blade", "faceted_armour", "prism_eye"],
bonuses: {
2: { clickMultiplier: 1.25 },
3: { goldMultiplier: 1.25 },
},
},
{
id: "void_emperor",
name: "Void Emperor",
description: "The regalia of the Void Sanctum's lord — power carved from absolute nothingness.",
pieces: ["void_annihilator", "eternal_shroud", "void_heart_gem"],
bonuses: {
2: { goldMultiplier: 1.3 },
3: { combatMultiplier: 1.3 },
},
},
{
id: "eternal_throne",
name: "Eternal Throne",
description: "The armaments of the Eternal Throne — weapons and armour that have endured all of time.",
pieces: ["throne_blade", "eternal_armour", "eternity_stone"],
bonuses: {
2: { combatMultiplier: 1.35, goldMultiplier: 1.25 },
3: { clickMultiplier: 1.35 },
},
},
];
+11 -4
View File
@@ -1,4 +1,6 @@
import type { Achievement, Equipment, GameState } from "@elysium/types"; import type { Achievement, Equipment, GameState } from "@elysium/types";
import { computeSetBonuses } from "@elysium/types";
import { EQUIPMENT_SETS } from "../data/equipmentSets.js";
import { updateChallengeProgress } from "../utils/dailyChallenges.js"; import { updateChallengeProgress } from "../utils/dailyChallenges.js";
/** /**
@@ -57,6 +59,7 @@ export const applyTick = (state: GameState, deltaSeconds: number): GameState =>
(mult, e) => mult * (e.bonus.goldMultiplier ?? 1), (mult, e) => mult * (e.bonus.goldMultiplier ?? 1),
1, 1,
); );
const setGoldMultiplier = computeSetBonuses(equippedItems.map((e) => e.id), EQUIPMENT_SETS).goldMultiplier;
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1; const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1; const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
@@ -88,6 +91,7 @@ export const applyTick = (state: GameState, deltaSeconds: number): GameState =>
prestige * prestige *
runestonesIncome * runestonesIncome *
equipmentGoldMultiplier * equipmentGoldMultiplier *
setGoldMultiplier *
deltaSeconds; deltaSeconds;
essenceGained += essenceGained +=
@@ -228,7 +232,7 @@ export const applyTick = (state: GameState, deltaSeconds: number): GameState =>
essence: newEssence, essence: newEssence,
crystals: capResource(state.resources.crystals + questCrystals + challengeCrystals), crystals: capResource(state.resources.crystals + questCrystals + challengeCrystals),
}, },
dailyChallenges: updatedDailyChallenges, ...(updatedDailyChallenges !== undefined ? { dailyChallenges: updatedDailyChallenges } : {}),
player: { player: {
...state.player, ...state.player,
totalGoldEarned: newTotalGoldEarned, totalGoldEarned: newTotalGoldEarned,
@@ -271,9 +275,11 @@ export const calculateClickPower = (state: GameState): number => {
.filter((u) => u.purchased && u.target === "click") .filter((u) => u.purchased && u.target === "click")
.reduce((mult, upgrade) => mult * upgrade.multiplier, 1); .reduce((mult, upgrade) => mult * upgrade.multiplier, 1);
const equipmentClickMultiplier = (state.equipment ?? []) const equippedItems = (state.equipment ?? []).filter((e) => e.equipped);
.filter((e) => e.equipped && e.bonus.clickMultiplier != null) const equipmentClickMultiplier = equippedItems
.filter((e) => e.bonus.clickMultiplier != null)
.reduce((mult, e) => mult * (e.bonus.clickMultiplier ?? 1), 1); .reduce((mult, e) => mult * (e.bonus.clickMultiplier ?? 1), 1);
const setClickMultiplier = computeSetBonuses(equippedItems.map((e) => e.id), EQUIPMENT_SETS).clickMultiplier;
const runestonesClick = state.prestige.runestonesClickMultiplier ?? 1; const runestonesClick = state.prestige.runestonesClickMultiplier ?? 1;
@@ -282,6 +288,7 @@ export const calculateClickPower = (state: GameState): number => {
clickMultiplier * clickMultiplier *
state.prestige.productionMultiplier * state.prestige.productionMultiplier *
runestonesClick * runestonesClick *
equipmentClickMultiplier equipmentClickMultiplier *
setClickMultiplier
); );
}; };
+50
View File
@@ -498,6 +498,11 @@ body {
font-size: 0.9rem; font-size: 0.9rem;
} }
.boss-bounty {
color: #a78bfa;
font-weight: bold;
}
.attack-button { .attack-button {
align-self: flex-start; align-self: flex-start;
background: linear-gradient(135deg, #ef4444, #b91c1c); background: linear-gradient(135deg, #ef4444, #b91c1c);
@@ -862,6 +867,11 @@ body {
margin: 0.5rem 0; margin: 0.5rem 0;
} }
.battle-bounty {
color: #a78bfa;
font-weight: bold;
}
.dismiss-button { .dismiss-button {
background: var(--colour-accent); background: var(--colour-accent);
border: none; border: none;
@@ -945,6 +955,46 @@ body {
margin-bottom: 1.25rem; margin-bottom: 1.25rem;
} }
.active-sets {
background: rgba(138, 43, 226, 0.08);
border: 1px solid rgba(138, 43, 226, 0.3);
border-radius: 8px;
padding: 0.75rem 1rem;
margin-bottom: 1.25rem;
}
.active-sets-heading {
font-size: 0.9rem;
font-weight: 600;
color: #bf7fff;
margin: 0 0 0.5rem;
}
.active-set-row {
display: flex;
flex-wrap: wrap;
gap: 0.4rem 1rem;
align-items: baseline;
font-size: 0.82rem;
margin-bottom: 0.25rem;
}
.active-set-name {
font-weight: 600;
color: #d4a0ff;
}
.active-set-bonus {
color: var(--colour-text-muted);
}
.equipment-set-badge {
display: inline-block;
font-size: 0.75rem;
color: #bf7fff;
margin-top: 0.2rem;
}
.equipment-slot-section { .equipment-slot-section {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
+2
View File
@@ -35,6 +35,8 @@ export type {
EquipmentRarity, EquipmentRarity,
EquipmentType, EquipmentType,
} from "./interfaces/Equipment.js"; } from "./interfaces/Equipment.js";
export type { EquipmentSet, EquipmentSetBonus } from "./interfaces/EquipmentSet.js";
export { computeSetBonuses } from "./interfaces/EquipmentSet.js";
export type { GameState } from "./interfaces/GameState.js"; export type { GameState } from "./interfaces/GameState.js";
export type { Player } from "./interfaces/Player.js"; export type { Player } from "./interfaces/Player.js";
export type { PrestigeData } from "./interfaces/Prestige.js"; export type { PrestigeData } from "./interfaces/Prestige.js";
+2
View File
@@ -23,4 +23,6 @@ export interface Boss {
prestigeRequirement: number; prestigeRequirement: number;
/** Zone this boss belongs to */ /** Zone this boss belongs to */
zoneId: string; zoneId: string;
/** One-time runestone bounty awarded on first-ever defeat */
bountyRunestones: number;
} }
@@ -24,4 +24,6 @@ export interface Equipment {
equipped: boolean; equipped: boolean;
/** If set, this item can be purchased directly rather than obtained via boss drops */ /** If set, this item can be purchased directly rather than obtained via boss drops */
cost?: { gold: number; essence: number; crystals: number }; cost?: { gold: number; essence: number; crystals: number };
/** Equipment set this item belongs to, if any */
setId?: string;
} }
@@ -0,0 +1,44 @@
export interface EquipmentSetBonus {
goldMultiplier?: number;
combatMultiplier?: number;
clickMultiplier?: number;
}
export interface EquipmentSet {
id: string;
name: string;
description: string;
/** Equipment IDs that make up this set */
pieces: string[];
bonuses: {
2: EquipmentSetBonus;
3: EquipmentSetBonus;
};
}
/**
* Given a list of equipped item IDs and a set catalogue, returns the combined
* multiplicative bonuses granted by all active set bonuses.
*/
export const computeSetBonuses = (
equippedItemIds: string[],
sets: EquipmentSet[],
): { goldMultiplier: number; combatMultiplier: number; clickMultiplier: number } => {
let goldMultiplier = 1;
let combatMultiplier = 1;
let clickMultiplier = 1;
for (const set of sets) {
const count = set.pieces.filter((id) => equippedItemIds.includes(id)).length;
for (const threshold of [2, 3] as const) {
if (count >= threshold) {
const bonus = set.bonuses[threshold];
goldMultiplier *= bonus.goldMultiplier ?? 1;
combatMultiplier *= bonus.combatMultiplier ?? 1;
clickMultiplier *= bonus.clickMultiplier ?? 1;
}
}
}
return { goldMultiplier, combatMultiplier, clickMultiplier };
};
+1 -1
View File
@@ -5,5 +5,5 @@
"rootDir": ".", "rootDir": ".",
"declaration": true "declaration": true
}, },
"exclude": ["test/**/*.ts"] "exclude": ["test/**/*.ts", "prod/**"]
} }