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] || }
-
+
);
};