Some cleanup
This commit is contained in:
parent
a96d0b207a
commit
2e4a7ab467
@ -50,17 +50,17 @@ add_executable(space-game001
|
|||||||
../src/UiManager.h
|
../src/UiManager.h
|
||||||
../src/Projectile.h
|
../src/Projectile.h
|
||||||
../src/Projectile.cpp
|
../src/Projectile.cpp
|
||||||
../src/network/NetworkInterface.h
|
# ../src/network/NetworkInterface.h
|
||||||
../src/network/LocalClient.h
|
# ../src/network/LocalClient.h
|
||||||
../src/network/LocalClient.cpp
|
# ../src/network/LocalClient.cpp
|
||||||
../src/network/ClientState.h
|
# ../src/network/ClientState.h
|
||||||
../src/network/ClientState.cpp
|
# ../src/network/ClientState.cpp
|
||||||
../src/network/WebSocketClient.h
|
# ../src/network/WebSocketClient.h
|
||||||
../src/network/WebSocketClient.cpp
|
# ../src/network/WebSocketClient.cpp
|
||||||
../src/network/WebSocketClientBase.h
|
# ../src/network/WebSocketClientBase.h
|
||||||
../src/network/WebSocketClientBase.cpp
|
# ../src/network/WebSocketClientBase.cpp
|
||||||
../src/network/WebSocketClientEmscripten.h
|
# ../src/network/WebSocketClientEmscripten.h
|
||||||
../src/network/WebSocketClientEmscripten.cpp
|
# ../src/network/WebSocketClientEmscripten.cpp
|
||||||
../src/render/TextRenderer.h
|
../src/render/TextRenderer.h
|
||||||
../src/render/TextRenderer.cpp
|
../src/render/TextRenderer.cpp
|
||||||
../src/MenuManager.h
|
../src/MenuManager.h
|
||||||
|
|||||||
@ -31,8 +31,6 @@ bool Environment::tapDownHold = false;
|
|||||||
Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 };
|
Eigen::Vector2f Environment::tapDownStartPos = { 0, 0 };
|
||||||
Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 };
|
Eigen::Vector2f Environment::tapDownCurrentPos = { 0, 0 };
|
||||||
|
|
||||||
ClientState Environment::shipState;
|
|
||||||
|
|
||||||
const float Environment::CONST_Z_NEAR = 0.1f;
|
const float Environment::CONST_Z_NEAR = 0.1f;
|
||||||
const float Environment::CONST_Z_FAR = 100.f;
|
const float Environment::CONST_Z_FAR = 100.f;
|
||||||
|
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
#include "render/OpenGlExtensions.h"
|
#include "render/OpenGlExtensions.h"
|
||||||
#endif
|
#endif
|
||||||
#include <Eigen/Dense>
|
#include <Eigen/Dense>
|
||||||
#include "network/ClientState.h"
|
|
||||||
|
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
@ -32,8 +31,6 @@ public:
|
|||||||
static Eigen::Vector2f tapDownStartPos;
|
static Eigen::Vector2f tapDownStartPos;
|
||||||
static Eigen::Vector2f tapDownCurrentPos;
|
static Eigen::Vector2f tapDownCurrentPos;
|
||||||
|
|
||||||
static ClientState shipState;
|
|
||||||
|
|
||||||
static const float CONST_Z_NEAR;
|
static const float CONST_Z_NEAR;
|
||||||
static const float CONST_Z_FAR;
|
static const float CONST_Z_FAR;
|
||||||
|
|
||||||
|
|||||||
11
src/Game.cpp
11
src/Game.cpp
@ -15,21 +15,12 @@
|
|||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef NETWORK
|
|
||||||
#include "network/WebSocketClientBase.h"
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
#include "network/WebSocketClientEmscripten.h"
|
|
||||||
#else
|
|
||||||
#include "network/WebSocketClient.h"
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
#include <emscripten.h>
|
#include <emscripten.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "network/LocalClient.h"
|
|
||||||
#include "network/ClientState.h"
|
|
||||||
#include "GameConstants.h"
|
#include "GameConstants.h"
|
||||||
|
|
||||||
namespace ZL
|
namespace ZL
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
#include "UiManager.h"
|
#include "UiManager.h"
|
||||||
#include "Projectile.h"
|
#include "Projectile.h"
|
||||||
#include "utils/TaskManager.h"
|
#include "utils/TaskManager.h"
|
||||||
#include "network/NetworkInterface.h"
|
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -16,7 +15,6 @@
|
|||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <render/TextRenderer.h>
|
#include <render/TextRenderer.h>
|
||||||
#include "MenuManager.h"
|
#include "MenuManager.h"
|
||||||
#include "Space.h"
|
|
||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
@ -113,9 +111,6 @@ namespace ZL {
|
|||||||
static void onResourcesZipError(const char* filename);
|
static void onResourcesZipError(const char* filename);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
//SDL_Window* window;
|
|
||||||
//SDL_GLContext glContext;
|
|
||||||
|
|
||||||
int64_t newTickCount;
|
int64_t newTickCount;
|
||||||
int64_t lastTickCount;
|
int64_t lastTickCount;
|
||||||
uint32_t connectingStartTicks = 0;
|
uint32_t connectingStartTicks = 0;
|
||||||
@ -125,7 +120,6 @@ namespace ZL {
|
|||||||
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
static const size_t CONST_MAX_TIME_INTERVAL = 1000;
|
||||||
|
|
||||||
MenuManager menuManager;
|
MenuManager menuManager;
|
||||||
//Space space;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -94,16 +94,9 @@ namespace ZL {
|
|||||||
for (const auto& particle : particles) {
|
for (const auto& particle : particles) {
|
||||||
if (particle.active) {
|
if (particle.active) {
|
||||||
Vector3f posCam;
|
Vector3f posCam;
|
||||||
if (withRotation) {
|
|
||||||
Vector3f rel = particle.position - Environment::shipState.position;
|
posCam = particle.position;
|
||||||
posCam = Environment::inverseShipMatrix * rel;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
posCam = particle.position;
|
|
||||||
//Vector3f rel = particle.position - Environment::shipState.position;
|
|
||||||
//posCam = Environment::shipState.position + Eigen::Quaternionf(Eigen::AngleAxisf(M_PI * 150 / 180.f, Eigen::Vector3f::UnitY())).toRotationMatrix() * Environment::inverseShipMatrix * rel;
|
|
||||||
}
|
|
||||||
//sortedParticles.push_back({ &particle, particle.position(2) });
|
|
||||||
sortedParticles.push_back({ &particle, posCam(2) });
|
sortedParticles.push_back({ &particle, posCam(2) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,39 +109,12 @@ namespace ZL {
|
|||||||
for (const auto& [particlePtr, depth] : sortedParticles) {
|
for (const auto& [particlePtr, depth] : sortedParticles) {
|
||||||
const auto& particle = *particlePtr;
|
const auto& particle = *particlePtr;
|
||||||
Vector3f posCam;
|
Vector3f posCam;
|
||||||
if (useWorldSpace) {
|
posCam = particle.position;
|
||||||
Vector3f rel = particle.position - Environment::shipState.position;
|
|
||||||
posCam = Environment::inverseShipMatrix * rel;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
posCam = particle.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
float size = particleSize * particle.scale;
|
float size = particleSize * particle.scale;
|
||||||
|
|
||||||
if (withRotation)
|
|
||||||
{
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 0.0f, 1.0f });
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 1.0f, 1.0f });
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 1.0f, 1.0f });
|
|
||||||
|
|
||||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, -size, 0 } + posCam);
|
|
||||||
drawTexCoords.push_back({ 1.0f, 0.0f });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
|
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
|
||||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||||
|
|
||||||
@ -167,7 +133,7 @@ namespace ZL {
|
|||||||
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
|
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
|
||||||
drawTexCoords.push_back({ 1.0f, 0.0f });
|
drawTexCoords.push_back({ 1.0f, 0.0f });
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
drawDataDirty = false;
|
drawDataDirty = false;
|
||||||
|
|||||||
@ -1,305 +0,0 @@
|
|||||||
#include "ClientState.h"
|
|
||||||
|
|
||||||
uint32_t fnv1a_hash(const std::string& data) {
|
|
||||||
uint32_t hash = 0x811c9dc5;
|
|
||||||
for (unsigned char c : data) {
|
|
||||||
hash ^= c;
|
|
||||||
hash *= 0x01000193;
|
|
||||||
}
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientState::simulate_physics(size_t delta) {
|
|
||||||
if (discreteMag > 0.01f)
|
|
||||||
{
|
|
||||||
float rad = static_cast<float>(discreteAngle) * static_cast<float>(M_PI) / 180.0f;
|
|
||||||
|
|
||||||
// Целевая угловая скорость (дискретная сила определяет модуль вектора)
|
|
||||||
// Вектор {cos, sin, 0} дает нам направление отклонения джойстика
|
|
||||||
Eigen::Vector3f targetAngularVelDir(sinf(rad), cosf(rad), 0.0f);
|
|
||||||
Eigen::Vector3f targetAngularVelocity = targetAngularVelDir * discreteMag;
|
|
||||||
|
|
||||||
Eigen::Vector3f diffVel = targetAngularVelocity - currentAngularVelocity;
|
|
||||||
float diffLen = diffVel.norm();
|
|
||||||
|
|
||||||
if (diffLen > 0.0001f) {
|
|
||||||
// Вычисляем, на сколько мы можем изменить скорость в этом кадре
|
|
||||||
float maxChange = ANGULAR_ACCEL * static_cast<float>(delta);
|
|
||||||
|
|
||||||
if (diffLen <= maxChange) {
|
|
||||||
// Если до цели осталось меньше, чем шаг ускорения — просто прыгаем в цель
|
|
||||||
currentAngularVelocity = targetAngularVelocity;
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
|
||||||
// Линейно двигаемся в сторону целевого вектора
|
|
||||||
currentAngularVelocity += (diffVel / diffLen) * maxChange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float currentSpeed = currentAngularVelocity.norm();
|
|
||||||
|
|
||||||
if (currentSpeed > 0.0001f) {
|
|
||||||
float drop = ANGULAR_ACCEL * static_cast<float>(delta);
|
|
||||||
if (currentSpeed <= drop) {
|
|
||||||
currentAngularVelocity = Eigen::Vector3f::Zero();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Уменьшаем модуль вектора, сохраняя направление
|
|
||||||
currentAngularVelocity -= (currentAngularVelocity / currentSpeed) * drop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
float distToCenter = position.norm(); // Расстояние до {0,0,0}
|
|
||||||
float landingZone = PLANET_RADIUS * PLANET_ALIGN_ZONE;
|
|
||||||
|
|
||||||
if (distToCenter <= landingZone) {
|
|
||||||
Eigen::Vector3f planetNormal = position.normalized();
|
|
||||||
|
|
||||||
// --- 1. ВЫРАВНИВАНИЕ КРЕНА (Roll - ось Z) ---
|
|
||||||
Eigen::Vector3f localX = rotation.col(0);
|
|
||||||
float rollError = localX.dot(planetNormal);
|
|
||||||
|
|
||||||
if (std::abs(rollError) > 0.001f) {
|
|
||||||
currentAngularVelocity.z() -= rollError * PLANET_ANGULAR_ACCEL * delta;
|
|
||||||
currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
|
|
||||||
std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY));
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- 2. ОГРАНИЧЕНИЕ ТАНГАЖА (Pitch - ось X) ---
|
|
||||||
// Нос корабля в локальных координатах — это -Z (третий столбец матрицы)
|
|
||||||
Eigen::Vector3f forwardDir = -rotation.col(2);
|
|
||||||
|
|
||||||
// В твоем случае dot < 0 означает, что нос направлен К планете
|
|
||||||
float pitchSin = forwardDir.dot(planetNormal);
|
|
||||||
float currentPitchAngle = asinf(std::clamp(pitchSin, -1.0f, 1.0f));
|
|
||||||
|
|
||||||
// Лимит у нас M_PI / 6.0 (примерно 0.523)
|
|
||||||
// По твоим данным: -0.89 < -0.523, значит мы превысили наклон вниз
|
|
||||||
if (currentPitchAngle < -PITCH_LIMIT) {
|
|
||||||
// Вычисляем ошибку (насколько мы ушли "ниже" лимита)
|
|
||||||
// -0.89 - (-0.52) = -0.37
|
|
||||||
float pitchError = currentPitchAngle + PITCH_LIMIT;
|
|
||||||
|
|
||||||
// Теперь важно: нам нужно ПОДНЯТЬ нос.
|
|
||||||
// Если pitchError отрицательный, а нам нужно уменьшить вращение,
|
|
||||||
// пробуем прибавлять или вычитать в зависимости от твоей оси X.
|
|
||||||
// Судя по стандартной логике Eigen, нам нужно ПЛЮСОВАТЬ:
|
|
||||||
currentAngularVelocity.x() -= pitchError * PLANET_ANGULAR_ACCEL * delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Вне зоны: тормозим Z (крен) И X (тангаж), если они были активны
|
|
||||||
float drop = ANGULAR_ACCEL * delta;
|
|
||||||
|
|
||||||
if (std::abs(currentAngularVelocity[2]) > 0.0001f) {
|
|
||||||
if (std::abs(currentAngularVelocity[2]) <= drop) {
|
|
||||||
currentAngularVelocity[2] = 0.0f;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
currentAngularVelocity[2] -= (currentAngularVelocity[2] > 0 ? 1.0f : -1.0f) * drop;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ограничение скорости
|
|
||||||
currentAngularVelocity.x() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
|
|
||||||
std::min(currentAngularVelocity.x(), PLANET_MAX_ANGULAR_VELOCITY));
|
|
||||||
// Ограничение скорости
|
|
||||||
currentAngularVelocity.y() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
|
|
||||||
std::min(currentAngularVelocity.y(), PLANET_MAX_ANGULAR_VELOCITY));
|
|
||||||
// Ограничение скорости
|
|
||||||
currentAngularVelocity.z() = std::max(-PLANET_MAX_ANGULAR_VELOCITY,
|
|
||||||
std::min(currentAngularVelocity.z(), PLANET_MAX_ANGULAR_VELOCITY));
|
|
||||||
|
|
||||||
float speedScale = currentAngularVelocity.norm();
|
|
||||||
if (speedScale > 0.0001f) {
|
|
||||||
// Коэффициент чувствительности вращения
|
|
||||||
|
|
||||||
float deltaAlpha = speedScale * static_cast<float>(delta) * ROTATION_SENSITIVITY;
|
|
||||||
|
|
||||||
Eigen::Vector3f axis = currentAngularVelocity.normalized();
|
|
||||||
Eigen::Quaternionf rotateQuat(Eigen::AngleAxisf(deltaAlpha, axis));
|
|
||||||
|
|
||||||
rotation = rotation * rotateQuat.toRotationMatrix();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 4. Линейное изменение линейной скорости
|
|
||||||
float shipDesiredVelocity = selectedVelocity * 100.f;
|
|
||||||
|
|
||||||
if (velocity < shipDesiredVelocity)
|
|
||||||
{
|
|
||||||
velocity += delta * SHIP_ACCEL;
|
|
||||||
if (velocity > shipDesiredVelocity)
|
|
||||||
{
|
|
||||||
velocity = shipDesiredVelocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (velocity > shipDesiredVelocity)
|
|
||||||
{
|
|
||||||
velocity -= delta * SHIP_ACCEL;
|
|
||||||
if (velocity < shipDesiredVelocity)
|
|
||||||
{
|
|
||||||
velocity = shipDesiredVelocity;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fabs(velocity) > 0.01f)
|
|
||||||
{
|
|
||||||
Eigen::Vector3f velocityDirection = { 0,0, -velocity * delta / 1000.f };
|
|
||||||
Eigen::Vector3f velocityDirectionAdjusted = rotation * velocityDirection;
|
|
||||||
position = position + velocityDirectionAdjusted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientState::apply_lag_compensation(std::chrono::system_clock::time_point nowTime) {
|
|
||||||
|
|
||||||
// 2. Вычисляем задержку
|
|
||||||
long long deltaMs = 0;
|
|
||||||
if (nowTime > lastUpdateServerTime) {
|
|
||||||
deltaMs = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - lastUpdateServerTime).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
long long deltaMsLeftover = deltaMs;
|
|
||||||
|
|
||||||
while (deltaMsLeftover > 0)
|
|
||||||
{
|
|
||||||
long long miniDelta = std::min(50LL, deltaMsLeftover);
|
|
||||||
simulate_physics(miniDelta);
|
|
||||||
deltaMsLeftover -= miniDelta;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// 3. Защита от слишком больших скачков (Clamp)
|
|
||||||
// Если лаг более 500мс, ограничиваем его, чтобы избежать резких рывков
|
|
||||||
long long final_lag_ms = deltaMs;//min(deltaMs, 500ll);
|
|
||||||
|
|
||||||
if (final_lag_ms > 0) {
|
|
||||||
// Доматываем симуляцию на величину задержки
|
|
||||||
// Мы предполагаем, что за это время параметры управления не менялись
|
|
||||||
simulate_physics(final_lag_ms);
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientState::handle_full_sync(const std::vector<std::string>& parts, int startFrom) {
|
|
||||||
// Позиция
|
|
||||||
position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
|
|
||||||
|
|
||||||
Eigen::Quaternionf q(
|
|
||||||
std::stof(parts[startFrom + 3]),
|
|
||||||
std::stof(parts[startFrom + 4]),
|
|
||||||
std::stof(parts[startFrom + 5]),
|
|
||||||
std::stof(parts[startFrom + 6]));
|
|
||||||
rotation = q.toRotationMatrix();
|
|
||||||
|
|
||||||
currentAngularVelocity = Eigen::Vector3f{
|
|
||||||
std::stof(parts[startFrom + 7]),
|
|
||||||
std::stof(parts[startFrom + 8]),
|
|
||||||
std::stof(parts[startFrom + 9]) };
|
|
||||||
velocity = std::stof(parts[startFrom + 10]);
|
|
||||||
selectedVelocity = std::stoi(parts[startFrom + 11]);
|
|
||||||
discreteMag = std::stof(parts[startFrom + 12]);
|
|
||||||
discreteAngle = std::stoi(parts[startFrom + 13]);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string ClientState::formPingMessageContent() const
|
|
||||||
{
|
|
||||||
Eigen::Quaternionf q(rotation);
|
|
||||||
|
|
||||||
std::string pingMsg = std::to_string(position.x()) + ":"
|
|
||||||
+ std::to_string(position.y()) + ":"
|
|
||||||
+ std::to_string(position.z()) + ":"
|
|
||||||
+ std::to_string(q.w()) + ":"
|
|
||||||
+ std::to_string(q.x()) + ":"
|
|
||||||
+ std::to_string(q.y()) + ":"
|
|
||||||
+ std::to_string(q.z()) + ":"
|
|
||||||
+ std::to_string(currentAngularVelocity.x()) + ":"
|
|
||||||
+ std::to_string(currentAngularVelocity.y()) + ":"
|
|
||||||
+ std::to_string(currentAngularVelocity.z()) + ":"
|
|
||||||
+ std::to_string(velocity) + ":"
|
|
||||||
+ std::to_string(selectedVelocity) + ":"
|
|
||||||
+ std::to_string(discreteMag) + ":" // Используем те же static переменные из блока ROT
|
|
||||||
+ std::to_string(discreteAngle);
|
|
||||||
|
|
||||||
return pingMsg;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void ClientStateInterval::add_state(const ClientState& state)
|
|
||||||
{
|
|
||||||
auto nowTime = std::chrono::system_clock::now();
|
|
||||||
|
|
||||||
if (timedStates.size() > 0 && timedStates[timedStates.size() - 1].lastUpdateServerTime == state.lastUpdateServerTime)
|
|
||||||
{
|
|
||||||
timedStates[timedStates.size() - 1] = state;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
timedStates.push_back(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cutoff_time = nowTime - std::chrono::milliseconds(CUTOFF_TIME);
|
|
||||||
|
|
||||||
while (timedStates.size() > 0 && timedStates[0].lastUpdateServerTime < cutoff_time)
|
|
||||||
{
|
|
||||||
timedStates.erase(timedStates.begin());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ClientStateInterval::canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const
|
|
||||||
{
|
|
||||||
if (timedStates.empty())
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (timedStates[0].lastUpdateServerTime > targetTime)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientState ClientStateInterval::fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const {
|
|
||||||
|
|
||||||
ClientState closestState;
|
|
||||||
|
|
||||||
if (timedStates.empty())
|
|
||||||
{
|
|
||||||
throw std::runtime_error("No timed client states available");
|
|
||||||
return closestState;
|
|
||||||
}
|
|
||||||
if (timedStates[0].lastUpdateServerTime > targetTime)
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Found time but it is in future");
|
|
||||||
return closestState;
|
|
||||||
}
|
|
||||||
if (timedStates.size() == 1)
|
|
||||||
{
|
|
||||||
closestState = timedStates[0];
|
|
||||||
closestState.apply_lag_compensation(targetTime);
|
|
||||||
return closestState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (size_t i = 0; i < timedStates.size() - 1; ++i)
|
|
||||||
{
|
|
||||||
const auto& earlierState = timedStates[i];
|
|
||||||
const auto& laterState = timedStates[i + 1];
|
|
||||||
if (earlierState.lastUpdateServerTime <= targetTime && laterState.lastUpdateServerTime >= targetTime)
|
|
||||||
{
|
|
||||||
closestState = earlierState;
|
|
||||||
closestState.apply_lag_compensation(targetTime);
|
|
||||||
return closestState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
closestState = timedStates[timedStates.size() - 1];
|
|
||||||
closestState.apply_lag_compensation(targetTime);
|
|
||||||
return closestState;
|
|
||||||
}
|
|
||||||
@ -1,76 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <chrono>
|
|
||||||
#include <Eigen/Dense>
|
|
||||||
#define _USE_MATH_DEFINES
|
|
||||||
#include <math.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
|
|
||||||
using std::min;
|
|
||||||
using std::max;
|
|
||||||
|
|
||||||
constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f";
|
|
||||||
#define ENABLE_NETWORK_CHECKSUM
|
|
||||||
|
|
||||||
constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f;
|
|
||||||
constexpr float SHIP_ACCEL = 1.0f * 200.0f;
|
|
||||||
constexpr float ROTATION_SENSITIVITY = 0.002f;
|
|
||||||
|
|
||||||
constexpr float PLANET_RADIUS = 20000.f;
|
|
||||||
constexpr float PLANET_ALIGN_ZONE = 1.05f;
|
|
||||||
constexpr float PLANET_ANGULAR_ACCEL = 0.01f;
|
|
||||||
constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f;
|
|
||||||
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
|
|
||||||
|
|
||||||
constexpr long long SERVER_DELAY = 0; //ms
|
|
||||||
constexpr long long CLIENT_DELAY = 500; //ms
|
|
||||||
constexpr long long CUTOFF_TIME = 5000; //ms
|
|
||||||
constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD received
|
|
||||||
|
|
||||||
constexpr float PROJECTILE_VELOCITY = 600.f;
|
|
||||||
constexpr float PROJECTILE_LIFE = 15000.f; //ms
|
|
||||||
|
|
||||||
const float projectileHitRadius = 2.5f * 4;
|
|
||||||
const float boxCollisionRadius = 3.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);
|
|
||||||
|
|
||||||
struct ClientState {
|
|
||||||
int id = 0;
|
|
||||||
Eigen::Vector3f position = { 0, 0, 45000.0f };
|
|
||||||
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
|
|
||||||
Eigen::Vector3f currentAngularVelocity = Eigen::Vector3f::Zero();
|
|
||||||
float velocity = 0.0f;
|
|
||||||
int selectedVelocity = 0;
|
|
||||||
float discreteMag = 0;
|
|
||||||
int discreteAngle = -1;
|
|
||||||
|
|
||||||
std::string nickname = "Player";
|
|
||||||
int shipType = 0;
|
|
||||||
|
|
||||||
std::chrono::system_clock::time_point lastUpdateServerTime;
|
|
||||||
|
|
||||||
void simulate_physics(size_t delta);
|
|
||||||
|
|
||||||
void apply_lag_compensation(std::chrono::system_clock::time_point nowTime);
|
|
||||||
|
|
||||||
void handle_full_sync(const std::vector<std::string>& parts, int startFrom);
|
|
||||||
|
|
||||||
std::string formPingMessageContent() const;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ClientStateInterval
|
|
||||||
{
|
|
||||||
std::vector<ClientState> timedStates;
|
|
||||||
|
|
||||||
void add_state(const ClientState& state);
|
|
||||||
|
|
||||||
bool canFetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const;
|
|
||||||
|
|
||||||
ClientState fetchClientStateAtTime(std::chrono::system_clock::time_point targetTime) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
@ -1,581 +0,0 @@
|
|||||||
#include "LocalClient.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <algorithm>
|
|
||||||
#include <cmath>
|
|
||||||
#define _USE_MATH_DEFINES
|
|
||||||
#include <math.h>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
void LocalClient::Connect(const std::string& host, uint16_t port) {
|
|
||||||
generateBoxes();
|
|
||||||
initializeNPCs();
|
|
||||||
lastUpdateMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::generateBoxes() {
|
|
||||||
serverBoxes.clear();
|
|
||||||
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 gen(rd());
|
|
||||||
|
|
||||||
const float MIN_COORD = -1000.0f;
|
|
||||||
const float MAX_COORD = 1000.0f;
|
|
||||||
const float MIN_DISTANCE = 3.0f;
|
|
||||||
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
|
||||||
const int MAX_ATTEMPTS = 1000;
|
|
||||||
|
|
||||||
std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
|
|
||||||
std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);
|
|
||||||
|
|
||||||
for (int i = 0; i < 50; i++) {
|
|
||||||
bool accepted = false;
|
|
||||||
int attempts = 0;
|
|
||||||
|
|
||||||
while (!accepted && attempts < MAX_ATTEMPTS) {
|
|
||||||
LocalServerBox box;
|
|
||||||
box.position = Eigen::Vector3f(
|
|
||||||
(float)posDistrib(gen),
|
|
||||||
(float)posDistrib(gen),
|
|
||||||
(float)posDistrib(gen)
|
|
||||||
);
|
|
||||||
|
|
||||||
accepted = true;
|
|
||||||
for (const auto& existingBox : serverBoxes) {
|
|
||||||
Eigen::Vector3f diff = box.position - existingBox.position;
|
|
||||||
if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
|
|
||||||
accepted = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accepted) {
|
|
||||||
float randomAngle = (float)angleDistrib(gen);
|
|
||||||
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
|
|
||||||
box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
|
|
||||||
serverBoxes.push_back(box);
|
|
||||||
}
|
|
||||||
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "LocalClient: Generated " << serverBoxes.size() << " boxes\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::Vector3f LocalClient::generateRandomPosition() {
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 gen(rd());
|
|
||||||
std::uniform_real_distribution<> distrib(-5000.0, 5000.0);
|
|
||||||
|
|
||||||
return Eigen::Vector3f(
|
|
||||||
(float)distrib(gen),
|
|
||||||
(float)distrib(gen),
|
|
||||||
(float)distrib(gen) + 45000.0f
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::initializeNPCs() {
|
|
||||||
npcs.clear();
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 gen(rd());
|
|
||||||
std::uniform_int_distribution<int> typeDistrib(0, 1); // 0 = default ship, 1 = cargo
|
|
||||||
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
LocalNPC npc;
|
|
||||||
npc.id = 100 + i;
|
|
||||||
npc.currentState.id = npc.id;
|
|
||||||
npc.currentState.position = generateRandomPosition();
|
|
||||||
npc.currentState.rotation = Eigen::Matrix3f::Identity();
|
|
||||||
npc.currentState.velocity = 0.0f;
|
|
||||||
npc.currentState.selectedVelocity = 0;
|
|
||||||
npc.currentState.discreteMag = 0.0f;
|
|
||||||
npc.currentState.discreteAngle = -1;
|
|
||||||
npc.currentState.currentAngularVelocity = Eigen::Vector3f::Zero();
|
|
||||||
|
|
||||||
// random
|
|
||||||
int shipType = typeDistrib(gen);
|
|
||||||
npc.shipType = shipType;
|
|
||||||
npc.currentState.shipType = shipType;
|
|
||||||
|
|
||||||
npc.targetPosition = generateRandomPosition();
|
|
||||||
npc.lastStateUpdateMs = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
npc.destroyed = false;
|
|
||||||
|
|
||||||
npc.stateHistory.add_state(npc.currentState);
|
|
||||||
npcs.push_back(npc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::updateNPCs() {
|
|
||||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
for (auto& npc : npcs) {
|
|
||||||
if (npc.destroyed) continue;
|
|
||||||
|
|
||||||
uint64_t deltaMs = now_ms - npc.lastStateUpdateMs;
|
|
||||||
if (deltaMs == 0) {
|
|
||||||
npc.lastStateUpdateMs = now_ms;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
npc.lastStateUpdateMs = now_ms;
|
|
||||||
|
|
||||||
Eigen::Vector3f toTarget = npc.targetPosition - npc.currentState.position;
|
|
||||||
float distance = toTarget.norm();
|
|
||||||
|
|
||||||
const float ARRIVAL_THRESHOLD = 100.0f;
|
|
||||||
|
|
||||||
if (distance < ARRIVAL_THRESHOLD) {
|
|
||||||
npc.targetPosition = generateRandomPosition();
|
|
||||||
toTarget = npc.targetPosition - npc.currentState.position;
|
|
||||||
distance = toTarget.norm();
|
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::Vector3f forwardWorld = -npc.currentState.rotation.col(2);
|
|
||||||
forwardWorld.normalize();
|
|
||||||
|
|
||||||
Eigen::Vector3f desiredDir = (distance > 0.001f) ? toTarget.normalized() : Eigen::Vector3f::UnitZ();
|
|
||||||
float dot = forwardWorld.dot(desiredDir);
|
|
||||||
float angleErrorRad = std::acos(std::clamp(dot, -1.0f, 1.0f));
|
|
||||||
|
|
||||||
const float ALIGN_TOLERANCE = 0.15f;
|
|
||||||
|
|
||||||
const float HYSTERESIS_FACTOR = 1.35f;
|
|
||||||
const float SOFT_THRUST_ANGLE = ALIGN_TOLERANCE * HYSTERESIS_FACTOR;
|
|
||||||
|
|
||||||
if (angleErrorRad < ALIGN_TOLERANCE) {
|
|
||||||
npc.currentState.selectedVelocity = 1;
|
|
||||||
npc.currentState.discreteMag = 0.0f;
|
|
||||||
}
|
|
||||||
else if (angleErrorRad < SOFT_THRUST_ANGLE) {
|
|
||||||
npc.currentState.selectedVelocity = 1;
|
|
||||||
npc.currentState.discreteMag = std::min(0.50f, (angleErrorRad - ALIGN_TOLERANCE) * 10.0f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
npc.currentState.selectedVelocity = 0;
|
|
||||||
|
|
||||||
Eigen::Vector3f localDesired = npc.currentState.rotation.transpose() * desiredDir;
|
|
||||||
float dx = localDesired.x();
|
|
||||||
float dy = localDesired.y();
|
|
||||||
float dz = localDesired.z();
|
|
||||||
|
|
||||||
float turnX = dy;
|
|
||||||
float turnY = -dx;
|
|
||||||
float turnLen = std::sqrt(turnX * turnX + turnY * turnY);
|
|
||||||
|
|
||||||
if (turnLen > 0.0001f) {
|
|
||||||
turnX /= turnLen;
|
|
||||||
turnY /= turnLen;
|
|
||||||
|
|
||||||
float rad = std::atan2(turnX, turnY);
|
|
||||||
int angleDeg = static_cast<int>(std::round(rad * 180.0f / M_PI));
|
|
||||||
if (angleDeg < 0) angleDeg += 360;
|
|
||||||
|
|
||||||
npc.currentState.discreteAngle = angleDeg;
|
|
||||||
npc.currentState.discreteMag = std::min(1.0f, angleErrorRad * 2.2f);
|
|
||||||
}
|
|
||||||
else if (angleErrorRad > 0.1f) {
|
|
||||||
npc.currentState.discreteAngle = 0;
|
|
||||||
npc.currentState.discreteMag = 1.0f;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
npc.currentState.discreteMag = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
npc.currentState.simulate_physics(static_cast<size_t>(deltaMs));
|
|
||||||
npc.currentState.lastUpdateServerTime = std::chrono::system_clock::time_point(
|
|
||||||
std::chrono::milliseconds(now_ms));
|
|
||||||
npc.stateHistory.add_state(npc.currentState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::Poll() {
|
|
||||||
updatePhysics();
|
|
||||||
updateNPCs();
|
|
||||||
checkCollisions();
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::updatePhysics() {
|
|
||||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
if (lastUpdateMs == 0) {
|
|
||||||
lastUpdateMs = now_ms;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t deltaMs = now_ms - lastUpdateMs;
|
|
||||||
float dt = deltaMs / 1000.0f;
|
|
||||||
lastUpdateMs = now_ms;
|
|
||||||
|
|
||||||
std::vector<int> indicesToRemove;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < projectiles.size(); ++i) {
|
|
||||||
auto& pr = projectiles[i];
|
|
||||||
pr.pos += pr.vel * dt;
|
|
||||||
|
|
||||||
if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
|
|
||||||
indicesToRemove.push_back(static_cast<int>(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!indicesToRemove.empty()) {
|
|
||||||
std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
|
|
||||||
for (int idx : indicesToRemove) {
|
|
||||||
if (idx >= 0 && idx < (int)projectiles.size()) {
|
|
||||||
projectiles.erase(projectiles.begin() + idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::checkCollisions() {
|
|
||||||
auto now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
|
|
||||||
|
|
||||||
for (size_t bi = 0; bi < serverBoxes.size(); ++bi) {
|
|
||||||
if (serverBoxes[bi].destroyed) continue;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
|
||||||
|
|
||||||
for (size_t pi = 0; pi < projectiles.size(); ++pi) {
|
|
||||||
const auto& pr = projectiles[pi];
|
|
||||||
Eigen::Vector3f diff = pr.pos - boxWorld;
|
|
||||||
float thresh = boxCollisionRadius + projectileHitRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= thresh * thresh) {
|
|
||||||
boxProjectileCollisions.push_back({ bi, pi });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> projIndicesToRemove;
|
|
||||||
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
|
|
||||||
if (!serverBoxes[boxIdx].destroyed) {
|
|
||||||
serverBoxes[boxIdx].destroyed = true;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
|
||||||
|
|
||||||
BoxDestroyedInfo destruction;
|
|
||||||
destruction.boxIndex = static_cast<int>(boxIdx);
|
|
||||||
destruction.serverTime = now_ms;
|
|
||||||
destruction.position = boxWorld;
|
|
||||||
destruction.destroyedBy = projectiles[projIdx].shooterId;
|
|
||||||
|
|
||||||
pendingBoxDestructions.push_back(destruction);
|
|
||||||
|
|
||||||
std::cout << "LocalClient: Box " << boxIdx << " destroyed by projectile from player "
|
|
||||||
<< projectiles[projIdx].shooterId << std::endl;
|
|
||||||
|
|
||||||
// Respawn box
|
|
||||||
{
|
|
||||||
std::random_device rd2;
|
|
||||||
std::mt19937 gen2(rd2());
|
|
||||||
std::uniform_real_distribution<float> angleDist(0.f, static_cast<float>(M_PI * 2.0));
|
|
||||||
Eigen::Vector3f newPos = generateRespawnBoxPos(static_cast<int>(boxIdx));
|
|
||||||
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
|
|
||||||
Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix();
|
|
||||||
serverBoxes[boxIdx].position = newPos;
|
|
||||||
serverBoxes[boxIdx].rotation = newRot;
|
|
||||||
serverBoxes[boxIdx].destroyed = false;
|
|
||||||
BoxRespawnInfo respawn;
|
|
||||||
respawn.boxIndex = static_cast<int>(boxIdx);
|
|
||||||
respawn.position = newPos;
|
|
||||||
respawn.rotation = newRot;
|
|
||||||
pendingBoxRespawns.push_back(respawn);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (std::find(projIndicesToRemove.begin(), projIndicesToRemove.end(), (int)projIdx)
|
|
||||||
== projIndicesToRemove.end()) {
|
|
||||||
projIndicesToRemove.push_back(static_cast<int>(projIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<size_t, size_t>> npcProjectileCollisions;
|
|
||||||
|
|
||||||
for (size_t ni = 0; ni < npcs.size(); ++ni) {
|
|
||||||
if (npcs[ni].destroyed) continue;
|
|
||||||
|
|
||||||
for (size_t pi = 0; pi < projectiles.size(); ++pi) {
|
|
||||||
const auto& pr = projectiles[pi];
|
|
||||||
Eigen::Vector3f diff = pr.pos - npcs[ni].currentState.position;
|
|
||||||
float thresh = npcCollisionRadius + projectileHitRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= thresh * thresh) {
|
|
||||||
npcProjectileCollisions.push_back({ ni, pi });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto& [npcIdx, projIdx] : npcProjectileCollisions) {
|
|
||||||
if (!npcs[npcIdx].destroyed) {
|
|
||||||
npcs[npcIdx].destroyed = true;
|
|
||||||
|
|
||||||
DeathInfo death;
|
|
||||||
death.targetId = npcs[npcIdx].id;
|
|
||||||
death.serverTime = now_ms;
|
|
||||||
death.position = npcs[npcIdx].currentState.position;
|
|
||||||
death.killerId = projectiles[projIdx].shooterId;
|
|
||||||
|
|
||||||
pendingDeaths.push_back(death);
|
|
||||||
|
|
||||||
std::cout << "LocalClient: NPC " << npcs[npcIdx].id << " destroyed by projectile from player "
|
|
||||||
<< projectiles[projIdx].shooterId << " at position ("
|
|
||||||
<< npcs[npcIdx].currentState.position.x() << ", "
|
|
||||||
<< npcs[npcIdx].currentState.position.y() << ", "
|
|
||||||
<< npcs[npcIdx].currentState.position.z() << ")" << std::endl;
|
|
||||||
|
|
||||||
if (std::find(projIndicesToRemove.begin(), projIndicesToRemove.end(), (int)projIdx)
|
|
||||||
== projIndicesToRemove.end()) {
|
|
||||||
projIndicesToRemove.push_back(static_cast<int>(projIdx));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!projIndicesToRemove.empty()) {
|
|
||||||
std::sort(projIndicesToRemove.rbegin(), projIndicesToRemove.rend());
|
|
||||||
for (int idx : projIndicesToRemove) {
|
|
||||||
if (idx >= 0 && idx < (int)projectiles.size()) {
|
|
||||||
projectiles.erase(projectiles.begin() + idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasLocalPlayerState) {
|
|
||||||
for (size_t bi = 0; bi < serverBoxes.size(); ++bi) {
|
|
||||||
if (serverBoxes[bi].destroyed) continue;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
|
||||||
Eigen::Vector3f diff = localPlayerState.position - boxWorld;
|
|
||||||
float thresh = shipCollisionRadius + boxCollisionRadius;
|
|
||||||
|
|
||||||
if (diff.squaredNorm() <= thresh * thresh) {
|
|
||||||
serverBoxes[bi].destroyed = true;
|
|
||||||
|
|
||||||
BoxDestroyedInfo destruction;
|
|
||||||
destruction.boxIndex = static_cast<int>(bi);
|
|
||||||
destruction.serverTime = now_ms;
|
|
||||||
destruction.position = boxWorld;
|
|
||||||
destruction.destroyedBy = GetClientId();
|
|
||||||
|
|
||||||
pendingBoxDestructions.push_back(destruction);
|
|
||||||
|
|
||||||
std::cout << "LocalClient: Box " << bi << " destroyed by ship collision with player "
|
|
||||||
<< GetClientId() << std::endl;
|
|
||||||
|
|
||||||
// Respawn box
|
|
||||||
{
|
|
||||||
std::random_device rd2;
|
|
||||||
std::mt19937 gen2(rd2());
|
|
||||||
std::uniform_real_distribution<float> angleDist(0.f, static_cast<float>(M_PI * 2.0));
|
|
||||||
Eigen::Vector3f newPos = generateRespawnBoxPos(static_cast<int>(bi));
|
|
||||||
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
|
|
||||||
Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix();
|
|
||||||
serverBoxes[bi].position = newPos;
|
|
||||||
serverBoxes[bi].rotation = newRot;
|
|
||||||
serverBoxes[bi].destroyed = false;
|
|
||||||
BoxRespawnInfo respawn;
|
|
||||||
respawn.boxIndex = static_cast<int>(bi);
|
|
||||||
respawn.position = newPos;
|
|
||||||
respawn.rotation = newRot;
|
|
||||||
pendingBoxRespawns.push_back(respawn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Eigen::Vector3f LocalClient::generateRespawnBoxPos(int skipIdx) {
|
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 gen(rd());
|
|
||||||
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
|
|
||||||
|
|
||||||
for (int attempt = 0; attempt < 500; ++attempt) {
|
|
||||||
Eigen::Vector3f cand(dist(gen), dist(gen), dist(gen));
|
|
||||||
bool safe = true;
|
|
||||||
for (int i = 0; i < (int)serverBoxes.size(); ++i) {
|
|
||||||
if (i == skipIdx) continue;
|
|
||||||
if (serverBoxes[i].destroyed) continue;
|
|
||||||
if ((cand - serverBoxes[i].position).squaredNorm() < 9.f) {
|
|
||||||
safe = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (safe) return cand;
|
|
||||||
}
|
|
||||||
return Eigen::Vector3f(dist(gen), dist(gen), dist(gen));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxPickedUpInfo> LocalClient::getPendingBoxPickups() {
|
|
||||||
auto result = pendingBoxPickups;
|
|
||||||
pendingBoxPickups.clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxRespawnInfo> LocalClient::getPendingBoxRespawns() {
|
|
||||||
auto result = pendingBoxRespawns;
|
|
||||||
pendingBoxRespawns.clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void LocalClient::Send(const std::string& message) {
|
|
||||||
auto parts = [](const std::string& s, char delimiter) {
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
std::string token;
|
|
||||||
std::istringstream tokenStream(s);
|
|
||||||
while (std::getline(tokenStream, token, delimiter)) {
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}(message, ':');
|
|
||||||
|
|
||||||
if (parts.empty()) return;
|
|
||||||
|
|
||||||
std::string type = parts[0];
|
|
||||||
|
|
||||||
if (type == "BOX_PICKUP") {
|
|
||||||
if (parts.size() < 2) return;
|
|
||||||
if (!hasLocalPlayerState || localPlayerState.shipType != 1) return;
|
|
||||||
|
|
||||||
int boxIdx = -1;
|
|
||||||
try { boxIdx = std::stoi(parts[1]); } catch (...) { return; }
|
|
||||||
|
|
||||||
if (boxIdx < 0 || boxIdx >= (int)serverBoxes.size()) return;
|
|
||||||
if (serverBoxes[boxIdx].destroyed) return;
|
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = serverBoxes[boxIdx].position + Eigen::Vector3f(0.f, 0.f, 45000.f);
|
|
||||||
float distSq = (localPlayerState.position - boxWorld).squaredNorm();
|
|
||||||
if (distSq > BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) return;
|
|
||||||
|
|
||||||
serverBoxes[boxIdx].destroyed = true;
|
|
||||||
|
|
||||||
BoxPickedUpInfo pickup;
|
|
||||||
pickup.boxIndex = boxIdx;
|
|
||||||
pickup.pickedUpBy = GetClientId();
|
|
||||||
pendingBoxPickups.push_back(pickup);
|
|
||||||
|
|
||||||
std::cout << "LocalClient: Box " << boxIdx << " picked up by player " << GetClientId() << "\n";
|
|
||||||
|
|
||||||
// Respawn box at new position
|
|
||||||
{
|
|
||||||
std::random_device rd2;
|
|
||||||
std::mt19937 gen2(rd2());
|
|
||||||
std::uniform_real_distribution<float> angleDist(0.f, static_cast<float>(M_PI * 2.0));
|
|
||||||
Eigen::Vector3f newPos = generateRespawnBoxPos(boxIdx);
|
|
||||||
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
|
|
||||||
Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(gen2), axis).toRotationMatrix();
|
|
||||||
serverBoxes[boxIdx].position = newPos;
|
|
||||||
serverBoxes[boxIdx].rotation = newRot;
|
|
||||||
serverBoxes[boxIdx].destroyed = false;
|
|
||||||
|
|
||||||
BoxRespawnInfo respawn;
|
|
||||||
respawn.boxIndex = boxIdx;
|
|
||||||
respawn.position = newPos;
|
|
||||||
respawn.rotation = newRot;
|
|
||||||
pendingBoxRespawns.push_back(respawn);
|
|
||||||
std::cout << "LocalClient: Box " << boxIdx << " respawned after pickup\n";
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == "FIRE") {
|
|
||||||
if (parts.size() < 10) return;
|
|
||||||
|
|
||||||
uint64_t clientTime = std::stoull(parts[1]);
|
|
||||||
Eigen::Vector3f pos{
|
|
||||||
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
|
|
||||||
};
|
|
||||||
Eigen::Quaternionf dir(
|
|
||||||
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])
|
|
||||||
);
|
|
||||||
float velocity = std::stof(parts[9]);
|
|
||||||
|
|
||||||
int shotCount = 2;
|
|
||||||
if (parts.size() >= 11) {
|
|
||||||
try { shotCount = std::stoi(parts[10]); }
|
|
||||||
catch (...) { shotCount = 2; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::vector<Eigen::Vector3f> localOffsets = {
|
|
||||||
Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f),
|
|
||||||
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
|
|
||||||
};
|
|
||||||
|
|
||||||
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {
|
|
||||||
LocalProjectile pr;
|
|
||||||
pr.shooterId = GetClientId();
|
|
||||||
pr.spawnMs = now_ms;
|
|
||||||
Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
|
|
||||||
pr.pos = shotPos;
|
|
||||||
Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
|
|
||||||
Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
|
|
||||||
float len = worldForward.norm();
|
|
||||||
if (len > 1e-6f) worldForward /= len;
|
|
||||||
pr.vel = worldForward * velocity;
|
|
||||||
pr.lifeMs = 5000.0f;
|
|
||||||
projectiles.push_back(pr);
|
|
||||||
|
|
||||||
ProjectileInfo pinfo;
|
|
||||||
pinfo.shooterId = pr.shooterId;
|
|
||||||
pinfo.clientTime = clientTime;
|
|
||||||
pinfo.position = pr.pos;
|
|
||||||
pinfo.rotation = dir.toRotationMatrix();
|
|
||||||
pinfo.velocity = velocity;
|
|
||||||
|
|
||||||
if (pinfo.shooterId != GetClientId()) {
|
|
||||||
pendingProjectiles.push_back(pinfo);
|
|
||||||
}
|
|
||||||
std::cout << "LocalClient: Created server projectile at pos (" << shotPos.x() << ", "
|
|
||||||
<< shotPos.y() << ", " << shotPos.z() << ") vel (" << pr.vel.x() << ", "
|
|
||||||
<< pr.vel.y() << ", " << pr.vel.z() << ") shooter=" << pr.shooterId << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ProjectileInfo> LocalClient::getPendingProjectiles() {
|
|
||||||
auto result = pendingProjectiles;
|
|
||||||
pendingProjectiles.clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<DeathInfo> LocalClient::getPendingDeaths() {
|
|
||||||
auto result = pendingDeaths;
|
|
||||||
pendingDeaths.clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unordered_map<int, ClientStateInterval> LocalClient::getRemotePlayers() {
|
|
||||||
std::unordered_map<int, ClientStateInterval> result;
|
|
||||||
for (const auto& npc : npcs) {
|
|
||||||
if (!npc.destroyed) {
|
|
||||||
result[npc.id] = npc.stateHistory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> LocalClient::getServerBoxes() {
|
|
||||||
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> result;
|
|
||||||
for (const auto& box : serverBoxes) {
|
|
||||||
result.push_back({ box.position, box.rotation });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxDestroyedInfo> LocalClient::getPendingBoxDestructions() {
|
|
||||||
auto result = pendingBoxDestructions;
|
|
||||||
pendingBoxDestructions.clear();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,93 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "NetworkInterface.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <vector>
|
|
||||||
#include <Eigen/Dense>
|
|
||||||
#include <chrono>
|
|
||||||
#include <random>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
struct LocalServerBox {
|
|
||||||
Eigen::Vector3f position;
|
|
||||||
Eigen::Matrix3f rotation;
|
|
||||||
float collisionRadius = 2.0f;
|
|
||||||
bool destroyed = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LocalProjectile {
|
|
||||||
int shooterId = -1;
|
|
||||||
uint64_t spawnMs = 0;
|
|
||||||
Eigen::Vector3f pos;
|
|
||||||
Eigen::Vector3f vel;
|
|
||||||
float lifeMs = 5000.0f;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct LocalNPC {
|
|
||||||
int id = -1;
|
|
||||||
ClientState currentState;
|
|
||||||
ClientStateInterval stateHistory;
|
|
||||||
Eigen::Vector3f targetPosition;
|
|
||||||
uint64_t lastStateUpdateMs = 0;
|
|
||||||
bool destroyed = false;
|
|
||||||
int shipType = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LocalClient : public INetworkClient {
|
|
||||||
private:
|
|
||||||
std::queue<std::string> messageQueue;
|
|
||||||
std::vector<LocalServerBox> serverBoxes;
|
|
||||||
std::vector<LocalProjectile> projectiles;
|
|
||||||
std::vector<ProjectileInfo> pendingProjectiles;
|
|
||||||
std::vector<DeathInfo> pendingDeaths;
|
|
||||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions;
|
|
||||||
std::vector<BoxPickedUpInfo> pendingBoxPickups;
|
|
||||||
std::vector<BoxRespawnInfo> pendingBoxRespawns;
|
|
||||||
std::vector<int> pendingRespawns;
|
|
||||||
|
|
||||||
uint64_t lastUpdateMs = 0;
|
|
||||||
ClientState localPlayerState;
|
|
||||||
bool hasLocalPlayerState = false;
|
|
||||||
|
|
||||||
std::vector<LocalNPC> npcs;
|
|
||||||
|
|
||||||
void updatePhysics();
|
|
||||||
void checkCollisions();
|
|
||||||
void generateBoxes();
|
|
||||||
void initializeNPCs();
|
|
||||||
void updateNPCs();
|
|
||||||
Eigen::Vector3f generateRandomPosition();
|
|
||||||
Eigen::Vector3f generateRespawnBoxPos(int skipIdx);
|
|
||||||
|
|
||||||
public:
|
|
||||||
void Connect(const std::string& host, uint16_t port) override;
|
|
||||||
|
|
||||||
void Poll() override;
|
|
||||||
|
|
||||||
void Send(const std::string& message) override;
|
|
||||||
|
|
||||||
bool IsConnected() const override { return true; }
|
|
||||||
int GetClientId() const override { return 1; }
|
|
||||||
std::vector<ProjectileInfo> getPendingProjectiles() override;
|
|
||||||
|
|
||||||
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override;
|
|
||||||
|
|
||||||
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override;
|
|
||||||
|
|
||||||
std::vector<DeathInfo> getPendingDeaths() override;
|
|
||||||
|
|
||||||
std::vector<int> getPendingRespawns() override {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
|
||||||
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
|
|
||||||
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
|
|
||||||
|
|
||||||
void setLocalPlayerState(const ClientState& state) {
|
|
||||||
localPlayerState = state;
|
|
||||||
hasLocalPlayerState = true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
#include "ClientState.h"
|
|
||||||
|
|
||||||
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
struct ProjectileInfo {
|
|
||||||
int shooterId = -1;
|
|
||||||
uint64_t clientTime = 0;
|
|
||||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
||||||
//Eigen::Vector3f direction = Eigen::Vector3f::Zero();
|
|
||||||
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
|
|
||||||
float velocity = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DeathInfo {
|
|
||||||
int targetId = -1;
|
|
||||||
uint64_t serverTime = 0;
|
|
||||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
||||||
int killerId = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BoxDestroyedInfo {
|
|
||||||
int boxIndex = -1;
|
|
||||||
uint64_t serverTime = 0;
|
|
||||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
||||||
int destroyedBy = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BoxPickedUpInfo {
|
|
||||||
int boxIndex = -1;
|
|
||||||
int pickedUpBy = -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct BoxRespawnInfo {
|
|
||||||
int boxIndex = -1;
|
|
||||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
|
||||||
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
|
|
||||||
};
|
|
||||||
|
|
||||||
class INetworkClient {
|
|
||||||
public:
|
|
||||||
virtual ~INetworkClient() = default;
|
|
||||||
virtual void Connect(const std::string& host, uint16_t port) = 0;
|
|
||||||
virtual void Disconnect() {}
|
|
||||||
virtual void Send(const std::string& message) = 0;
|
|
||||||
virtual bool IsConnected() const = 0;
|
|
||||||
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
|
|
||||||
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
|
|
||||||
|
|
||||||
virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0;
|
|
||||||
|
|
||||||
virtual std::vector<bool> getServerBoxDestroyedFlags() { return {}; }
|
|
||||||
|
|
||||||
virtual std::vector<ProjectileInfo> getPendingProjectiles() = 0;
|
|
||||||
|
|
||||||
virtual std::vector<DeathInfo> getPendingDeaths() = 0;
|
|
||||||
virtual std::vector<int> getPendingRespawns() = 0;
|
|
||||||
virtual int GetClientId() const { return -1; }
|
|
||||||
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
|
|
||||||
virtual std::vector<BoxPickedUpInfo> getPendingBoxPickups() { return {}; }
|
|
||||||
virtual std::vector<BoxRespawnInfo> getPendingBoxRespawns() { return {}; }
|
|
||||||
virtual int64_t getTimeOffset() const { return 0; }
|
|
||||||
virtual std::vector<int> getPendingDisconnects() { return {}; }
|
|
||||||
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,127 +0,0 @@
|
|||||||
#ifdef NETWORK
|
|
||||||
|
|
||||||
#include "WebSocketClient.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
void WebSocketClient::Connect(const std::string& host, uint16_t port) {
|
|
||||||
try {
|
|
||||||
boost::asio::ip::tcp::resolver resolver(ioc_);
|
|
||||||
auto const results = resolver.resolve(host, std::to_string(port));
|
|
||||||
|
|
||||||
ws_ = std::make_unique<boost::beast::websocket::stream<boost::beast::tcp_stream>>(ioc_);
|
|
||||||
|
|
||||||
// Выполняем синхронный коннект и handshake для простоты старта
|
|
||||||
boost::beast::get_lowest_layer(*ws_).connect(results);
|
|
||||||
ws_->handshake(host, "/");
|
|
||||||
|
|
||||||
connected = true;
|
|
||||||
|
|
||||||
// Запускаем асинхронное чтение в пуле потоков TaskManager
|
|
||||||
startAsyncRead();
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (std::exception& e) {
|
|
||||||
std::cerr << "Network Error: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClient::Disconnect() {
|
|
||||||
if (!ws_ || !connected) return;
|
|
||||||
connected = false;
|
|
||||||
try {
|
|
||||||
boost::beast::get_lowest_layer(*ws_).cancel();
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClient::startAsyncRead() {
|
|
||||||
ws_->async_read(buffer_, [this](boost::beast::error_code ec, std::size_t bytes) {
|
|
||||||
if (!ec) {
|
|
||||||
std::string msg = boost::beast::buffers_to_string(buffer_.data());
|
|
||||||
buffer_.consume(bytes);
|
|
||||||
processIncomingMessage(msg);
|
|
||||||
startAsyncRead();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClient::processIncomingMessage(const std::string& msg) {
|
|
||||||
// Lock-free push: producer (I/O thread) pushes to its buffer
|
|
||||||
readProducerBuf_.load(std::memory_order_relaxed)->push_back(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClient::Poll() {
|
|
||||||
// Lock-free drain: swap consumer buffer with producer if ours is empty, then process all
|
|
||||||
MessageBuf* c = readConsumerBuf_.load(std::memory_order_acquire);
|
|
||||||
if (c->empty()) {
|
|
||||||
MessageBuf* p = readProducerBuf_.exchange(c, std::memory_order_acq_rel);
|
|
||||||
readConsumerBuf_.store(p, std::memory_order_release);
|
|
||||||
c = p;
|
|
||||||
}
|
|
||||||
for (std::string& msg : *c) {
|
|
||||||
HandlePollMessage(msg);
|
|
||||||
}
|
|
||||||
c->clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void WebSocketClient::Send(const std::string& message) {
|
|
||||||
if (!connected) return;
|
|
||||||
|
|
||||||
std::string finalMessage = SignMessage(message);
|
|
||||||
auto ss = std::make_shared<std::string>(std::move(finalMessage));
|
|
||||||
|
|
||||||
// Lock-free push to write queue
|
|
||||||
writeProducerBuf_.load(std::memory_order_relaxed)->push_back(ss);
|
|
||||||
|
|
||||||
// Start write chain if not already writing
|
|
||||||
bool expected = false;
|
|
||||||
if (isWriting_.compare_exchange_strong(expected, true, std::memory_order_acq_rel)) {
|
|
||||||
doWrite();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClient::doWrite() {
|
|
||||||
// Lock-free: take next message from consumer buffer; swap buffers if drained
|
|
||||||
WriteBuf* c = writeConsumerBuf_.load(std::memory_order_acquire);
|
|
||||||
if (currentWriteBuf_ == nullptr || currentWriteIndex_ >= currentWriteBuf_->size()) {
|
|
||||||
if (currentWriteBuf_) {
|
|
||||||
currentWriteBuf_->clear();
|
|
||||||
}
|
|
||||||
currentWriteBuf_ = c;
|
|
||||||
if (currentWriteBuf_->empty()) {
|
|
||||||
WriteBuf* p = writeProducerBuf_.exchange(currentWriteBuf_, std::memory_order_acq_rel);
|
|
||||||
writeConsumerBuf_.store(p, std::memory_order_release);
|
|
||||||
currentWriteBuf_ = p;
|
|
||||||
}
|
|
||||||
currentWriteIndex_ = 0;
|
|
||||||
}
|
|
||||||
if (currentWriteIndex_ >= currentWriteBuf_->size()) {
|
|
||||||
isWriting_.store(false, std::memory_order_release);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<std::string> message = (*currentWriteBuf_)[currentWriteIndex_++];
|
|
||||||
|
|
||||||
ws_->async_write(
|
|
||||||
boost::asio::buffer(*message),
|
|
||||||
[this, message](boost::beast::error_code ec, std::size_t) {
|
|
||||||
if (ec) {
|
|
||||||
connected = false;
|
|
||||||
isWriting_.store(false, std::memory_order_release);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
doWrite();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,67 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef NETWORK
|
|
||||||
|
|
||||||
#include "WebSocketClientBase.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <atomic>
|
|
||||||
#include <memory>
|
|
||||||
#include <boost/beast/core.hpp>
|
|
||||||
#include <boost/beast/websocket.hpp>
|
|
||||||
#include <boost/asio/connect.hpp>
|
|
||||||
#include <boost/asio/ip/tcp.hpp>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
// Lock-free SPSC double-buffer: producer pushes to one buffer, consumer swaps and drains the other.
|
|
||||||
// No mutexes; avoids contention under high message load.
|
|
||||||
class WebSocketClient : public WebSocketClientBase {
|
|
||||||
private:
|
|
||||||
boost::asio::io_context& ioc_;
|
|
||||||
std::unique_ptr<boost::beast::websocket::stream<boost::beast::tcp_stream>> ws_;
|
|
||||||
boost::beast::flat_buffer buffer_;
|
|
||||||
|
|
||||||
// Incoming messages: I/O thread pushes, main thread drains in Poll()
|
|
||||||
using MessageBuf = std::vector<std::string>;
|
|
||||||
MessageBuf readBuffer0_;
|
|
||||||
MessageBuf readBuffer1_;
|
|
||||||
std::atomic<MessageBuf*> readProducerBuf_;
|
|
||||||
std::atomic<MessageBuf*> readConsumerBuf_;
|
|
||||||
|
|
||||||
// Outgoing messages: main thread pushes in Send(), doWrite()/completion drains
|
|
||||||
using WriteBuf = std::vector<std::shared_ptr<std::string>>;
|
|
||||||
WriteBuf writeBuffer0_;
|
|
||||||
WriteBuf writeBuffer1_;
|
|
||||||
std::atomic<WriteBuf*> writeProducerBuf_;
|
|
||||||
std::atomic<WriteBuf*> writeConsumerBuf_;
|
|
||||||
WriteBuf* currentWriteBuf_ = nullptr;
|
|
||||||
size_t currentWriteIndex_ = 0;
|
|
||||||
std::atomic<bool> isWriting_{ false };
|
|
||||||
|
|
||||||
bool connected = false;
|
|
||||||
|
|
||||||
|
|
||||||
void startAsyncRead();
|
|
||||||
void processIncomingMessage(const std::string& msg);
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit WebSocketClient(boost::asio::io_context& ioc)
|
|
||||||
: ioc_(ioc)
|
|
||||||
, readProducerBuf_(&readBuffer0_)
|
|
||||||
, readConsumerBuf_(&readBuffer1_)
|
|
||||||
, writeProducerBuf_(&writeBuffer0_)
|
|
||||||
, writeConsumerBuf_(&writeBuffer1_)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void Connect(const std::string& host, uint16_t port) override;
|
|
||||||
void Disconnect() override;
|
|
||||||
|
|
||||||
void Poll() override;
|
|
||||||
|
|
||||||
void Send(const std::string& message) override;
|
|
||||||
void doWrite();
|
|
||||||
|
|
||||||
bool IsConnected() const override { return connected; }
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,436 +0,0 @@
|
|||||||
#ifdef NETWORK
|
|
||||||
|
|
||||||
#include "WebSocketClientBase.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
// Вспомогательный split
|
|
||||||
std::vector<std::string> split(const std::string& s, char delimiter) {
|
|
||||||
std::vector<std::string> tokens;
|
|
||||||
std::string token;
|
|
||||||
std::istringstream tokenStream(s);
|
|
||||||
while (std::getline(tokenStream, token, delimiter)) {
|
|
||||||
tokens.push_back(token);
|
|
||||||
}
|
|
||||||
return tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
void WebSocketClientBase::HandlePollMessage(const std::string& msg) {
|
|
||||||
auto parts = split(msg, ':');
|
|
||||||
if (parts.empty()) return;
|
|
||||||
|
|
||||||
if (parts[0] == "ID") {
|
|
||||||
std::cout << "ID Message Received:" << msg << std::endl;
|
|
||||||
clientId = std::stoi(parts[1]);
|
|
||||||
if (parts.size() >= 3) {
|
|
||||||
std::cout << "ID Message Received step 2" << std::endl;
|
|
||||||
uint64_t serverTime = std::stoull(parts[2]);
|
|
||||||
uint64_t localTime = std::chrono::duration_cast<std::chrono::milliseconds>(
|
|
||||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
|
||||||
|
|
||||||
std::cout << "ID Message Received localTime = " << localTime << std::endl;
|
|
||||||
std::cout << "ID Message Received serverTime = " << serverTime << std::endl;
|
|
||||||
|
|
||||||
// Вычисляем смещение
|
|
||||||
timeOffset = static_cast<int64_t>(serverTime) - static_cast<int64_t>(localTime);
|
|
||||||
|
|
||||||
std::cout << "Time synchronized. Offset: " << timeOffset << " ms" << std::endl;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("BOXES:", 0) == 0) {
|
|
||||||
std::string payload = msg.substr(6);
|
|
||||||
std::vector<std::tuple<int, Eigen::Vector3f, Eigen::Matrix3f, bool>> parsed;
|
|
||||||
if (!payload.empty()) {
|
|
||||||
auto items = split(payload, '|');
|
|
||||||
for (auto& item : items) {
|
|
||||||
if (item.empty()) continue;
|
|
||||||
auto parts = split(item, ':');
|
|
||||||
if (parts.size() < 9) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
int idx = std::stoi(parts[0]);
|
|
||||||
float px = std::stof(parts[1]);
|
|
||||||
float py = std::stof(parts[2]);
|
|
||||||
float pz = std::stof(parts[3]);
|
|
||||||
Eigen::Quaternionf q(
|
|
||||||
std::stof(parts[4]),
|
|
||||||
std::stof(parts[5]),
|
|
||||||
std::stof(parts[6]),
|
|
||||||
std::stof(parts[7])
|
|
||||||
);
|
|
||||||
bool destroyed = (std::stoi(parts[8]) != 0);
|
|
||||||
|
|
||||||
Eigen::Matrix3f rot = q.toRotationMatrix();
|
|
||||||
parsed.emplace_back(idx, Eigen::Vector3f{ px, py, pz }, rot, destroyed);
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxIdx = -1;
|
|
||||||
for (auto& t : parsed) {
|
|
||||||
int idx = std::get<0>(t);
|
|
||||||
if (idx > maxIdx) maxIdx = idx;
|
|
||||||
}
|
|
||||||
if (maxIdx < 0) {
|
|
||||||
serverBoxes_.clear();
|
|
||||||
serverBoxesDestroyed_.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
serverBoxes_.clear();
|
|
||||||
serverBoxes_.resize((size_t)maxIdx + 1);
|
|
||||||
serverBoxesDestroyed_.clear();
|
|
||||||
serverBoxesDestroyed_.resize((size_t)maxIdx + 1, true);
|
|
||||||
|
|
||||||
for (auto& t : parsed) {
|
|
||||||
int idx = std::get<0>(t);
|
|
||||||
const Eigen::Vector3f& pos = std::get<1>(t);
|
|
||||||
const Eigen::Matrix3f& rot = std::get<2>(t);
|
|
||||||
bool destroyed = std::get<3>(t);
|
|
||||||
if (idx >= 0 && idx < serverBoxes_.size()) {
|
|
||||||
serverBoxes_[idx] = { pos, rot };
|
|
||||||
serverBoxesDestroyed_[idx] = destroyed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (msg.rfind("PLAYER_LEFT:", 0) == 0) {
|
|
||||||
if (parts.size() >= 2) {
|
|
||||||
try {
|
|
||||||
int pid = std::stoi(parts[1]);
|
|
||||||
remotePlayers.erase(pid);
|
|
||||||
pendingDisconnects_.push_back(pid);
|
|
||||||
std::cout << "Client: Player " << pid << " disconnected (PLAYER_LEFT)\n";
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("RESPAWN_ACK:", 0) == 0) {
|
|
||||||
//auto parts = split(msg, ':');
|
|
||||||
if (parts.size() >= 2) {
|
|
||||||
try {
|
|
||||||
int respawnedPlayerId = std::stoi(parts[1]);
|
|
||||||
pendingRespawns_.push_back(respawnedPlayerId);
|
|
||||||
std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl;
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("BOX_PICKED_UP:", 0) == 0) {
|
|
||||||
if (parts.size() >= 3) {
|
|
||||||
try {
|
|
||||||
BoxPickedUpInfo pickup;
|
|
||||||
pickup.boxIndex = std::stoi(parts[1]);
|
|
||||||
pickup.pickedUpBy = std::stoi(parts[2]);
|
|
||||||
pendingBoxPickups_.push_back(pickup);
|
|
||||||
std::cout << "Client: Received BOX_PICKED_UP box=" << pickup.boxIndex
|
|
||||||
<< " by player " << pickup.pickedUpBy << std::endl;
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("BOX_RESPAWN:", 0) == 0) {
|
|
||||||
if (parts.size() >= 9) {
|
|
||||||
try {
|
|
||||||
BoxRespawnInfo respawn;
|
|
||||||
respawn.boxIndex = std::stoi(parts[1]);
|
|
||||||
float px = std::stof(parts[2]);
|
|
||||||
float py = std::stof(parts[3]);
|
|
||||||
float pz = std::stof(parts[4]);
|
|
||||||
Eigen::Quaternionf q(
|
|
||||||
std::stof(parts[5]),
|
|
||||||
std::stof(parts[6]),
|
|
||||||
std::stof(parts[7]),
|
|
||||||
std::stof(parts[8])
|
|
||||||
);
|
|
||||||
respawn.position = Eigen::Vector3f(px, py, pz);
|
|
||||||
respawn.rotation = q.toRotationMatrix();
|
|
||||||
pendingBoxRespawns_.push_back(respawn);
|
|
||||||
std::cout << "Client: Received BOX_RESPAWN box=" << respawn.boxIndex << std::endl;
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("BOX_DESTROYED:", 0) == 0) {
|
|
||||||
//auto parts = split(msg, ':');
|
|
||||||
if (parts.size() >= 7) {
|
|
||||||
try {
|
|
||||||
BoxDestroyedInfo destruction;
|
|
||||||
destruction.boxIndex = std::stoi(parts[1]);
|
|
||||||
destruction.serverTime = std::stoull(parts[2]);
|
|
||||||
destruction.position = Eigen::Vector3f(
|
|
||||||
std::stof(parts[3]),
|
|
||||||
std::stof(parts[4]),
|
|
||||||
std::stof(parts[5])
|
|
||||||
);
|
|
||||||
destruction.destroyedBy = std::stoi(parts[6]);
|
|
||||||
|
|
||||||
pendingBoxDestructions_.push_back(destruction);
|
|
||||||
|
|
||||||
std::cout << "Client: Received BOX_DESTROYED for box " << destruction.boxIndex
|
|
||||||
<< " destroyed by player " << destruction.destroyedBy << std::endl;
|
|
||||||
}
|
|
||||||
catch (const std::exception& e) {
|
|
||||||
std::cerr << "Client: Error parsing BOX_DESTROYED: " << e.what() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("PROJECTILE:", 0) == 0) {
|
|
||||||
//auto parts = split(msg, ':');
|
|
||||||
if (parts.size() >= 11) {
|
|
||||||
try {
|
|
||||||
ProjectileInfo pi;
|
|
||||||
pi.shooterId = std::stoi(parts[1]);
|
|
||||||
pi.clientTime = std::stoull(parts[2]);
|
|
||||||
pi.position = Eigen::Vector3f(
|
|
||||||
std::stof(parts[3]),
|
|
||||||
std::stof(parts[4]),
|
|
||||||
std::stof(parts[5])
|
|
||||||
);
|
|
||||||
Eigen::Quaternionf q(
|
|
||||||
std::stof(parts[6]),
|
|
||||||
std::stof(parts[7]),
|
|
||||||
std::stof(parts[8]),
|
|
||||||
std::stof(parts[9])
|
|
||||||
);
|
|
||||||
pi.rotation = q.toRotationMatrix();
|
|
||||||
|
|
||||||
pi.velocity = std::stof(parts[10]);
|
|
||||||
pendingProjectiles_.push_back(pi);
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("DEAD:", 0) == 0) {
|
|
||||||
//auto parts = split(msg, ':');
|
|
||||||
if (parts.size() >= 7) {
|
|
||||||
try {
|
|
||||||
DeathInfo di;
|
|
||||||
di.serverTime = std::stoull(parts[1]);
|
|
||||||
di.targetId = std::stoi(parts[2]);
|
|
||||||
di.position = Eigen::Vector3f(
|
|
||||||
std::stof(parts[3]),
|
|
||||||
std::stof(parts[4]),
|
|
||||||
std::stof(parts[5])
|
|
||||||
);
|
|
||||||
di.killerId = std::stoi(parts[6]);
|
|
||||||
|
|
||||||
pendingDeaths_.push_back(di);
|
|
||||||
}
|
|
||||||
catch (...) {}
|
|
||||||
}
|
|
||||||
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...
|
|
||||||
|
|
||||||
int remoteId = std::stoi(parts[1]);
|
|
||||||
std::string subType = parts[2];
|
|
||||||
uint64_t sentTime = std::stoull(parts[3]);
|
|
||||||
|
|
||||||
ClientState remoteState;
|
|
||||||
remoteState.id = remoteId;
|
|
||||||
|
|
||||||
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(sentTime)) };
|
|
||||||
remoteState.lastUpdateServerTime = uptime_timepoint;
|
|
||||||
|
|
||||||
if (subType == "UPD") {
|
|
||||||
int startFrom = 4;
|
|
||||||
remoteState.position = { std::stof(parts[startFrom]), std::stof(parts[startFrom + 1]), std::stof(parts[startFrom + 2]) };
|
|
||||||
Eigen::Quaternionf q(
|
|
||||||
std::stof(parts[startFrom + 3]),
|
|
||||||
std::stof(parts[startFrom + 4]),
|
|
||||||
std::stof(parts[startFrom + 5]),
|
|
||||||
std::stof(parts[startFrom + 6]));
|
|
||||||
remoteState.rotation = q.toRotationMatrix();
|
|
||||||
|
|
||||||
remoteState.currentAngularVelocity = Eigen::Vector3f{
|
|
||||||
std::stof(parts[startFrom + 7]),
|
|
||||||
std::stof(parts[startFrom + 8]),
|
|
||||||
std::stof(parts[startFrom + 9]) };
|
|
||||||
remoteState.velocity = std::stof(parts[startFrom + 10]);
|
|
||||||
remoteState.selectedVelocity = std::stoi(parts[startFrom + 11]);
|
|
||||||
remoteState.discreteMag = std::stof(parts[startFrom + 12]);
|
|
||||||
remoteState.discreteAngle = std::stoi(parts[startFrom + 13]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw std::runtime_error("Unknown EVENT subtype: " + subType);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto& rp = remotePlayers[remoteId];
|
|
||||||
if (!rp.timedStates.empty()) {
|
|
||||||
const ClientState& last = rp.timedStates.back();
|
|
||||||
remoteState.nickname = last.nickname;
|
|
||||||
remoteState.shipType = last.shipType;
|
|
||||||
}
|
|
||||||
rp.add_state(remoteState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("SNAPSHOT:", 0) == 0) {
|
|
||||||
auto mainParts = split(msg.substr(9), '|'); // Отсекаем "SNAPSHOT:" и делим по игрокам
|
|
||||||
if (mainParts.empty()) return;
|
|
||||||
|
|
||||||
uint64_t serverTimestamp = std::stoull(mainParts[0]);
|
|
||||||
std::chrono::system_clock::time_point serverTime{ std::chrono::milliseconds(serverTimestamp) };
|
|
||||||
|
|
||||||
for (size_t i = 1; i < mainParts.size(); ++i) {
|
|
||||||
auto playerParts = split(mainParts[i], ':');
|
|
||||||
if (playerParts.size() < 15) return; // ID + 14 полей ClientState
|
|
||||||
|
|
||||||
int rId = std::stoi(playerParts[0]);
|
|
||||||
if (rId == clientId) return; // Свое состояние игрок знает лучше всех, (Client Side Prediction)
|
|
||||||
|
|
||||||
ClientState remoteState;
|
|
||||||
remoteState.id = rId;
|
|
||||||
remoteState.lastUpdateServerTime = serverTime;
|
|
||||||
|
|
||||||
// Используем твой handle_full_sync, начиная со 2-го индекса (пропускаем ID в playerParts)
|
|
||||||
remoteState.handle_full_sync(playerParts, 1);
|
|
||||||
|
|
||||||
remotePlayers[rId].add_state(remoteState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.rfind("PLAYERINFO:", 0) == 0) {
|
|
||||||
if (parts.size() >= 4) {
|
|
||||||
try {
|
|
||||||
int pid = std::stoi(parts[1]);
|
|
||||||
if (pid == clientId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string nick = parts[2];
|
|
||||||
int st = std::stoi(parts[3]);
|
|
||||||
|
|
||||||
auto it = remotePlayers.find(pid);
|
|
||||||
if (it != remotePlayers.end() && !it->second.timedStates.empty()) {
|
|
||||||
auto& states = it->second.timedStates;
|
|
||||||
states.back().nickname = nick;
|
|
||||||
states.back().shipType = st;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ClientState cs;
|
|
||||||
cs.id = pid;
|
|
||||||
cs.nickname = nick;
|
|
||||||
cs.shipType = st;
|
|
||||||
cs.lastUpdateServerTime = std::chrono::system_clock::now();
|
|
||||||
remotePlayers[pid].add_state(cs);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Client: PLAYERINFO received. id=" << pid << " nick=" << nick << " shipType=" << st << std::endl;
|
|
||||||
}
|
|
||||||
catch (...) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string WebSocketClientBase::SignMessage(const std::string& msg) {
|
|
||||||
#ifdef ENABLE_NETWORK_CHECKSUM
|
|
||||||
size_t hashValue = fnv1a_hash(msg + NET_SECRET);
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << msg << "#hash:" << std::hex << hashValue;
|
|
||||||
return ss.str();
|
|
||||||
#else
|
|
||||||
return msg;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ProjectileInfo> WebSocketClientBase::getPendingProjectiles() {
|
|
||||||
std::vector<ProjectileInfo> copy;
|
|
||||||
copy.swap(pendingProjectiles_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<DeathInfo> WebSocketClientBase::getPendingDeaths() {
|
|
||||||
std::vector<DeathInfo> copy;
|
|
||||||
copy.swap(pendingDeaths_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> WebSocketClientBase::getPendingRespawns() {
|
|
||||||
std::vector<int> copy;
|
|
||||||
copy.swap(pendingRespawns_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxDestroyedInfo> WebSocketClientBase::getPendingBoxDestructions() {
|
|
||||||
std::vector<BoxDestroyedInfo> copy;
|
|
||||||
copy.swap(pendingBoxDestructions_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxPickedUpInfo> WebSocketClientBase::getPendingBoxPickups() {
|
|
||||||
std::vector<BoxPickedUpInfo> copy;
|
|
||||||
copy.swap(pendingBoxPickups_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<BoxRespawnInfo> WebSocketClientBase::getPendingBoxRespawns() {
|
|
||||||
std::vector<BoxRespawnInfo> copy;
|
|
||||||
copy.swap(pendingBoxRespawns_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
|
|
||||||
std::vector<int> copy;
|
|
||||||
copy.swap(pendingDisconnects_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
|
|
||||||
std::vector<ClientState> copy;
|
|
||||||
copy.swap(pendingSpawns_);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@ -1,63 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "NetworkInterface.h"
|
|
||||||
#include <vector>
|
|
||||||
#include <unordered_map>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
// All state in WebSocketClientBase is only accessed from the main thread:
|
|
||||||
// HandlePollMessage() runs from Poll(), and get*() are called from Game/Space on the main thread.
|
|
||||||
// No mutexes needed.
|
|
||||||
class WebSocketClientBase : public INetworkClient {
|
|
||||||
protected:
|
|
||||||
std::unordered_map<int, ClientStateInterval> remotePlayers;
|
|
||||||
|
|
||||||
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
|
|
||||||
std::vector<bool> serverBoxesDestroyed_;
|
|
||||||
|
|
||||||
std::vector<ProjectileInfo> pendingProjectiles_;
|
|
||||||
std::vector<DeathInfo> pendingDeaths_;
|
|
||||||
std::vector<int> pendingRespawns_;
|
|
||||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
|
||||||
std::vector<BoxPickedUpInfo> pendingBoxPickups_;
|
|
||||||
std::vector<BoxRespawnInfo> pendingBoxRespawns_;
|
|
||||||
std::vector<int> pendingDisconnects_;
|
|
||||||
int clientId = -1;
|
|
||||||
int64_t timeOffset = 0;
|
|
||||||
std::vector<ClientState> pendingSpawns_;
|
|
||||||
|
|
||||||
public:
|
|
||||||
int GetClientId() const override { return clientId; }
|
|
||||||
|
|
||||||
int64_t getTimeOffset() const override { return timeOffset; }
|
|
||||||
|
|
||||||
void HandlePollMessage(const std::string& msg);
|
|
||||||
|
|
||||||
std::string SignMessage(const std::string& msg);
|
|
||||||
|
|
||||||
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
|
|
||||||
return remotePlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
|
|
||||||
return serverBoxes_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<bool> getServerBoxDestroyedFlags() override {
|
|
||||||
return serverBoxesDestroyed_;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ProjectileInfo> getPendingProjectiles() override;
|
|
||||||
std::vector<DeathInfo> getPendingDeaths() override;
|
|
||||||
std::vector<int> getPendingRespawns() override;
|
|
||||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
|
||||||
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
|
|
||||||
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
|
|
||||||
std::vector<int> getPendingDisconnects() override;
|
|
||||||
std::vector<ClientState> getPendingSpawns();
|
|
||||||
int getClientId() const { return clientId; }
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
#ifdef NETWORK
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
#include "WebSocketClientEmscripten.h"
|
|
||||||
#include <iostream>
|
|
||||||
#include <SDL2/SDL.h>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
void WebSocketClientEmscripten::Connect(const std::string& host, uint16_t port) {
|
|
||||||
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
|
|
||||||
//std::string url = "ws://" + host + ":" + std::to_string(port);
|
|
||||||
std::string url = "wss://api.spacegame.fishrungames.com";
|
|
||||||
//std::string url = "ws://localhost:8081";
|
|
||||||
EmscriptenWebSocketCreateAttributes attr = {
|
|
||||||
url.c_str(),
|
|
||||||
nullptr,
|
|
||||||
EM_TRUE // create_on_main_thread
|
|
||||||
};
|
|
||||||
|
|
||||||
socket_ = emscripten_websocket_new(&attr);
|
|
||||||
|
|
||||||
emscripten_websocket_set_onopen_callback(socket_, this, onOpen);
|
|
||||||
emscripten_websocket_set_onmessage_callback(socket_, this, onMessage);
|
|
||||||
emscripten_websocket_set_onerror_callback(socket_, this, onError);
|
|
||||||
emscripten_websocket_set_onclose_callback(socket_, this, onClose);
|
|
||||||
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClientEmscripten::Disconnect() {
|
|
||||||
if (socket_ > 0) {
|
|
||||||
emscripten_websocket_close(socket_, 1000, "User disconnected");
|
|
||||||
emscripten_websocket_delete(socket_);
|
|
||||||
socket_ = 0;
|
|
||||||
}
|
|
||||||
connected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClientEmscripten::flushOutgoingQueue() {
|
|
||||||
std::lock_guard<std::mutex> lock(outgoingMutex);
|
|
||||||
if (!socket_) return;
|
|
||||||
while (!outgoingQueue.empty()) {
|
|
||||||
const std::string &m = outgoingQueue.front();
|
|
||||||
emscripten_websocket_send_utf8_text(socket_, m.c_str());
|
|
||||||
outgoingQueue.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClientEmscripten::Send(const std::string& message) {
|
|
||||||
std::string signedMsg = SignMessage(message);
|
|
||||||
|
|
||||||
{
|
|
||||||
std::lock_guard<std::mutex> lock(outgoingMutex);
|
|
||||||
if (connected && socket_ > 0) {
|
|
||||||
//std::cout << "[WebWS] Sending message (immediate): " << signedMsg << std::endl;
|
|
||||||
emscripten_websocket_send_utf8_text(socket_, signedMsg.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
outgoingQueue.push(signedMsg);
|
|
||||||
//std::cout << "[WebWS] Queued outgoing message (waiting for open): " << signedMsg << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebSocketClientEmscripten::Poll() {
|
|
||||||
// Локальная очередь для минимизации времени блокировки мьютекса
|
|
||||||
std::queue<std::string> localQueue;
|
|
||||||
|
|
||||||
if (messageQueue.empty()) return;
|
|
||||||
std::swap(localQueue, messageQueue);
|
|
||||||
|
|
||||||
while (!localQueue.empty()) {
|
|
||||||
const std::string& msg = localQueue.front();
|
|
||||||
//std::cout << "[WebWS] Processing message: " << msg << std::endl;
|
|
||||||
|
|
||||||
// Передаем в базовый класс для парсинга игровых событий (BOXES, UPD, и т.д.)
|
|
||||||
HandlePollMessage(msg);
|
|
||||||
|
|
||||||
localQueue.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Колбэки ---
|
|
||||||
|
|
||||||
EM_BOOL WebSocketClientEmscripten::onOpen(int eventType, const EmscriptenWebSocketOpenEvent* e, void* userData) {
|
|
||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
|
||||||
self->connected = true;
|
|
||||||
//std::cout << "[WebWS] Connection opened" << std::endl;
|
|
||||||
|
|
||||||
self->flushOutgoingQueue();
|
|
||||||
|
|
||||||
return EM_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
EM_BOOL WebSocketClientEmscripten::onMessage(int eventType, const EmscriptenWebSocketMessageEvent* e, void* userData) {
|
|
||||||
//std::cout << "[WebWS] onMessage " << std::endl;
|
|
||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
|
||||||
if (e->isText && e->data) {
|
|
||||||
std::string msg(reinterpret_cast<const char*>(e->data), e->numBytes);
|
|
||||||
self->messageQueue.push(msg);
|
|
||||||
}
|
|
||||||
return EM_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
EM_BOOL WebSocketClientEmscripten::onError(int eventType, const EmscriptenWebSocketErrorEvent* e, void* userData) {
|
|
||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
|
||||||
self->connected = false;
|
|
||||||
//std::cerr << "[WebWS] Error detected" << std::endl;
|
|
||||||
return EM_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
EM_BOOL WebSocketClientEmscripten::onClose(int eventType, const EmscriptenWebSocketCloseEvent* e, void* userData) {
|
|
||||||
auto* self = static_cast<WebSocketClientEmscripten*>(userData);
|
|
||||||
self->connected = false;
|
|
||||||
//std::cout << "[WebWS] Connection closed" << std::endl;
|
|
||||||
return EM_TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#ifdef NETWORK
|
|
||||||
#ifdef EMSCRIPTEN
|
|
||||||
|
|
||||||
#include <emscripten/websocket.h>
|
|
||||||
#include "WebSocketClientBase.h"
|
|
||||||
#include <queue>
|
|
||||||
#include <mutex>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace ZL {
|
|
||||||
|
|
||||||
class WebSocketClientEmscripten : public WebSocketClientBase {
|
|
||||||
private:
|
|
||||||
EMSCRIPTEN_WEBSOCKET_T socket_ = 0;
|
|
||||||
bool connected = false;
|
|
||||||
|
|
||||||
// Очередь для хранения сырых строк от браузера
|
|
||||||
std::queue<std::string> messageQueue;
|
|
||||||
|
|
||||||
std::queue<std::string> outgoingQueue;
|
|
||||||
std::mutex outgoingMutex;
|
|
||||||
void flushOutgoingQueue();
|
|
||||||
|
|
||||||
public:
|
|
||||||
WebSocketClientEmscripten() = default;
|
|
||||||
virtual ~WebSocketClientEmscripten() = default;
|
|
||||||
|
|
||||||
void Connect(const std::string& host, uint16_t port) override;
|
|
||||||
void Disconnect() override;
|
|
||||||
void Send(const std::string& message) override;
|
|
||||||
void Poll() override;
|
|
||||||
|
|
||||||
bool IsConnected() const override { return connected; }
|
|
||||||
|
|
||||||
// Статические методы-переходники для C-API Emscripten
|
|
||||||
static EM_BOOL onOpen(int eventType, const EmscriptenWebSocketOpenEvent* e, void* userData);
|
|
||||||
static EM_BOOL onMessage(int eventType, const EmscriptenWebSocketMessageEvent* e, void* userData);
|
|
||||||
static EM_BOOL onError(int eventType, const EmscriptenWebSocketErrorEvent* e, void* userData);
|
|
||||||
static EM_BOOL onClose(int eventType, const EmscriptenWebSocketCloseEvent* e, void* userData);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user