diff --git a/client/src/context/AuthContext.jsx b/client/src/context/AuthContext.jsx index dd7c282..0f39e8f 100644 --- a/client/src/context/AuthContext.jsx +++ b/client/src/context/AuthContext.jsx @@ -4,7 +4,7 @@ import axios from "axios"; export const AuthContext = createContext(); export const AuthProvider = ({ children }) => { - const API_URL = "http://localhost:3000/api/auth"; + const API_URL = `${import.meta.env.API_URL}/api/auth`; const [user, setUser] = useState(() => { const savedUser = localStorage.getItem("user"); diff --git a/client/src/views/GameInterface/tabs/InventoryTab.jsx b/client/src/views/GameInterface/tabs/InventoryTab.jsx index 98394ba..e13bc7b 100644 --- a/client/src/views/GameInterface/tabs/InventoryTab.jsx +++ b/client/src/views/GameInterface/tabs/InventoryTab.jsx @@ -3,6 +3,7 @@ import { useSocket } from "../../../hooks/useSocket"; import GameDataManager from "../../../services/GameDataManager.js"; import Button from "../../../components/ui/Button"; import "./styles/InventoryTab.css"; +import { getServerUrl } from "../../../config/api.js"; const InventoryTab = () => { const { socket } = useSocket(); @@ -10,19 +11,24 @@ const InventoryTab = () => { const [equipment, setEquipment] = useState({}); const [selectedItem, setSelectedItem] = useState(null); const [showModal, setShowModal] = useState(false); + const CONNECT_URL = getServerUrl(); + const ASSET_BASE_URL = `${CONNECT_URL}/static/`; - const ASSET_BASE_URL = "http://localhost:5003/static/"; - - const slots = [ - "helmet", - "armor", - "weapon", - "pants", - "boots", - "hands", - "accessory", - "shipHull", - ]; + const equipmentSlots = { + personal: [ + { id: "personal_helmet", label: "HELMET" }, + { id: "personal_suit", label: "SUIT" }, + { id: "personal_gloves", label: "GLOVES" }, + { id: "personal_boots", label: "BOOTS" }, + { id: "personal_backpack", label: "BACKPACK" }, + { id: "personal_weapons", label: "WEAPON" }, + ], + ship: [ + { id: "ship_hull", label: "HULL" }, + { id: "ship_shields", label: "SHIELDS" }, + { id: "ship_engines", label: "ENGINES" }, + ], + }; const getFullTextureUrl = (path) => { if (!path) return "/assets/no-image.png"; @@ -95,6 +101,36 @@ const InventoryTab = () => { socket.emit("player:unequip_item", { slot }); }; + const renderSlotGroup = (title, groupSlots) => ( +
+
{title}
+
+ {groupSlots.map((slot) => ( +
equipment[slot.id] && unequipItem(slot.id)} + > + {slot.label} +
+ {equipment[slot.id] ? ( + {slot.id} + ) : ( + + + )} +
+
+ ))} +
+
+ ); + return (
@@ -109,30 +145,9 @@ const InventoryTab = () => {
CORE_SYSTEMS
-
- {slots.map((slot) => ( -
equipment[slot] && unequipItem(slot)} - > - {slot.toUpperCase()} -
- {equipment[slot] ? ( - {slot} - ) : ( - + - )} -
-
- ))} -
+ + {renderSlotGroup("PERSON", equipmentSlots.personal)} + {renderSlotGroup("SHIP", equipmentSlots.ship)}
@@ -179,7 +194,8 @@ const InventoryTab = () => { Object.entries(selectedItem.stats).map(([k, v]) => (
- {GameDataManager.getStatName(k).toUpperCase()} + {GameDataManager.getStatName?.(k)?.toUpperCase() || + k.toUpperCase()} +{v}
diff --git a/client/src/views/GameInterface/tabs/components/ItemModal.jsx b/client/src/views/GameInterface/tabs/components/ItemModal.jsx index 76d9faf..4fd4a75 100644 --- a/client/src/views/GameInterface/tabs/components/ItemModal.jsx +++ b/client/src/views/GameInterface/tabs/components/ItemModal.jsx @@ -1,39 +1,72 @@ -import React from 'react'; -import './ItemModal.css'; +import React from "react"; +import "./ItemModal.css"; -const ItemModal = ({ item, onClose, onEquip, getStatIcon, formatStatName }) => { +const ItemModal = ({ + item, + onClose, + onEquip, + onUnequip, + isEquipped, + getStatIcon, + formatStatName, +}) => { if (!item) return null; return (
-
e.stopPropagation()}> - +
e.stopPropagation()}> +
-

{item.name}

+

+ {item.displayName || item.name} +

{item.rarity}

{item.description}

-
- Quality - {item.rarity.toUpperCase()} -
- {item.stats &&
} - {item.stats && Object.entries(item.stats).map(([statName, value]) => ( -
- {formatStatName(statName)} - +{value} -
- ))} + {item.stats && + Object.entries(item.stats).map(([statName, value]) => ( +
+ + {" "} + {formatStatName + ? formatStatName(statName) + : statName.toUpperCase()} + + +{value} +
+ ))}
- + {isEquipped ? ( + + ) : ( + item.canEquip && ( + + ) + )}
diff --git a/client/src/views/MainMenu/sections/ServerSection.jsx b/client/src/views/MainMenu/sections/ServerSection.jsx index 7a77478..50183f5 100644 --- a/client/src/views/MainMenu/sections/ServerSection.jsx +++ b/client/src/views/MainMenu/sections/ServerSection.jsx @@ -7,18 +7,17 @@ const ServerSection = ({ onBack, onSelect }) => { const [servers, setServers] = useState([]); const [loading, setLoading] = useState(false); const [joiningId, setJoiningId] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); // Стан для пошуку + const [searchTerm, setSearchTerm] = useState(""); const fetchServers = async () => { setLoading(true); const startTime = Date.now(); try { - const response = await axios.get( - "http://localhost:3000/api/servers/list", - ); + const API_URL = import.meta.env.API_URL; + const response = await axios.get(`${API_URL}/api/servers/list`); const elapsedTime = Date.now() - startTime; - const remainingTime = Math.max(0, 2000 - elapsedTime); // Трохи зменшив штучну затримку + const remainingTime = Math.max(0, 2000 - elapsedTime); await new Promise((resolve) => setTimeout(resolve, remainingTime)); setServers(response.data); @@ -59,7 +58,6 @@ const ServerSection = ({ onBack, onSelect }) => { fetchServers(); }, []); - // Фільтрація серверів на основі searchTerm const filteredServers = servers.filter((server) => searchTerm.length > 0 ? server.name?.toLowerCase().includes(searchTerm.toLowerCase()) diff --git a/game-server/src/game/InventoryManager.js b/game-server/src/game/InventoryManager.js new file mode 100644 index 0000000..100e5c4 --- /dev/null +++ b/game-server/src/game/InventoryManager.js @@ -0,0 +1,59 @@ +const { Player, Inventory } = require("../models"); +const DatapackLoader = require("./DatapackLoader"); + +class InventoryManager { + async getInventory(playerId) { + return await Inventory.findAll({ + where: { playerId }, + attributes: ["itemId", "quantity"], + }); + } + + async getEquipment(playerId) { + const player = await Player.findByPk(playerId); + if (!player) return {}; + + const slots = [ + "personal_helmet", + "personal_suit", + "personal_gloves", + "personal_backpack", + "personal_boots", + "personal_weapons", + "ship_hull", + "ship_shields", + "ship_engines", + ]; + + const equipment = {}; + slots.forEach((slot) => { + equipment[slot] = player[`equipped_${slot}`] || null; + }); + return equipment; + } + + async equipItem(playerId, itemId, slot) { + const hasItem = await Inventory.findOne({ where: { playerId, itemId } }); + if (!hasItem) throw new Error("ITEM_NOT_FOUND"); + + const itemInfo = DatapackLoader.getItem(itemId); + if (!itemInfo) throw new Error("INVALID_ITEM_DATA"); + + const allowedSlot = itemInfo.meta?.equipmentSlot; + if (allowedSlot !== slot) { + throw new Error("INVALID_SLOT_FOR_ITEM"); + } + + const dbField = `equipped_${slot}`.replace("original:", ""); + await Player.update({ [dbField]: itemId }, { where: { id: playerId } }); + return itemInfo; + } + + async unequipItem(playerId, slot) { + const dbField = `equipped_${slot}`; + await Player.update({ [dbField]: null }, { where: { id: playerId } }); + return true; + } +} + +module.exports = new InventoryManager(); diff --git a/game-server/src/game/SessionManager.js b/game-server/src/game/SessionManager.js index cd938ee..74e9188 100644 --- a/game-server/src/game/SessionManager.js +++ b/game-server/src/game/SessionManager.js @@ -16,19 +16,31 @@ class SessionManager { sceneData: null, joinedAt: Date.now(), equipment: { - weapon: playerRaw.equippedWeapon, - armor: playerRaw.equippedArmor, - engine: playerRaw.equippedEngine, - shield: playerRaw.equippedShield, - helmet: playerRaw.equippedHelmet, - boots: playerRaw.equippedBoots, - hands: playerRaw.equippedHands, - pants: playerRaw.equippedPants, - accessory: playerRaw.equippedAccessory, + personal_helmet: playerRaw.equipped_personal_helmet, + personal_suit: playerRaw.equipped_personal_suit, + personal_gloves: playerRaw.equipped_personal_gloves, + personal_backpack: playerRaw.equipped_personal_backpack, + personal_boots: playerRaw.equipped_personal_boots, + personal_weapons: playerRaw.equipped_personal_weapons, + + ship_hull: playerRaw.equipped_ship_hull, + ship_shields: playerRaw.equipped_ship_shields, + ship_engines: playerRaw.equipped_ship_engines, + ship_weapon_1: playerRaw.equipped_ship_weapon_1, + ship_weapon_2: playerRaw.equipped_ship_weapon_2, }, }); } + updateEquipment(socketId, slot, itemId) { + const session = this.sessions.get(socketId); + if (session && session.equipment.hasOwnProperty(slot)) { + session.equipment[slot] = itemId; + } else if (session) { + session.equipment[slot] = itemId; + } + } + setPlayerScene(socketId, sceneName, data = null) { const session = this.sessions.get(socketId); if (session) { @@ -43,13 +55,6 @@ class SessionManager { return this.sessions.get(socketId); } - updateEquipment(socketId, slot, itemId) { - const session = this.sessions.get(socketId); - if (session) { - session.equipment[slot] = itemId; - } - } - updateStatus(socketId, newStatus, location = null) { const session = this.sessions.get(socketId); if (session) { diff --git a/game-server/src/models/Player.js b/game-server/src/models/Player.js index 5c708ec..9679098 100644 --- a/game-server/src/models/Player.js +++ b/game-server/src/models/Player.js @@ -42,14 +42,22 @@ const Player = sequelize.define("Player", { type: DataTypes.DATE, defaultValue: DataTypes.NOW, }, - equippedWeapon: { type: DataTypes.STRING }, - equippedArmor: { type: DataTypes.STRING }, - equippedEngine: { type: DataTypes.STRING }, - equippedAccessory: { type: DataTypes.STRING }, - equippedHelmet: { type: DataTypes.STRING }, - equippedBoots: { type: DataTypes.STRING }, - equippedHands: { type: DataTypes.STRING }, - equippedPants: { type: DataTypes.STRING }, + + equipped_personal_helmet: { type: DataTypes.STRING }, + equipped_personal_suit: { type: DataTypes.STRING }, + equipped_personal_gloves: { type: DataTypes.STRING }, + equipped_personal_backpack: { type: DataTypes.STRING }, + equipped_personal_boots: { type: DataTypes.STRING }, + equipped_personal_weapons: { type: DataTypes.STRING }, + + equipped_personal_accessory_1: { type: DataTypes.STRING }, + equipped_personal_accessory_2: { type: DataTypes.STRING }, + + equipped_ship_hull: { type: DataTypes.STRING }, + equipped_ship_shields: { type: DataTypes.STRING }, + equipped_ship_engines: { type: DataTypes.STRING }, + equipped_ship_weapon_1: { type: DataTypes.STRING }, + equipped_ship_weapon_2: { type: DataTypes.STRING }, }); module.exports = Player; diff --git a/game-server/src/sockets/handlers/inventoryHandler.js b/game-server/src/sockets/handlers/inventoryHandler.js index e5a31ae..074ecea 100644 --- a/game-server/src/sockets/handlers/inventoryHandler.js +++ b/game-server/src/sockets/handlers/inventoryHandler.js @@ -1,82 +1,35 @@ -const { Player, Inventory } = require("../../models"); +const inventoryManager = require("../../game/InventoryManager"); const sessionManager = require("../../game/SessionManager"); module.exports = (io, socket) => { const userId = socket.user?.id; - if (!userId) return; socket.on("player:get_inventory", async () => { try { - const userItems = await Inventory.findAll({ - where: { playerId: userId }, - attributes: ["itemId", "quantity"], - }); - - socket.emit("player:inventory_data", userItems); + const items = await inventoryManager.getInventory(userId); + socket.emit("player:inventory_data", items); } catch (err) { - console.error("Inventory Fetch Error:", err.message); - socket.emit("error", { message: "Failed to load inventory" }); + socket.emit("error", { message: "LOAD_INVENTORY_FAILED" }); } }); socket.on("player:get_equipment", async () => { try { - const player = await Player.findByPk(userId); - if (!player) return; - - const slots = [ - "Weapon", - "Armor", - "Helmet", - "Boots", - "Hands", - "Pants", - "Engine", - "Accessory", - ]; - const equipment = {}; - - slots.forEach((slot) => { - const itemId = player[`equipped${slot}`]; - equipment[slot.toLowerCase()] = itemId || null; - }); - + const equipment = await inventoryManager.getEquipment(userId); socket.emit("player:equipment_data", equipment); } catch (err) { - console.error("Equipment Fetch Error:", err.message); + console.error(err); } }); socket.on("player:equip_item", async ({ itemId, slot }) => { try { - const hasItem = await Inventory.findOne({ - where: { playerId: userId, itemId: itemId }, - }); - - if (!hasItem) { - return socket.emit("error", { message: "You don't own this item" }); - } - - const DatapackLoader = require("../../game/DatapackLoader"); - const itemInfo = DatapackLoader.getItem(itemId); - if (!itemInfo) return; - - if (slot === "weapon" && itemInfo.type !== "weapon") - return socket.emit("error", { message: "Not a weapon" }); - if ( - ["helmet", "armor", "boots", "pants", "hands"].includes(slot) && - itemInfo.type !== "armour" - ) { - return socket.emit("error", { message: "Not armor" }); - } - - const slotField = `equipped${slot.charAt(0).toUpperCase() + slot.slice(1)}`; - await Player.update({ [slotField]: itemId }, { where: { id: userId } }); + const itemInfo = await inventoryManager.equipItem(userId, itemId, slot); sessionManager.updateEquipment(socket.id, slot, itemId); - socket.emit("player:item_equipped", { slot, itemId: itemId }); + socket.emit("player:item_equipped", { slot, itemId }); socket.broadcast.emit("player:visible_changed", { playerId: userId, @@ -84,21 +37,23 @@ module.exports = (io, socket) => { texturePath: itemInfo.texture, }); } catch (err) { - console.error("Equip Error:", err.message); - socket.emit("error", { message: "Equip failed" }); + socket.emit("error", { message: err.message }); } }); socket.on("player:unequip_item", async ({ slot }) => { try { - const slotField = `equipped${slot.charAt(0).toUpperCase() + slot.slice(1)}`; - await Player.update({ [slotField]: null }, { where: { id: userId } }); - + await inventoryManager.unequipItem(userId, slot); sessionManager.updateEquipment(socket.id, slot, null); socket.emit("player:item_unequipped", { slot }); + socket.broadcast.emit("player:visible_changed", { + playerId: userId, + slot, + texturePath: null, + }); } catch (err) { - console.error("Unequip Error:", err.message); + console.error(err); } }); };