diff --git a/resources/config/ui.json b/resources/config/ui.json index 644f035..f8f1f7d 100644 --- a/resources/config/ui.json +++ b/resources/config/ui.json @@ -141,9 +141,9 @@ "type": "Slider", "name": "velocitySlider", "x": 1140, - "y": 100, + "y": 300, "width": 50, - "height": 500, + "height": 300, "value": 0.0, "orientation": "vertical", "textures": { @@ -164,6 +164,19 @@ "pressed": "resources/shoot_pressed.png" } }, + { + "type": "Button", + "name": "shootButton2", + "x": 1000, + "y": 100, + "width": 100, + "height": 100, + "textures": { + "normal": "resources/shoot_normal.png", + "hover": "resources/shoot_hover.png", + "pressed": "resources/shoot_pressed.png" + } + }, { "type": "TextView", "name": "velocityText", diff --git a/resources/sky/space_red.png b/resources/sky/space_red.png new file mode 100644 index 0000000..5f94f48 --- /dev/null +++ b/resources/sky/space_red.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:31aebc19f6d7b7d5c8e5760bb08ec0acecab7886f20396722860add3618e03e5 +size 740565 diff --git a/server/server.cpp b/server/server.cpp index 0e8c6b4..8a0ef00 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -41,6 +41,42 @@ 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; @@ -63,9 +99,6 @@ struct BoxDestroyedInfo { int destroyedBy = -1; }; -std::vector g_boxDestructions; -std::mutex g_boxDestructions_mutex; - std::vector g_serverBoxes; std::mutex g_boxes_mutex; @@ -152,20 +185,6 @@ public: } private: - /* - void init() { - sendBoxesToClient(); - - auto timer = std::make_shared(ws_.get_executor()); - timer->expires_after(std::chrono::milliseconds(100)); - timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) { - if (!ec) { - self->send_message("ID:" + std::to_string(self->id_)); - self->do_read(); - } - }); - } - */ void sendBoxesToClient() { std::lock_guard lock(g_boxes_mutex); @@ -189,10 +208,6 @@ private: public: - /* - explicit Session(tcp::socket&& socket, int id) - : ws_(std::move(socket)), id_(id) { - }*/ void init() { @@ -209,31 +224,7 @@ public: } }); } - /* - void run() { - - { - std::lock_guard lock(g_sessions_mutex); - g_sessions.push_back(shared_from_this()); - } - - ws_.async_accept([self = shared_from_this()](beast::error_code ec) { - if (ec) return; - std::cout << "Client " << self->id_ << " connected\n"; - self->init(); - // self->send_message("ID:" + std::to_string(self->id_)); - // self->do_read(); - }); - }*/ - - /*void send_message(std::string msg) { - auto ss = std::make_shared(std::move(msg)); - ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {}); - } - - int get_id() const { - return id_; - }*/ + ClientState get_latest_state(std::chrono::system_clock::time_point now) { if (timedClientStates.timedStates.empty()) { @@ -250,31 +241,7 @@ public: return latest; } - /* - void send_message(std::string msg) { - auto ss = std::make_shared(std::move(msg)); - - if (is_writing_) { - - ws_.async_write(net::buffer(*ss), - [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { - if (ec) { - std::cerr << "Write error: " << ec.message() << std::endl; - } - }); - } - else { - is_writing_ = true; - ws_.async_write(net::buffer(*ss), - [self = shared_from_this(), ss](beast::error_code ec, std::size_t) { - self->is_writing_ = false; - if (ec) { - std::cerr << "Write error: " << ec.message() << std::endl; - } - }); - } - }*/ - + void doWrite() { std::lock_guard lock(writeMutex_); if (is_writing_ || writeQueue_.empty()) { @@ -460,66 +427,95 @@ void broadcastToAll(const std::string& message) { } } -void update_world(net::steady_timer& timer, net::io_context& ioc) { +void checkShipBoxCollisions(std::chrono::system_clock::time_point now, uint64_t now_ms, std::vector& boxDestructions) { + // Внимание: Мьютексы g_boxes_mutex и g_sessions_mutex должны быть захвачены + // внешним кодом в update_world перед вызовом этой функции. - static auto last_snapshot_time = std::chrono::steady_clock::now(); - auto now = std::chrono::steady_clock::now(); - /*static uint64_t lastTickCount = 0; + const float shipCollisionRadius = 15.0f; + const float boxCollisionRadius = 2.0f; + const float thresh = shipCollisionRadius + boxCollisionRadius; + const float threshSq = thresh * thresh; - if (lastTickCount == 0) { - //lastTickCount = SDL_GetTicks64(); - lastTickCount = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); + for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) { + if (g_serverBoxes[bi].destroyed) continue; - lastTickCount = (lastTickCount / 50) * 50; + // Центр ящика в мировых координатах + Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); - return; + 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; + } + } + } +} + +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. Рассылка событий разрушения ящиков - auto newTickCount = std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count(); + 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); - newTickCount = (newTickCount / 50) * 50; + broadcastToAll(boxMsg); - 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) { - session->send_message(snapshot_msg); - } - }*/ + 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); @@ -527,205 +523,90 @@ 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 = static_cast(std::chrono::duration_cast(now.time_since_epoch()).count()); + uint64_t now_ms = std::chrono::duration_cast(now.time_since_epoch()).count(); + float dt = 50.0f / 1000.0f; std::vector deathEvents; + std::vector boxDestructions; + std::vector projectilesToRemove; { + // Захватываем необходимые данные под мьютексами один раз std::lock_guard pl(g_projectiles_mutex); - std::vector indicesToRemove; - - float dt = 50.0f / 1000.0f; + std::lock_guard bm(g_boxes_mutex); + std::lock_guard sm(g_sessions_mutex); + std::lock_guard gm(g_dead_mutex); 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. Проверка времени жизни снаряда if (now_ms > pr.spawnMs + static_cast(pr.lifeMs)) { - indicesToRemove.push_back(static_cast(i)); + projectilesToRemove.push_back(static_cast(i)); continue; } bool hitDetected = false; - { - std::lock_guard lm(g_sessions_mutex); - std::lock_guard gd(g_dead_mutex); - - for (auto& session : g_sessions) { - int targetId = session->get_id(); - - 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) continue; - } - - 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); - } - } - } - } - - { - 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); - + // 2. Проверка коллизий снаряда с игроками (Ray-cast) 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; - } - } + int targetId = session->get_id(); + if (targetId == pr.shooterId || g_dead_players.count(targetId)) continue; - ClientState shipState; - if (!session->fetchStateAtTime(now, shipState)) continue; + ClientState targetState; + if (!session->fetchStateAtTime(now, targetState)) 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; + 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 (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)); + } + } + + // Удаляем отработавшие снаряды (с конца) + std::sort(projectilesToRemove.rbegin(), projectilesToRemove.rend()); + for (int idx : projectilesToRemove) { + g_projectiles.erase(g_projectiles.begin() + idx); } } - 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); + // 4. Отдельная проверка столкновения кораблей с ящиками (Point-Sphere) + // Эту логику оставляем отдельно, так как она не привязана к снарядам + checkShipBoxCollisions(now, now_ms, boxDestructions); - 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(); - } + // Рассылка событий + dispatchEvents(deathEvents, boxDestructions); update_world(timer, ioc); }); diff --git a/src/Game.cpp b/src/Game.cpp index 9cb5e6a..9e6c6ee 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -30,8 +30,8 @@ namespace ZL #ifdef EMSCRIPTEN const char* CONST_ZIP_FILE = "resources.zip"; #else - const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip"; - //const char* CONST_ZIP_FILE = ""; + //const char* CONST_ZIP_FILE = "C:\\Work\\Projects\\space-game001\\resources.zip"; + const char* CONST_ZIP_FILE = ""; #endif static bool g_exitBgAnimating = false; @@ -366,9 +366,16 @@ namespace ZL uiManager.setButtonCallback("shootButton", [this](const std::string& name) { firePressed = true; }); - + uiManager.setButtonCallback("shootButton2", [this](const std::string& name) { + firePressed = true; + }); uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { int newVel = roundf(value * 10); + if (newVel > 2) + { + newVel = 2; + } + if (newVel != Environment::shipState.selectedVelocity) { newShipVelocity = newVel; } @@ -390,12 +397,12 @@ namespace ZL cubemapTexture = std::make_shared( std::array{ - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE), - CreateTextureDataFromPng("resources/sky/space1.png", CONST_ZIP_FILE) + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE), + CreateTextureDataFromPng("resources/sky/space_red.png", CONST_ZIP_FILE) }); @@ -703,11 +710,12 @@ namespace ZL { glClear(GL_DEPTH_BUFFER_BIT); } - drawShip(); + drawRemoteShips(); drawRemoteShipsLabels(); drawBoxes(); drawBoxesLabels(); + drawShip(); drawUI(); CheckGlError(); diff --git a/src/planet/PlanetObject.cpp b/src/planet/PlanetObject.cpp index dd6a083..02503a2 100644 --- a/src/planet/PlanetObject.cpp +++ b/src/planet/PlanetObject.cpp @@ -291,7 +291,7 @@ namespace ZL { drawPlanet(renderer); drawStones(renderer); - drawCamp(renderer); + //drawCamp(renderer); glClear(GL_DEPTH_BUFFER_BIT); drawAtmosphere(renderer); }