/** * @file Read-only panel displaying vampire quests grouped by zone. * @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 -- QuestCard sub-component is tightly coupled */ import { useState, type JSX } from "react"; import { useGame } from "../../context/gameContext.js"; import type { VampireQuest, VampireQuestReward, VampireZone, } from "@elysium/types"; /** * Formats a duration in seconds to a human-readable string. * @param seconds - The total number of seconds to format. * @returns The formatted duration string. */ const formatDuration = (seconds: number): string => { const secondsPerHour = 3600; const secondsPerMinute = 60; if (seconds >= secondsPerHour) { const hours = Math.floor(seconds / secondsPerHour); const remainderSeconds = seconds % secondsPerHour; const minutes = Math.floor(remainderSeconds / secondsPerMinute); return `${String(hours)}h ${String(minutes)}m`; } if (seconds >= secondsPerMinute) { const minutes = Math.floor(seconds / secondsPerMinute); const secs = seconds % secondsPerMinute; return `${String(minutes)}m ${String(secs)}s`; } return `${String(seconds)}s`; }; /** * Returns a human-readable label string for a vampire quest reward. * @param reward - The reward to describe. * @param formatNumber - The number formatter function. * @returns The label string for the given reward type. */ const getRewardLabel = ( reward: VampireQuestReward, formatNumber: (value: number)=> string, ): string => { if (reward.type === "blood") { return `🩸 ${formatNumber(reward.amount ?? 0)} Blood`; } if (reward.type === "ichor") { return `💧 ${formatNumber(reward.amount ?? 0)} Ichor`; } if (reward.type === "soulShards") { return `💠 ${formatNumber(reward.amount ?? 0)} Soul Shards`; } if (reward.type === "upgrade") { return "🔓 Upgrade Unlocked"; } if (reward.type === "thrall") { return "🧟 New Thrall Tier"; } return "🦇 Equipment Unlocked"; }; interface VampireQuestCardProperties { readonly quest: VampireQuest; readonly unlockHint: string | undefined; readonly zoneIsOpen: boolean; } /** * Renders a single vampire quest card (read-only). * @param props - The component properties. * @param props.quest - The vampire quest to display. * @param props.unlockHint - The name of the prerequisite quest, if locked. * @param props.zoneIsOpen - Whether the quest's zone is currently unlocked. * @returns The JSX element. */ const VampireQuestCard = ({ quest, unlockHint, zoneIsOpen, }: VampireQuestCardProperties): JSX.Element => { const { formatNumber } = useGame(); return (

{quest.name}

{quest.description}

{"⏱ "} {formatDuration(quest.durationSeconds)}

{quest.rewards.map((reward, rewardIndex) => { return {getRewardLabel(reward, formatNumber)} ; })}
{quest.status === "locked" && !zoneIsOpen && {"🔒 Zone Locked"} } {quest.status === "locked" && zoneIsOpen ? <> {"🔒 Locked"} {unlockHint !== undefined &&

{"📜 Complete: "} {unlockHint}

} : null } {quest.status === "available" && {"📋 Available"} } {quest.status === "active" && {"⏳ In Progress"} } {quest.status === "completed" && {"✅ Completed"} }
); }; /** * Renders the vampire quests panel with zone selection and quest list. * @returns The JSX element. */ const VampireQuestsPanel = (): JSX.Element => { const { state } = useGame(); const [ activeZoneId, setActiveZoneId ] = useState(() => { return sessionStorage.getItem("elysium_vampire_quest_zone") ?? "vampire_haunted_catacombs"; }); if (state === null) { return (

{"Loading..."}

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

{"Vampire expansion not yet unlocked."}

); } const { zones, quests } = vampireState; const activeZone = zones.find((zone: VampireZone) => { return zone.id === activeZoneId; }); const zoneIsOpen = activeZone?.status === "unlocked"; const zoneQuests = quests.filter((quest: VampireQuest) => { return quest.zoneId === activeZoneId; }); const questNameById = new Map( quests.map((quest: VampireQuest) => { return [ quest.id, quest.name ]; }), ); const getUnlockHint = (quest: VampireQuest): string | undefined => { if (quest.status !== "locked" || quest.prerequisiteIds.length === 0) { return undefined; } const [ prereqId ] = quest.prerequisiteIds; if (prereqId === undefined) { return undefined; } return questNameById.get(prereqId); }; function handleZoneSelect(zoneId: string): void { setActiveZoneId(zoneId); sessionStorage.setItem("elysium_vampire_quest_zone", zoneId); } const completedCount = zoneQuests.filter((quest: VampireQuest) => { return quest.status === "completed"; }).length; return (

{"Vampire Quests"}

{zones.map((zone: VampireZone) => { function handleClick(): void { handleZoneSelect(zone.id); } return ; })}
{activeZone !== undefined &&

{activeZone.description}

{String(completedCount)} {" / "} {String(zoneQuests.length)} {" quests completed"}

{activeZone.status === "locked" &&

{"🔒 This zone is locked. Defeat the required vampire boss"} {" to unlock it."}

}
}
{zoneQuests.length === 0 ?

{"No quests in this zone."}

: zoneQuests.map((quest: VampireQuest) => { return ; }) }
); }; export { VampireQuestsPanel };