import type { Boss } from "@elysium/types"; import { useState } from "react"; import { useGame } from "../../context/GameContext.js"; import { formatNumber } from "../../utils/format.js"; import { LockToggle } from "../ui/LockToggle.js"; import { ZoneSelector } from "./ZoneSelector.js"; interface BossCardProps { boss: Boss; prestigeCount: number; onChallenge: (bossId: string) => void; isChallenging: boolean; unlockHint?: string | undefined; } const BossCard = ({ boss, prestigeCount, onChallenge, isChallenging, unlockHint, }: BossCardProps): React.JSX.Element => { const hpPercent = (boss.currentHp / boss.maxHp) * 100; const isPrestigeLocked = boss.prestigeRequirement > prestigeCount; const canChallenge = (boss.status === "available" || boss.status === "in_progress") && !isChallenging; return (

{boss.name}

{boss.description}

{isPrestigeLocked && boss.status === "locked" && (

🔒 Requires Prestige {boss.prestigeRequirement}

)} {!isPrestigeLocked && boss.status === "locked" && unlockHint && (

{unlockHint}

)}
{boss.status !== "locked" && boss.status !== "defeated" && (
{formatNumber(boss.currentHp)} / {formatNumber(boss.maxHp)} HP
)}
💢 Boss DPS: {formatNumber(boss.damagePerSecond)}
🪙 {formatNumber(boss.goldReward)} {boss.essenceReward > 0 && ( ✨ {formatNumber(boss.essenceReward)} )} {boss.crystalReward > 0 && ( 💎 {formatNumber(boss.crystalReward)} )} {(boss.equipmentRewards ?? []).length > 0 && ( 🗡️ {boss.equipmentRewards.length} Equipment )}
{(boss.status === "available" || boss.status === "in_progress") && ( )} {boss.status === "defeated" && ( ☠️ Defeated )}
); }; export const BossPanel = (): React.JSX.Element => { const { state, challengeBoss } = useGame(); const [challengingBossId, setChallengingBossId] = useState(null); const [activeZoneId, setActiveZoneId] = useState("verdant_vale"); const [showLocked, setShowLocked] = useState(true); if (!state) return

Loading...

; // Calculate party combat stats including equipment multiplier let globalMultiplier = 1; for (const upgrade of state.upgrades) { if (upgrade.purchased && upgrade.target === "global") { globalMultiplier *= upgrade.multiplier; } } const prestigeMultiplier = 1 + state.prestige.count * 0.1; const equipmentCombatMultiplier = (state.equipment ?? []) .filter((e) => e.equipped && e.bonus.combatMultiplier != null) .reduce((mult, e) => mult * (e.bonus.combatMultiplier ?? 1), 1); let partyDPS = 0; let partyHP = 0; for (const adventurer of state.adventurers) { if (adventurer.count === 0) continue; let adventurerMultiplier = 1; for (const upgrade of state.upgrades) { if ( upgrade.purchased && upgrade.target === "adventurer" && upgrade.adventurerId === adventurer.id ) { adventurerMultiplier *= upgrade.multiplier; } } partyDPS += adventurer.combatPower * adventurer.count * adventurerMultiplier * globalMultiplier * prestigeMultiplier; partyHP += adventurer.level * 50 * adventurer.count; } partyDPS *= equipmentCombatMultiplier; const handleChallenge = async (bossId: string): Promise => { setChallengingBossId(bossId); try { await challengeBoss(bossId); } finally { setChallengingBossId(null); } }; const zones = state.zones ?? []; const zoneBosses = state.bosses.filter((b) => b.zoneId === activeZoneId); const lockedCount = zoneBosses.filter((b) => b.status === "locked").length; const visibleBosses = showLocked ? zoneBosses : zoneBosses.filter((b) => b.status !== "locked"); const bossUnlockHints = new Map(); for (const zone of zones) { const allZoneBosses = state.bosses.filter((b) => b.zoneId === zone.id); for (let i = 0; i < allZoneBosses.length; i++) { const boss = allZoneBosses[i]; if (!boss || boss.status !== "locked") continue; if (i === 0) { const parts: string[] = []; if (zone.unlockBossId) { const gateBoss = state.bosses.find((b) => b.id === zone.unlockBossId); if (gateBoss) parts.push(`⚔️ Defeat: ${gateBoss.name}`); } if (zone.unlockQuestId) { const gateQuest = state.quests.find((q) => q.id === zone.unlockQuestId); if (gateQuest) parts.push(`📜 Complete: ${gateQuest.name}`); } if (parts.length > 0) { bossUnlockHints.set(boss.id, parts.join(" & ")); } } else { const prevBoss = allZoneBosses[i - 1]; if (prevBoss) { bossUnlockHints.set(boss.id, `⚔️ Defeat: ${prevBoss.name} first`); } } } } return (

Boss Encounters

{ setShowLocked((v) => !v); }} />
⚔️ Party DPS {formatNumber(partyDPS)}
❤️ Party HP {formatNumber(partyHP)}
{visibleBosses.map((boss) => ( { void handleChallenge(id); }} /> ))} {visibleBosses.length === 0 && (

No bosses to show in this zone.

)}
); };