/** * @file Thralls panel component for purchasing vampire thralls. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable max-lines-per-function -- Complex component with many render paths */ /* eslint-disable react/no-multi-comp -- ThrallCard sub-component is tightly coupled */ /* eslint-disable complexity -- ThrallCard has inherent branching for batch/afford logic */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import type { VampireThrall } from "@elysium/types"; type BatchSize = 1 | 10 | "max"; const batchOptions: Array = [ 1, 10, "max" ]; const growthRate = 1.15; /** * Computes the total blood cost to buy a batch of thralls. * @param thrall - The thrall tier to purchase. * @param quantity - The number to buy. * @returns The total blood cost. */ const computeBatchCost = ( thrall: VampireThrall, quantity: number, ): number => { let total = 0; for (let index = 0; index < quantity; index = index + 1) { const exponent = thrall.count + index; const cost = thrall.baseCost * Math.pow(growthRate, exponent); total = total + cost; } return total; }; /** * Computes the maximum number of thralls affordable with the available blood. * @param thrall - The thrall tier. * @param blood - The available blood balance. * @returns The maximum affordable quantity. */ const computeMaxAffordable = ( thrall: VampireThrall, blood: number, ): number => { let total = 0; let quantity = 0; for (let index = 0; index < 100_000; index = index + 1) { const exponent = thrall.count + index; const cost = thrall.baseCost * Math.pow(growthRate, exponent); if (total + cost > blood) { break; } total = total + cost; quantity = quantity + 1; } return quantity; }; /** * Parses a localStorage string back into a valid BatchSize, defaulting to 1. * @param stored - The raw string from localStorage (or null if absent). * @returns A valid BatchSize value. */ const parseBatchSize = (stored: string | null): BatchSize => { if (stored === "max") { return "max"; } if (stored === "10") { return 10; } return 1; }; interface ThrallCardProperties { readonly thrall: VampireThrall; readonly blood: number; readonly selectedBatch: BatchSize; } /** * Renders a single thrall purchase card. * @param props - The component properties. * @param props.thrall - The thrall tier to display. * @param props.blood - The player's current blood balance. * @param props.selectedBatch - The active batch size selection. * @returns The JSX element. */ const ThrallCard = ({ thrall, blood, selectedBatch, }: ThrallCardProperties): JSX.Element => { const { buyVampireThrall, formatNumber } = useGame(); const maxAffordable = computeMaxAffordable(thrall, blood); const effectiveBatch = selectedBatch === "max" ? maxAffordable : selectedBatch; const batchCost = computeBatchCost(thrall, effectiveBatch); const canAffordBatch = blood >= batchCost && effectiveBatch > 0; const singleCost = computeBatchCost(thrall, 1); function handleBuy(): void { if (effectiveBatch > 0) { buyVampireThrall(thrall.id, effectiveBatch); } } function getBuyButtonLabel(): string { if (selectedBatch === "max") { if (maxAffordable === 0) { return "Can't Afford"; } return `Buy Max (×${String(maxAffordable)})`; } return `Buy ×${String(effectiveBatch)}`; } return (

{thrall.name}

{thrall.class}
{"×"} {formatNumber(thrall.count)}
{thrall.bloodPerSecond > 0 && {"🩸 "} {formatNumber(thrall.bloodPerSecond)} {"/s blood"} } {thrall.ichorPerSecond > 0 && {"💧 "} {formatNumber(thrall.ichorPerSecond)} {"/s ichor"} } {"⚔️ "} {formatNumber(thrall.combatPower)} {" combat power each"}
{"Next: 🩸 "} {formatNumber(singleCost)} {selectedBatch !== 1 && effectiveBatch > 0 && {selectedBatch === "max" ? "Max" : String(selectedBatch)} {" (×"} {String(effectiveBatch)} {"): 🩸 "} {formatNumber(batchCost)} }
{thrall.unlocked ? : {"🔒 Locked"} }
); }; /** * Renders the thralls panel for purchasing vampire thralls. * @returns The JSX element. */ const VampireThrallsPanel = (): JSX.Element => { const { state, formatNumber, toggleVampireAutoThrall } = useGame(); const [ selectedBatch, setSelectedBatch ] = useState(() => { return parseBatchSize(localStorage.getItem("elysium_thrall_batch")); }); if (state === null) { return (

{"Loading..."}

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

{"Vampire expansion not yet unlocked."}

); } const blood = state.resources.blood ?? 0; const { thralls, autoThrall } = vampireState; const autoThrallOn = autoThrall === true; function handleBatchSelect(batch: BatchSize): void { setSelectedBatch(batch); localStorage.setItem("elysium_thrall_batch", String(batch)); } return (

{"Thralls"}

{"🩸 Blood: "} {formatNumber(blood)}
{batchOptions.map((batch) => { function handleClick(): void { handleBatchSelect(batch); } return ; })}
{thralls.map((thrall: VampireThrall) => { return ; })}
); }; export { VampireThrallsPanel };