/** * @file Boss panel component for viewing and challenging zone bosses. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* 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 -- Boss card requires many conditional render paths */ /* eslint-disable max-statements -- Boss panel requires many variable declarations */ /* eslint-disable max-lines -- Boss panel with sub-component and helper function */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { computePartyCombatPower } from "../../engine/tick.js"; import { cdnImage } from "../../utils/cdn.js"; import { LockToggle } from "../ui/lockToggle.js"; import { ZoneSelector } from "./zoneSelector.js"; import type { Boss } from "@elysium/types"; interface BossCardProperties { readonly boss: Boss; readonly prestigeCount: number; readonly onChallenge: (bossId: string)=> void; readonly isChallenging: boolean; readonly unlockHint: string | undefined; readonly formatInteger: (n: number)=> string; readonly formatNumber: (n: number)=> string; } /** * Renders a single boss card. * @param props - The boss card properties. * @param props.boss - The boss data. * @param props.prestigeCount - The current prestige count for lock checking. * @param props.onChallenge - Callback to challenge this boss. * @param props.isChallenging - Whether this boss is currently being challenged. * @param props.unlockHint - Optional hint for how to unlock this boss. * @param props.formatInteger - The integer formatting utility function. * @param props.formatNumber - The number formatting utility function. * @returns The JSX element. */ const BossCard = ({ boss, prestigeCount, onChallenge, isChallenging, unlockHint, formatInteger, formatNumber, }: BossCardProperties): JSX.Element => { const scaled = boss.currentHp * 100; const hpPercent = scaled / boss.maxHp; const isPrestigeLocked = boss.prestigeRequirement > prestigeCount; const canChallenge = (boss.status === "available" || boss.status === "in_progress") && !isChallenging; function handleChallenge(): void { onChallenge(boss.id); } return (
{boss.name}

{boss.name}

{boss.description}

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

{"๐Ÿ”’ Requires Prestige "} {boss.prestigeRequirement}

: null} {!isPrestigeLocked && boss.status === "locked" && unlockHint !== undefined ?

{unlockHint}

: null}
{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 && {"๐Ÿ’Ž "} {formatInteger(boss.crystalReward)} } {boss.equipmentRewards.length > 0 && {"๐Ÿ—ก๏ธ "} {boss.equipmentRewards.length} {" Equipment"} } {boss.status !== "defeated" && boss.bountyRunestones > 0 && boss.bountyRunestonesClaimed !== true && {"๐Ÿ”ฎ "} {boss.bountyRunestones} {" (first kill)"} }
{(boss.status === "available" || boss.status === "in_progress") && } {boss.status === "defeated" && {"โ˜ ๏ธ Defeated"} }
); }; /** * Renders the boss panel with zone selection and boss list. * @returns The JSX element. */ const BossPanel = (): JSX.Element => { const { state, challengeBoss, formatInteger, formatNumber, toggleAutoBoss, autoBossLastResult, autoBossError, bossError, } = useGame(); const [ challengingBossId, setChallengingBossId ] = useState( null, ); const [ activeZoneId, setActiveZoneId ] = useState(() => { return sessionStorage.getItem("elysium_boss_zone") ?? "verdant_vale"; }); const [ showLocked, setShowLocked ] = useState(true); if (state === null) { return (

{"Loading..."}

); } async function handleChallenge(bossId: string): Promise { setChallengingBossId(bossId); try { await challengeBoss(bossId); } finally { setChallengingBossId(null); } } function handleChallengeClick(bossId: string): void { void handleChallenge(bossId); } const { adventurers, autoBoss, bosses, prestige: playerPrestige, quests, zones, } = state; const activeZone = zones.find((zone) => { return zone.id === activeZoneId; }); const zoneIsLocked = activeZone?.status === "locked"; const unlockBoss = activeZone?.unlockBossId === null || activeZone?.unlockBossId === undefined ? undefined : bosses.find((boss) => { return boss.id === activeZone.unlockBossId; }); const unlockQuest = activeZone?.unlockQuestId === null || activeZone?.unlockQuestId === undefined ? undefined : quests.find((quest) => { return quest.id === activeZone.unlockQuestId; }); const zoneBosses = bosses.filter((boss) => { return boss.zoneId === activeZoneId; }); const lockedCount = zoneBosses.filter((boss) => { return boss.status === "locked"; }).length; const visibleBosses = showLocked ? zoneBosses : zoneBosses.filter((boss) => { return boss.status !== "locked"; }); const bossUnlockHints = new Map(); for (const zone of zones) { const { id: zoneId, unlockBossId, unlockQuestId } = zone; const allZoneBosses = bosses.filter((boss) => { return boss.zoneId === zoneId; }); for (let index = 0; index < allZoneBosses.length; index = index + 1) { const boss = allZoneBosses[index]; if (boss === undefined || boss.status !== "locked") { continue; } if (index === 0) { const parts: Array = []; if (unlockBossId !== null) { const gateBoss = bosses.find((candidate) => { return candidate.id === unlockBossId; }); if (gateBoss !== undefined) { parts.push(`โš”๏ธ Defeat: ${gateBoss.name}`); } } if (unlockQuestId !== null) { const gateQuest = quests.find((candidate) => { return candidate.id === unlockQuestId; }); if (gateQuest !== undefined) { parts.push(`๐Ÿ“œ Complete: ${gateQuest.name}`); } } if (parts.length > 0) { bossUnlockHints.set(boss.id, parts.join(" & ")); } } else { const previousBoss = allZoneBosses[index - 1]; if (previousBoss !== undefined) { bossUnlockHints.set(boss.id, `โš”๏ธ Defeat: ${previousBoss.name} first`); } } } } function handleZoneSelect(zoneId: string): void { setActiveZoneId(zoneId); sessionStorage.setItem("elysium_boss_zone", zoneId); } function handleToggle(): void { setShowLocked((current) => { return !current; }); } const autoBossOn = autoBoss === true; const partyDps = computePartyCombatPower(state); let partyHp = 0; for (const { level, count } of adventurers) { // eslint-disable-next-line stylistic/no-mixed-operators -- level * 50 * count is clear partyHp = partyHp + level * 50 * count; } const { count: prestigeCount } = playerPrestige; return (

{"Boss Encounters"}

{bossError === null ? null :

{"โš ๏ธ "} {bossError}

} {autoBossError === null ? null :

{"โš ๏ธ Auto-boss stopped: "} {autoBossError}

} {autoBossLastResult !== null && autoBossError === null ?

{"๐Ÿค– Last fight: "} {autoBossLastResult.bossName} {autoBossLastResult.won ? " โ€” โœ… Won" : " โ€” โŒ Lost"}

: null} {zoneIsLocked && (unlockBoss !== undefined || unlockQuest !== undefined) ?

{"๐Ÿ”’ This zone is locked. Unlock bosses by:"}

{unlockBoss === undefined ? null :

{"โš”๏ธ Defeat: "} {unlockBoss.name}

} {unlockQuest === undefined ? null :

{"๐Ÿ“œ Complete: "} {unlockQuest.name}

}
: null }
{"โš”๏ธ Party DPS"} {formatNumber(partyDps)}
{"โค๏ธ Party HP"} {formatNumber(partyHp)}
{visibleBosses.map((boss) => { const { id: bossId } = boss; return ( ); })} {visibleBosses.length === 0 &&

{"No bosses to show in this zone."}

}
); }; export { BossPanel };