From f01d9d56adb20ceeef83d97cf89b482d954171e1 Mon Sep 17 00:00:00 2001 From: MaksSlyzar Date: Sun, 29 Mar 2026 10:28:07 +0300 Subject: [PATCH] player manager, console update --- client/src/components/Console/Console.jsx | 26 +++++++++-- client/src/context/SocketContext.jsx | 20 ++++++-- client/src/services/ConsoleManager.js | 18 ++++---- client/src/services/PlayerManager.js | 46 +++++++++++++++++++ .../src/views/GameInterface/GameInterface.jsx | 2 +- 5 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 client/src/services/PlayerManager.js diff --git a/client/src/components/Console/Console.jsx b/client/src/components/Console/Console.jsx index 08e7123..7b01929 100644 --- a/client/src/components/Console/Console.jsx +++ b/client/src/components/Console/Console.jsx @@ -2,8 +2,9 @@ import React, { useState, useEffect, useRef } from "react"; import "./Console.css"; import { useSocket } from "../../hooks/useSocket"; import ConsoleManager from "../../services/ConsoleManager"; +import PlayerManager from "../../services/PlayerManager"; // Імпортуємо менеджер гравців -const Console = ({ players = [] }) => { +const Console = () => { const { socket } = useSocket(); const [isOpen, setIsOpen] = useState(false); const [input, setInput] = useState(""); @@ -11,6 +12,8 @@ const Console = ({ players = [] }) => { const [selectedIndex, setSelectedIndex] = useState(0); const [logs, setLogs] = useState(ConsoleManager.logs); + const [playerNames, setPlayerNames] = useState([]); + const inputRef = useRef(null); const scrollRef = useRef(null); @@ -18,6 +21,21 @@ const Console = ({ players = [] }) => { ConsoleManager.init(socket, setLogs); }, [socket]); + useEffect(() => { + const updatePlayers = (data) => { + setPlayerNames([...data.online, ...data.offline]); + }; + + const unsubscribe = PlayerManager.subscribe(updatePlayers); + + setPlayerNames([ + ...PlayerManager.onlinePlayers, + ...PlayerManager.offlinePlayers, + ]); + + return () => unsubscribe(); + }, []); + useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; @@ -36,12 +54,11 @@ const Console = ({ players = [] }) => { }, []); useEffect(() => { - setSuggestions(ConsoleManager.getSuggestions(input, players)); + setSuggestions(ConsoleManager.getSuggestions(input, playerNames)); setSelectedIndex(0); - }, [input, players]); + }, [input, playerNames]); const handleKeyDown = (e) => { - // 1. Tab для автозаповнення if (e.key === "Tab" && suggestions.length > 0) { e.preventDefault(); const parts = input.split(" "); @@ -74,6 +91,7 @@ const Console = ({ players = [] }) => { const handleSubmit = (e) => { e.preventDefault(); + if (!input.trim()) return; ConsoleManager.execute(input); setInput(""); }; diff --git a/client/src/context/SocketContext.jsx b/client/src/context/SocketContext.jsx index 049f8af..47b5df8 100644 --- a/client/src/context/SocketContext.jsx +++ b/client/src/context/SocketContext.jsx @@ -6,6 +6,7 @@ import React, { useEffect, } from "react"; import { io } from "socket.io-client"; +import PlayerManager from "../services/PlayerManager"; // Імпортуємо менеджер export const SocketContext = createContext(null); @@ -19,12 +20,12 @@ export const SocketProvider = ({ children }) => { if (socketRef.current?.connected) { return socketRef.current; } + const userInfo = JSON.parse(localStorage.getItem("user")); + const newSocket = io(url, { - auth: { token, username: userInfo.username }, + auth: { token, username: userInfo?.username }, transports: ["websocket"], - upgrade: true, - withCredentials: true, reconnectionAttempts: 5, }); @@ -33,8 +34,19 @@ export const SocketProvider = ({ children }) => { setIsConnected(true); }); + newSocket.on("session:ready", (data) => { + PlayerManager.setInitialState(data.onlinePlayers, data.offlinePlayers); + }); + + newSocket.on("player:joined", (data) => { + PlayerManager.handlePlayerJoined(data.username); + }); + + newSocket.on("player:left", (data) => { + PlayerManager.handlePlayerLeft(data.username); + }); + newSocket.on("disconnect", (reason) => { - console.log("❌ Disconnected:", reason); setIsConnected(false); }); diff --git a/client/src/services/ConsoleManager.js b/client/src/services/ConsoleManager.js index 6131296..51cd7c4 100644 --- a/client/src/services/ConsoleManager.js +++ b/client/src/services/ConsoleManager.js @@ -2,7 +2,6 @@ import GameDataManager from "./GameDataManager"; class ConsoleManager { constructor() { - // Залишили тільки вказані команди this.commands = ["/clear", "/give", "/set_exp"]; this.logs = ["[System] Console Manager Ready. Press F9 to hide."]; this.history = []; @@ -27,46 +26,45 @@ class ConsoleManager { } getSuggestions(input, players = []) { + const safePlayers = (players || []).filter((p) => typeof p === "string"); + const parts = input.split(" "); if (parts.length === 0) return []; const cmd = parts[0].toLowerCase(); - // 1. Пропозиції команд if (parts.length === 1 && input.startsWith("/")) { return this.commands.filter((c) => c.startsWith(cmd)); } - // 2. Логіка для /give (Гравець -> Предмет) if (cmd === "/give") { if (parts.length === 2) { const search = parts[1].toLowerCase(); - return players.filter((p) => p.toLowerCase().startsWith(search)); + return safePlayers.filter((p) => p.toLowerCase().startsWith(search)); } if (parts.length === 3) { const search = parts[2].toLowerCase(); const itemIds = Array.from(GameDataManager.items.keys()); - return itemIds.filter((id) => id.includes(search)).slice(0, 15); + return itemIds + .filter((id) => id.toLowerCase().includes(search)) + .slice(0, 15); } } - // 3. Логіка для /set_exp (Гравець -> Кількість) if (cmd === "/set_exp") { if (parts.length === 2) { const search = parts[1].toLowerCase(); - return players.filter((p) => p.toLowerCase().startsWith(search)); + return safePlayers.filter((p) => p.toLowerCase().startsWith(search)); } } - // 4. Логіка для /clear (Гравець) if (cmd === "/clear" && parts.length === 2) { const search = parts[1].toLowerCase(); - return players.filter((p) => p.toLowerCase().startsWith(search)); + return safePlayers.filter((p) => p.toLowerCase().startsWith(search)); } return []; } - execute(input) { const trimmed = input.trim(); if (!trimmed) return; diff --git a/client/src/services/PlayerManager.js b/client/src/services/PlayerManager.js new file mode 100644 index 0000000..3f431d9 --- /dev/null +++ b/client/src/services/PlayerManager.js @@ -0,0 +1,46 @@ +class PlayerManager { + constructor() { + this.onlinePlayers = []; + this.offlinePlayers = []; + this.listeners = new Set(); + } + + setInitialState(online, offline) { + this.onlinePlayers = online || []; + this.offlinePlayers = offline || []; + + this.notify(); + } + + handlePlayerJoined(username) { + if (!this.onlinePlayers.includes(username)) { + this.onlinePlayers.push(username); + this.offlinePlayers = this.offlinePlayers.filter((u) => u !== username); + this.notify(); + } + } + + handlePlayerLeft(username) { + this.onlinePlayers = this.onlinePlayers.filter((u) => u !== username); + if (!this.offlinePlayers.includes(username)) { + this.offlinePlayers.push(username); + } + this.notify(); + } + + subscribe(listener) { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + } + + notify() { + this.listeners.forEach((listener) => + listener({ + online: this.onlinePlayers, + offline: this.offlinePlayers, + }), + ); + } +} + +export default new PlayerManager(); diff --git a/client/src/views/GameInterface/GameInterface.jsx b/client/src/views/GameInterface/GameInterface.jsx index 39222a8..393603c 100644 --- a/client/src/views/GameInterface/GameInterface.jsx +++ b/client/src/views/GameInterface/GameInterface.jsx @@ -93,7 +93,7 @@ const GameInterface = ({ onExit }) => { {tabs[activeTab] || } - + ); };