fixed: spawning ships
This commit is contained in:
parent
7e5aa22fee
commit
26233f934f
@ -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) {
|
||||||
|
|||||||
26
src/Game.cpp
26
src/Game.cpp
@ -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();
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
@ -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; }
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user