From 010b4ea1dad7db0c7eb9022a71c4b941a109f745 Mon Sep 17 00:00:00 2001 From: Hikari Date: Tue, 31 Mar 2026 18:00:21 -0700 Subject: [PATCH] feat: support runestone rewards in achievement system (#190) - Add runestones field to AchievementReward type - Update tick engine to accumulate and apply runestone rewards when achievements unlock, alongside the existing crystal rewards --- apps/web/src/engine/tick.ts | 33 +++++++++++--------- packages/types/src/interfaces/achievement.ts | 3 +- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/apps/web/src/engine/tick.ts b/apps/web/src/engine/tick.ts index db663ab..f299be9 100644 --- a/apps/web/src/engine/tick.ts +++ b/apps/web/src/engine/tick.ts @@ -11,7 +11,6 @@ /* 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/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 */ import { type Achievement, @@ -818,24 +817,30 @@ export const applyTick = ( 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 crystalsFromAchievements = updatedAchievements.reduce( - (sum, achievement, index) => { - const wasLocked = state.achievements[index]?.unlockedAt === null; - const isNowUnlocked = achievement.unlockedAt !== null; - if (wasLocked && isNowUnlocked) { - return sum + (achievement.reward?.crystals ?? 0); - } - return sum; - }, - 0, - ); + let crystalsFromAchievements = 0; + let runestonesFromAchievements = 0; + for (const [ index, achievement ] of updatedAchievements.entries()) { + const wasLocked = state.achievements[index]?.unlockedAt === null; + const isNowUnlocked = achievement.unlockedAt !== null; + if (wasLocked && isNowUnlocked) { + crystalsFromAchievements + = crystalsFromAchievements + (achievement.reward?.crystals ?? 0); + runestonesFromAchievements + = runestonesFromAchievements + (achievement.reward?.runestones ?? 0); + } + } return { ...partialState, achievements: updatedAchievements, - resources: { + prestige: { + ...partialState.prestige, + runestones: + partialState.prestige.runestones + runestonesFromAchievements, + }, + resources: { ...partialState.resources, crystals: capResource( partialState.resources.crystals + crystalsFromAchievements, diff --git a/packages/types/src/interfaces/achievement.ts b/packages/types/src/interfaces/achievement.ts index b40bb44..dd862eb 100644 --- a/packages/types/src/interfaces/achievement.ts +++ b/packages/types/src/interfaces/achievement.ts @@ -20,7 +20,8 @@ interface AchievementCondition { } interface AchievementReward { - crystals?: number; + crystals?: number; + runestones?: number; } interface Achievement {