/** * @file Achievement panel component displaying all game 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 */ import { type JSX, useState } from "react"; import { useGame } from "../../context/gameContext.js"; import { cdnImage } from "../../utils/cdn.js"; import { LockToggle } from "../ui/lockToggle.js"; import type { Achievement, GameState } 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 an achievement. * @param achievement - The achievement to describe. * @param formatNumber - The number formatting utility function. * @returns A string describing the achievement condition. */ const conditionDescription = ( achievement: Achievement, formatNumber: (n: number)=> string, ): string => { const { condition } = achievement; switch (condition.type) { case "totalGoldEarned": return `Earn ${formatNumber(condition.amount)} total gold`; case "totalClicks": return `Click ${formatNumber(condition.amount)} times`; case "bossesDefeated": return `Defeat ${String(condition.amount)} ${pluralise(condition.amount, "boss")}`; case "questsCompleted": return `Complete ${String(condition.amount)} ${pluralise(condition.amount, "quest")}`; case "adventurerTotal": return `Recruit ${formatNumber(condition.amount)} total adventurers`; case "prestigeCount": return `Prestige ${String(condition.amount)} ${pluralise(condition.amount, "time")}`; case "equipmentOwned": return `Own ${String(condition.amount)} equipment ${pluralise(condition.amount, "item")}`; default: return "Unknown condition"; } }; /** * Returns the player's current progress value toward an achievement's unlock condition, * mirroring the logic used by the tick engine's checkAchievements function. * @param achievement - The achievement to evaluate progress for. * @param state - The current game state. * @returns The current numeric progress toward the achievement condition. */ const getCurrentProgress = ( achievement: Achievement, state: GameState, ): number => { const { condition } = achievement; switch (condition.type) { case "totalGoldEarned": return state.player.totalGoldEarned; case "totalClicks": return state.player.totalClicks; case "bossesDefeated": return state.bosses.filter((boss) => { return boss.status === "defeated"; }).length; case "questsCompleted": return state.quests.filter((quest) => { return quest.status === "completed"; }).length; case "adventurerTotal": return state.adventurers.reduce((sum, adventurer) => { return sum + adventurer.count; }, 0); case "prestigeCount": return state.prestige.count; case "equipmentOwned": return state.equipment.filter((item) => { return item.owned; }).length; default: return 0; } }; interface AchievementCardProperties { readonly achievement: Achievement; readonly formatNumber: (n: number)=> string; readonly progressValue: number; } /** * Renders a single 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. */ // eslint-disable-next-line max-lines-per-function -- Progress bar adds necessary lines for locked state const AchievementCard = ({ achievement, formatNumber, progressValue, }: AchievementCardProperties): JSX.Element => { const isUnlocked = achievement.unlockedAt !== null; const crystals = achievement.reward?.crystals; const cappedProgress = Math.min(progressValue, achievement.condition.amount); return (
{achievement.description}
{conditionDescription(achievement, formatNumber)}
{!isUnlocked &&{"💎 +"} {crystals} {" Crystals"}
}{"Loading..."}
{unlocked.length} {" / "} {achievementList.length} {" unlocked"}