generated from nhcarrigan/template
fix: unlock exploration areas on zone unlock, correct quest_eternal count, fix quest status filter in timers
This commit is contained in:
@@ -334,8 +334,8 @@ export const defaultAchievements: Array<Achievement> = [
|
|||||||
unlockedAt: null,
|
unlockedAt: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: { amount: 112, type: "questsCompleted" },
|
condition: { amount: 122, type: "questsCompleted" },
|
||||||
description: "Complete all 112 quests across the known multiverse.",
|
description: "Complete all 122 quests across the known multiverse.",
|
||||||
icon: "🌌",
|
icon: "🌌",
|
||||||
id: "quest_eternal",
|
id: "quest_eternal",
|
||||||
name: "Quest Eternal",
|
name: "Quest Eternal",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import { defaultBosses } from "../data/bosses.js";
|
import { defaultBosses } from "../data/bosses.js";
|
||||||
import { defaultEquipmentSets } from "../data/equipmentSets.js";
|
import { defaultEquipmentSets } from "../data/equipmentSets.js";
|
||||||
|
import { defaultExplorations } from "../data/explorations.js";
|
||||||
import { prisma } from "../db/client.js";
|
import { prisma } from "../db/client.js";
|
||||||
import { authMiddleware } from "../middleware/auth.js";
|
import { authMiddleware } from "../middleware/auth.js";
|
||||||
import { updateChallengeProgress } from "../services/dailyChallenges.js";
|
import { updateChallengeProgress } from "../services/dailyChallenges.js";
|
||||||
@@ -284,6 +285,19 @@ bossRouter.post("/challenge", async(context) => {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
zone.status = "unlocked";
|
zone.status = "unlocked";
|
||||||
|
|
||||||
|
// Unlock exploration areas for the newly unlocked zone
|
||||||
|
for (const area of state.exploration?.areas ?? []) {
|
||||||
|
const areaDefinition = defaultExplorations.find((explorationArea) => {
|
||||||
|
return explorationArea.id === area.id;
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
|
/* v8 ignore next 3 -- @preserve */
|
||||||
|
if (areaDefinition?.zoneId === zone.id && area.status === "locked") {
|
||||||
|
area.status = "available";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const updatedZoneBosses = state.bosses.filter((b) => {
|
const updatedZoneBosses = state.bosses.filter((b) => {
|
||||||
return b.zoneId === zone.id;
|
return b.zoneId === zone.id;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -36,10 +36,11 @@ const getQuestTimers = (
|
|||||||
}> => {
|
}> => {
|
||||||
return state.quests.
|
return state.quests.
|
||||||
filter((quest) => {
|
filter((quest) => {
|
||||||
return quest.status === "in_progress" && quest.startedAt !== undefined;
|
return quest.status === "active" && quest.startedAt !== undefined;
|
||||||
}).
|
}).
|
||||||
map((quest) => {
|
map((quest) => {
|
||||||
const durationMs = quest.durationSeconds * 1000;
|
const durationMs = quest.durationSeconds * 1000;
|
||||||
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
/* v8 ignore next -- @preserve */
|
/* v8 ignore next -- @preserve */
|
||||||
const endsAt = (quest.startedAt ?? 0) + durationMs;
|
const endsAt = (quest.startedAt ?? 0) + durationMs;
|
||||||
return {
|
return {
|
||||||
@@ -71,6 +72,7 @@ const getExplorationTimers = (
|
|||||||
return area.status === "in_progress" && area.endsAt !== undefined;
|
return area.status === "in_progress" && area.endsAt !== undefined;
|
||||||
}).
|
}).
|
||||||
map((area) => {
|
map((area) => {
|
||||||
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
||||||
/* v8 ignore next -- @preserve */
|
/* v8 ignore next -- @preserve */
|
||||||
const endsAt = area.endsAt ?? 0;
|
const endsAt = area.endsAt ?? 0;
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -294,6 +294,52 @@ describe("boss route", () => {
|
|||||||
expect(body.won).toBe(true);
|
expect(body.won).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("handles zone unlock gracefully when exploration state is undefined", async () => {
|
||||||
|
const state = makeState({
|
||||||
|
bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"],
|
||||||
|
adventurers: [makeAdventurer()] as GameState["adventurers"],
|
||||||
|
zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"],
|
||||||
|
quests: [],
|
||||||
|
exploration: undefined,
|
||||||
|
});
|
||||||
|
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||||
|
vi.mocked(prisma.gameState.update).mockResolvedValueOnce({} as never);
|
||||||
|
const res = await challenge({ bossId: "test_boss" });
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
const body = await res.json() as { won: boolean };
|
||||||
|
expect(body.won).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unlocks exploration areas when a zone is unlocked on boss defeat", async () => {
|
||||||
|
const state = makeState({
|
||||||
|
bosses: [makeBoss({ currentHp: 100, maxHp: 100, damagePerSecond: 1 })] as GameState["bosses"],
|
||||||
|
adventurers: [makeAdventurer()] as GameState["adventurers"],
|
||||||
|
zones: [{ id: "test_zone", status: "locked", unlockBossId: "test_boss", unlockQuestId: null }] as GameState["zones"],
|
||||||
|
quests: [],
|
||||||
|
exploration: {
|
||||||
|
areas: [{ id: "test_area", status: "locked" as const }],
|
||||||
|
materials: [],
|
||||||
|
craftedRecipeIds: [],
|
||||||
|
craftedGoldMultiplier: 1,
|
||||||
|
craftedEssenceMultiplier: 1,
|
||||||
|
craftedClickMultiplier: 1,
|
||||||
|
craftedCombatMultiplier: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
vi.mocked(prisma.gameState.findUnique).mockResolvedValueOnce({ state } as never);
|
||||||
|
let savedState: GameState | undefined;
|
||||||
|
vi.mocked(prisma.gameState.update).mockImplementationOnce(async (args) => {
|
||||||
|
/* eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Test assertion */
|
||||||
|
savedState = (args as { data: { state: GameState } }).data.state;
|
||||||
|
return {} as never;
|
||||||
|
});
|
||||||
|
const res = await challenge({ bossId: "test_boss" });
|
||||||
|
expect(res.status).toBe(200);
|
||||||
|
// Exploration area should remain locked — no matching defaultExploration for "test_area"
|
||||||
|
const area = savedState?.exploration?.areas.find((a) => a.id === "test_area");
|
||||||
|
expect(area?.status).toBe("locked");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns 500 when the database throws", async () => {
|
it("returns 500 when the database throws", async () => {
|
||||||
vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error"));
|
vi.mocked(prisma.gameState.findUnique).mockRejectedValueOnce(new Error("DB error"));
|
||||||
const res = await challenge({ bossId: "test_boss" });
|
const res = await challenge({ bossId: "test_boss" });
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ describe("timers route", () => {
|
|||||||
{
|
{
|
||||||
id: "q1",
|
id: "q1",
|
||||||
name: "Forest Patrol",
|
name: "Forest Patrol",
|
||||||
status: "in_progress",
|
status: "active",
|
||||||
startedAt: startedAt,
|
startedAt: startedAt,
|
||||||
durationSeconds: 600,
|
durationSeconds: 600,
|
||||||
},
|
},
|
||||||
@@ -110,7 +110,7 @@ describe("timers route", () => {
|
|||||||
{
|
{
|
||||||
id: "q1",
|
id: "q1",
|
||||||
name: "Old Quest",
|
name: "Old Quest",
|
||||||
status: "in_progress",
|
status: "active",
|
||||||
startedAt: startedAt,
|
startedAt: startedAt,
|
||||||
durationSeconds: 600,
|
durationSeconds: 600,
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user