366 lines
10 KiB
C++
366 lines
10 KiB
C++
#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 |