diff --git a/server/server.cpp b/server/server.cpp index e13d653..bce913b 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -45,6 +45,7 @@ struct ServerBox { Eigen::Vector3f position; Eigen::Matrix3f rotation; float collisionRadius = 2.0f; + bool destroyed = false; }; struct Projectile { @@ -55,6 +56,16 @@ struct Projectile { float lifeMs = 5000.0f; }; +struct BoxDestroyedInfo { + int boxIndex = -1; + uint64_t serverTime = 0; + Eigen::Vector3f position = Eigen::Vector3f::Zero(); + int destroyedBy = -1; +}; + +std::vector g_boxDestructions; +std::mutex g_boxDestructions_mutex; + std::vector g_serverBoxes; std::mutex g_boxes_mutex; @@ -419,6 +430,36 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { static auto last_snapshot_time = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); + /*static uint64_t lastTickCount = 0; + + if (lastTickCount == 0) { + //lastTickCount = SDL_GetTicks64(); + lastTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + lastTickCount = (lastTickCount / 50) * 50; + + return; + } + + + auto newTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); + + newTickCount = (newTickCount / 50) * 50; + + int64_t deltaMs = static_cast(newTickCount - lastTickCount); + + std::chrono::system_clock::time_point nowRounded = std::chrono::system_clock::time_point(std::chrono::milliseconds(newTickCount)); + */ + // For each player + // Get letest state + add time (until newTickCount) + // Calculate if collisions with boxes + + + // Рассылка Snapshot раз в 1000мс /* @@ -526,6 +567,98 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { } } + { + std::lock_guard bm(g_boxes_mutex); + const float projectileHitRadius = 1.5f; + const float boxCollisionRadius = 2.0f; + + std::vector> boxProjectileCollisions; + + for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { + if (g_serverBoxes[bi].destroyed) continue; + + Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 6.0f, 45000.0f); + + for (size_t pi = 0; pi < g_projectiles.size(); ++pi) { + const auto& pr = g_projectiles[pi]; + Eigen::Vector3f diff = pr.pos - boxWorld; + float thresh = boxCollisionRadius + projectileHitRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + boxProjectileCollisions.push_back({ bi, pi }); + } + } + } + + for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { + g_serverBoxes[boxIdx].destroyed = true; + + Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + + BoxDestroyedInfo destruction; + destruction.boxIndex = static_cast(boxIdx); + destruction.serverTime = now_ms; + destruction.position = boxWorld; + destruction.destroyedBy = g_projectiles[projIdx].shooterId; + + { + std::lock_guard dm(g_boxDestructions_mutex); + g_boxDestructions.push_back(destruction); + } + + std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player " + << g_projectiles[projIdx].shooterId << std::endl; + } + } + + { + std::lock_guard bm(g_boxes_mutex); + std::lock_guard lm(g_sessions_mutex); + + const float shipCollisionRadius = 15.0f; + const float boxCollisionRadius = 2.0f; + + for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { + if (g_serverBoxes[bi].destroyed) continue; + + Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + + for (auto& session : g_sessions) { + { + std::lock_guard gd(g_dead_mutex); + if (g_dead_players.find(session->get_id()) != g_dead_players.end()) { + continue; + } + } + + ClientState shipState; + if (!session->fetchStateAtTime(now, shipState)) continue; + + Eigen::Vector3f diff = shipState.position - boxWorld; + float thresh = shipCollisionRadius + boxCollisionRadius; + + if (diff.squaredNorm() <= thresh * thresh) { + g_serverBoxes[bi].destroyed = true; + + BoxDestroyedInfo destruction; + destruction.boxIndex = static_cast(bi); + destruction.serverTime = now_ms; + destruction.position = boxWorld; + destruction.destroyedBy = session->get_id(); + + { + std::lock_guard dm(g_boxDestructions_mutex); + g_boxDestructions.push_back(destruction); + } + + std::cout << "Server: Box " << bi << " destroyed by ship collision with player " + << session->get_id() << std::endl; + break; + } + } + } + } + if (!deathEvents.empty()) { for (const auto& death : deathEvents) { std::string deadMsg = "DEAD:" + @@ -543,6 +676,23 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { } } + { + std::lock_guard dm(g_boxDestructions_mutex); + for (const auto& destruction : g_boxDestructions) { + std::string boxMsg = "BOX_DESTROYED:" + + std::to_string(destruction.boxIndex) + ":" + + std::to_string(destruction.serverTime) + ":" + + std::to_string(destruction.position.x()) + ":" + + std::to_string(destruction.position.y()) + ":" + + std::to_string(destruction.position.z()) + ":" + + std::to_string(destruction.destroyedBy); + + broadcastToAll(boxMsg); + std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl; + } + g_boxDestructions.clear(); + } + update_world(timer, ioc); }); } diff --git a/src/Game.cpp b/src/Game.cpp index dc864e2..b5d0cc1 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -246,7 +246,7 @@ namespace ZL ZL::CheckGlError(); -//#ifndef SIMPLIFIED + //#ifndef SIMPLIFIED renderer.shaderManager.AddShaderFromFiles("defaultColor", "resources/shaders/defaultColor.vertex", "resources/shaders/defaultColor_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/env_sky.vertex", "resources/shaders/env_sky_web.fragment", CONST_ZIP_FILE); @@ -255,14 +255,14 @@ namespace ZL renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/planet_stone.vertex", "resources/shaders/planet_stone_web.fragment", CONST_ZIP_FILE); renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/planet_land.vertex", "resources/shaders/planet_land_web.fragment", CONST_ZIP_FILE); -/*#else - renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); - renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); -#endif*/ + /*#else + renderer.shaderManager.AddShaderFromFiles("default", "resources/shaders/default.vertex", "resources/shaders/default_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("env_sky", "resources/shaders/default_env.vertex", "resources/shaders/default_env_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("defaultAtmosphere", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetBake", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetStone", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); + renderer.shaderManager.AddShaderFromFiles("planetLand", "resources/shaders/default_texture.vertex", "resources/shaders/default_texture_web.fragment", CONST_ZIP_FILE); + #endif*/ bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE); bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE); @@ -764,6 +764,7 @@ namespace ZL for (auto const& [id, remotePlayer] : remotePlayerStates) { const ClientState& playerState = remotePlayer; + if (deadRemotePlayers.count(id)) continue; renderer.PushMatrix(); renderer.LoadIdentity(); @@ -796,53 +797,53 @@ 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); +#ifdef NETWORK + // 2D поверх 3D + glDisable(GL_DEPTH_TEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - // Берем удаленных игроков - //latestRemotePlayers = networkClient->getRemotePlayers(); + // Берем удаленных игроков + //latestRemotePlayers = networkClient->getRemotePlayers(); - auto now = std::chrono::system_clock::now(); - now -= std::chrono::milliseconds(CLIENT_DELAY); + auto now = std::chrono::system_clock::now(); + now -= std::chrono::milliseconds(CLIENT_DELAY); - for (auto const& [id, remotePlayer] : remotePlayerStates) - { - - const ClientState& st = remotePlayer; - // Позиция корабля в мире - Vector3f shipWorld = st.position; + for (auto const& [id, remotePlayer] : remotePlayerStates) + { - 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 sx, sy, depth; - if (!worldToScreen(labelWorld, sx, sy, depth)) - continue; - - float uiX = sx, uiY = sy; - float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); + const ClientState& st = remotePlayer; + // Позиция корабля в мире + Vector3f shipWorld = st.position; - // Дефолтный лейбл - std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; + 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 sx, sy, depth; + if (!worldToScreen(labelWorld, sx, sy, depth)) + continue; - // TODO: nickname sync + float uiX = sx, uiY = sy; + float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); - textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, {0.f, 0.f, 0.f, alpha}); // color param - textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); - } + // Дефолтный лейбл + std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; - glDisable(GL_BLEND); - glEnable(GL_DEPTH_TEST); - #endif + // 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, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); + } + + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); +#endif } void Game::processTickCount() { @@ -853,7 +854,7 @@ namespace ZL std::chrono::system_clock::now().time_since_epoch() ).count(); - lastTickCount = (lastTickCount / 50)*50; + lastTickCount = (lastTickCount / 50) * 50; return; } @@ -1177,7 +1178,7 @@ namespace ZL } } - for (int i = 0; i < boxCoordsArr.size(); ++i) { + /*for (int i = 0; i < boxCoordsArr.size(); ++i) { if (!boxAlive[i]) continue; Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.0f, 0.0f, 45000.0f }; Vector3f diff = Environment::shipState.position - boxWorld; @@ -1230,7 +1231,7 @@ namespace ZL break; } } - } + }*/ uiManager.update(static_cast(delta)); lastTickCount = newTickCount; @@ -1326,7 +1327,7 @@ namespace ZL handleMotion(mx, my); } - + if (event.type == SDL_MOUSEWHEEL) { static const float zoomstep = 2.0f; if (event.wheel.y > 0) { @@ -1510,9 +1511,46 @@ namespace ZL if (!respawns.empty()) { for (const auto& respawnId : respawns) { deadRemotePlayers.erase(respawnId); + + auto it = remotePlayerStates.find(respawnId); + if (it != remotePlayerStates.end()) { + it->second.position = Vector3f{ 0.f, 0.f, 45000.f }; + it->second.velocity = 0.0f; + it->second.rotation = Eigen::Matrix3f::Identity(); + } + std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl; } } + + auto boxDestructions = networkClient->getPendingBoxDestructions(); + if (!boxDestructions.empty()) { + std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl; + + for (const auto& destruction : boxDestructions) { + int idx = destruction.boxIndex; + + if (idx >= 0 && idx < (int)boxCoordsArr.size()) { + if (boxAlive[idx]) { + boxAlive[idx] = false; + + boxRenderArr[idx].data.PositionData.clear(); + boxRenderArr[idx].vao.reset(); + boxRenderArr[idx].positionVBO.reset(); + boxRenderArr[idx].texCoordVBO.reset(); + + showExplosion = true; + explosionEmitter.setUseWorldSpace(true); + explosionEmitter.setEmissionPoints(std::vector{ destruction.position }); + explosionEmitter.emit(); + lastExplosionTime = SDL_GetTicks64(); + + std::cout << "Game: Box " << idx << " destroyed by player " + << destruction.destroyedBy << std::endl; + } + } + } + } } } diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 063b666..840f765 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -23,6 +23,13 @@ namespace ZL { int killerId = -1; }; + struct BoxDestroyedInfo { + int boxIndex = -1; + uint64_t serverTime = 0; + Eigen::Vector3f position = Eigen::Vector3f::Zero(); + int destroyedBy = -1; + }; + class INetworkClient { public: virtual ~INetworkClient() = default; @@ -39,5 +46,6 @@ namespace ZL { virtual std::vector getPendingDeaths() = 0; virtual std::vector getPendingRespawns() = 0; virtual int GetClientId() const { return -1; } + virtual std::vector getPendingBoxDestructions() = 0; }; } diff --git a/src/network/WebSocketClient.cpp b/src/network/WebSocketClient.cpp index c28496b..6dc7865 100644 --- a/src/network/WebSocketClient.cpp +++ b/src/network/WebSocketClient.cpp @@ -85,6 +85,13 @@ namespace ZL { return copy; } + std::vector WebSocketClient::getPendingBoxDestructions() { + std::lock_guard lock(boxDestructionsMutex_); + auto copy = pendingBoxDestructions_; + pendingBoxDestructions_.clear(); + return copy; + } + void WebSocketClient::Poll() { std::lock_guard lock(queueMutex); @@ -159,6 +166,35 @@ namespace ZL { continue; } + if (msg.rfind("BOX_DESTROYED:", 0) == 0) { + auto parts = split(msg, ':'); + if (parts.size() >= 7) { + try { + BoxDestroyedInfo destruction; + destruction.boxIndex = std::stoi(parts[1]); + destruction.serverTime = std::stoull(parts[2]); + destruction.position = Eigen::Vector3f( + std::stof(parts[3]), + std::stof(parts[4]), + std::stof(parts[5]) + ); + destruction.destroyedBy = std::stoi(parts[6]); + + { + std::lock_guard lock(boxDestructionsMutex_); + pendingBoxDestructions_.push_back(destruction); + } + + std::cout << "Client: Received BOX_DESTROYED for box " << destruction.boxIndex + << " destroyed by player " << destruction.destroyedBy << std::endl; + } + catch (const std::exception& e) { + std::cerr << "Client: Error parsing BOX_DESTROYED: " << e.what() << std::endl; + } + } + continue; + } + if (msg.rfind("PROJECTILE:", 0) == 0) { auto parts = split(msg, ':'); if (parts.size() >= 10) { diff --git a/src/network/WebSocketClient.h b/src/network/WebSocketClient.h index 451f25c..e877f50 100644 --- a/src/network/WebSocketClient.h +++ b/src/network/WebSocketClient.h @@ -46,6 +46,9 @@ namespace ZL { std::vector pendingRespawns_; std::mutex respawnMutex_; + std::vector pendingBoxDestructions_; + std::mutex boxDestructionsMutex_; + void startAsyncRead(); void processIncomingMessage(const std::string& msg); @@ -75,6 +78,7 @@ namespace ZL { std::vector getPendingProjectiles() override; std::vector getPendingDeaths() override; std::vector getPendingRespawns() override; + std::vector getPendingBoxDestructions() override; }; } #endif