fixed: spawning ships

This commit is contained in:
Ariari04 2026-03-02 17:47:46 +06:00
parent 7e5aa22fee
commit 26233f934f
4 changed files with 195 additions and 4 deletions

View File

@ -23,6 +23,15 @@ namespace http = beast::http;
namespace websocket = beast::websocket; namespace websocket = beast::websocket;
namespace net = boost::asio; namespace net = boost::asio;
using tcp = net::ip::tcp; using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
struct DeathInfo { struct DeathInfo {
int targetId = -1; int targetId = -1;
@ -92,6 +101,10 @@ class Session : public std::enable_shared_from_this<Session> {
public: public:
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
bool joined_ = false;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player"; std::string nickname = "Player";
int shipType = 0; int shipType = 0;
@ -102,6 +115,9 @@ public:
int get_id() const { return id_; } int get_id() const { return id_; }
bool hasSpawnReserved() const { return hasReservedSpawn_; }
const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; }
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const { bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
if (timedClientStates.canFetchClientStateAtTime(targetTime)) { if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
outState = timedClientStates.fetchClientStateAtTime(targetTime); outState = timedClientStates.fetchClientStateAtTime(targetTime);
@ -189,7 +205,11 @@ public:
timer->expires_after(std::chrono::milliseconds(100)); timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) { timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
if (!ec) { if (!ec) {
self->send_message("ID:" + std::to_string(self->id_)); auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
self->do_read(); self->do_read();
} }
}); });
@ -281,7 +301,45 @@ private:
this->nickname = nick; this->nickname = nick;
this->shipType = sType; this->shipType = sType;
this->joined_ = true;
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
ClientState st;
st.id = id_;
st.position = spawnPos;
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
);
std::string eventMsg =
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == id_) continue;
session->send_message(eventMsg);
}
}
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl; std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType); std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
@ -298,12 +356,16 @@ private:
for (auto& session : g_sessions) { for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue; if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType); std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
// Отправляем именно новому клиенту
this->send_message(otherInfo); this->send_message(otherInfo);
} }
} }
} }
else if (type == "UPD") { else if (type == "UPD") {
if (!joined_) {
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
return;
}
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(id_) != g_dead_players.end()) { if (g_dead_players.find(id_) != g_dead_players.end()) {
@ -338,7 +400,13 @@ private:
ClientState st; ClientState st;
st.id = id_; st.id = id_;
st.position = Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
st.position = spawnPos;
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
st.rotation = Eigen::Matrix3f::Identity(); st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero(); st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f; st.velocity = 0.0f;
@ -350,7 +418,9 @@ private:
st.shipType = this->shipType; st.shipType = this->shipType;
timedClientStates.add_state(st); timedClientStates.add_state(st);
this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
);
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
broadcastToAll(respawnMsg); broadcastToAll(respawnMsg);
@ -436,6 +506,71 @@ private:
}; };
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
{
static thread_local std::mt19937 rng{ std::random_device{}() };
std::scoped_lock lock(g_boxes_mutex, g_sessions_mutex, g_dead_mutex);
auto isSafe = [&](const Eigen::Vector3f& pWorld) -> bool
{
for (const auto& box : g_serverBoxes) {
if (box.destroyed) continue;
Eigen::Vector3f boxWorld = box.position + kWorldOffset;
float minDist = kShipRadius + box.collisionRadius + kSpawnBoxMargin;
if ((pWorld - boxWorld).squaredNorm() < minDist * minDist)
return false;
}
for (const auto& s : g_sessions) {
int pid = s->get_id();
if (pid == forPlayerId) continue;
if (g_dead_players.count(pid)) continue;
Eigen::Vector3f otherPos;
if (!s->timedClientStates.timedStates.empty()) {
otherPos = s->timedClientStates.timedStates.back().position;
}
else if (s->hasSpawnReserved()) {
otherPos = s->reservedSpawn();
}
else {
continue;
}
float minDist = (kShipRadius * 2.0f) + kSpawnShipMargin;
if ((pWorld - otherPos).squaredNorm() < minDist * minDist)
return false;
}
return true;
};
const float radii[] = { 150.f, 250.f, 400.f, 650.f, 1000.f, 1600.f };
for (float r : radii) {
std::uniform_real_distribution<float> dxy(-r, r);
std::uniform_real_distribution<float> dz(-kSpawnZJitter, kSpawnZJitter);
for (int attempt = 0; attempt < 250; ++attempt) {
Eigen::Vector3f cand(
dxy(rng),
dxy(rng),
kWorldZOffset + dz(rng)
);
if (isSafe(cand))
return cand;
}
}
int a = (forPlayerId % 10);
int b = ((forPlayerId / 10) % 10);
return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset);
}
void broadcastToAll(const std::string& message) { void broadcastToAll(const std::string& message) {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (const auto& session : g_sessions) { for (const auto& session : g_sessions) {

View File

@ -16,6 +16,7 @@
#endif #endif
#ifdef NETWORK #ifdef NETWORK
#include "network/WebSocketClientBase.h"
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
#include "network/WebSocketClientEmscripten.h" #include "network/WebSocketClientEmscripten.h"
#else #else
@ -443,6 +444,31 @@ namespace ZL
} }
#endif #endif
networkClient->Poll(); networkClient->Poll();
#ifdef NETWORK
auto* wsBase = dynamic_cast<ZL::WebSocketClientBase*>(networkClient.get());
if (wsBase) {
auto spawns = wsBase->getPendingSpawns();
for (auto& st : spawns) {
if (st.id == wsBase->getClientId()) {
// применяем к локальному кораблю
ZL::Environment::shipState.position = st.position;
ZL::Environment::shipState.rotation = st.rotation;
// обнуляем движение чтобы не было рывков
ZL::Environment::shipState.currentAngularVelocity = Eigen::Vector3f::Zero();
ZL::Environment::shipState.velocity = 0.0f;
ZL::Environment::shipState.selectedVelocity = 0;
ZL::Environment::shipState.discreteMag = 0.0f;
ZL::Environment::shipState.discreteAngle = -1;
std::cout << "Game: Applied SPAWN at "
<< st.position.x() << ", "
<< st.position.y() << ", "
<< st.position.z() << std::endl;
}
}
}
#endif
} }
mainThreadHandler.processMainThreadTasks(); mainThreadHandler.processMainThreadTasks();

