From ca2edb090e9cb6a79146cfe856341dba20e27388 Mon Sep 17 00:00:00 2001 From: Hikari Date: Thu, 19 Mar 2026 08:51:08 -0700 Subject: [PATCH] fix: correct equipment balance and sort items by stat power (#69) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Two improvements to the equipment system in one PR: ### Balance fixes (closes #54) Full equipment audit revealed 9 items with duplicated stats, regressions, or purchasable items weaker than free boss drops: | Item | Change | Reason | |---|---|---| | Void Conduit | 4x → 7x combat | 100M essence sink was equal to a zone-6 boss drop | | Void Edge | 2.75x → 3.25x combat | Purchasable was weaker than free Celestial Blade (3x) | | Astral Robe | 2.25x → 2.75x gold | Boss drop was weaker than purchasable Titan's Aegis (2.5x) | | Philosopher's Stone | 2x → 2.25x click | Duplicated Frost Crystal's click multiplier | | Eternal Flame | 1.15x → 1.25x gold | Gold regressed vs Philosopher's Stone (1.25x) | | Celestial Focus | 2.5x → 3x click | 20M essence sink was weaker than free Angel's Halo (2.75x click + 1.3x gold) | | Abyssal Tome | 3x → 3.75x gold | 50M essence sink was equal to free Heaven's Mantle (3x) | | Crystal Matrix | 4x → 4.75x gold | 20M crystal sink was equal to free Sinslayer Aegis (4x) | | Infernal Gem | 3.5x → 4x click | 5M crystal sink was identical to free Prism Eye | ### Equipment sorting (closes #55) Equipment cards within each slot now render in ascending order of combined bonus power — the sum of all multiplier bonuses — so stronger items always appear further down the list. Hybrid items such as Volcanic Plate sort correctly without needing a per-slot primary stat. ## Test plan - [ ] All purchasable weapons/armour/trinkets now exceed the stats of the highest free boss drop at their tier - [ ] No duplicate stat values between adjacent items in the same progression track - [ ] Equipment cards within each slot render weakest → strongest - [ ] Hybrid multi-stat items sort sensibly alongside single-stat items - [ ] Full pipeline green (lint + build + tests at 100% coverage) ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: https://git.nhcarrigan.com/nhcarrigan/elysium/pulls/69 Co-authored-by: Hikari Co-committed-by: Hikari --- apps/api/src/data/equipment.ts | 18 +++++++++--------- .../web/src/components/game/equipmentPanel.tsx | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/apps/api/src/data/equipment.ts b/apps/api/src/data/equipment.ts index 551035b..014ff27 100644 --- a/apps/api/src/data/equipment.ts +++ b/apps/api/src/data/equipment.ts @@ -101,7 +101,7 @@ export const defaultEquipment: Array = [ type: "weapon", }, { - bonus: { combatMultiplier: 2.75 }, + bonus: { combatMultiplier: 3.25 }, cost: { crystals: 500, essence: 2000, gold: 0 }, description: "A blade made of compressed nothingness. It does not cut — it simply unmakes.", @@ -204,7 +204,7 @@ export const defaultEquipment: Array = [ type: "armour", }, { - bonus: { goldMultiplier: 2.25 }, + bonus: { goldMultiplier: 2.75 }, description: "Woven from threads of pure starlight harvested by the Astral Wraith. Income flows like cosmic energy.", equipped: false, @@ -305,7 +305,7 @@ export const defaultEquipment: Array = [ type: "trinket", }, { - bonus: { clickMultiplier: 2, goldMultiplier: 1.25 }, + bonus: { clickMultiplier: 2.25, goldMultiplier: 1.25 }, description: "The legendary stone that grants mastery over gold and combat alike.", equipped: false, @@ -316,7 +316,7 @@ export const defaultEquipment: Array = [ type: "trinket", }, { - bonus: { clickMultiplier: 2.25, goldMultiplier: 1.15 }, + bonus: { clickMultiplier: 2.25, goldMultiplier: 1.25 }, description: "A flame that has never been extinguished, sealed in crystal by the Phoenix Lord. It burns with the power of rebirth.", equipped: false, @@ -697,7 +697,7 @@ export const defaultEquipment: Array = [ }, // ── Purchasable endgame sinks ───────────────────────────────────────────── { - bonus: { clickMultiplier: 2.5 }, + bonus: { clickMultiplier: 3 }, cost: { crystals: 0, essence: 20_000_000, gold: 0 }, description: "A lens of compressed celestial light that sharpens every strike with divine precision.", @@ -709,7 +709,7 @@ export const defaultEquipment: Array = [ type: "trinket", }, { - bonus: { goldMultiplier: 3 }, + bonus: { goldMultiplier: 3.75 }, cost: { crystals: 0, essence: 50_000_000, gold: 0 }, description: "A book written in the language of the deep — reading it aligns your guild's operations with abyssal efficiency.", @@ -721,7 +721,7 @@ export const defaultEquipment: Array = [ type: "armour", }, { - bonus: { combatMultiplier: 4 }, + bonus: { combatMultiplier: 7 }, cost: { crystals: 0, essence: 100_000_000, gold: 0 }, description: "A weapon that channels void energy — the absence of resistance makes every strike devastating.", @@ -733,7 +733,7 @@ export const defaultEquipment: Array = [ type: "weapon", }, { - bonus: { clickMultiplier: 3.5, goldMultiplier: 1.5 }, + bonus: { clickMultiplier: 4, goldMultiplier: 1.5 }, cost: { crystals: 5_000_000, essence: 0, gold: 0 }, description: "A gem forged in the heart of the Infernal Court — it burns with productivity and righteous fury in equal measure.", @@ -745,7 +745,7 @@ export const defaultEquipment: Array = [ type: "trinket", }, { - bonus: { goldMultiplier: 4 }, + bonus: { goldMultiplier: 4.75 }, cost: { crystals: 20_000_000, essence: 0, gold: 0 }, description: "Armour structured around a crystalline lattice of optimal income calculations. Every gold piece finds you faster.", diff --git a/apps/web/src/components/game/equipmentPanel.tsx b/apps/web/src/components/game/equipmentPanel.tsx index 376df27..004bd0e 100644 --- a/apps/web/src/components/game/equipmentPanel.tsx +++ b/apps/web/src/components/game/equipmentPanel.tsx @@ -7,6 +7,7 @@ /* eslint-disable react/no-multi-comp -- Sub-component is tightly coupled to the panel */ /* eslint-disable max-lines-per-function -- Complex component with many render paths */ /* eslint-disable complexity -- Complex component with many conditional render paths */ +/* eslint-disable max-lines -- Equipment panel with set bonus display and sort logic */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { EQUIPMENT_SETS } from "../../data/equipmentSets.js"; @@ -188,6 +189,20 @@ const EquipmentCard = ({ ); }; +/** + * Computes a combined power score for sorting — sum of all bonus multipliers. + * Using the sum (rather than a single stat) keeps hybrid items in sensible order. + * @param item - The equipment piece whose bonus multipliers are summed. + * @returns The combined bonus value. + */ +const equipmentPower = (item: Equipment): number => { + return ( + (item.bonus.combatMultiplier ?? 1) + + (item.bonus.goldMultiplier ?? 1) + + (item.bonus.clickMultiplier ?? 1) + ); +}; + const slotOrder: Array = [ "weapon", "armour", "trinket" ]; const slotLabel: Record = { armour: "🛡️ Armour", @@ -320,6 +335,8 @@ const EquipmentPanel = (): JSX.Element => { {slotOrder.map((slotType) => { const items = equipment.filter((item) => { return item.type === slotType && (showLocked || item.owned); + }).sort((a, b) => { + return equipmentPower(a) - equipmentPower(b); }); return (