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 net = boost::asio;
|
||||
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 {
|
||||
int targetId = -1;
|
||||
@ -92,6 +101,10 @@ class Session : public std::enable_shared_from_this<Session> {
|
||||
|
||||
public:
|
||||
ClientStateInterval timedClientStates;
|
||||
bool joined_ = false;
|
||||
|
||||
bool hasReservedSpawn_ = false;
|
||||
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
|
||||
|
||||
std::string nickname = "Player";
|
||||
int shipType = 0;
|
||||
@ -102,6 +115,9 @@ public:
|
||||
|
||||
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 {
|
||||
if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
|
||||
outState = timedClientStates.fetchClientStateAtTime(targetTime);
|
||||
@ -189,7 +205,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) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
@ -281,7 +301,45 @@ private:
|
||||
|
||||
this->nickname = nick;
|
||||
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::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
|
||||
@ -298,12 +356,16 @@ private:
|
||||
for (auto& session : g_sessions) {
|
||||
if (session->get_id() == this->id_) continue;
|
||||
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
|
||||
// Отправляем именно новому клиенту
|
||||
|
||||
this->send_message(otherInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
if (g_dead_players.find(id_) != g_dead_players.end()) {
|
||||
@ -338,7 +400,13 @@ private:
|
||||
|
||||
ClientState st;
|
||||
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.currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||
st.velocity = 0.0f;
|
||||
@ -350,7 +418,9 @@ private:
|
||||
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 respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
|
||||
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) {
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
for (const auto& session : g_sessions) {
|
||||
|
||||
26
src/Game.cpp
26
src/Game.cpp
@ -16,6 +16,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef NETWORK
|
||||
#include "network/WebSocketClientBase.h"
|
||||
#ifdef EMSCRIPTEN
|
||||
#include "network/WebSocketClientEmscripten.h"
|
||||
#else
|
||||
@ -443,6 +444,31 @@ namespace ZL
|
||||
}
|
||||
#endif
|
||||
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();
|
||||
|
||||
|
||||
@ -190,7 +190,28 @@ namespace ZL {
|
||||
}
|
||||
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) {
|
||||
//auto parts = split(msg, ':');
|
||||
if (parts.size() < 5) return; // EVENT:ID:TYPE:TIME:DATA...
|
||||
@ -334,6 +355,12 @@ namespace ZL {
|
||||
copy.swap(pendingBoxDestructions_);
|
||||
return copy;
|
||||
}
|
||||
|
||||
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
|
||||
std::vector<ClientState> copy;
|
||||
copy.swap(pendingSpawns_);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -22,6 +22,7 @@ namespace ZL {
|
||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
||||
int clientId = -1;
|
||||
int64_t timeOffset = 0;
|
||||
std::vector<ClientState> pendingSpawns_;
|
||||
|
||||
public:
|
||||
int GetClientId() const override { return clientId; }
|
||||
@ -48,6 +49,8 @@ namespace ZL {
|
||||
std::vector<DeathInfo> getPendingDeaths() override;
|
||||
std::vector<int> getPendingRespawns() override;
|
||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||
std::vector<ClientState> getPendingSpawns();
|
||||
int getClientId() const { return clientId; }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user