diff --git a/client/src/views/GameInterface/tabs/ChatTab.jsx b/client/src/views/GameInterface/tabs/ChatTab.jsx index 75ada32..76a728d 100644 --- a/client/src/views/GameInterface/tabs/ChatTab.jsx +++ b/client/src/views/GameInterface/tabs/ChatTab.jsx @@ -28,7 +28,6 @@ const ChatTab = () => { useEffect(() => { if (!socket) return; - // Початкові запити socket.emit("friend:get_list"); if (activeChat === "global") { socket.emit("chat:get_global_history"); @@ -89,6 +88,8 @@ const ChatTab = () => { receiverId: activeChat === "global" ? null : activeChat, }); setInputValue(""); + + console.log(activeChat === "global" ? null : activeChat); }; const selectChat = (id) => { diff --git a/client/src/views/GameInterface/tabs/NotificationTab.jsx b/client/src/views/GameInterface/tabs/NotificationTab.jsx index c29b5b1..e57fe3c 100644 --- a/client/src/views/GameInterface/tabs/NotificationTab.jsx +++ b/client/src/views/GameInterface/tabs/NotificationTab.jsx @@ -12,7 +12,6 @@ const NotificationsTab = () => { socket.emit("notifications:get_all"); const handleNewNotify = (notify) => { - console.log(notify); setNotifications((prev) => [notify, ...prev]); }; @@ -31,9 +30,14 @@ const NotificationsTab = () => { const handleAction = (id, action, data) => { if (action === "accept_friend") { - socket.emit("friend:add", { friendId: data.fromId }); + socket.emit("friend:accept", { friendId: data.fromId }); + socket.emit("notification:read", { id }); + } else if (action === "dismiss") { + socket.emit("notification:dismiss", { id }); + } else { + socket.emit("notification:read", { id }); } - socket.emit("notification:read", { id }); + setNotifications((prev) => prev.filter((n) => n.id !== id)); }; @@ -45,6 +49,10 @@ const NotificationsTab = () => { return "fas fa-hammer"; case "system": return "fas fa-robot"; + case "item_received": + return "fas fa-box-open"; + case "inventory_clear": + return "fas fa-trash-alt"; default: return "fas fa-bell"; } @@ -66,7 +74,7 @@ const NotificationsTab = () => { )} {notifications.map((n) => ( -
+
diff --git a/game-server/src/game/AdminManager.js b/game-server/src/game/AdminManager.js new file mode 100644 index 0000000..452112d --- /dev/null +++ b/game-server/src/game/AdminManager.js @@ -0,0 +1,91 @@ +const { Player, Inventory } = require("../models"); +const DatapackLoader = require("../game/DatapackLoader"); +const notificationManager = require("./NotificationManager"); + +class AdminManager { + constructor() { + this.io = null; + } + + init(io) { + this.io = io; + } + + async giveItem(targetName, itemId, amount) { + const targetPlayer = await Player.findOne({ + where: { username: targetName }, + }); + if (!targetPlayer) throw new Error(`Player '${targetName}' not found.`); + + const itemData = DatapackLoader.getItem(itemId); + if (!itemData) throw new Error(`Item ID '${itemId}' does not exist.`); + + const [inventoryItem, created] = await Inventory.findOrCreate({ + where: { playerId: targetPlayer.id, itemId: itemId }, + defaults: { quantity: amount }, + }); + + if (!created) { + await inventoryItem.increment("quantity", { by: amount }); + } + + await notificationManager.send({ + playerId: targetPlayer.id, + type: "item_received", + title: "Items Received", + message: `Admin granted you ${amount}x ${itemData.name || itemId}.`, + data: { itemId, amount }, + priority: "normal", + }); + + this._updatePlayerInventory(targetPlayer.id); + return { targetName, itemId, amount }; + } + + async clearInventory(targetName) { + const targetPlayer = await Player.findOne({ + where: { username: targetName }, + }); + if (!targetPlayer) throw new Error(`Player '${targetName}' not found.`); + + await Inventory.destroy({ where: { playerId: targetPlayer.id } }); + + await notificationManager.send({ + playerId: targetPlayer.id, + type: "inventory_clear", + title: "Inventory Wiped", + message: "Your inventory has been cleared by an administrator.", + priority: "high", + }); + + this._updatePlayerInventory(targetPlayer.id, true); + return targetName; + } + + async reloadData() { + DatapackLoader.loadAll(); + if (this.io) { + this.io.emit("admin:log", "System: Datapacks reloaded by admin."); + } + } + + async _updatePlayerInventory(playerId, isEmpty = false) { + if (!this.io) return; + + const targetSocket = [...this.io.sockets.sockets.values()].find( + (s) => s.user?.id === playerId, + ); + + if (targetSocket) { + const items = isEmpty + ? [] + : await Inventory.findAll({ + where: { playerId }, + attributes: ["itemId", "quantity"], + }); + targetSocket.emit("player:inventory_data", items); + } + } +} + +module.exports = new AdminManager(); diff --git a/game-server/src/game/NotificationManager.js b/game-server/src/game/NotificationManager.js index 62e3da4..9a97e09 100644 --- a/game-server/src/game/NotificationManager.js +++ b/game-server/src/game/NotificationManager.js @@ -1,33 +1,93 @@ const Notification = require("../models/Notification"); class NotificationManager { - async createNotification({ playerId, type, title, message, data = {} }) { + constructor() { + this.io = null; + } + + init(io) { + this.io = io; + console.log("[NotificationManager] Initialized with Socket.io"); + } + + async send({ + playerId, + type = "info", + title, + message, + data = {}, + priority = "normal", + }) { try { - return await Notification.create({ + const notification = await Notification.create({ playerId, type, title, message, data, + priority, + isRead: false, }); + + const targetSocket = this._getSocketByPlayerId(playerId); + + if (targetSocket) { + targetSocket.emit("notification:new", notification); + + const unreadCount = await this.getUnreadCount(playerId); + targetSocket.emit("notifications:unread_count", unreadCount); + } + + return notification; } catch (error) { - console.error("Notify Error:", error); + console.error( + `[NotificationManager] Error sending to ${playerId}:`, + error, + ); } } - async getPlayerNotifications(playerId) { + _getSocketByPlayerId(playerId) { + if (!this.io) return null; + return [...this.io.sockets.sockets.values()].find( + (s) => s.user?.id === playerId, + ); + } + async getPlayerHistory(playerId, limit = 50) { return await Notification.findAll({ where: { playerId }, order: [["createdAt", "DESC"]], - limit: 20, + limit, }); } - async markAsRead(notificationId) { - return await Notification.update( + async getUnreadCount(playerId) { + return await Notification.count({ + where: { playerId, isRead: false }, + }); + } + + async markAsRead(notificationId, playerId) { + await Notification.update( { isRead: true }, - { where: { id: notificationId } }, + { where: { id: notificationId, playerId } }, ); + return await this.getUnreadCount(playerId); + } + + async markAllAsRead(playerId) { + await Notification.update( + { isRead: true }, + { where: { playerId, isRead: false } }, + ); + return 0; + } + + async delete(notificationId, playerId) { + await Notification.destroy({ + where: { id: notificationId, playerId }, + }); + return await this.getUnreadCount(playerId); } } diff --git a/game-server/src/index.js b/game-server/src/index.js index 36759da..f311579 100644 --- a/game-server/src/index.js +++ b/game-server/src/index.js @@ -10,6 +10,8 @@ const DatapackLoader = require("./game/DatapackLoader.js"); const path = require("path"); const app = express(); const economyService = require("./game/EconomyService.js"); +const NotificationManager = require("./game/NotificationManager.js"); +const AdminManager = require("./game/AdminManager.js"); app.use( cors({ @@ -80,6 +82,8 @@ server.listen(config.port, async () => { DatapackLoader.init(datapacksPath, io); await sequelize.initDatabase(); initSockets(io); + NotificationManager.init(io); + AdminManager.init(io); economyService.init(io); await registerInApi(); setInterval(sendHeartbeat, HEARTBEAT_INTERVAL); diff --git a/game-server/src/sockets/handlers/adminHandler.js b/game-server/src/sockets/handlers/adminHandler.js index 716199a..9ab1fbe 100644 --- a/game-server/src/sockets/handlers/adminHandler.js +++ b/game-server/src/sockets/handlers/adminHandler.js @@ -1,7 +1,8 @@ -const { Player, Inventory } = require("../../models"); -const DatapackLoader = require("../../game/DatapackLoader"); +const adminManager = require("../../game/AdminManager"); module.exports = (io, socket) => { + if (!adminManager.io) adminManager.init(io); + const handleAdminCommand = async ({ command }) => { const args = command.trim().split(/\s+/); const cmd = args[0].toLowerCase(); @@ -13,76 +14,33 @@ module.exports = (io, socket) => { const amount = parseInt(amountStr) || 1; if (!targetName || !itemId) { - throw new Error("Usage: /give [player_name] [item_id] [amount]"); - } - - const targetPlayer = await Player.findOne({ - where: { username: targetName }, - }); - if (!targetPlayer) - throw new Error(`Player '${targetName}' not found in database.`); - - const itemData = DatapackLoader.getItem(itemId); - if (!itemData) - throw new Error(`Item ID '${itemId}' does not exist in datapacks.`); - - const [inventoryItem, created] = await Inventory.findOrCreate({ - where: { playerId: targetPlayer.id, itemId: itemId }, - defaults: { quantity: amount }, - }); - - if (!created) { - await inventoryItem.increment("quantity", { by: amount }); + throw new Error("Usage: /give [player] [item] [amount]"); } + const result = await adminManager.giveItem( + targetName, + itemId, + amount, + ); socket.emit( "admin:log", - `Successfully gave ${amount}x [${itemId}] to ${targetName}.`, + `Successfully gave ${result.amount}x [${result.itemId}] to ${result.targetName}.`, ); - - const targetSocket = [...io.sockets.sockets.values()].find( - (s) => s.user?.id === targetPlayer.id, - ); - - if (targetSocket) { - const updatedItems = await Inventory.findAll({ - where: { playerId: targetPlayer.id }, - attributes: ["itemId", "quantity"], - }); - targetSocket.emit("player:inventory_data", updatedItems); - targetSocket.emit( - "admin:log", - `Admin gave you ${amount}x ${itemId}`, - ); - } break; } case "/clear": { const [_, targetName] = args; - const targetPlayer = await Player.findOne({ - where: { username: targetName }, - }); - if (!targetPlayer) throw new Error("Player not found."); + if (!targetName) throw new Error("Usage: /clear [player]"); - await Inventory.destroy({ where: { playerId: targetPlayer.id } }); - - socket.emit( - "admin:log", - `Inventory for ${targetName} has been wiped.`, - ); - - const targetSocket = [...io.sockets.sockets.values()].find( - (s) => s.user?.id === targetPlayer.id, - ); - if (targetSocket) targetSocket.emit("player:inventory_data", []); + const target = await adminManager.clearInventory(targetName); + socket.emit("admin:log", `Inventory for ${target} has been wiped.`); break; } case "/reload_data": { socket.emit("admin:log", "Reloading all datapacks..."); - DatapackLoader.loadAll(); - io.emit("admin:log", "System: Datapacks reloaded by admin."); + await adminManager.reloadData(); break; } @@ -90,7 +48,6 @@ module.exports = (io, socket) => { socket.emit("admin:log", `Unknown admin command: ${cmd}`); } } catch (err) { - console.error("Admin Command Error:", err.message); socket.emit("admin:log", `Error: ${err.message}`); } }; diff --git a/game-server/src/sockets/handlers/chatHandler.js b/game-server/src/sockets/handlers/chatHandler.js index d9cb627..1cdc2b4 100644 --- a/game-server/src/sockets/handlers/chatHandler.js +++ b/game-server/src/sockets/handlers/chatHandler.js @@ -42,6 +42,7 @@ module.exports = (io, socket) => { if (messageData.type === "global") { io.emit("chat:new_message", messageData); } else { + console.log(payload.receiverId); socket .to(`user_${payload.receiverId}`) .emit("chat:new_message", messageData); diff --git a/game-server/src/sockets/handlers/friendHandler.js b/game-server/src/sockets/handlers/friendHandler.js index 6065315..00d2ab3 100644 --- a/game-server/src/sockets/handlers/friendHandler.js +++ b/game-server/src/sockets/handlers/friendHandler.js @@ -51,7 +51,7 @@ module.exports = (io, socket) => { const exists = await Friend.findOne({ where: { playerId: myId, friendId: friendId }, }); - + console.log(myId, friendId); if (!exists) { await Friend.bulkCreate([ { playerId: myId, friendId: friendId }, diff --git a/game-server/src/sockets/handlers/notificationHandler.js b/game-server/src/sockets/handlers/notificationHandler.js index 0f0e1e0..d1d190f 100644 --- a/game-server/src/sockets/handlers/notificationHandler.js +++ b/game-server/src/sockets/handlers/notificationHandler.js @@ -37,11 +37,11 @@ module.exports = (io, socket) => { socket.on("notification:dismiss", async ({ id }) => { try { await Notification.destroy({ - where: { id, playerId: socket.player.id }, + where: { id, playerId: socket.user.id }, }); const unreadCount = await Notification.count({ - where: { playerId: socket.player.id, isRead: false }, + where: { playerId: socket.user.id, isRead: false }, }); socket.emit("notifications:unread_count", unreadCount); } catch (e) {