/** * @file Vampire Boss panel β€” challenge vampire realm bosses. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines -- Panel with sub-component, modal, and zone filter */ /* 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 -- Panel requires many variable declarations */ /* eslint-disable react/no-multi-comp -- Sub-components are tightly coupled to this panel */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import type { VampireBoss, VampireBossChallengeResponse, VampireZone, } from "@elysium/types"; interface VampireBossCardProperties { readonly boss: VampireBoss; readonly onChallenge: (bossId: string)=> void; readonly isChallenging: boolean; readonly unlockHint: string | undefined; readonly formatNumber: (n: number)=> string; readonly formatInteger: (n: number)=> string; } /** * Renders a single vampire boss card. * @param props - The boss card properties. * @param props.boss - The boss data. * @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.formatNumber - The number formatting utility function. * @param props.formatInteger - The integer formatting utility function. * @returns The JSX element. */ const VampireBossCard = ({ boss, onChallenge, isChallenging, unlockHint, formatNumber, formatInteger, }: VampireBossCardProperties): JSX.Element => { const canChallenge = (boss.status === "available" || boss.status === "in_progress") && !isChallenging; const hpRatio = boss.currentHp / boss.maxHp; const hpPercent = hpRatio * 100; function handleChallenge(): void { onChallenge(boss.id); } return (

{boss.name}

{boss.description}

{boss.status === "locked" && unlockHint !== undefined ?

{unlockHint}

: null} {boss.siringRequirement > 0 ?

{"🩸 Requires Siring "} {boss.siringRequirement}

: null}
{boss.status !== "locked" && boss.status !== "defeated" ?
{formatNumber(boss.currentHp)} {" / "} {formatNumber(boss.maxHp)} {" HP"}
: null}
{"πŸ’’ Boss DPS: "} {formatNumber(boss.damagePerSecond)}
{boss.bloodReward > 0 && {"🩸 "} {formatNumber(boss.bloodReward)} {" Blood"} } {boss.ichorReward > 0 && {"πŸ’§ "} {formatInteger(boss.ichorReward)} {" Ichor"} } {boss.soulShardsReward > 0 && {"πŸ’  "} {formatInteger(boss.soulShardsReward)} {" Soul Shards"} } {boss.equipmentRewards.length > 0 && {"πŸ¦‡ "} {boss.equipmentRewards.length} {" Equipment"} } {boss.status !== "defeated" && boss.bountyIchor > 0 && boss.bountyIchorClaimed !== true ? {"πŸ’§ "} {boss.bountyIchor} {" Ichor (first kill)"} : null}
{boss.status === "available" || boss.status === "in_progress" ? : null} {boss.status === "defeated" ? {"☠️ Defeated"} : null}
); }; interface VampireBattleModalProperties { readonly result: VampireBossChallengeResponse; readonly onDismiss: ()=> void; readonly formatNumber: (n: number)=> string; readonly formatInteger: (n: number)=> string; } /** * Renders the vampire battle result modal overlay. * @param props - The modal properties. * @param props.result - The battle result data. * @param props.onDismiss - Callback to dismiss the modal. * @param props.formatNumber - The number formatting utility function. * @param props.formatInteger - The integer formatting utility function. * @returns The JSX element. */ const VampireBattleModal = ({ result, onDismiss, formatNumber, formatInteger, }: VampireBattleModalProperties): JSX.Element => { return (

{result.won ? "🩸 Victory!" : "πŸ’€ Defeated!"}

{"βš”οΈ Your Thrall DPS"} {formatNumber(result.partyDPS)}
{"πŸ’’ Boss DPS"} {formatNumber(result.bossDPS)}
{"❀️ Boss HP Before"} {formatNumber(result.bossHpBefore)} {" / "} {formatNumber(result.bossMaxHp)}
{"❀️ Boss HP After"} {formatNumber(result.bossNewHp)}
{"πŸ›‘οΈ Thrall HP Remaining"} {formatNumber(result.partyHpRemaining)} {" / "} {formatNumber(result.partyMaxHp)}
{result.won && result.rewards !== undefined ?

{"Rewards"}

{result.rewards.blood > 0 ?

{"🩸 "} {formatNumber(result.rewards.blood)} {" Blood"}

: null} {result.rewards.ichor > 0 ?

{"πŸ’§ "} {formatInteger(result.rewards.ichor)} {" Ichor"}

: null} {result.rewards.soulShards > 0 ?

{"πŸ’  "} {formatInteger(result.rewards.soulShards)} {" Soul Shards"}

: null} {result.rewards.bountyIchor > 0 ?

{"πŸ’§ "} {formatInteger(result.rewards.bountyIchor)} {" Ichor (first kill bonus!)"}

: null} {result.rewards.upgradeIds.length > 0 ?

{"πŸ”“ "} {result.rewards.upgradeIds.length} {" Upgrade(s) unlocked"}

: null} {result.rewards.equipmentIds.length > 0 ?

{"πŸ¦‡ "} {result.rewards.equipmentIds.length} {" Equipment item(s) gained"}

: null}
: null} {result.casualties !== undefined && result.casualties.length > 0 ?

{"Casualties"}

{result.casualties.map((casualty) => { return (

{casualty.killed} {" "} {casualty.thrallId} {" lost"}

); })}
: null}
); }; /** * Renders the Vampire Boss panel with zone filtering and battle result modal. * @returns The JSX element. */ const VampireBossPanel = (): JSX.Element => { const { state, challengeVampireBoss, vampireBattleResult, dismissVampireBattle, formatNumber, formatInteger, vampirePreview, } = useGame(); const [ challengingBossId, setChallengingBossId ] = useState( null, ); const [ activeZoneId, setActiveZoneId ] = useState(() => { return sessionStorage.getItem("elysium_vampire_boss_zone"); }); if (state === null) { return (

{"Loading..."}

); } const vampire = state.vampire ?? vampirePreview; if (vampire === undefined) { return (

{"The Vampire expansion is not yet unlocked."}

); } const { bosses, quests, zones } = vampire; async function handleChallenge(bossId: string): Promise { setChallengingBossId(bossId); try { await challengeVampireBoss(bossId); } finally { setChallengingBossId(null); } } function handleChallengeClick(bossId: string): void { void handleChallenge(bossId); } function handleZoneSelect(zoneId: string): void { setActiveZoneId(zoneId); sessionStorage.setItem("elysium_vampire_boss_zone", zoneId); } function handleShowAll(): void { setActiveZoneId(null); sessionStorage.removeItem("elysium_vampire_boss_zone"); } const filteredBosses = activeZoneId === null ? bosses : bosses.filter((boss) => { return boss.zoneId === activeZoneId; }); const bossUnlockHints = new Map(); for (const zone of zones) { const { id: zoneId, unlockBossId, unlockQuestId } = zone; const zoneBosses = bosses.filter((boss) => { return boss.zoneId === zoneId; }); for (let index = 0; index < zoneBosses.length; index = index + 1) { const boss = zoneBosses[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 = zoneBosses[index - 1]; if (previousBoss !== undefined) { bossUnlockHints.set(boss.id, `🩸 Defeat: ${previousBoss.name} first`); } } } } const activeZoneData: VampireZone | undefined = activeZoneId === null ? undefined : zones.find((zone) => { return zone.id === activeZoneId; }); return (

{"🩸 Vampire Bosses"}

{zones.map((zone) => { function handleSelect(): void { handleZoneSelect(zone.id); } return ( ); })}
{activeZoneData?.status === "locked" ?

{"πŸ”’ This zone is locked."}

{activeZoneData.unlockBossId === null ? null :

{"🩸 Defeat: "} {bosses.find((boss) => { return boss.id === activeZoneData.unlockBossId; })?.name ?? activeZoneData.unlockBossId}

} {activeZoneData.unlockQuestId === null ? null :

{"πŸ“œ Complete: "} {quests.find((quest) => { return quest.id === activeZoneData.unlockQuestId; })?.name ?? activeZoneData.unlockQuestId}

}
: null}
{filteredBosses.map((boss) => { return ( ); })} {filteredBosses.length === 0 ?

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

: null}
{vampireBattleResult === null ? null : }
); }; export { VampireBossPanel };