diff --git a/server/server.cpp b/server/server.cpp index 8a0ef00..4898cae 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -41,42 +41,6 @@ std::vector split(const std::string& s, char delimiter) { return tokens; } -// Вспомогательная функция для проверки столкновения снаряда с объектом-сферой -bool checkSegmentSphereCollision( - int x, - const Eigen::Vector3f& pStart, - const Eigen::Vector3f& pEnd, - const Eigen::Vector3f& targetCenter, - float combinedRadius) -{ - Eigen::Vector3f segment = pEnd - pStart; - Eigen::Vector3f toTarget = targetCenter - pStart; - - float segmentLenSq = segment.squaredNorm(); - if (segmentLenSq < 1e-6f) { - return toTarget.norm() <= combinedRadius; - } - - // Находим проекцию точки targetCenter на прямую, содержащую отрезок - // t — это нормализованный параметр вдоль отрезка (от 0 до 1) - float t = toTarget.dot(segment) / segmentLenSq; - - // Ограничиваем t, чтобы найти ближайшую точку именно на ОТРЕЗКЕ - t = std::max(0.0f, std::min(1.0f, t)); - - // Ближайшая точка на отрезке к центру цели - Eigen::Vector3f closestPoint = pStart + t * segment; - /* - std::cout << "Collision for box: " << x << " pStart: " << pStart - << " pEnd: " << pEnd - << " targetCenter: " << targetCenter - << " closestPoint: " << closestPoint - << " t: " << t << std::endl; - */ - // Проверяем расстояние от ближайшей точки до центра цели - return (targetCenter - closestPoint).squaredNorm() <= (combinedRadius * combinedRadius); -} - struct ServerBox { Eigen::Vector3f position; Eigen::Matrix3f rotation; @@ -99,6 +63,9 @@ struct BoxDestroyedInfo { int destroyedBy = -1; }; +std::vector g_boxDestructions; +std::mutex g_boxDestructions_mutex; + std::vector g_serverBoxes; std::mutex g_boxes_mutex; @@ -217,14 +184,11 @@ public: timer->expires_after(std::chrono::milliseconds(100)); timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) { if (!ec) { - int64_t serverNow = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch()).count(); - self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(serverNow)); + self->send_message("ID:" + std::to_string(self->id_)); self->do_read(); } }); } - ClientState get_latest_state(std::chrono::system_clock::time_point now) { if (timedClientStates.timedStates.empty()) { @@ -295,8 +259,6 @@ private: } std::string cleanMessage = msg.substr(0, msg.find("#hash:")); - - std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl; auto parts = split(cleanMessage, ':'); @@ -427,95 +389,66 @@ void broadcastToAll(const std::string& message) { } } -void checkShipBoxCollisions(std::chrono::system_clock::time_point now, uint64_t now_ms, std::vector& boxDestructions) { - // Внимание: Мьютексы g_boxes_mutex и g_sessions_mutex должны быть захвачены - // внешним кодом в update_world перед вызовом этой функции. +void update_world(net::steady_timer& timer, net::io_context& ioc) { - const float shipCollisionRadius = 15.0f; - const float boxCollisionRadius = 2.0f; - const float thresh = shipCollisionRadius + boxCollisionRadius; - const float threshSq = thresh * thresh; + static auto last_snapshot_time = std::chrono::steady_clock::now(); + auto now = std::chrono::steady_clock::now(); + /*static uint64_t lastTickCount = 0; - for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { - if (g_serverBoxes[bi].destroyed) continue; + if (lastTickCount == 0) { + //lastTickCount = SDL_GetTicks64(); + lastTickCount = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count(); - // Центр ящика в мировых координатах - Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); + 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мс + /* + if (std::chrono::duration_cast(now - last_snapshot_time).count() >= 1000) { + last_snapshot_time = now; + + auto system_now = std::chrono::system_clock::now(); + + std::string snapshot_msg = "SNAPSHOT:" + std::to_string( + std::chrono::duration_cast( + system_now.time_since_epoch()).count() + ); + + std::lock_guard lock(g_sessions_mutex); + + // Формируем общую строку состояний всех игроков + for (auto& session : g_sessions) { + ClientState st = session->get_latest_state(system_now); + snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); + } for (auto& session : g_sessions) { - int playerId = session->get_id(); - - // Пропускаем мертвых игроков - { - // Если g_dead_mutex не захвачен глобально в update_world, раскомментируйте: - // std::lock_guard gd(g_dead_mutex); - if (g_dead_players.count(playerId)) continue; - } - - ClientState shipState; - // Получаем состояние игрока на текущий момент времени сервера - if (!session->fetchStateAtTime(now, shipState)) continue; - - Eigen::Vector3f diff = shipState.position - boxWorld; - - // Проверка столкновения сфер - if (diff.squaredNorm() <= threshSq) { - g_serverBoxes[bi].destroyed = true; - - // Регистрируем уничтожение ящика - BoxDestroyedInfo destruction; - destruction.boxIndex = static_cast(bi); - destruction.serverTime = now_ms; - destruction.position = boxWorld; - destruction.destroyedBy = playerId; - - boxDestructions.push_back(destruction); - - std::cout << "Server: Box " << bi << " smashed by player " << playerId << std::endl; - - // Один ящик не может быть уничтожен дважды за один проход - break; - } + session->send_message(snapshot_msg); } - } -} + }*/ -void dispatchEvents(const std::vector& deathEvents, const std::vector& boxDestructions) { - // 1. Рассылка событий смерти игроков - for (const auto& death : deathEvents) { - std::string deadMsg = "DEAD:" + - std::to_string(death.serverTime) + ":" + - std::to_string(death.targetId) + ":" + - std::to_string(death.position.x()) + ":" + - std::to_string(death.position.y()) + ":" + - std::to_string(death.position.z()) + ":" + - std::to_string(death.killerId); - - broadcastToAll(deadMsg); - - std::cout << "Server: Sent DEAD event - Player " << death.targetId - << " killed by " << death.killerId << std::endl; - } - - // 2. Рассылка событий разрушения ящиков - - for (const auto& destruction : 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; - } -} - -void update_world(net::steady_timer& timer, net::io_context& ioc) { const std::chrono::milliseconds interval(50); timer.expires_after(interval); @@ -523,90 +456,205 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { if (ec) return; auto now = std::chrono::system_clock::now(); - uint64_t now_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); - float dt = 50.0f / 1000.0f; + uint64_t now_ms = static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()); std::vector deathEvents; - std::vector boxDestructions; - std::vector projectilesToRemove; { - // Захватываем необходимые данные под мьютексами один раз std::lock_guard pl(g_projectiles_mutex); - std::lock_guard bm(g_boxes_mutex); - std::lock_guard sm(g_sessions_mutex); - std::lock_guard gm(g_dead_mutex); + std::vector indicesToRemove; + + float dt = 50.0f / 1000.0f; for (size_t i = 0; i < g_projectiles.size(); ++i) { auto& pr = g_projectiles[i]; - Eigen::Vector3f oldPos = pr.pos; - pr.pos += pr.vel * dt; - Eigen::Vector3f newPos = pr.pos; - // 1. Проверка времени жизни снаряда + pr.pos += pr.vel * dt; + if (now_ms > pr.spawnMs + static_cast(pr.lifeMs)) { - projectilesToRemove.push_back(static_cast(i)); + indicesToRemove.push_back(static_cast(i)); continue; } bool hitDetected = false; - // 2. Проверка коллизий снаряда с игроками (Ray-cast) - for (auto& session : g_sessions) { - int targetId = session->get_id(); - if (targetId == pr.shooterId || g_dead_players.count(targetId)) continue; + { + std::lock_guard lm(g_sessions_mutex); + std::lock_guard gd(g_dead_mutex); - ClientState targetState; - if (!session->fetchStateAtTime(now, targetState)) continue; + for (auto& session : g_sessions) { + int targetId = session->get_id(); - if (checkSegmentSphereCollision(0, oldPos, newPos, targetState.position, 15.0f + 1.5f)) { - deathEvents.push_back({ targetId, now_ms, newPos, pr.shooterId }); - g_dead_players.insert(targetId); - hitDetected = true; - break; + if (targetId == pr.shooterId) continue; + if (g_dead_players.find(targetId) != g_dead_players.end()) continue; + + ClientState targetState; + if (!session->fetchStateAtTime(now, targetState)) continue; + + Eigen::Vector3f diff = pr.pos - targetState.position; + const float shipRadius = 15.0f; + const float projectileRadius = 1.5f; + float combinedRadius = shipRadius + projectileRadius; + + if (diff.squaredNorm() <= combinedRadius * combinedRadius) { + DeathInfo death; + death.targetId = targetId; + death.serverTime = now_ms; + death.position = pr.pos; + death.killerId = pr.shooterId; + + deathEvents.push_back(death); + g_dead_players.insert(targetId); + indicesToRemove.push_back(static_cast(i)); + hitDetected = true; + + std::cout << "Server: *** HIT DETECTED! ***" << std::endl; + std::cout << "Server: Projectile at (" + << pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z() + << ") hit player " << targetId << std::endl; + break; + } } } - if (hitDetected) { - projectilesToRemove.push_back(static_cast(i)); - continue; - } - - // 3. Проверка коллизий снаряда с ящиками (Ray-cast) - 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); - - if (checkSegmentSphereCollision(bi, oldPos, newPos, boxWorld, 2.0f + 1.5f)) { - g_serverBoxes[bi].destroyed = true; - - boxDestructions.push_back({ static_cast(bi), now_ms, boxWorld, pr.shooterId }); - - hitDetected = true; - break; - } - } - - if (hitDetected) { - projectilesToRemove.push_back(static_cast(i)); - } + if (hitDetected) continue; } - // Удаляем отработавшие снаряды (с конца) - std::sort(projectilesToRemove.rbegin(), projectilesToRemove.rend()); - for (int idx : projectilesToRemove) { - g_projectiles.erase(g_projectiles.begin() + idx); + if (!indicesToRemove.empty()) { + std::sort(indicesToRemove.rbegin(), indicesToRemove.rend()); + for (int idx : indicesToRemove) { + if (idx >= 0 && idx < (int)g_projectiles.size()) { + g_projectiles.erase(g_projectiles.begin() + idx); + } + } } } - // 4. Отдельная проверка столкновения кораблей с ящиками (Point-Sphere) - // Эту логику оставляем отдельно, так как она не привязана к снарядам - checkShipBoxCollisions(now, now_ms, boxDestructions); + { + std::lock_guard bm(g_boxes_mutex); + const float projectileHitRadius = 1.5f; + const float boxCollisionRadius = 2.0f; - // Рассылка событий - dispatchEvents(deathEvents, boxDestructions); + 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:" + + std::to_string(death.serverTime) + ":" + + std::to_string(death.targetId) + ":" + + std::to_string(death.position.x()) + ":" + + std::to_string(death.position.y()) + ":" + + std::to_string(death.position.z()) + ":" + + std::to_string(death.killerId); + + broadcastToAll(deadMsg); + + std::cout << "Server: Sent DEAD event - Player " << death.targetId + << " killed by " << death.killerId << std::endl; + } + } + + { + 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); });