player manager, console update

This commit is contained in:
MaksSlyzar 2026-03-29 10:28:07 +03:00
parent 57a7accdd7
commit f01d9d56ad
5 changed files with 93 additions and 19 deletions

View File

@ -2,8 +2,9 @@ import React, { useState, useEffect, useRef } from "react";
import "./Console.css"; import "./Console.css";
import { useSocket } from "../../hooks/useSocket"; import { useSocket } from "../../hooks/useSocket";
import ConsoleManager from "../../services/ConsoleManager"; import ConsoleManager from "../../services/ConsoleManager";
import PlayerManager from "../../services/PlayerManager"; // Імпортуємо менеджер гравців
const Console = ({ players = [] }) => { const Console = () => {
const { socket } = useSocket(); const { socket } = useSocket();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [input, setInput] = useState(""); const [input, setInput] = useState("");
@ -11,6 +12,8 @@ const Console = ({ players = [] }) => {
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
const [logs, setLogs] = useState(ConsoleManager.logs); const [logs, setLogs] = useState(ConsoleManager.logs);
const [playerNames, setPlayerNames] = useState([]);
const inputRef = useRef(null); const inputRef = useRef(null);
const scrollRef = useRef(null); const scrollRef = useRef(null);
@ -18,6 +21,21 @@ const Console = ({ players = [] }) => {
ConsoleManager.init(socket, setLogs); ConsoleManager.init(socket, setLogs);
}, [socket]); }, [socket]);
useEffect(() => {
const updatePlayers = (data) => {
setPlayerNames([...data.online, ...data.offline]);
};
const unsubscribe = PlayerManager.subscribe(updatePlayers);
setPlayerNames([
...PlayerManager.onlinePlayers,
...PlayerManager.offlinePlayers,
]);
return () => unsubscribe();
}, []);
useEffect(() => { useEffect(() => {
if (scrollRef.current) { if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight; scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
@ -36,12 +54,11 @@ const Console = ({ players = [] }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
setSuggestions(ConsoleManager.getSuggestions(input, players)); setSuggestions(ConsoleManager.getSuggestions(input, playerNames));
setSelectedIndex(0); setSelectedIndex(0);
}, [input, players]); }, [input, playerNames]);
const handleKeyDown = (e) => { const handleKeyDown = (e) => {
// 1. Tab для автозаповнення
if (e.key === "Tab" && suggestions.length > 0) { if (e.key === "Tab" && suggestions.length > 0) {
e.preventDefault(); e.preventDefault();
const parts = input.split(" "); const parts = input.split(" ");
@ -74,6 +91,7 @@ const Console = ({ players = [] }) => {
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
if (!input.trim()) return;
ConsoleManager.execute(input); ConsoleManager.execute(input);
setInput(""); setInput("");
}; };

View File

@ -6,6 +6,7 @@ import React, {
useEffect, useEffect,
} from "react"; } from "react";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import PlayerManager from "../services/PlayerManager"; // Імпортуємо менеджер
export const SocketContext = createContext(null); export const SocketContext = createContext(null);
@ -19,12 +20,12 @@ export const SocketProvider = ({ children }) => {
if (socketRef.current?.connected) { if (socketRef.current?.connected) {
return socketRef.current; return socketRef.current;
} }
const userInfo = JSON.parse(localStorage.getItem("user")); const userInfo = JSON.parse(localStorage.getItem("user"));
const newSocket = io(url, { const newSocket = io(url, {
auth: { token, username: userInfo.username }, auth: { token, username: userInfo?.username },
transports: ["websocket"], transports: ["websocket"],
upgrade: true,
withCredentials: true,
reconnectionAttempts: 5, reconnectionAttempts: 5,
}); });
@ -33,8 +34,19 @@ export const SocketProvider = ({ children }) => {
setIsConnected(true); 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) => { newSocket.on("disconnect", (reason) => {
console.log("❌ Disconnected:", reason);
setIsConnected(false); setIsConnected(false);
}); });

View File

@ -2,7 +2,6 @@ import GameDataManager from "./GameDataManager";
class ConsoleManager { class ConsoleManager {
constructor() { constructor() {
// Залишили тільки вказані команди
this.commands = ["/clear", "/give", "/set_exp"]; this.commands = ["/clear", "/give", "/set_exp"];
this.logs = ["[System] Console Manager Ready. Press F9 to hide."]; this.logs = ["[System] Console Manager Ready. Press F9 to hide."];
this.history = []; this.history = [];
@ -27,46 +26,45 @@ class ConsoleManager {
} }
getSuggestions(input, players = []) { getSuggestions(input, players = []) {
const safePlayers = (players || []).filter((p) => typeof p === "string");
const parts = input.split(" "); const parts = input.split(" ");
if (parts.length === 0) return []; if (parts.length === 0) return [];
const cmd = parts[0].toLowerCase(); const cmd = parts[0].toLowerCase();
// 1. Пропозиції команд
if (parts.length === 1 && input.startsWith("/")) { if (parts.length === 1 && input.startsWith("/")) {
return this.commands.filter((c) => c.startsWith(cmd)); return this.commands.filter((c) => c.startsWith(cmd));
} }
// 2. Логіка для /give (Гравець -> Предмет)
if (cmd === "/give") { if (cmd === "/give") {
if (parts.length === 2) { if (parts.length === 2) {
const search = parts[1].toLowerCase(); 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) { if (parts.length === 3) {
const search = parts[2].toLowerCase(); const search = parts[2].toLowerCase();
const itemIds = Array.from(GameDataManager.items.keys()); 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 (cmd === "/set_exp") {
if (parts.length === 2) { if (parts.length === 2) {
const search = parts[1].toLowerCase(); 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) { if (cmd === "/clear" && parts.length === 2) {
const search = parts[1].toLowerCase(); const search = parts[1].toLowerCase();
return players.filter((p) => p.toLowerCase().startsWith(search)); return safePlayers.filter((p) => p.toLowerCase().startsWith(search));
} }
return []; return [];
} }
execute(input) { execute(input) {
const trimmed = input.trim(); const trimmed = input.trim();
if (!trimmed) return; if (!trimmed) return;

View File

@ -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();

View File

@ -93,7 +93,7 @@ const GameInterface = ({ onExit }) => {
{tabs[activeTab] || <DashboardTab />} {tabs[activeTab] || <DashboardTab />}
</main> </main>
<Console players={onlinePlayers} /> <Console />
</div> </div>
); );
}; };