diff --git a/server/server.cpp b/server/server.cpp index 687658e..18d08c0 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -93,6 +93,9 @@ class Session : public std::enable_shared_from_this { public: ClientStateInterval timedClientStates; + std::string nickname = "Player"; + int shipType = 0; + explicit Session(tcp::socket&& socket, int id) : ws_(std::move(socket)), id_(id) { } @@ -205,7 +208,7 @@ public: return latest; } - + void doWrite() { std::lock_guard lock(writeMutex_); if (is_writing_ || writeQueue_.empty()) { @@ -253,7 +256,6 @@ private: void process_message(const std::string& msg) { if (!IsMessageValid(msg)) { - // Логируем попытку подмены и просто выходим из обработки std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; return; } @@ -266,7 +268,40 @@ private: std::string type = parts[0]; - if (type == "UPD") { + if (type == "JOIN") { + std::string nick = "Player"; + int sType = 0; + if (parts.size() >= 2) nick = parts[1]; + if (parts.size() >= 3) { + try { sType = std::stoi(parts[2]); } + catch (...) { sType = 0; } + } + + this->nickname = nick; + this->shipType = sType; + + std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl; + + std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType); + + { + std::lock_guard lock(g_sessions_mutex); + for (auto& session : g_sessions) { + if (session->get_id() == this->id_) continue; + session->send_message(info); + } + } + { + std::lock_guard lock(g_sessions_mutex); + for (auto& session : g_sessions) { + if (session->get_id() == this->id_) continue; + std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType); + // Отправляем именно новому клиенту + this->send_message(otherInfo); + } + } + } + else if (type == "UPD") { { std::lock_guard gd(g_dead_mutex); if (g_dead_players.find(id_) != g_dead_players.end()) { @@ -284,20 +319,48 @@ private: }; receivedState.lastUpdateServerTime = uptime_timepoint; receivedState.handle_full_sync(parts, 2); + receivedState.nickname = this->nickname; + receivedState.shipType = this->shipType; timedClientStates.add_state(receivedState); retranslateMessage(cleanMessage); } - else if (parts[0] == "RESPAWN") { + else if (type == "RESPAWN") { { std::lock_guard gd(g_dead_mutex); g_dead_players.erase(id_); } - std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); - broadcastToAll(respawnMsg); + { + auto now_tp = std::chrono::system_clock::now(); + uint64_t now_ms = static_cast(std::chrono::duration_cast(now_tp.time_since_epoch()).count()); - std::cout << "Server: Player " << id_ << " respawned\n"; + ClientState st; + st.id = id_; + st.position = Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + st.rotation = Eigen::Matrix3f::Identity(); + st.currentAngularVelocity = Eigen::Vector3f::Zero(); + st.velocity = 0.0f; + st.selectedVelocity = 0; + st.discreteMag = 0.0f; + st.discreteAngle = -1; + st.lastUpdateServerTime = now_tp; + st.nickname = this->nickname; + st.shipType = this->shipType; + + timedClientStates.add_state(st); + + std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); + broadcastToAll(respawnMsg); + + std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType); + broadcastToAll(playerInfo); + + std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); + broadcastToAll(eventMsg); + + std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n"; + } } else if (parts[0] == "FIRE") { if (parts.size() < 10) return; @@ -344,8 +407,8 @@ private: Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f) }; - uint64_t now_ms = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); + uint64_t now_ms = std::chrono::duration_cast(( + std::chrono::system_clock::now().time_since_epoch())).count(); std::lock_guard pl(g_projectiles_mutex); for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) { diff --git a/src/Game.cpp b/src/Game.cpp index 96a09a5..de0dd61 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -24,6 +24,7 @@ #endif #include "network/LocalClient.h" +#include "network/ClientState.h" namespace ZL @@ -122,7 +123,11 @@ namespace ZL spaceGameStarted = 1; }; - menuManager.onMultiplayerPressed = [this]() { + menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) { + Environment::shipState.nickname = nickname; + Environment::shipState.shipType = shipType; + + networkClient = std::make_unique(); #ifdef NETWORK #ifdef EMSCRIPTEN networkClient = std::make_unique(); @@ -131,7 +136,26 @@ namespace ZL networkClient = std::make_unique(taskManager.getIOContext()); networkClient->Connect("localhost", 8081); #endif +#else + networkClient->Connect("", 0); #endif + +#ifndef NETWORK + auto localClient = dynamic_cast(networkClient.get()); + if (localClient) { + ZL::ClientState st = Environment::shipState; + st.id = localClient->GetClientId(); + localClient->setLocalPlayerState(st); + } +#endif + + + if (networkClient) { + std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType); + networkClient->Send(joinMsg); + std::cerr << "Sent JOIN: " << joinMsg << std::endl; + } + lastTickCount = 0; spaceGameStarted = 1; }; diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 2dca211..c153cf0 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -148,10 +148,39 @@ namespace ZL { std::cerr << "Failed to push ship selection menu\n"; } }); - uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI, this](const std::string& name) { - std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n"; - loadGameplayUI(); - onMultiplayerPressed(); + + uiManager.setButtonCallback("multiplayerButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) { + std::cerr << "Multiplayer button pressed: " << name << " -> open ship selection UI\n"; + if (!shipSelectionRoot) { + std::cerr << "Failed to load ship selection UI\n"; + return; + } + if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) { + uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) { + std::string nick = uiManager.getTextFieldValue("nicknameInput"); + if (nick.empty()) nick = "Player"; + int shipType = 0; + uiManager.popMenu(); + loadGameplayUI(); + if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType); + }); + + uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) { + std::string nick = uiManager.getTextFieldValue("nicknameInput"); + if (nick.empty()) nick = "Player"; + int shipType = 1; + uiManager.popMenu(); + loadGameplayUI(); + if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType); + }); + + uiManager.setButtonCallback("backButton", [this](const std::string& btnName) { + uiManager.popMenu(); + }); + } + else { + std::cerr << "Failed to push ship selection menu\n"; + } }); uiManager.setButtonCallback("multiplayerButton2", [this](const std::string& name) { @@ -164,7 +193,6 @@ namespace ZL { if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) { - // Callback для кнопки подключения uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) { std::string serverAddress = uiManager.getTextFieldValue("serverInputField"); @@ -176,16 +204,12 @@ namespace ZL { uiManager.setText("statusText", "Connecting to " + serverAddress + "..."); std::cerr << "Connecting to server: " << serverAddress << std::endl; - // Здесь добавить вашу логику подключения к серверу - // connectToServer(serverAddress); }); - // Callback для кнопки назад uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) { uiManager.popMenu(); }); - // Callback для отслеживания ввода текста uiManager.setTextFieldCallback("serverInputField", [this](const std::string& fieldName, const std::string& newText) { std::cout << "Server input field changed to: " << newText << std::endl; diff --git a/src/MenuManager.h b/src/MenuManager.h index 18b935f..2bb4819 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -35,7 +35,7 @@ namespace ZL { std::function onFirePressed; std::function onSingleplayerPressed; - std::function onMultiplayerPressed; + std::function onMultiplayerPressed; }; }; diff --git a/src/Space.cpp b/src/Space.cpp index 32a5e9f..d5eb509 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -267,6 +267,16 @@ namespace ZL Environment::zoom = DEFAULT_ZOOM; Environment::tapDownHold = false; + if (networkClient) { + try { + networkClient->Send(std::string("RESPAWN")); + std::cout << "Client: Sent RESPAWN to server\n"; + } + catch (...) { + std::cerr << "Client: Failed to send RESPAWN\n"; + } + } + std::cerr << "Game restarted\n"; }; @@ -677,8 +687,6 @@ namespace ZL { if (!textRenderer) return; - //#ifdef NETWORK - // 2D поверх 3D glDisable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -688,18 +696,12 @@ namespace ZL if (deadRemotePlayers.count(id)) continue; const ClientState& st = remotePlayer; - // Позиция корабля в мире Vector3f shipWorld = st.position; float distSq = (Environment::shipState.position - shipWorld).squaredNorm(); - /*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма - continue;*/ float dist = sqrt(distSq); - float alpha = 1.0f; // постоянная видимость - /*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма - if (alpha < 0.01f) - continue; */ - Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты + float alpha = 1.0f; + Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; float sx, sy, depth; if (!worldToScreen(labelWorld, sx, sy, depth)) continue; @@ -707,18 +709,21 @@ namespace ZL float uiX = sx, uiY = sy; float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); - // Дефолтный лейбл - std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; + std::string displayName; + if (!st.nickname.empty() && st.nickname != "Player") { + displayName = st.nickname; + } + else { + displayName = "Player (" + std::to_string(st.id) + ")"; + } + std::string label = displayName + " " + std::to_string((int)dist) + "m"; - // TODO: nickname sync - - textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); // color param + textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); } glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); - //#endif } int Space::pickTargetId() const @@ -1288,6 +1293,10 @@ namespace ZL for (auto const& [id, remotePlayer] : latestRemotePlayers) { + if (networkClient && id == networkClient->GetClientId()) { + continue; + } + if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay)) { continue; @@ -1296,7 +1305,6 @@ namespace ZL ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay); remotePlayerStates[id] = playerState; - } for (auto& p : projectiles) { diff --git a/src/network/WebSocketClientBase.cpp b/src/network/WebSocketClientBase.cpp index 48b99f2..52c82a4 100644 --- a/src/network/WebSocketClientBase.cpp +++ b/src/network/WebSocketClientBase.cpp @@ -79,7 +79,6 @@ namespace ZL { try { int respawnedPlayerId = std::stoi(parts[1]); pendingRespawns_.push_back(respawnedPlayerId); - remotePlayers.erase(respawnedPlayerId); std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl; } catch (...) {} @@ -202,6 +201,11 @@ namespace ZL { { auto& rp = remotePlayers[remoteId]; + if (!rp.timedStates.empty()) { + const ClientState& last = rp.timedStates.back(); + remoteState.nickname = last.nickname; + remoteState.shipType = last.shipType; + } rp.add_state(remoteState); } } @@ -218,7 +222,7 @@ namespace ZL { if (playerParts.size() < 15) return; // ID + 14 полей ClientState int rId = std::stoi(playerParts[0]); - if (rId == clientId) return; // Свое состояние игрок знает лучше всех (Client Side Prediction) + if (rId == clientId) return; // Свое состояние игрок знает лучше всех, (Client Side Prediction) ClientState remoteState; remoteState.id = rId; @@ -230,6 +234,40 @@ namespace ZL { remotePlayers[rId].add_state(remoteState); } } + + if (msg.rfind("PLAYERINFO:", 0) == 0) { + if (parts.size() >= 4) { + try { + int pid = std::stoi(parts[1]); + if (pid == clientId) { + return; + } + + std::string nick = parts[2]; + int st = std::stoi(parts[3]); + + auto it = remotePlayers.find(pid); + if (it != remotePlayers.end() && !it->second.timedStates.empty()) { + auto& states = it->second.timedStates; + states.back().nickname = nick; + states.back().shipType = st; + } + else { + ClientState cs; + cs.id = pid; + cs.nickname = nick; + cs.shipType = st; + cs.lastUpdateServerTime = std::chrono::system_clock::now(); + remotePlayers[pid].add_state(cs); + } + + std::cout << "Client: PLAYERINFO received. id=" << pid << " nick=" << nick << " shipType=" << st << std::endl; + } + catch (...) { + } + } + return; + } } std::string WebSocketClientBase::SignMessage(const std::string& msg) {