View File

@ -190,7 +190,28 @@ namespace ZL {
} }
return; return;
} }
if (msg.rfind("SPAWN:", 0) == 0) {
// SPAWN:playerId:serverTime:<14 полей>
if (parts.size() >= 3 + 14) {
try {
int pid = std::stoi(parts[1]);
uint64_t serverTime = std::stoull(parts[2]);
ClientState st;
st.id = pid;
std::chrono::system_clock::time_point tp{ std::chrono::milliseconds(serverTime) };
st.lastUpdateServerTime = tp;
// данные начинаются с parts[3]
st.handle_full_sync(parts, 3);
pendingSpawns_.push_back(st);
std::cout << "Client: SPAWN received for player " << pid << std::endl;
}
catch (...) {}
}
return;
}
if (msg.rfind("EVENT:", 0) == 0) { if (msg.rfind("EVENT:", 0) == 0) {
//auto parts = split(msg, ':'); //auto parts = split(msg, ':');
if (parts.size() < 5) return; // EVENT:ID:TYPE:TIME:DATA... if (parts.size() < 5) return; // EVENT:ID:TYPE:TIME:DATA...
@ -334,6 +355,12 @@ namespace ZL {
copy.swap(pendingBoxDestructions_); copy.swap(pendingBoxDestructions_);
return copy; return copy;
} }
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
std::vector<ClientState> copy;
copy.swap(pendingSpawns_);
return copy;
}
} }
#endif #endif

View File

@ -22,6 +22,7 @@ namespace ZL {
std::vector<BoxDestroyedInfo> pendingBoxDestructions_; std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
int clientId = -1; int clientId = -1;
int64_t timeOffset = 0; int64_t timeOffset = 0;
std::vector<ClientState> pendingSpawns_;
public: public:
int GetClientId() const override { return clientId; } int GetClientId() const override { return clientId; }
@ -48,6 +49,8 @@ namespace ZL {
std::vector<DeathInfo> getPendingDeaths() override; std::vector<DeathInfo> getPendingDeaths() override;
std::vector<int> getPendingRespawns() override; std::vector<int> getPendingRespawns() override;
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override; std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
std::vector<ClientState> getPendingSpawns();
int getClientId() const { return clientId; }
}; };
} }