feat: add daily challenges system

Three PST-midnight-resetting challenges generated deterministically per
day from click, boss, quest, and prestige types. Progress tracked
server-side for bosses and prestige, client-side for clicks and quests.
Crystal rewards awarded on completion and preserved through prestige resets.
This commit is contained in:
2026-03-06 22:22:18 -08:00
committed by Naomi Carrigan
parent a7d4b72805
commit aaeece1a18
15 changed files with 462 additions and 8 deletions
+12
View File
@@ -2,6 +2,7 @@ import type { BossChallengeResponse, GameState } from "@elysium/types";
import { Hono } from "hono";
import { prisma } from "../db/client.js";
import { authMiddleware } from "../middleware/auth.js";
import { updateChallengeProgress } from "../services/dailyChallenges.js";
export const bossRouter = new Hono();
@@ -173,6 +174,17 @@ bossRouter.post("/challenge", async (context) => {
}
}
// Update daily boss challenge progress
if (state.dailyChallenges) {
const { updatedChallenges, crystalsAwarded } = updateChallengeProgress(
state.dailyChallenges,
"bossesDefeated",
1,
);
state.dailyChallenges = updatedChallenges;
state.resources.crystals += crystalsAwarded;
}
rewards = {
gold: boss.goldReward,
essence: boss.essenceReward,
+4
View File
@@ -6,6 +6,7 @@ import { DEFAULT_ACHIEVEMENTS } from "../data/achievements.js";
import { DEFAULT_ADVENTURERS } from "../data/adventurers.js";
import { DEFAULT_EQUIPMENT } from "../data/equipment.js";
import { authMiddleware } from "../middleware/auth.js";
import { getOrResetDailyChallenges } from "../services/dailyChallenges.js";
import { calculateOfflineEarnings } from "../services/offlineProgress.js";
const RESOURCE_CAP = 1e300;
@@ -327,6 +328,9 @@ gameRouter.get("/load", async (context) => {
state.resources.essence += offlineEssence;
}
// Generate or reset daily challenges if a new day has begun
state.dailyChallenges = getOrResetDailyChallenges(state);
state.lastTickAt = now;
if (needsBackfill || offlineGold > 0 || offlineEssence > 0) {
+21 -1
View File
@@ -3,6 +3,7 @@ import { Hono } from "hono";
import { prisma } from "../db/client.js";
import { authMiddleware } from "../middleware/auth.js";
import { DEFAULT_PRESTIGE_UPGRADES } from "../data/prestigeUpgrades.js";
import { updateChallengeProgress } from "../services/dailyChallenges.js";
import {
buildPostPrestigeState,
computeRunestoneMultipliers,
@@ -37,15 +38,34 @@ prestigeRouter.post("/", async (context) => {
);
}
// Update daily prestige challenge progress before resetting the run
let updatedDailyChallenges = state.dailyChallenges;
let challengeCrystals = 0;
if (updatedDailyChallenges) {
const result = updateChallengeProgress(updatedDailyChallenges, "prestige", 1);
updatedDailyChallenges = result.updatedChallenges;
challengeCrystals = result.crystalsAwarded;
}
const { newState, newPrestigeData, runestonesEarned } = buildPostPrestigeState(
state,
characterName,
);
// Preserve daily challenges across the prestige reset and apply any crystal rewards
const finalState: GameState = {
...newState,
dailyChallenges: updatedDailyChallenges,
resources: {
...newState.resources,
crystals: newState.resources.crystals + challengeCrystals,
},
};
const now = Date.now();
await prisma.gameState.update({
where: { discordId },
data: { state: newState as object, updatedAt: now },
data: { state: finalState as object, updatedAt: now },
});
await prisma.player.update({