forked from syntaxbullet/aurorabot
feat: Implement quest event handling and integrate it into leveling, economy, and inventory services.
This commit is contained in:
@@ -196,6 +196,10 @@ export const economyService = {
|
|||||||
description: description,
|
description: description,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Trigger Quest Event
|
||||||
|
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||||
|
await questService.handleEvent(id, type, 1, txFn);
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}, tx);
|
}, tx);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ export const inventoryService = {
|
|||||||
eq(inventory.itemId, itemId)
|
eq(inventory.itemId, itemId)
|
||||||
))
|
))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
// Trigger Quest Event
|
||||||
|
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||||
|
await questService.handleEvent(userId, 'ITEM_COLLECT', Number(quantity), txFn);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
} else {
|
} else {
|
||||||
// Check Slot Limit
|
// Check Slot Limit
|
||||||
@@ -60,6 +65,11 @@ export const inventoryService = {
|
|||||||
quantity: quantity,
|
quantity: quantity,
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
// Trigger Quest Event
|
||||||
|
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||||
|
await questService.handleEvent(userId, 'ITEM_COLLECT', Number(quantity), txFn);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
}, tx);
|
}, tx);
|
||||||
@@ -179,6 +189,10 @@ export const inventoryService = {
|
|||||||
await inventoryService.removeItem(userId, itemId, 1n, txFn);
|
await inventoryService.removeItem(userId, itemId, 1n, txFn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trigger Quest Event
|
||||||
|
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||||
|
await questService.handleEvent(userId, 'ITEM_USE', 1, txFn);
|
||||||
|
|
||||||
return { success: true, results, usageData, item };
|
return { success: true, results, usageData, item };
|
||||||
}, tx);
|
}, tx);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -68,6 +68,10 @@ export const levelingService = {
|
|||||||
.where(eq(users.id, BigInt(id)))
|
.where(eq(users.id, BigInt(id)))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
// Trigger Quest Event
|
||||||
|
const { questService } = await import("@shared/modules/quest/quest.service");
|
||||||
|
await questService.handleEvent(id, 'XP_GAIN', Number(amount), txFn);
|
||||||
|
|
||||||
return { user: updatedUser, levelUp, currentLevel: newLevel };
|
return { user: updatedUser, levelUp, currentLevel: newLevel };
|
||||||
}, tx);
|
}, tx);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -148,4 +148,60 @@ describe("questService", () => {
|
|||||||
expect(result).toEqual(mockData as any);
|
expect(result).toEqual(mockData as any);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("handleEvent", () => {
|
||||||
|
it("should progress a quest", async () => {
|
||||||
|
const mockUserQuest = {
|
||||||
|
userId: 1n,
|
||||||
|
questId: 101,
|
||||||
|
progress: 0,
|
||||||
|
completedAt: null,
|
||||||
|
quest: { triggerEvent: "TEST_EVENT", requirements: { target: 5 } }
|
||||||
|
};
|
||||||
|
mockFindMany.mockResolvedValue([mockUserQuest]);
|
||||||
|
mockReturning.mockResolvedValue([{ userId: 1n, questId: 101, progress: 1 }]);
|
||||||
|
|
||||||
|
await questService.handleEvent("1", "TEST_EVENT", 1);
|
||||||
|
|
||||||
|
expect(mockUpdate).toHaveBeenCalled();
|
||||||
|
expect(mockSet).toHaveBeenCalledWith({ progress: 1 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should complete a quest when target reached", async () => {
|
||||||
|
const mockUserQuest = {
|
||||||
|
userId: 1n,
|
||||||
|
questId: 101,
|
||||||
|
progress: 4,
|
||||||
|
completedAt: null,
|
||||||
|
quest: {
|
||||||
|
triggerEvent: "TEST_EVENT",
|
||||||
|
requirements: { target: 5 },
|
||||||
|
rewards: { balance: 100 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mockFindMany.mockResolvedValue([mockUserQuest]);
|
||||||
|
mockFindFirst.mockResolvedValue(mockUserQuest); // For completeQuest
|
||||||
|
|
||||||
|
await questService.handleEvent("1", "TEST_EVENT", 1);
|
||||||
|
|
||||||
|
// Verify completeQuest was called (it will update completedAt)
|
||||||
|
expect(mockUpdate).toHaveBeenCalled();
|
||||||
|
expect(mockSet).toHaveBeenCalledWith({ completedAt: expect.any(Date) });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should ignore irrelevant events", async () => {
|
||||||
|
const mockUserQuest = {
|
||||||
|
userId: 1n,
|
||||||
|
questId: 101,
|
||||||
|
progress: 0,
|
||||||
|
completedAt: null,
|
||||||
|
quest: { triggerEvent: "DIFFERENT_EVENT", requirements: { target: 5 } }
|
||||||
|
};
|
||||||
|
mockFindMany.mockResolvedValue([mockUserQuest]);
|
||||||
|
|
||||||
|
await questService.handleEvent("1", "TEST_EVENT", 1);
|
||||||
|
|
||||||
|
expect(mockUpdate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,37 @@ export const questService = {
|
|||||||
}, tx);
|
}, tx);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleEvent: async (userId: string, eventName: string, weight: number = 1, tx?: Transaction) => {
|
||||||
|
return await withTransaction(async (txFn) => {
|
||||||
|
// 1. Fetch active user quests for this event
|
||||||
|
const activeUserQuests = await txFn.query.userQuests.findMany({
|
||||||
|
where: and(
|
||||||
|
eq(userQuests.userId, BigInt(userId)),
|
||||||
|
),
|
||||||
|
with: {
|
||||||
|
quest: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const relevant = activeUserQuests.filter(uq =>
|
||||||
|
uq.quest.triggerEvent === eventName && !uq.completedAt
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const uq of relevant) {
|
||||||
|
const requirements = uq.quest.requirements as { target?: number };
|
||||||
|
const target = requirements?.target || 1;
|
||||||
|
|
||||||
|
const newProgress = (uq.progress || 0) + weight;
|
||||||
|
|
||||||
|
if (newProgress >= target) {
|
||||||
|
await questService.completeQuest(userId, uq.questId, txFn);
|
||||||
|
} else {
|
||||||
|
await questService.updateProgress(userId, uq.questId, newProgress, txFn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, tx);
|
||||||
|
},
|
||||||
|
|
||||||
completeQuest: async (userId: string, questId: number, tx?: Transaction) => {
|
completeQuest: async (userId: string, questId: number, tx?: Transaction) => {
|
||||||
return await withTransaction(async (txFn) => {
|
return await withTransaction(async (txFn) => {
|
||||||
const userQuest = await txFn.query.userQuests.findFirst({
|
const userQuest = await txFn.query.userQuests.findFirst({
|
||||||
|
|||||||
Reference in New Issue
Block a user