/** * @file Goddess Boss panel β€” challenge divine 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 { GoddessBoss, GoddessBossChallengeResponse, GoddessZone, } from "@elysium/types"; interface GoddessBossCardProperties { readonly boss: GoddessBoss; 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 goddess 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 GoddessBossCard = ({ boss, onChallenge, isChallenging, unlockHint, formatNumber, formatInteger, }: GoddessBossCardProperties): 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.consecrationRequirement > 0 ?

{"πŸ•ŠοΈ Requires Consecration "} {boss.consecrationRequirement}

: null}
{boss.status !== "locked" && boss.status !== "defeated" ?
{formatNumber(boss.currentHp)} {" / "} {formatNumber(boss.maxHp)} {" HP"}
: null}
{"πŸ’’ Boss DPS: "} {formatNumber(boss.damagePerSecond)}
{boss.prayersReward > 0 && {"πŸ™ "} {formatNumber(boss.prayersReward)} } {boss.divinityReward > 0 && {"✨ "} {formatInteger(boss.divinityReward)} {" Divinity"} } {boss.stardustReward > 0 && {"⭐ "} {formatInteger(boss.stardustReward)} {" Stardust"} } {boss.equipmentRewards.length > 0 && {"πŸ—‘οΈ "} {boss.equipmentRewards.length} {" Equipment"} } {boss.status !== "defeated" && boss.bountyDivinity > 0 && boss.bountyDivinityClaimed !== true ? {"✨ "} {boss.bountyDivinity} {" Divinity (first kill)"} : null}
{boss.status === "available" || boss.status === "in_progress" ? : null} {boss.status === "defeated" ? {"☠️ Defeated"} : null}
); }; interface GoddessBattleModalProperties { readonly result: GoddessBossChallengeResponse; readonly onDismiss: ()=> void; readonly formatNumber: (n: number)=> string; readonly formatInteger: (n: number)=> string; } /** * Renders the goddess 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 GoddessBattleModal = ({ result, onDismiss, formatNumber, formatInteger, }: GoddessBattleModalProperties): JSX.Element => { return (

{result.won ? "βš”οΈ Victory!" : "πŸ’€ Defeated!"}

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

{"Rewards"}

{result.rewards.prayers > 0 ?

{"πŸ™ "} {formatNumber(result.rewards.prayers)} {" Prayers"}

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

{"✨ "} {formatInteger(result.rewards.divinity)} {" Divinity"}

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

{"⭐ "} {formatInteger(result.rewards.stardust)} {" Stardust"}

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

{"✨ "} {formatInteger(result.rewards.bountyDivinity)} {" Divinity (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.discipleId} {" lost"}

); })}
: null}
); }; /** * Renders the Goddess Boss panel with zone filtering and battle result modal. * @returns The JSX element. */ const GoddessBossPanel = (): JSX.Element => { const { state, challengeGoddessBoss, goddessBattleResult, dismissGoddessBattle, formatNumber, formatInteger, goddessPreview, } = useGame(); const [ challengingBossId, setChallengingBossId ] = useState( null, ); const [ activeZoneId, setActiveZoneId ] = useState(() => { return sessionStorage.getItem("elysium_goddess_boss_zone"); }); if (state === null) { return (

{"Loading..."}

); } const goddess = state.goddess ?? goddessPreview; if (goddess === undefined) { return (

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

); } const { bosses, quests, zones } = goddess; async function handleChallenge(bossId: string): Promise { setChallengingBossId(bossId); try { await challengeGoddessBoss(bossId); } finally { setChallengingBossId(null); } } function handleChallengeClick(bossId: string): void { void handleChallenge(bossId); } function handleZoneSelect(zoneId: string): void { setActiveZoneId(zoneId); sessionStorage.setItem("elysium_goddess_boss_zone", zoneId); } function handleShowAll(): void { setActiveZoneId(null); sessionStorage.removeItem("elysium_goddess_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: GoddessZone | undefined = activeZoneId === null ? undefined : zones.find((zone) => { return zone.id === activeZoneId; }); return (

{"βš”οΈ Goddess 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}
{goddessBattleResult === null ? null : }
); }; export { GoddessBossPanel };