From e8fb14b809c8a28ecbb2669a8878b725385216fb Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 27 Jan 2026 19:38:18 +0600 Subject: [PATCH] added sync projectiles/boxes --- server/server.cpp | 79 +++++-- src/Game.cpp | 65 +++++- src/network/LocalClient.cpp | 16 +- src/network/LocalClient.h | 1 + src/network/NetworkInterface.h | 9 + src/network/WebSocketClient.cpp | 355 +++++++++++++++++--------------- src/network/WebSocketClient.h | 5 + 7 files changed, 338 insertions(+), 192 deletions(-) diff --git a/server/server.cpp b/server/server.cpp index 96350f3..1dd04fa 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -56,33 +56,76 @@ class Session : public std::enable_shared_from_this { void process_message(const std::string& msg) { auto now_server = std::chrono::system_clock::now(); - auto parts = split(msg, ':'); - if (parts.size() < 16) - { - throw std::runtime_error("Unknown message type received, too small"); + if (parts.empty()) { + std::cerr << "Empty message received\n"; + return; } - uint64_t clientTimestamp = std::stoull(parts[1]); + std::string type = parts[0]; - ClientState receivedState; - - receivedState.id = id_; - - std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(clientTimestamp)) }; - receivedState.lastUpdateServerTime = uptime_timepoint; - - if (parts[0] == "UPD") { + if (type == "UPD") { + if (parts.size() < 16) { + std::cerr << "Invalid UPD message: too few parts (" << parts.size() << ")\n"; + return; + } + uint64_t clientTimestamp = std::stoull(parts[1]); + ClientState receivedState; + receivedState.id = id_; + std::chrono::system_clock::time_point uptime_timepoint{ + std::chrono::milliseconds(clientTimestamp) + }; + receivedState.lastUpdateServerTime = uptime_timepoint; receivedState.handle_full_sync(parts, 2); retranslateMessage(msg); + timedClientStates.add_state(receivedState); } - else - { - throw std::runtime_error("Unknown message type received: " + parts[0]); - } + else if (parts[0] == "FIRE") { + if (parts.size() < 8) { + std::cerr << "Invalid FIRE: too few parts\n"; + return; + } - timedClientStates.add_state(receivedState); + uint64_t clientTime = std::stoull(parts[1]); + Eigen::Vector3f pos{ + std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4]) + }; + Eigen::Vector3f dir{ + std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]) + }; + + int shotCount = 1; + if (parts.size() >= 9) { + try { + shotCount = std::stoi(parts[8]); + } + catch (...) { + shotCount = 1; + } + } + + std::string broadcast = "PROJECTILE:" + + std::to_string(id_) + ":" + + std::to_string(clientTime) + ":" + + std::to_string(pos.x()) + ":" + + std::to_string(pos.y()) + ":" + + std::to_string(pos.z()) + ":" + + std::to_string(dir.x()) + ":" + + std::to_string(dir.y()) + ":" + + std::to_string(dir.z()) + ":" + + std::to_string(shotCount); + + std::lock_guard lock(g_sessions_mutex); + std::cout << "Player " << id_ << " fired " << shotCount << " shots → broadcasting\n"; + + for (auto& session : g_sessions) { + session->send_message(broadcast); + } + } + else { + std::cerr << "Unknown message type: " << type << "\n"; + } } void retranslateMessage(const std::string& msg) diff --git a/src/Game.cpp b/src/Game.cpp index b0b7cb2..4d875ce 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -232,7 +232,24 @@ namespace ZL uint64_t now = SDL_GetTicks64(); if (now - lastProjectileFireTime >= static_cast(projectileCooldownMs)) { lastProjectileFireTime = now; - fireProjectiles(); + + Eigen::Vector3f localForward = { 0, 0, -1 }; + Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized(); + + Eigen::Vector3f centerPos = Environment::shipState.position + + Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f }; + + std::string fireMsg = "FIRE:" + + std::to_string(now) + ":" + + std::to_string(centerPos.x()) + ":" + + std::to_string(centerPos.y()) + ":" + + std::to_string(centerPos.z()) + ":" + + std::to_string(worldForward.x()) + ":" + + std::to_string(worldForward.y()) + ":" + + std::to_string(worldForward.z()) + ":" + + "2"; + + networkClient->Send(fireMsg); } }); @@ -582,7 +599,7 @@ namespace ZL auto sboxes = networkClient->getServerBoxes(); if (!sboxes.empty()) { boxCoordsArr.clear(); - for (auto &b : sboxes) { + for (auto& b : sboxes) { BoxCoords bc; bc.pos = b.first; bc.m = b.second; @@ -607,7 +624,7 @@ namespace ZL } ClientState playerState = remotePlayer.fetchClientStateAtTime(now); - + renderer.PushMatrix(); renderer.LoadIdentity(); @@ -704,7 +721,7 @@ namespace ZL float radians = atan2f(diffy, diffx); discreteAngle = static_cast(radians * 180.0f / M_PI); if (discreteAngle < 0) discreteAngle += 360; - + } else { @@ -718,7 +735,7 @@ namespace ZL discreteMag = 0.0f; } - + if (discreteAngle != Environment::shipState.discreteAngle || discreteMag != Environment::shipState.discreteMag) { Environment::shipState.discreteAngle = discreteAngle; Environment::shipState.discreteMag = discreteMag; @@ -1097,7 +1114,7 @@ namespace ZL { if (event.key.keysym.sym == SDLK_i) { - + } }*/ #endif @@ -1105,6 +1122,42 @@ namespace ZL render(); mainThreadHandler.processMainThreadTasks(); networkClient->Poll(); + + if (networkClient) { + auto pending = networkClient->getPendingProjectiles(); + if (!pending.empty()) { + const float projectileSpeed = 60.0f; + const float lifeMs = 5000.0f; + const float size = 0.5f; + + for (const auto& pi : pending) { + Eigen::Vector3f dir = pi.direction; + float len = dir.norm(); + if (len <= 1e-6f) continue; + dir /= len; + Eigen::Vector3f baseVel = dir * projectileSpeed; + + int shotCount = 1; + shotCount = 2; + + std::vector localOffsets = { + {-1.5f, 0.9f, 5.0f}, + { 1.5f, 0.9f, 5.0f} + }; + + for (int i = 0; i < shotCount; ++i) { + Eigen::Vector3f shotPos = pi.position + localOffsets[i]; + + for (auto& p : projectiles) { + if (!p->isActive()) { + p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer); + break; + } + } + } + } + } + } } void Game::handleDown(int mx, int my) diff --git a/src/network/LocalClient.cpp b/src/network/LocalClient.cpp index 264f526..b460a6f 100644 --- a/src/network/LocalClient.cpp +++ b/src/network/LocalClient.cpp @@ -4,15 +4,17 @@ namespace ZL { - void LocalClient::Connect(const std::string& host, uint16_t port) { - } + void LocalClient::Connect(const std::string& host, uint16_t port) { + } - void LocalClient::Poll() { - } + void LocalClient::Poll() { + } - void LocalClient::Send(const std::string& message) { - - } + void LocalClient::Send(const std::string& message) { + } + std::vector LocalClient::getPendingProjectiles() { + return {}; + } } \ No newline at end of file diff --git a/src/network/LocalClient.h b/src/network/LocalClient.h index 8cd1cee..c45c0c8 100644 --- a/src/network/LocalClient.h +++ b/src/network/LocalClient.h @@ -17,6 +17,7 @@ namespace ZL { bool IsConnected() const override { return true; } int GetClientId() const { return 1; } + std::vector getPendingProjectiles(); std::unordered_map getRemotePlayers() override { return std::unordered_map(); diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index d2c513d..6bb64be 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -7,6 +7,13 @@ // NetworkInterface.h - »нтерфейс дл¤ разных типов соединений namespace ZL { + struct ProjectileInfo { + int shooterId = -1; + uint64_t clientTime = 0; + Eigen::Vector3f position = Eigen::Vector3f::Zero(); + Eigen::Vector3f direction = Eigen::Vector3f::Zero(); + }; + class INetworkClient { public: virtual ~INetworkClient() = default; @@ -17,5 +24,7 @@ namespace ZL { virtual std::unordered_map getRemotePlayers() = 0; virtual std::vector> getServerBoxes() = 0; + + virtual std::vector getPendingProjectiles() = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index c0aa195..047066b 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -6,206 +6,239 @@ // Вспомогательный split std::vector split(const std::string& s, char delimiter) { - std::vector tokens; - std::string token; - std::istringstream tokenStream(s); - while (std::getline(tokenStream, token, delimiter)) { - tokens.push_back(token); - } - return tokens; + std::vector tokens; + std::string token; + std::istringstream tokenStream(s); + while (std::getline(tokenStream, token, delimiter)) { + tokens.push_back(token); + } + return tokens; } namespace ZL { - void WebSocketClient::Connect(const std::string& host, uint16_t port) { - try { - boost::asio::ip::tcp::resolver resolver(ioc_); - auto const results = resolver.resolve(host, std::to_string(port)); + void WebSocketClient::Connect(const std::string& host, uint16_t port) { + try { + boost::asio::ip::tcp::resolver resolver(ioc_); + auto const results = resolver.resolve(host, std::to_string(port)); - ws_ = std::make_unique>(ioc_); + ws_ = std::make_unique>(ioc_); - // Выполняем синхронный коннект и handshake для простоты старта - boost::beast::get_lowest_layer(*ws_).connect(results); - ws_->handshake(host, "/"); + // Выполняем синхронный коннект и handshake для простоты старта + boost::beast::get_lowest_layer(*ws_).connect(results); + ws_->handshake(host, "/"); - connected = true; + connected = true; - // Запускаем асинхронное чтение в пуле потоков TaskManager - startAsyncRead(); + // Запускаем асинхронное чтение в пуле потоков TaskManager + startAsyncRead(); - } - catch (std::exception& e) { - std::cerr << "Network Error: " << e.what() << std::endl; - } - } + } + catch (std::exception& e) { + std::cerr << "Network Error: " << e.what() << std::endl; + } + } - void WebSocketClient::startAsyncRead() { - ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { - if (!ec) { - std::string msg = boost::beast::buffers_to_string(buffer_.data()); - buffer_.consume(bytes); - processIncomingMessage(msg); - startAsyncRead(); - } - else { - connected = false; - } - }); - } + void WebSocketClient::startAsyncRead() { + ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) { + if (!ec) { + std::string msg = boost::beast::buffers_to_string(buffer_.data()); + buffer_.consume(bytes); + processIncomingMessage(msg); + startAsyncRead(); + } + else { + connected = false; + } + }); + } - void WebSocketClient::processIncomingMessage(const std::string& msg) { - // Логика парсинга... - if (msg.rfind("ID:", 0) == 0) { - clientId = std::stoi(msg.substr(3)); - } + void WebSocketClient::processIncomingMessage(const std::string& msg) { + // Логика парсинга... + if (msg.rfind("ID:", 0) == 0) { + clientId = std::stoi(msg.substr(3)); + } - // Безопасно кладем в очередь для главного потока - std::lock_guard lock(queueMutex); - messageQueue.push(msg); - } + // Безопасно кладем в очередь для главного потока + std::lock_guard lock(queueMutex); + messageQueue.push(msg); + } - void WebSocketClient::Poll() { - std::lock_guard lock(queueMutex); + std::vector WebSocketClient::getPendingProjectiles() { + std::lock_guard lock(projMutex_); + auto copy = pendingProjectiles_; + pendingProjectiles_.clear(); + return copy; + } - while (!messageQueue.empty()) { + void WebSocketClient::Poll() { + std::lock_guard lock(queueMutex); - auto nowTime = std::chrono::system_clock::now(); + while (!messageQueue.empty()) { - //Apply server delay: - nowTime -= std::chrono::milliseconds(CLIENT_DELAY); + auto nowTime = std::chrono::system_clock::now(); - auto now_ms = std::chrono::duration_cast( - nowTime.time_since_epoch() - ).count(); + //Apply server delay: + nowTime -= std::chrono::milliseconds(CLIENT_DELAY); + + auto now_ms = std::chrono::duration_cast( + nowTime.time_since_epoch() + ).count(); - std::string msg = messageQueue.front(); - messageQueue.pop(); + std::string msg = messageQueue.front(); + messageQueue.pop(); - // Обработка списка коробок от сервера - if (msg.rfind("BOXES:", 0) == 0) { - std::string payload = msg.substr(6); // после "BOXES:" - std::vector> parsedBoxes; - if (!payload.empty()) { - auto items = split(payload, '|'); - for (auto& item : items) { - if (item.empty()) continue; - auto parts = split(item, ':'); - if (parts.size() < 7) continue; - try { - float px = std::stof(parts[0]); - float py = std::stof(parts[1]); - float pz = std::stof(parts[2]); - Eigen::Quaternionf q( - std::stof(parts[3]), - std::stof(parts[4]), - std::stof(parts[5]), - std::stof(parts[6]) - ); - Eigen::Matrix3f rot = q.toRotationMatrix(); - parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot); - } - catch (...) { - // пропускаем некорректную запись - continue; - } - } - } - { - std::lock_guard bLock(boxesMutex); - serverBoxes_ = std::move(parsedBoxes); - } - continue; - } + // Обработка списка коробок от сервера + if (msg.rfind("BOXES:", 0) == 0) { + std::string payload = msg.substr(6); // после "BOXES:" + std::vector> parsedBoxes; + if (!payload.empty()) { + auto items = split(payload, '|'); + for (auto& item : items) { + if (item.empty()) continue; + auto parts = split(item, ':'); + if (parts.size() < 7) continue; + try { + float px = std::stof(parts[0]); + float py = std::stof(parts[1]); + float pz = std::stof(parts[2]); + Eigen::Quaternionf q( + std::stof(parts[3]), + std::stof(parts[4]), + std::stof(parts[5]), + std::stof(parts[6]) + ); + Eigen::Matrix3f rot = q.toRotationMatrix(); + parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot); + } + catch (...) { + // пропускаем некорректную запись + continue; + } + } + } + { + std::lock_guard bLock(boxesMutex); + serverBoxes_ = std::move(parsedBoxes); + } + continue; + } - if (msg.rfind("EVENT:", 0) == 0) { - auto parts = split(msg, ':'); - if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... + if (msg.rfind("PROJECTILE:", 0) == 0) { + auto parts = split(msg, ':'); + if (parts.size() >= 9) { + try { + ProjectileInfo pi; + pi.shooterId = std::stoi(parts[1]); + pi.clientTime = std::stoull(parts[2]); + pi.position = Eigen::Vector3f( + std::stof(parts[3]), + std::stof(parts[4]), + std::stof(parts[5]) + ); + pi.direction = Eigen::Vector3f( + std::stof(parts[6]), + std::stof(parts[7]), + std::stof(parts[8]) + ); + std::lock_guard pl(projMutex_); + pendingProjectiles_.push_back(pi); + } + catch (...) { + } + } + continue; + } - int remoteId = std::stoi(parts[1]); - std::string subType = parts[2]; - uint64_t sentTime = std::stoull(parts[3]); + if (msg.rfind("EVENT:", 0) == 0) { + auto parts = split(msg, ':'); + if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA... - ClientState remoteState; + int remoteId = std::stoi(parts[1]); + std::string subType = parts[2]; + uint64_t sentTime = std::stoull(parts[3]); + + ClientState remoteState; remoteState.id = remoteId; - std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(sentTime)) }; - remoteState.lastUpdateServerTime = uptime_timepoint; - - if (subType == "UPD") { - int startFrom = 4; - remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; - Eigen::Quaternionf q( - std::stof(parts[startFrom + 3]), - std::stof(parts[startFrom + 4]), - std::stof(parts[startFrom + 5]), - std::stof(parts[startFrom + 6])); - remoteState.rotation = q.toRotationMatrix(); + std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast(std::chrono::milliseconds(sentTime)) }; + remoteState.lastUpdateServerTime = uptime_timepoint; - remoteState.currentAngularVelocity = Eigen::Vector3f{ - std::stof(parts[startFrom + 7]), - std::stof(parts[startFrom + 8]), - std::stof(parts[startFrom + 9]) }; - remoteState.velocity = std::stof(parts[startFrom + 10]); - remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); - remoteState.discreteMag = std::stof(parts[startFrom + 12]); - remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); - } - else - { + if (subType == "UPD") { + int startFrom = 4; + remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) }; + Eigen::Quaternionf q( + std::stof(parts[startFrom + 3]), + std::stof(parts[startFrom + 4]), + std::stof(parts[startFrom + 5]), + std::stof(parts[startFrom + 6])); + remoteState.rotation = q.toRotationMatrix(); + + remoteState.currentAngularVelocity = Eigen::Vector3f{ + std::stof(parts[startFrom + 7]), + std::stof(parts[startFrom + 8]), + std::stof(parts[startFrom + 9]) }; + remoteState.velocity = std::stof(parts[startFrom + 10]); + remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]); + remoteState.discreteMag = std::stof(parts[startFrom + 12]); + remoteState.discreteAngle = std::stoi(parts[startFrom + 13]); + } + else + { throw std::runtime_error("Unknown EVENT subtype: " + subType); - } + } - { - std::lock_guard pLock(playersMutex); + { + std::lock_guard pLock(playersMutex); auto& rp = remotePlayers[remoteId]; - rp.add_state(remoteState); - } - } + rp.add_state(remoteState); + } + } - } - } - void WebSocketClient::Send(const std::string& message) { - if (!connected) return; + } + } + void WebSocketClient::Send(const std::string& message) { + if (!connected) return; - auto ss = std::make_shared(message); + auto ss = std::make_shared(message); - std::lock_guard lock(writeMutex_); - writeQueue_.push(ss); + std::lock_guard lock(writeMutex_); + writeQueue_.push(ss); - // Если сейчас ничего не записывается, инициируем первую запись - if (!isWriting_) { - doWrite(); - } - } + // Если сейчас ничего не записывается, инициируем первую запись + if (!isWriting_) { + doWrite(); + } + } - void WebSocketClient::doWrite() { - // Эта функция всегда вызывается под мьютексом или из колбэка - if (writeQueue_.empty()) { - isWriting_ = false; - return; - } + void WebSocketClient::doWrite() { + // Эта функция всегда вызывается под мьютексом или из колбэка + if (writeQueue_.empty()) { + isWriting_ = false; + return; + } - isWriting_ = true; - auto message = writeQueue_.front(); + isWriting_ = true; + auto message = writeQueue_.front(); - // Захватываем self (shared_from_this), чтобы объект не удалился во время записи - ws_->async_write( - boost::asio::buffer(*message), - [this, message](boost::beast::error_code ec, std::size_t) { - if (ec) { - connected = false; - return; - } + // Захватываем self (shared_from_this), чтобы объект не удалился во время записи + ws_->async_write( + boost::asio::buffer(*message), + [this, message](boost::beast::error_code ec, std::size_t) { + if (ec) { + connected = false; + return; + } - std::lock_guard lock(writeMutex_); - writeQueue_.pop(); // Удаляем отправленное сообщение - doWrite(); // Проверяем следующее - } - ); - } + std::lock_guard lock(writeMutex_); + writeQueue_.pop(); // Удаляем отправленное сообщение + doWrite(); // Проверяем следующее + } + ); + } } #endif diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 0692d67..093e008 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -38,6 +38,9 @@ namespace ZL { std::vector> serverBoxes_; std::mutex boxesMutex; + std::vector pendingProjectiles_; + std::mutex projMutex_; + void startAsyncRead(); void processIncomingMessage(const std::string& msg); @@ -63,6 +66,8 @@ namespace ZL { std::lock_guard lock(boxesMutex); return serverBoxes_; } + + std::vector getPendingProjectiles() override; }; } #endif