import type { Equipment, EquipmentType } from "@elysium/types"; import { useState } from "react"; import { useGame } from "../../context/GameContext.js"; import { EQUIPMENT_SETS } from "../../data/equipmentSets.js"; import { LockToggle } from "../ui/LockToggle.js"; const RARITY_LABEL: Record = { common: "Common", rare: "Rare", epic: "Epic", legendary: "Legendary", }; const TYPE_ICON: Record = { weapon: "⚔️", armour: "🛡️", trinket: "💍", }; const bonusDescription = (item: Equipment): string => { const parts: string[] = []; if (item.bonus.combatMultiplier != null) { parts.push(`+${Math.round((item.bonus.combatMultiplier - 1) * 100)}% Combat`); } if (item.bonus.goldMultiplier != null) { parts.push(`+${Math.round((item.bonus.goldMultiplier - 1) * 100)}% Gold/s`); } if (item.bonus.clickMultiplier != null) { parts.push(`+${Math.round((item.bonus.clickMultiplier - 1) * 100)}% Click`); } return parts.join(", "); }; interface EquipmentCardProps { item: Equipment; gold: number; essence: number; crystals: number; dropBossName?: string | undefined; setName?: string | undefined; } const costLabel = (cost: { gold: number; essence: number; crystals: number }): string => { const parts: string[] = []; if (cost.gold > 0) parts.push(`🪙 ${cost.gold.toLocaleString()}`); if (cost.essence > 0) parts.push(`✨ ${cost.essence.toLocaleString()}`); if (cost.crystals > 0) parts.push(`💎 ${cost.crystals.toLocaleString()}`); return parts.join(" "); }; const EquipmentCard = ({ item, gold, essence, crystals, dropBossName, setName }: 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 (
{TYPE_ICON[item.type]}

{item.name}

{RARITY_LABEL[item.rarity]}

{item.description}

{bonusDescription(item)}

{setName && 🔗 {setName}} {!item.owned && item.cost && (

{costLabel(item.cost)}

)}
{!item.owned && !item.cost && ( {dropBossName ? `⚔️ Drop: ${dropBossName}` : "🔒 Boss drop"} )} {!item.owned && item.cost && ( )} {item.owned && item.equipped && ✓ Equipped} {item.owned && !item.equipped && ( )}
); }; const SLOT_ORDER: EquipmentType[] = ["weapon", "armour", "trinket"]; const SLOT_LABEL: Record = { weapon: "⚔️ Weapons", armour: "🛡️ Armour", trinket: "💍 Trinkets", }; export const EquipmentPanel = (): React.JSX.Element => { const { state } = useGame(); const [showLocked, setShowLocked] = useState(true); if (!state) return

Loading...

; const equipment = state.equipment ?? []; const unownedCount = equipment.filter((e) => !e.owned).length; const equipmentDropSources = new Map(); for (const boss of state.bosses) { for (const equipmentId of (boss.equipmentRewards ?? [])) { equipmentDropSources.set(equipmentId, boss.name); } } // Build set name lookup for card badges const setNameById = new Map( 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 (

Equipment

{ setShowLocked((v) => !v); }} />

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!

{activeSets.length > 0 && (

✨ Active Set Bonuses

{activeSets.map(({ set, count }) => (
{set.name} ({count}/{set.pieces.length}) {setBonusDescription(set, count)}
))}
)} {SLOT_ORDER.map((slotType) => { const items = equipment.filter( (e) => e.type === slotType && (showLocked || e.owned), ); return (

{SLOT_LABEL[slotType]}

{items.map((item) => ( ))} {items.length === 0 && (

No items to show in this slot.

)}
); })}
); };