space-game001/src/network/WebSocketClientBase.cpp
2026-03-02 17:47:46 +06:00

366 lines
10 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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("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_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() >= 10) {
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<ClientState> WebSocketClientBase::getPendingSpawns() {
std::vector<ClientState> copy;
copy.swap(pendingSpawns_);
return copy;
}
}
#endif