/** * @file Companion panel component for managing active companions. * @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 companion card with conditional renders */ import { COMPANIONS, type Companion } from "@elysium/types"; import { useGame } from "../../context/gameContext.js"; import { cdnImage } from "../../utils/cdn.js"; import type { JSX } from "react"; const bonusLabels: Record = { bossDamage: "Boss Damage", clickGold: "Click Gold", essenceIncome: "Essence Income", passiveGold: "Passive Gold", questTime: "Quest Time", }; const unlockLabels: Record = { apotheosis: "apotheosis", lifetimeBosses: "lifetime bosses defeated", lifetimeGold: "lifetime gold earned", lifetimeQuests: "lifetime quests completed", prestige: "prestige(s)", transcendence: "transcendence(s)", }; /** * Formats a companion unlock threshold for display. * @param type - The unlock condition type. * @param threshold - The threshold value. * @returns The formatted threshold string. */ const formatThreshold = (type: string, threshold: number): string => { if (type === "lifetimeGold") { if (threshold >= 1e18) { return `${(threshold / 1e18).toFixed(0)}Qt`; } if (threshold >= 1e15) { return `${(threshold / 1e15).toFixed(0)}Q`; } if (threshold >= 1e12) { return `${(threshold / 1e12).toFixed(0)}T`; } if (threshold >= 1e9) { return `${(threshold / 1e9).toFixed(0)}B`; } if (threshold >= 1e6) { return `${(threshold / 1e6).toFixed(0)}M`; } if (threshold >= 1e3) { return `${(threshold / 1e3).toFixed(0)}K`; } } return threshold.toString(); }; interface CompanionCardProperties { readonly companion: Companion; readonly isUnlocked: boolean; readonly isActive: boolean; readonly onSelect: ()=> void; } /** * Renders a single companion card. * @param props - The companion card properties. * @param props.companion - The companion data. * @param props.isUnlocked - Whether this companion is unlocked. * @param props.isActive - Whether this companion is currently active. * @param props.onSelect - Callback when the companion is selected/deselected. * @returns The JSX element. */ const CompanionCard = ({ companion, isUnlocked, isActive, onSelect, }: CompanionCardProperties): JSX.Element => { const bonusSign = companion.bonus.type === "questTime" ? "-" : "+"; const bonusPercent = Math.round(companion.bonus.value * 100); const bonusLabel = bonusLabels[companion.bonus.type] ?? companion.bonus.type; return (
{companion.name}
{companion.name} {companion.title}
{isActive ? {"Active"} : null}

{companion.description}

{bonusLabel} {bonusSign} {bonusPercent} {"%"}
{isUnlocked ? :
{"🔒 Unlock: "} {formatThreshold( companion.unlock.type, companion.unlock.threshold, )}{" "} {unlockLabels[companion.unlock.type] ?? companion.unlock.type}
}
); }; /** * Renders the companion panel with all companions. * @returns The JSX element. */ const CompanionPanel = (): JSX.Element => { const { state, setActiveCompanion } = useGame(); if (state === null) { return (

{"Loading..."}

); } const unlockedIds = state.companions?.unlockedCompanionIds ?? []; const activeId = state.companions?.activeCompanionId ?? null; function handleSelect(companionId: string): void { setActiveCompanion(activeId === companionId ? null : companionId); } const activeCompanion = activeId === null ? undefined : COMPANIONS.find((companion) => { return companion.id === activeId; }); return (

{"👥 Companions"}

{"Companions provide powerful bonuses while active." + " You can only have one companion active at a time."} {activeId === null ? null : <> {" Currently active: "} {activeCompanion?.name ?? activeId} {"."} }

{COMPANIONS.map((companion) => { function handleCompanionSelect(): void { handleSelect(companion.id); } return ( ); })}
); }; export { CompanionPanel };