feat: Implement flexible quest event matching to allow generic triggers to match specific event instances.

This commit is contained in:
syntaxbullet
2026-01-15 15:22:20 +01:00
parent 7d541825d8
commit eb108695d3
2 changed files with 68 additions and 3 deletions

View File

@@ -189,6 +189,68 @@ describe("questService", () => {
expect(mockSet).toHaveBeenCalledWith({ completedAt: expect.any(Date) });
});
it("should progress a quest with generic events", async () => {
const mockUserQuest = {
userId: 1n,
questId: 102,
progress: 0,
completedAt: null,
quest: { triggerEvent: "ITEM_COLLECT", requirements: { target: 5 } }
};
mockFindMany.mockResolvedValue([mockUserQuest]);
mockReturning.mockResolvedValue([{ userId: 1n, questId: 102, progress: 1 }]);
await questService.handleEvent("1", "ITEM_COLLECT:505", 1);
expect(mockUpdate).toHaveBeenCalled();
expect(mockSet).toHaveBeenCalledWith({ progress: 1 });
});
it("should ignore events that are not prefix matches", async () => {
const mockUserQuest = {
userId: 1n,
questId: 103,
progress: 0,
completedAt: null,
quest: { triggerEvent: "ITEM_COLLECT", requirements: { target: 5 } }
};
mockFindMany.mockResolvedValue([mockUserQuest]);
await questService.handleEvent("1", "ITEM_COLLECT_UNRELATED", 1);
expect(mockUpdate).not.toHaveBeenCalled();
});
it("should not progress a specific quest with a different specific event", async () => {
const mockUserQuest = {
userId: 1n,
questId: 104,
progress: 0,
completedAt: null,
quest: { triggerEvent: "ITEM_COLLECT:101", requirements: { target: 5 } }
};
mockFindMany.mockResolvedValue([mockUserQuest]);
await questService.handleEvent("1", "ITEM_COLLECT:202", 1);
expect(mockUpdate).not.toHaveBeenCalled();
});
it("should not progress a specific quest with a generic event", async () => {
const mockUserQuest = {
userId: 1n,
questId: 105,
progress: 0,
completedAt: null,
quest: { triggerEvent: "ITEM_COLLECT:101", requirements: { target: 5 } }
};
mockFindMany.mockResolvedValue([mockUserQuest]);
await questService.handleEvent("1", "ITEM_COLLECT", 1);
expect(mockUpdate).not.toHaveBeenCalled();
});
it("should ignore irrelevant events", async () => {
const mockUserQuest = {
userId: 1n,

View File

@@ -46,9 +46,12 @@ export const questService = {
}
});
const relevant = activeUserQuests.filter(uq =>
uq.quest.triggerEvent === eventName && !uq.completedAt
);
const relevant = activeUserQuests.filter(uq => {
const trigger = uq.quest.triggerEvent;
// Exact match or prefix match (e.g. ITEM_COLLECT matches ITEM_COLLECT:101)
const isMatch = eventName === trigger || eventName.startsWith(trigger + ":");
return isMatch && !uq.completedAt;
});
for (const uq of relevant) {
const requirements = uq.quest.requirements as { target?: number };