3 Commits

Author SHA1 Message Date
hikari 010b4ea1da 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
2026-03-31 18:00:21 -07:00
hikari 218a150540 feat: add missing achievement milestones and fix reward types (#188, #189, #190)
- Add quest_hero milestone at 75 quests (closes gap between 50 and 122)
- Add boss_legend milestone at 50 bosses (closes gap between 30 and 72)
- Replace crystal rewards on P50/P100/P150/P200 prestige achievements
  with runestones (100/500/2000/10000) — crystals become worthless by
  the time these are earned, runestones remain meaningful throughout
2026-03-31 18:00:16 -07:00
hikari ac42da4c3b balance: zero out crystal rewards for Zone 7+ bosses (#187)
Crystals are an early-game currency. The total crystal sink is ~125M
crystals (purchasable equipment), but Zone 7+ bosses were awarding up
to 5e139 crystals with the crystal multipliers applied. Bosses in
celestial_reaches and beyond now award 0 crystals, keeping the
crystal economy meaningful in early-mid game only.
2026-03-31 18:00:06 -07:00
4 changed files with 96 additions and 72 deletions
+22 -4
View File
@@ -315,6 +315,15 @@ export const defaultAchievements: Array<Achievement> = [
reward: { crystals: 5000 }, reward: { crystals: 5000 },
unlockedAt: null, unlockedAt: null,
}, },
{
condition: { amount: 75, type: "questsCompleted" },
description: "Complete 75 quests.",
icon: "🌠",
id: "quest_hero",
name: "Quest Hero",
reward: { crystals: 10_000 },
unlockedAt: null,
},
{ {
condition: { amount: 122, type: "questsCompleted" }, condition: { amount: 122, type: "questsCompleted" },
description: "Complete all 122 quests across the known multiverse.", description: "Complete all 122 quests across the known multiverse.",
@@ -343,6 +352,15 @@ export const defaultAchievements: Array<Achievement> = [
reward: { crystals: 5000 }, reward: { crystals: 5000 },
unlockedAt: null, unlockedAt: null,
}, },
{
condition: { amount: 50, type: "bossesDefeated" },
description: "Defeat 50 bosses.",
icon: "⚡",
id: "boss_legend",
name: "Legendary Vanquisher",
reward: { crystals: 15_000 },
unlockedAt: null,
},
{ {
condition: { amount: 72, type: "bossesDefeated" }, condition: { amount: 72, type: "bossesDefeated" },
description: "Defeat all 72 bosses across every plane of existence.", description: "Defeat all 72 bosses across every plane of existence.",
@@ -396,7 +414,7 @@ export const defaultAchievements: Array<Achievement> = [
icon: "✨", icon: "✨",
id: "prestige_transcendent", id: "prestige_transcendent",
name: "Transcendent", name: "Transcendent",
reward: { crystals: 10_000 }, reward: { runestones: 100 },
unlockedAt: null, unlockedAt: null,
}, },
{ {
@@ -405,7 +423,7 @@ export const defaultAchievements: Array<Achievement> = [
icon: "💎", icon: "💎",
id: "prestige_eternal", id: "prestige_eternal",
name: "Eternal Looper", name: "Eternal Looper",
reward: { crystals: 25_000 }, reward: { runestones: 500 },
unlockedAt: null, unlockedAt: null,
}, },
{ {
@@ -414,7 +432,7 @@ export const defaultAchievements: Array<Achievement> = [
icon: "🌟", icon: "🌟",
id: "prestige_immortal", id: "prestige_immortal",
name: "Immortal Cycler", name: "Immortal Cycler",
reward: { crystals: 50_000 }, reward: { runestones: 2000 },
unlockedAt: null, unlockedAt: null,
}, },
{ {
@@ -423,7 +441,7 @@ export const defaultAchievements: Array<Achievement> = [
icon: "👑", icon: "👑",
id: "prestige_absolute", id: "prestige_absolute",
name: "Absolute Champion", name: "Absolute Champion",
reward: { crystals: 100_000 }, reward: { runestones: 10_000 },
unlockedAt: null, unlockedAt: null,
}, },
]; ];
+53 -53
View File
@@ -360,7 +360,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 40, bountyRunestones: 40,
crystalReward: 40_000, crystalReward: 0,
currentHp: 2_000_000_000, currentHp: 2_000_000_000,
damagePerSecond: 120_000, damagePerSecond: 120_000,
description: description:
@@ -378,7 +378,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 50, bountyRunestones: 50,
crystalReward: 100_000, crystalReward: 0,
currentHp: 8_000_000_000, currentHp: 8_000_000_000,
damagePerSecond: 350_000, damagePerSecond: 350_000,
description: description:
@@ -396,7 +396,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 60, bountyRunestones: 60,
crystalReward: 300_000, crystalReward: 0,
currentHp: 30_000_000_000, currentHp: 30_000_000_000,
damagePerSecond: 1_000_000, damagePerSecond: 1_000_000,
description: description:
@@ -414,7 +414,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 75, bountyRunestones: 75,
crystalReward: 800_000, crystalReward: 0,
currentHp: 100_000_000_000, currentHp: 100_000_000_000,
damagePerSecond: 3_000_000, damagePerSecond: 3_000_000,
description: description:
@@ -433,7 +433,7 @@ export const defaultBosses: Array<Boss> = [
// ── Abyssal Trench ──────────────────────────────────────────────────────── // ── Abyssal Trench ────────────────────────────────────────────────────────
{ {
bountyRunestones: 40, bountyRunestones: 40,
crystalReward: 1_500_000, crystalReward: 0,
currentHp: 250_000_000_000, currentHp: 250_000_000_000,
damagePerSecond: 5_000_000, damagePerSecond: 5_000_000,
description: description:
@@ -451,7 +451,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 55, bountyRunestones: 55,
crystalReward: 4_000_000, crystalReward: 0,
currentHp: 1_000_000_000_000, currentHp: 1_000_000_000_000,
damagePerSecond: 15_000_000, damagePerSecond: 15_000_000,
description: description:
@@ -469,7 +469,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 70, bountyRunestones: 70,
crystalReward: 12_000_000, crystalReward: 0,
currentHp: 4_000_000_000_000, currentHp: 4_000_000_000_000,
damagePerSecond: 50_000_000, damagePerSecond: 50_000_000,
description: description:
@@ -487,7 +487,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 85, bountyRunestones: 85,
crystalReward: 40_000_000, crystalReward: 0,
currentHp: 15_000_000_000_000, currentHp: 15_000_000_000_000,
damagePerSecond: 150_000_000, damagePerSecond: 150_000_000,
description: description:
@@ -505,7 +505,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 100, bountyRunestones: 100,
crystalReward: 150_000_000, crystalReward: 0,
currentHp: 50_000_000_000_000, currentHp: 50_000_000_000_000,
damagePerSecond: 500_000_000, damagePerSecond: 500_000_000,
description: description:
@@ -524,7 +524,7 @@ export const defaultBosses: Array<Boss> = [
// ── Infernal Court ──────────────────────────────────────────────────────── // ── Infernal Court ────────────────────────────────────────────────────────
{ {
bountyRunestones: 55, bountyRunestones: 55,
crystalReward: 350_000_000, crystalReward: 0,
currentHp: 120_000_000_000_000, currentHp: 120_000_000_000_000,
damagePerSecond: 800_000_000, damagePerSecond: 800_000_000,
description: description:
@@ -542,7 +542,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 70, bountyRunestones: 70,
crystalReward: 1_000_000_000, crystalReward: 0,
currentHp: 500_000_000_000_000, currentHp: 500_000_000_000_000,
damagePerSecond: 2_500_000_000, damagePerSecond: 2_500_000_000,
description: description:
@@ -560,7 +560,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 90, bountyRunestones: 90,
crystalReward: 3_000_000_000, crystalReward: 0,
currentHp: 2_000_000_000_000_000, currentHp: 2_000_000_000_000_000,
damagePerSecond: 8_000_000_000, damagePerSecond: 8_000_000_000,
description: description:
@@ -578,7 +578,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 110, bountyRunestones: 110,
crystalReward: 10_000_000_000, crystalReward: 0,
currentHp: 6_000_000_000_000_000, currentHp: 6_000_000_000_000_000,
damagePerSecond: 25_000_000_000, damagePerSecond: 25_000_000_000,
description: description:
@@ -596,7 +596,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 135, bountyRunestones: 135,
crystalReward: 30_000_000_000, crystalReward: 0,
currentHp: 8_000_000_000_000_000, currentHp: 8_000_000_000_000_000,
damagePerSecond: 80_000_000_000, damagePerSecond: 80_000_000_000,
description: description:
@@ -615,7 +615,7 @@ export const defaultBosses: Array<Boss> = [
// ── Crystalline Spire ───────────────────────────────────────────────────── // ── Crystalline Spire ─────────────────────────────────────────────────────
{ {
bountyRunestones: 70, bountyRunestones: 70,
crystalReward: 8e10, crystalReward: 0,
currentHp: 2e16, currentHp: 2e16,
damagePerSecond: 120_000_000_000, damagePerSecond: 120_000_000_000,
description: description:
@@ -633,7 +633,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 90, bountyRunestones: 90,
crystalReward: 3e11, crystalReward: 0,
currentHp: 8e16, currentHp: 8e16,
damagePerSecond: 4e11, damagePerSecond: 4e11,
description: description:
@@ -651,7 +651,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 115, bountyRunestones: 115,
crystalReward: 1e12, crystalReward: 0,
currentHp: 3e17, currentHp: 3e17,
damagePerSecond: 1.2e12, damagePerSecond: 1.2e12,
description: description:
@@ -669,7 +669,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 140, bountyRunestones: 140,
crystalReward: 4e12, crystalReward: 0,
currentHp: 1e18, currentHp: 1e18,
damagePerSecond: 4e12, damagePerSecond: 4e12,
description: description:
@@ -687,7 +687,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 175, bountyRunestones: 175,
crystalReward: 1.5e13, crystalReward: 0,
currentHp: 4e18, currentHp: 4e18,
damagePerSecond: 1.5e13, damagePerSecond: 1.5e13,
description: description:
@@ -706,7 +706,7 @@ export const defaultBosses: Array<Boss> = [
// ── Void Sanctum ────────────────────────────────────────────────────────── // ── Void Sanctum ──────────────────────────────────────────────────────────
{ {
bountyRunestones: 90, bountyRunestones: 90,
crystalReward: 4e13, crystalReward: 0,
currentHp: 1e19, currentHp: 1e19,
damagePerSecond: 4e13, damagePerSecond: 4e13,
description: description:
@@ -724,7 +724,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 115, bountyRunestones: 115,
crystalReward: 1.5e14, crystalReward: 0,
currentHp: 5e19, currentHp: 5e19,
damagePerSecond: 1.5e14, damagePerSecond: 1.5e14,
description: description:
@@ -742,7 +742,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 145, bountyRunestones: 145,
crystalReward: 5e14, crystalReward: 0,
currentHp: 2e20, currentHp: 2e20,
damagePerSecond: 5e14, damagePerSecond: 5e14,
description: description:
@@ -760,7 +760,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 180, bountyRunestones: 180,
crystalReward: 2e15, crystalReward: 0,
currentHp: 8e20, currentHp: 8e20,
damagePerSecond: 2e15, damagePerSecond: 2e15,
description: description:
@@ -778,7 +778,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 225, bountyRunestones: 225,
crystalReward: 8e15, crystalReward: 0,
currentHp: 3e21, currentHp: 3e21,
damagePerSecond: 8e15, damagePerSecond: 8e15,
description: description:
@@ -797,7 +797,7 @@ export const defaultBosses: Array<Boss> = [
// ── Eternal Throne ──────────────────────────────────────────────────────── // ── Eternal Throne ────────────────────────────────────────────────────────
{ {
bountyRunestones: 115, bountyRunestones: 115,
crystalReward: 2e16, crystalReward: 0,
currentHp: 1e22, currentHp: 1e22,
damagePerSecond: 2e16, damagePerSecond: 2e16,
description: description:
@@ -815,7 +815,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 150, bountyRunestones: 150,
crystalReward: 8e16, crystalReward: 0,
currentHp: 5e22, currentHp: 5e22,
damagePerSecond: 8e16, damagePerSecond: 8e16,
description: description:
@@ -833,7 +833,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 190, bountyRunestones: 190,
crystalReward: 3e17, crystalReward: 0,
currentHp: 2e23, currentHp: 2e23,
damagePerSecond: 3e17, damagePerSecond: 3e17,
description: description:
@@ -851,7 +851,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 235, bountyRunestones: 235,
crystalReward: 1.2e18, crystalReward: 0,
currentHp: 8e23, currentHp: 8e23,
damagePerSecond: 1.2e18, damagePerSecond: 1.2e18,
description: description:
@@ -869,7 +869,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 295, bountyRunestones: 295,
crystalReward: 5e18, crystalReward: 0,
currentHp: 3e24, currentHp: 3e24,
damagePerSecond: 5e18, damagePerSecond: 5e18,
description: description:
@@ -888,7 +888,7 @@ export const defaultBosses: Array<Boss> = [
// ── Primordial Chaos ────────────────────────────────────────────────────── // ── Primordial Chaos ──────────────────────────────────────────────────────
{ {
bountyRunestones: 150, bountyRunestones: 150,
crystalReward: 2e20, crystalReward: 0,
currentHp: 1e26, currentHp: 1e26,
damagePerSecond: 2e20, damagePerSecond: 2e20,
description: description:
@@ -906,7 +906,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 200, bountyRunestones: 200,
crystalReward: 8e21, crystalReward: 0,
currentHp: 5e27, currentHp: 5e27,
damagePerSecond: 8e21, damagePerSecond: 8e21,
description: description:
@@ -924,7 +924,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 265, bountyRunestones: 265,
crystalReward: 4e23, crystalReward: 0,
currentHp: 2e29, currentHp: 2e29,
damagePerSecond: 4e23, damagePerSecond: 4e23,
description: description:
@@ -942,7 +942,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 350, bountyRunestones: 350,
crystalReward: 2e25, crystalReward: 0,
currentHp: 8e30, currentHp: 8e30,
damagePerSecond: 2e25, damagePerSecond: 2e25,
description: description:
@@ -961,7 +961,7 @@ export const defaultBosses: Array<Boss> = [
// ── Infinite Expanse ────────────────────────────────────────────────────── // ── Infinite Expanse ──────────────────────────────────────────────────────
{ {
bountyRunestones: 200, bountyRunestones: 200,
crystalReward: 8e27, crystalReward: 0,
currentHp: 3e33, currentHp: 3e33,
damagePerSecond: 8e27, damagePerSecond: 8e27,
description: description:
@@ -979,7 +979,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 265, bountyRunestones: 265,
crystalReward: 3e31, crystalReward: 0,
currentHp: 2e35, currentHp: 2e35,
damagePerSecond: 3e31, damagePerSecond: 3e31,
description: description:
@@ -997,7 +997,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 350, bountyRunestones: 350,
crystalReward: 1e35, crystalReward: 0,
currentHp: 5e37, currentHp: 5e37,
damagePerSecond: 1e35, damagePerSecond: 1e35,
description: description:
@@ -1015,7 +1015,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 465, bountyRunestones: 465,
crystalReward: 5e38, crystalReward: 0,
currentHp: 3e39, currentHp: 3e39,
damagePerSecond: 5e38, damagePerSecond: 5e38,
description: description:
@@ -1034,7 +1034,7 @@ export const defaultBosses: Array<Boss> = [
// ── Reality Forge ───────────────────────────────────────────────────────── // ── Reality Forge ─────────────────────────────────────────────────────────
{ {
bountyRunestones: 265, bountyRunestones: 265,
crystalReward: 2e42, crystalReward: 0,
currentHp: 8e47, currentHp: 8e47,
damagePerSecond: 2e42, damagePerSecond: 2e42,
description: description:
@@ -1052,7 +1052,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 350, bountyRunestones: 350,
crystalReward: 1e47, crystalReward: 0,
currentHp: 4e52, currentHp: 4e52,
damagePerSecond: 1e47, damagePerSecond: 1e47,
description: description:
@@ -1070,7 +1070,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 465, bountyRunestones: 465,
crystalReward: 6e51, crystalReward: 0,
currentHp: 2e57, currentHp: 2e57,
damagePerSecond: 6e51, damagePerSecond: 6e51,
description: description:
@@ -1088,7 +1088,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 615, bountyRunestones: 615,
crystalReward: 2e56, crystalReward: 0,
currentHp: 8e61, currentHp: 8e61,
damagePerSecond: 2e56, damagePerSecond: 2e56,
description: description:
@@ -1107,7 +1107,7 @@ export const defaultBosses: Array<Boss> = [
// ── Cosmic Maelstrom ────────────────────────────────────────────────────── // ── Cosmic Maelstrom ──────────────────────────────────────────────────────
{ {
bountyRunestones: 350, bountyRunestones: 350,
crystalReward: 1e60, crystalReward: 0,
currentHp: 4e65, currentHp: 4e65,
damagePerSecond: 1e60, damagePerSecond: 1e60,
description: description:
@@ -1125,7 +1125,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 465, bountyRunestones: 465,
crystalReward: 6e65, crystalReward: 0,
currentHp: 2e71, currentHp: 2e71,
damagePerSecond: 6e65, damagePerSecond: 6e65,
description: description:
@@ -1143,7 +1143,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 615, bountyRunestones: 615,
crystalReward: 3e71, crystalReward: 0,
currentHp: 1e77, currentHp: 1e77,
damagePerSecond: 3e71, damagePerSecond: 3e71,
description: description:
@@ -1161,7 +1161,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 815, bountyRunestones: 815,
crystalReward: 1e77, crystalReward: 0,
currentHp: 5e82, currentHp: 5e82,
damagePerSecond: 1e77, damagePerSecond: 1e77,
description: description:
@@ -1180,7 +1180,7 @@ export const defaultBosses: Array<Boss> = [
// ── Primeval Sanctum ────────────────────────────────────────────────────── // ── Primeval Sanctum ──────────────────────────────────────────────────────
{ {
bountyRunestones: 465, bountyRunestones: 465,
crystalReward: 5e82, crystalReward: 0,
currentHp: 2e88, currentHp: 2e88,
damagePerSecond: 5e82, damagePerSecond: 5e82,
description: description:
@@ -1198,7 +1198,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 615, bountyRunestones: 615,
crystalReward: 3e89, crystalReward: 0,
currentHp: 1e95, currentHp: 1e95,
damagePerSecond: 3e89, damagePerSecond: 3e89,
description: description:
@@ -1216,7 +1216,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 815, bountyRunestones: 815,
crystalReward: 2e96, crystalReward: 0,
currentHp: 8e101, currentHp: 8e101,
damagePerSecond: 2e96, damagePerSecond: 2e96,
description: description:
@@ -1234,7 +1234,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 1080, bountyRunestones: 1080,
crystalReward: 1e103, crystalReward: 0,
currentHp: 5e108, currentHp: 5e108,
damagePerSecond: 1e103, damagePerSecond: 1e103,
description: description:
@@ -1253,7 +1253,7 @@ export const defaultBosses: Array<Boss> = [
// ── The Absolute ────────────────────────────────────────────────────────── // ── The Absolute ──────────────────────────────────────────────────────────
{ {
bountyRunestones: 615, bountyRunestones: 615,
crystalReward: 5e110, crystalReward: 0,
currentHp: 2e116, currentHp: 2e116,
damagePerSecond: 5e110, damagePerSecond: 5e110,
description: description:
@@ -1271,7 +1271,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 815, bountyRunestones: 815,
crystalReward: 3e119, crystalReward: 0,
currentHp: 1e125, currentHp: 1e125,
damagePerSecond: 3e119, damagePerSecond: 3e119,
description: description:
@@ -1289,7 +1289,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 1080, bountyRunestones: 1080,
crystalReward: 1e129, crystalReward: 0,
currentHp: 5e134, currentHp: 5e134,
damagePerSecond: 1e129, damagePerSecond: 1e129,
description: description:
@@ -1307,7 +1307,7 @@ export const defaultBosses: Array<Boss> = [
}, },
{ {
bountyRunestones: 1430, bountyRunestones: 1430,
crystalReward: 5e139, crystalReward: 0,
currentHp: 2e145, currentHp: 2e145,
damagePerSecond: 5e139, damagePerSecond: 5e139,
description: description:
+19 -14
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,24 +817,30 @@ 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;
const wasLocked = state.achievements[index]?.unlockedAt === null; for (const [ index, achievement ] of updatedAchievements.entries()) {
const isNowUnlocked = achievement.unlockedAt !== null; const wasLocked = state.achievements[index]?.unlockedAt === null;
if (wasLocked && isNowUnlocked) { const isNowUnlocked = achievement.unlockedAt !== null;
return sum + (achievement.reward?.crystals ?? 0); if (wasLocked && isNowUnlocked) {
} crystalsFromAchievements
return sum; = crystalsFromAchievements + (achievement.reward?.crystals ?? 0);
}, runestonesFromAchievements
0, = runestonesFromAchievements + (achievement.reward?.runestones ?? 0);
); }
}
return { return {
...partialState, ...partialState,
achievements: updatedAchievements, achievements: updatedAchievements,
resources: { prestige: {
...partialState.prestige,
runestones:
partialState.prestige.runestones + runestonesFromAchievements,
},
resources: {
...partialState.resources, ...partialState.resources,
crystals: capResource( crystals: capResource(
partialState.resources.crystals + crystalsFromAchievements, partialState.resources.crystals + crystalsFromAchievements,
+2 -1
View File
@@ -20,7 +20,8 @@ interface AchievementCondition {
} }
interface AchievementReward { interface AchievementReward {
crystals?: number; crystals?: number;
runestones?: number;
} }
interface Achievement { interface Achievement {