/** * @file Vampire achievements panel component displaying all vampire expansion achievements. * @copyright nhcarrigan * @license Naomi's Public License * @author Naomi Carrigan */ /* eslint-disable react/no-multi-comp -- Sub-component is tightly coupled to this panel */ /* eslint-disable max-lines-per-function -- Achievement panel renders many achievement states */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { LockToggle } from "../ui/lockToggle.js"; import type { VampireAchievement, VampireState } from "@elysium/types"; /** * Returns the plural form of a word based on a count. * @param count - The count to check. * @param word - The base word to pluralise. * @returns The pluralised word string. */ const pluralise = (count: number, word: string): string => { return count > 1 ? `${word}s` : word; }; /** * Generates a human-readable condition description for a vampire achievement. * @param achievement - The vampire achievement to describe. * @param formatNumber - The number formatting utility function. * @returns A string describing the achievement condition. */ const conditionDescription = ( achievement: VampireAchievement, formatNumber: (n: number)=> string, ): string => { const { condition } = achievement; switch (condition.type) { case "totalBloodEarned": return `Earn ${formatNumber(condition.amount)} total blood`; case "vampireBossesDefeated": return `Defeat ${String(condition.amount)} vampire ${pluralise(condition.amount, "boss")}`; case "vampireQuestsCompleted": return `Complete ${String(condition.amount)} vampire ${pluralise(condition.amount, "quest")}`; case "thrallTotal": return `Recruit ${formatNumber(condition.amount)} total ${pluralise(condition.amount, "thrall")}`; case "siringCount": return `Sire ${String(condition.amount)} ${pluralise(condition.amount, "time")}`; case "vampireEquipmentOwned": return `Own ${String(condition.amount)} vampire equipment ${pluralise(condition.amount, "item")}`; default: return "Unknown condition"; } }; /** * Returns the player's current progress value toward a vampire achievement's unlock condition. * @param achievement - The achievement to evaluate progress for. * @param vampire - The current vampire state. * @returns The current numeric progress toward the achievement condition. */ const getCurrentProgress = ( achievement: VampireAchievement, vampire: VampireState, ): number => { const { condition } = achievement; switch (condition.type) { case "totalBloodEarned": return vampire.lifetimeBloodEarned; case "vampireBossesDefeated": return vampire.lifetimeBossesDefeated; case "vampireQuestsCompleted": return vampire.lifetimeQuestsCompleted; case "thrallTotal": return vampire.thralls.reduce((sum, thrall) => { return sum + thrall.count; }, 0); case "siringCount": return vampire.siring.count; case "vampireEquipmentOwned": return vampire.equipment.filter((item) => { return item.owned; }).length; default: return 0; } }; interface VampireAchievementCardProperties { readonly achievement: VampireAchievement; readonly formatNumber: (n: number)=> string; readonly progressValue: number; } /** * Renders a single vampire achievement card. * @param props - The achievement card properties. * @param props.achievement - The achievement to display. * @param props.formatNumber - The number formatting utility function. * @param props.progressValue - The player's current progress toward the unlock condition. * @returns The JSX element. */ const VampireAchievementCard = ({ achievement, formatNumber, progressValue, }: VampireAchievementCardProperties): JSX.Element => { const isUnlocked = achievement.unlockedAt !== null; const cappedProgress = Math.min(progressValue, achievement.condition.amount); return (
{achievement.icon}

{achievement.name}

{achievement.description}

{conditionDescription(achievement, formatNumber)}

{!isUnlocked &&
{formatNumber(progressValue)} {" / "} {formatNumber(achievement.condition.amount)}
} {achievement.reward !== undefined &&
{achievement.reward.ichor !== undefined &&

{"💧 +"} {achievement.reward.ichor} {" Ichor"}

} {achievement.reward.soulShards !== undefined &&

{"💠 +"} {achievement.reward.soulShards} {" Soul Shards"}

}
}
{isUnlocked ? <> {"✓ Unlocked"} {achievement.unlockedAt !== null && {new Date(achievement.unlockedAt).toLocaleDateString("en-GB", { day: "numeric", month: "short", year: "numeric", })} } : {"🔒"} }
); }; /** * Renders the vampire achievements panel with all vampire expansion achievements. * @returns The JSX element. */ const VampireAchievementsPanel = (): JSX.Element => { const { state, formatNumber } = useGame(); const [ showLocked, setShowLocked ] = useState(true); if (state === null) { return (

{"Loading..."}

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

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

); } const achievementList = vampire.achievements; const unlocked = achievementList.filter((achievement) => { return achievement.unlockedAt !== null; }); const locked = achievementList.filter((achievement) => { return achievement.unlockedAt === null; }); const visible = showLocked ? achievementList : unlocked; function handleToggle(): void { setShowLocked((current) => { return !current; }); } return (

{"🩸 Vampire Achievements"}

{unlocked.length} {" / "} {achievementList.length} {" unlocked"}

{visible.map((achievement) => { return ( ); })}
); }; export { VampireAchievementsPanel };