added inventory manager and .env for the client
This commit is contained in:
parent
7302a54cd7
commit
42ae328b56
@ -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");
|
||||
|
||||
@ -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) => (
|
||||
<div className="equip-group">
|
||||
<div className="group-label">{title}</div>
|
||||
<div className="equip-list-compact">
|
||||
{groupSlots.map((slot) => (
|
||||
<div
|
||||
key={slot.id}
|
||||
className={`equip-row-mini ${equipment[slot.id] ? "occupied" : ""}`}
|
||||
onClick={() => equipment[slot.id] && unequipItem(slot.id)}
|
||||
>
|
||||
<span className="slot-name-tiny">{slot.label}</span>
|
||||
<div
|
||||
className={`equip-box-mini ${equipment[slot.id]?.rarity || ""}`}
|
||||
>
|
||||
{equipment[slot.id] ? (
|
||||
<img
|
||||
src={equipment[slot.id].textureUrl}
|
||||
alt={slot.id}
|
||||
className="item-img-mini"
|
||||
/>
|
||||
) : (
|
||||
<span className="plus">+</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="inv-adaptive-container">
|
||||
<div className="inv-header-compact">
|
||||
@ -109,30 +145,9 @@ const InventoryTab = () => {
|
||||
<div className="inv-layout-wrapper">
|
||||
<section className="inv-panel loadout">
|
||||
<div className="panel-label">CORE_SYSTEMS</div>
|
||||
<div className="equip-list-compact">
|
||||
{slots.map((slot) => (
|
||||
<div
|
||||
key={slot}
|
||||
className={`equip-row-mini ${equipment[slot] ? "occupied" : ""}`}
|
||||
onClick={() => equipment[slot] && unequipItem(slot)}
|
||||
>
|
||||
<span className="slot-name-tiny">{slot.toUpperCase()}</span>
|
||||
<div
|
||||
className={`equip-box-mini ${equipment[slot]?.rarity || ""}`}
|
||||
>
|
||||
{equipment[slot] ? (
|
||||
<img
|
||||
src={equipment[slot].textureUrl}
|
||||
alt={slot}
|
||||
className="item-img-mini"
|
||||
/>
|
||||
) : (
|
||||
<span className="plus">+</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{renderSlotGroup("PERSON", equipmentSlots.personal)}
|
||||
{renderSlotGroup("SHIP", equipmentSlots.ship)}
|
||||
</section>
|
||||
|
||||
<section className="inv-panel cargo">
|
||||
@ -179,7 +194,8 @@ const InventoryTab = () => {
|
||||
Object.entries(selectedItem.stats).map(([k, v]) => (
|
||||
<div key={k} className="stat-line">
|
||||
<span className="label">
|
||||
{GameDataManager.getStatName(k).toUpperCase()}
|
||||
{GameDataManager.getStatName?.(k)?.toUpperCase() ||
|
||||
k.toUpperCase()}
|
||||
</span>
|
||||
<span className="value">+{v}</span>
|
||||
</div>
|
||||
|
||||
@ -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 (
|
||||
<div className="modal-overlay" onClick={onClose}>
|
||||
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
||||
<button className="modal-close" onClick={onClose}>×</button>
|
||||
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
|
||||
<button className="modal-close" onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
|
||||
<div className="details-view">
|
||||
<div className="details-header">
|
||||
<h4 className={`item-name ${item.rarity}`}>{item.name}</h4>
|
||||
<h4 className={`item-name ${item.rarity}`}>
|
||||
{item.displayName || item.name}
|
||||
</h4>
|
||||
<span className={`rarity-badge ${item.rarity}`}>{item.rarity}</span>
|
||||
</div>
|
||||
|
||||
<p className="item-description">{item.description}</p>
|
||||
|
||||
<div className="item-stats-container">
|
||||
<div className="stat-row quality-row">
|
||||
<span className="stat-label"><i className="fas fa-medal"></i> Quality</span>
|
||||
<span className={`stat-value ${item.rarity}`}>{item.rarity.toUpperCase()}</span>
|
||||
</div>
|
||||
{item.stats && <div className="stat-divider"></div>}
|
||||
{item.stats && Object.entries(item.stats).map(([statName, value]) => (
|
||||
{item.stats &&
|
||||
Object.entries(item.stats).map(([statName, value]) => (
|
||||
<div key={statName} className="stat-row">
|
||||
<span className="stat-label"><i className={getStatIcon(statName)}></i> {formatStatName(statName)}</span>
|
||||
<span className="stat-label">
|
||||
<i className={getStatIcon?.(statName)}></i>{" "}
|
||||
{formatStatName
|
||||
? formatStatName(statName)
|
||||
: statName.toUpperCase()}
|
||||
</span>
|
||||
<span className="stat-value">+{value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="btn-equip" onClick={() => { onEquip(item); onClose(); }}>
|
||||
Equip System
|
||||
{isEquipped ? (
|
||||
<button
|
||||
className="btn-equip unequip"
|
||||
onClick={() => {
|
||||
onUnequip(item.currentSlot);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
DISCONNECT_SYSTEM
|
||||
</button>
|
||||
) : (
|
||||
item.canEquip && (
|
||||
<button
|
||||
className="btn-equip"
|
||||
onClick={() => {
|
||||
onEquip(item);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
INITIALIZE_EQUIP
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -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())
|
||||
|
||||
59
game-server/src/game/InventoryManager.js
Normal file
59
game-server/src/game/InventoryManager.js
Normal file
@ -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();
|
||||
@ -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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user