feat: support runestone rewards in achievement system (#190)
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m4s
CI / Lint, Build & Test (pull_request) Successful in 1m11s

- Add runestones field to AchievementReward type
- Update tick engine to accumulate and apply runestone rewards
  when achievements unlock, alongside the existing crystal rewards
This commit is contained in:
2026-03-31 18:00:21 -07:00
committed by Naomi Carrigan
parent 218a150540
commit 010b4ea1da
2 changed files with 21 additions and 15 deletions
+14 -9
View File
@@ -11,7 +11,6 @@
/* eslint-disable max-lines -- Engine file necessarily exceeds line limit */ /* eslint-disable max-lines -- Engine file necessarily exceeds line limit */
/* eslint-disable import/group-exports -- Exports appear alongside their definitions for readability */ /* eslint-disable import/group-exports -- Exports appear alongside their definitions for readability */
/* eslint-disable import/exports-last -- Exports appear alongside their definitions for readability */ /* eslint-disable import/exports-last -- Exports appear alongside their definitions for readability */
/* eslint-disable unicorn/no-array-reduce -- reduce is the most readable approach for multiplier chains */
/* eslint-disable max-nested-callbacks -- Tick engine requires nested array operations for game logic */ /* eslint-disable max-nested-callbacks -- Tick engine requires nested array operations for game logic */
import { import {
type Achievement, type Achievement,
@@ -818,23 +817,29 @@ export const applyTick = (
zones: updatedZones, zones: updatedZones,
}; };
// Check achievements and apply crystal rewards for newly unlocked ones // Check achievements and apply crystal and runestone rewards for newly unlocked ones
const updatedAchievements = checkAchievements(partialState); const updatedAchievements = checkAchievements(partialState);
const crystalsFromAchievements = updatedAchievements.reduce( let crystalsFromAchievements = 0;
(sum, achievement, index) => { let runestonesFromAchievements = 0;
for (const [ index, achievement ] of updatedAchievements.entries()) {
const wasLocked = state.achievements[index]?.unlockedAt === null; const wasLocked = state.achievements[index]?.unlockedAt === null;
const isNowUnlocked = achievement.unlockedAt !== null; const isNowUnlocked = achievement.unlockedAt !== null;
if (wasLocked && isNowUnlocked) { if (wasLocked && isNowUnlocked) {
return sum + (achievement.reward?.crystals ?? 0); crystalsFromAchievements
= crystalsFromAchievements + (achievement.reward?.crystals ?? 0);
runestonesFromAchievements
= runestonesFromAchievements + (achievement.reward?.runestones ?? 0);
}
} }
return sum;
},
0,
);
return { return {
...partialState, ...partialState,
achievements: updatedAchievements, achievements: updatedAchievements,
prestige: {
...partialState.prestige,
runestones:
partialState.prestige.runestones + runestonesFromAchievements,
},
resources: { resources: {
...partialState.resources, ...partialState.resources,
crystals: capResource( crystals: capResource(
@@ -21,6 +21,7 @@ interface AchievementCondition {
interface AchievementReward { interface AchievementReward {
crystals?: number; crystals?: number;
runestones?: number;
} }
interface Achievement { interface Achievement {