diff --git a/server/server.cpp b/server/server.cpp index 2f1da7d..b72487e 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -31,8 +31,8 @@ bool Session::is_timed_out(std::chrono::system_clock::time_point now) const { } void Session::force_disconnect() { - ws_.async_close(websocket::close_code::normal, - [self = shared_from_this()](beast::error_code) {}); + beast::error_code ec; + ws_.next_layer().socket().close(ec); } int Session::get_id() const { return id_; } @@ -346,6 +346,64 @@ void Session::process_message(const std::string& msg) { std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n"; } } + else if (parts[0] == "BOX_PICKUP") { + if (parts.size() < 2) return; + + if (this->shipType != 1) { + std::cout << "Server: Player " << id_ << " tried BOX_PICKUP but is not a cargo ship\n"; + return; + } + + int boxIdx = -1; + try { boxIdx = std::stoi(parts[1]); } catch (...) { return; } + + std::lock_guard bm(server_.g_boxes_mutex); + + if (boxIdx < 0 || boxIdx >= (int)server_.g_serverBoxes.size()) return; + if (server_.g_serverBoxes[boxIdx].destroyed) return; + + if (timedClientStates.timedStates.empty()) return; + const ClientState& playerState = timedClientStates.timedStates.back(); + + Eigen::Vector3f boxWorld = server_.g_serverBoxes[boxIdx].position + kWorldOffset; + float distSq = (playerState.position - boxWorld).squaredNorm(); + if (distSq > BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) { + std::cout << "Server: Player " << id_ << " too far to pick up box " << boxIdx << "\n"; + return; + } + + server_.g_serverBoxes[boxIdx].destroyed = true; + + std::string pickedUpMsg = "BOX_PICKED_UP:" + std::to_string(boxIdx) + ":" + std::to_string(id_); + server_.broadcastToAll(pickedUpMsg); + + std::cout << "Server: Box " << boxIdx << " picked up by player " << id_ << "\n"; + + // Respawn box + { + static thread_local std::mt19937 rng{ std::random_device{}() }; + static thread_local std::uniform_real_distribution angleDist(0.f, static_cast(M_PI * 2.0)); + Eigen::Vector3f newPos = server_.PickSafeBoxPos(boxIdx); + Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); + Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(rng), axis).toRotationMatrix(); + server_.g_serverBoxes[boxIdx].position = newPos; + server_.g_serverBoxes[boxIdx].rotation = newRot; + server_.g_serverBoxes[boxIdx].destroyed = false; + + Eigen::Quaternionf q(newRot); + std::string respawnMsg = "BOX_RESPAWN:" + + std::to_string(boxIdx) + ":" + + std::to_string(newPos.x()) + ":" + + std::to_string(newPos.y()) + ":" + + std::to_string(newPos.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()); + server_.broadcastToAll(respawnMsg); + std::cout << "Server: Box " << boxIdx << " respawned after pickup\n"; + } + } else if (parts[0] == "FIRE") { if (parts.size() < 10) return; @@ -377,6 +435,28 @@ void Session::process_message(const std::string& msg) { } } +Eigen::Vector3f Server::PickSafeBoxPos(int skipIdx) +{ + // Assumes g_boxes_mutex is already held by the caller + static thread_local std::mt19937 rng{ std::random_device{}() }; + std::uniform_real_distribution dist(-1000.f, 1000.f); + + for (int attempt = 0; attempt < 500; ++attempt) { + Eigen::Vector3f cand(dist(rng), dist(rng), dist(rng)); + bool safe = true; + for (int i = 0; i < (int)g_serverBoxes.size(); ++i) { + if (i == skipIdx) continue; + if (g_serverBoxes[i].destroyed) continue; + if ((cand - g_serverBoxes[i].position).squaredNorm() < 9.f) { + safe = false; + break; + } + } + if (safe) return cand; + } + return Eigen::Vector3f(dist(rng), dist(rng), dist(rng)); +} + Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId) { static thread_local std::mt19937 rng{ std::random_device{}() }; @@ -608,6 +688,8 @@ void Server::update_world() { + std::vector boxesToRespawn; + // --- Tick: box-projectile collisions --- { std::lock_guard bm(g_boxes_mutex); @@ -632,6 +714,7 @@ void Server::update_world() { } for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) { + if (g_serverBoxes[boxIdx].destroyed) continue; g_serverBoxes[boxIdx].destroyed = true; Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f); @@ -647,6 +730,8 @@ void Server::update_world() { g_boxDestructions.push_back(destruction); } + boxesToRespawn.push_back(static_cast(boxIdx)); + std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player " << g_projectiles[projIdx].shooterId << std::endl; } @@ -690,6 +775,8 @@ void Server::update_world() { g_boxDestructions.push_back(destruction); } + boxesToRespawn.push_back(static_cast(bi)); + std::cout << "Server: Box " << bi << " destroyed by ship collision with player " << session->get_id() << std::endl; break; @@ -732,6 +819,41 @@ void Server::update_world() { g_boxDestructions.clear(); } + // --- Respawn destroyed boxes --- + if (!boxesToRespawn.empty()) { + static thread_local std::mt19937 rng{ std::random_device{}() }; + static thread_local std::uniform_real_distribution angleDist(0.f, static_cast(M_PI * 2.0)); + std::vector respawnMsgs; + { + std::lock_guard bm(g_boxes_mutex); + for (int idx : boxesToRespawn) { + if (idx < 0 || idx >= (int)g_serverBoxes.size()) continue; + Eigen::Vector3f newPos = PickSafeBoxPos(idx); + Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized(); + Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(rng), axis).toRotationMatrix(); + g_serverBoxes[idx].position = newPos; + g_serverBoxes[idx].rotation = newRot; + g_serverBoxes[idx].destroyed = false; + + Eigen::Quaternionf q(newRot); + std::string respawnMsg = "BOX_RESPAWN:" + + std::to_string(idx) + ":" + + std::to_string(newPos.x()) + ":" + + std::to_string(newPos.y()) + ":" + + std::to_string(newPos.z()) + ":" + + std::to_string(q.w()) + ":" + + std::to_string(q.x()) + ":" + + std::to_string(q.y()) + ":" + + std::to_string(q.z()); + respawnMsgs.push_back(respawnMsg); + std::cout << "Server: Box " << idx << " respawned" << std::endl; + } + } + for (const auto& msg : respawnMsgs) { + broadcastToAll(msg); + } + } + // --- Schedule next tick in 50ms --- timer.expires_after(std::chrono::milliseconds(50)); timer.async_wait([this](const boost::system::error_code& ec) { @@ -820,6 +942,7 @@ int main() { Server server(acceptor, ioc); + server.init(); server.accept(); std::cout << "Server started on port 8081...\n"; diff --git a/server/server.h b/server/server.h index 485f35c..a53ee50 100644 --- a/server/server.h +++ b/server/server.h @@ -135,6 +135,8 @@ public: void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity); void update_world(); Eigen::Vector3f PickSafeSpawnPos(int forPlayerId); + // Caller must hold g_boxes_mutex + Eigen::Vector3f PickSafeBoxPos(int skipIdx); void init(); void accept(); }; diff --git a/src/Space.cpp b/src/Space.cpp index 98f953f..cde5e60 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -265,6 +265,7 @@ namespace ZL Environment::shipState.position = Vector3f{ 0, 0, 45000.f }; Environment::shipState.velocity = 0.0f; Environment::shipState.selectedVelocity = 0; + newShipVelocity = 0; Environment::shipState.rotation = Eigen::Matrix3f::Identity(); Environment::inverseShipMatrix = Eigen::Matrix3f::Identity(); Environment::zoom = DEFAULT_ZOOM; @@ -274,7 +275,21 @@ namespace ZL { menuManager.uiManager.findButton("minusButton")->state = ButtonState::Disabled; } - + if (menuManager.uiManager.findButton("plusButton")) + { + menuManager.uiManager.findButton("plusButton")->state = ButtonState::Normal; + } + if (Environment::shipState.shipType == 0) + { + if (menuManager.uiManager.findButton("shootButton")) + { + menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal; + } + if (menuManager.uiManager.findButton("shootButton2")) + { + menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal; + } + } } void Space::setup() { @@ -1702,6 +1717,7 @@ namespace ZL gameOver = true; Environment::shipState.selectedVelocity = 0; Environment::shipState.velocity = 0.0f; + newShipVelocity = 0; showExplosion = true; explosionEmitter.setUseWorldSpace(true); @@ -1803,7 +1819,7 @@ namespace ZL } bool canPickup = false; - if (Environment::shipState.shipType == 1) { + if (Environment::shipState.shipType == 1 && Environment::shipState.velocity < 0.1f) { for (size_t i = 0; i < boxCoordsArr.size(); ++i) { if (i >= boxAlive.size() || !boxAlive[i]) continue; Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.f, 0.f, 45000.f }; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 12c1c32..fadb13e 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -31,11 +31,11 @@ constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD rec constexpr float PROJECTILE_VELOCITY = 600.f; constexpr float PROJECTILE_LIFE = 15000.f; //ms -const float projectileHitRadius = 1.5f * 5; -const float boxCollisionRadius = 2.0f * 5; -const float shipCollisionRadius = 15.0f * 5; -const float BOX_PICKUP_RADIUS = shipCollisionRadius * 5; -const float npcCollisionRadius = 5.0f * 5; +const float projectileHitRadius = 1.5f * 4; +const float boxCollisionRadius = 2.0f * 4; +const float shipCollisionRadius = 15.0f * 3; +const float BOX_PICKUP_RADIUS = shipCollisionRadius * 3; +const float npcCollisionRadius = 5.0f * 3; uint32_t fnv1a_hash(const std::string& data);