generated from nhcarrigan/template
feat: show progress toward unlock conditions on achievement cards #71
@@ -9,7 +9,7 @@ import { type JSX, useState } from "react";
|
|||||||
import { useGame } from "../../context/gameContext.js";
|
import { useGame } from "../../context/gameContext.js";
|
||||||
import { cdnImage } from "../../utils/cdn.js";
|
import { cdnImage } from "../../utils/cdn.js";
|
||||||
import { LockToggle } from "../ui/lockToggle.js";
|
import { LockToggle } from "../ui/lockToggle.js";
|
||||||
import type { Achievement } from "@elysium/types";
|
import type { Achievement, GameState } from "@elysium/types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the plural form of a word based on a count.
|
* Returns the plural form of a word based on a count.
|
||||||
@@ -54,9 +54,50 @@ const conditionDescription = (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
interface AchievementCardProperties {
|
||||||
readonly achievement: Achievement;
|
readonly achievement: Achievement;
|
||||||
readonly formatNumber: (n: number)=> string;
|
readonly formatNumber: (n: number)=> string;
|
||||||
|
readonly progressValue: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,14 +105,18 @@ interface AchievementCardProperties {
|
|||||||
* @param props - The achievement card properties.
|
* @param props - The achievement card properties.
|
||||||
* @param props.achievement - The achievement to display.
|
* @param props.achievement - The achievement to display.
|
||||||
* @param props.formatNumber - The number formatting utility function.
|
* @param props.formatNumber - The number formatting utility function.
|
||||||
|
* @param props.progressValue - The player's current progress toward the unlock condition.
|
||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line max-lines-per-function -- Progress bar adds necessary lines for locked state
|
||||||
const AchievementCard = ({
|
const AchievementCard = ({
|
||||||
achievement,
|
achievement,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
progressValue,
|
||||||
}: AchievementCardProperties): JSX.Element => {
|
}: AchievementCardProperties): JSX.Element => {
|
||||||
const isUnlocked = achievement.unlockedAt !== null;
|
const isUnlocked = achievement.unlockedAt !== null;
|
||||||
const crystals = achievement.reward?.crystals;
|
const crystals = achievement.reward?.crystals;
|
||||||
|
const cappedProgress = Math.min(progressValue, achievement.condition.amount);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`achievement-card ${isUnlocked
|
<div className={`achievement-card ${isUnlocked
|
||||||
@@ -88,6 +133,19 @@ const AchievementCard = ({
|
|||||||
<p className="achievement-condition">
|
<p className="achievement-condition">
|
||||||
{conditionDescription(achievement, formatNumber)}
|
{conditionDescription(achievement, formatNumber)}
|
||||||
</p>
|
</p>
|
||||||
|
{!isUnlocked
|
||||||
|
&& <div className="achievement-progress">
|
||||||
|
<progress
|
||||||
|
max={achievement.condition.amount}
|
||||||
|
value={cappedProgress}
|
||||||
|
/>
|
||||||
|
<span className="achievement-progress-label">
|
||||||
|
{formatNumber(progressValue)}
|
||||||
|
{" / "}
|
||||||
|
{formatNumber(achievement.condition.amount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{crystals !== undefined
|
{crystals !== undefined
|
||||||
&& <p className="achievement-reward">
|
&& <p className="achievement-reward">
|
||||||
{"💎 +"}
|
{"💎 +"}
|
||||||
@@ -163,6 +221,7 @@ const AchievementPanel = (): JSX.Element => {
|
|||||||
achievement={achievement}
|
achievement={achievement}
|
||||||
formatNumber={formatNumber}
|
formatNumber={formatNumber}
|
||||||
key={achievement.id}
|
key={achievement.id}
|
||||||
|
progressValue={getCurrentProgress(achievement, state)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
Reference in New Issue
Block a user