Client/game-server/src/game/QuestsManager.js
2026-04-21 08:48:52 +03:00

164 lines
4.3 KiB
JavaScript

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();