const DatapackLoader = require("./DatapackLoader"); const { PlayerQuest, Player, Inventory, sequelize } = require("../models"); class QuestsManager { async onPlayerLogin(playerId, socket = null) { try { await this.checkAutoQuests(playerId, socket); await this.trackProgress(playerId, "LOGIN", null, 1, socket); } catch (err) { console.error(err); } } async checkAutoQuests(playerId, socket = null) { const allQuests = Array.from(DatapackLoader.registry.quests); const player = await Player.findByPk(playerId); for (const quest of allQuests) { if (quest.meta?.autoAccept && player.level >= (quest.minLevel || 0)) { const [pq, created] = await PlayerQuest.findOrCreate({ where: { playerId, questId: quest.id }, defaults: { status: "active", progress: quest.objectives.map((obj) => ({ ...obj, currentAmount: 0, })), }, }); if (created && socket) { socket.emit("quest:new", { id: pq.questId, status: pq.status, objectives: pq.progress, }); } } } } async trackProgress(playerId, type, targetId, amount = 1, socket = null) { try { const activeQuests = await PlayerQuest.findAll({ where: { playerId, status: "active" }, }); for (const pq of activeQuests) { const staticData = DatapackLoader.getQuest(pq.questId); if (!staticData) continue; let isChanged = false; const currentProgress = pq.progress; const updatedProgress = currentProgress.map((obj) => { if ( obj.type === type && (obj.targetId === targetId || type === "LOGIN") ) { if (obj.currentAmount < obj.requiredAmount) { obj.currentAmount = Math.min( obj.currentAmount + amount, obj.requiredAmount, ); isChanged = true; } } return obj; }); if (isChanged) { const isReady = updatedProgress.every( (obj) => obj.currentAmount >= obj.requiredAmount, ); await pq.update({ progress: updatedProgress, status: isReady ? "ready" : "active", }); if (socket) { socket.emit("quest:update", { id: pq.questId, status: isReady ? "ready" : "active", objectives: updatedProgress, }); } if (isReady && staticData.meta?.autoComplete) { await this.claimRewards(playerId, pq.questId); } } } } catch (err) { console.error(err); } } async claimRewards(playerId, questId) { const t = await sequelize.transaction(); try { const pq = await PlayerQuest.findOne({ where: { playerId, questId, status: "ready" }, transaction: t, lock: t.LOCK.UPDATE, }); if (!pq) { await t.rollback(); throw new Error("QUEST_NOT_READY_OR_CLAIMED"); } const staticData = DatapackLoader.getQuest(questId); const player = await Player.findByPk(playerId, { transaction: t }); const rewards = staticData.rewards; await pq.update({ status: "completed" }, { transaction: t }); if (rewards.credits) { await player.increment("credits", { by: rewards.credits, transaction: t, }); } if (rewards.xp) { await player.increment("experience", { by: rewards.xp, transaction: t, }); } if (rewards.items?.length > 0) { for (const item of rewards.items) { const [invItem] = await Inventory.findOrCreate({ where: { playerId, itemId: item.id }, defaults: { quantity: 0 }, transaction: t, }); await invItem.increment("quantity", { by: item.count, transaction: t, }); } } await t.commit(); const updatedPlayer = await player.reload(); return { success: true, rewards, newTotalCredits: updatedPlayer.credits, }; } catch (err) { if (t) await t.rollback(); throw err; } } } module.exports = new QuestsManager();