added choising spaceship type for multiplayer and some fix in respawn

This commit is contained in:
Vlad 2026-02-25 19:17:42 +06:00
parent 5216965496
commit 74c2f786a1
6 changed files with 196 additions and 39 deletions

View File

@ -93,6 +93,9 @@ class Session : public std::enable_shared_from_this<Session> {
public: public:
ClientStateInterval timedClientStates; ClientStateInterval timedClientStates;
std::string nickname = "Player";
int shipType = 0;
explicit Session(tcp::socket&& socket, int id) explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) { : ws_(std::move(socket)), id_(id) {
} }
@ -253,7 +256,6 @@ private:
void process_message(const std::string& msg) { void process_message(const std::string& msg) {
if (!IsMessageValid(msg)) { if (!IsMessageValid(msg)) {
// Логируем попытку подмены и просто выходим из обработки
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return; return;
} }
@ -266,7 +268,40 @@ private:
std::string type = parts[0]; std::string type = parts[0];
if (type == "UPD") { if (type == "JOIN") {
std::string nick = "Player";
int sType = 0;
if (parts.size() >= 2) nick = parts[1];
if (parts.size() >= 3) {
try { sType = std::stoi(parts[2]); }
catch (...) { sType = 0; }
}
this->nickname = nick;
this->shipType = sType;
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
session->send_message(info);
}
}
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
// Отправляем именно новому клиенту
this->send_message(otherInfo);
}
}
}
else if (type == "UPD") {
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(id_) != g_dead_players.end()) { if (g_dead_players.find(id_) != g_dead_players.end()) {
@ -284,20 +319,48 @@ private:
}; };
receivedState.lastUpdateServerTime = uptime_timepoint; receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2); receivedState.handle_full_sync(parts, 2);
receivedState.nickname = this->nickname;
receivedState.shipType = this->shipType;
timedClientStates.add_state(receivedState); timedClientStates.add_state(receivedState);
retranslateMessage(cleanMessage); retranslateMessage(cleanMessage);
} }
else if (parts[0] == "RESPAWN") { else if (type == "RESPAWN") {
{ {
std::lock_guard<std::mutex> gd(g_dead_mutex); std::lock_guard<std::mutex> gd(g_dead_mutex);
g_dead_players.erase(id_); g_dead_players.erase(id_);
} }
{
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
ClientState st;
st.id = id_;
st.position = Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_); std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
broadcastToAll(respawnMsg); broadcastToAll(respawnMsg);
std::cout << "Server: Player " << id_ << " respawned\n"; std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
broadcastToAll(playerInfo);
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
broadcastToAll(eventMsg);
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
}
} }
else if (parts[0] == "FIRE") { else if (parts[0] == "FIRE") {
if (parts.size() < 10) return; if (parts.size() < 10) return;
@ -344,8 +407,8 @@ private:
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>( uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch()).count(); std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex); std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) { for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {

View File

@ -24,6 +24,7 @@
#endif #endif
#include "network/LocalClient.h" #include "network/LocalClient.h"
#include "network/ClientState.h"
namespace ZL namespace ZL
@ -122,7 +123,11 @@ namespace ZL
spaceGameStarted = 1; spaceGameStarted = 1;
}; };
menuManager.onMultiplayerPressed = [this]() { menuManager.onMultiplayerPressed = [this](const std::string& nickname, int shipType) {
Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType;
networkClient = std::make_unique<LocalClient>();
#ifdef NETWORK #ifdef NETWORK
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
networkClient = std::make_unique<WebSocketClientEmscripten>(); networkClient = std::make_unique<WebSocketClientEmscripten>();
@ -131,7 +136,26 @@ namespace ZL
networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext()); networkClient = std::make_unique<WebSocketClient>(taskManager.getIOContext());
networkClient->Connect("localhost", 8081); networkClient->Connect("localhost", 8081);
#endif #endif
#else
networkClient->Connect("", 0);
#endif #endif
#ifndef NETWORK
auto localClient = dynamic_cast<ZL::LocalClient*>(networkClient.get());
if (localClient) {
ZL::ClientState st = Environment::shipState;
st.id = localClient->GetClientId();
localClient->setLocalPlayerState(st);
}
#endif
if (networkClient) {
std::string joinMsg = std::string("JOIN:") + nickname + ":" + std::to_string(shipType);
networkClient->Send(joinMsg);
std::cerr << "Sent JOIN: " << joinMsg << std::endl;
}
lastTickCount = 0; lastTickCount = 0;
spaceGameStarted = 1; spaceGameStarted = 1;
}; };

View File

@ -148,10 +148,39 @@ namespace ZL {
std::cerr << "Failed to push ship selection menu\n"; std::cerr << "Failed to push ship selection menu\n";
} }
}); });
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI, this](const std::string& name) {
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n"; uiManager.setButtonCallback("multiplayerButton", [this, shipSelectionRoot, loadGameplayUI](const std::string& name) {
std::cerr << "Multiplayer button pressed: " << name << " -> open ship selection UI\n";
if (!shipSelectionRoot) {
std::cerr << "Failed to load ship selection UI\n";
return;
}
if (uiManager.pushMenuFromSavedRoot(shipSelectionRoot)) {
uiManager.setButtonCallback("spaceshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 0;
uiManager.popMenu();
loadGameplayUI(); loadGameplayUI();
onMultiplayerPressed(); if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("cargoshipButton", [this, loadGameplayUI](const std::string& btnName) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = "Player";
int shipType = 1;
uiManager.popMenu();
loadGameplayUI();
if (onMultiplayerPressed) onMultiplayerPressed(nick, shipType);
});
uiManager.setButtonCallback("backButton", [this](const std::string& btnName) {
uiManager.popMenu();
});
}
else {
std::cerr << "Failed to push ship selection menu\n";
}
}); });
uiManager.setButtonCallback("multiplayerButton2", [this](const std::string& name) { uiManager.setButtonCallback("multiplayerButton2", [this](const std::string& name) {
@ -164,7 +193,6 @@ namespace ZL {
if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) { if (uiManager.pushMenuFromSavedRoot(multiplayerSavedRoot)) {
// Callback для кнопки подключения
uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) { uiManager.setButtonCallback("connectButton", [this](const std::string& buttonName) {
std::string serverAddress = uiManager.getTextFieldValue("serverInputField"); std::string serverAddress = uiManager.getTextFieldValue("serverInputField");
@ -176,16 +204,12 @@ namespace ZL {
uiManager.setText("statusText", "Connecting to " + serverAddress + "..."); uiManager.setText("statusText", "Connecting to " + serverAddress + "...");
std::cerr << "Connecting to server: " << serverAddress << std::endl; std::cerr << "Connecting to server: " << serverAddress << std::endl;
// Здесь добавить вашу логику подключения к серверу
// connectToServer(serverAddress);
}); });
// Callback для кнопки назад
uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) { uiManager.setButtonCallback("backButton", [this](const std::string& buttonName) {
uiManager.popMenu(); uiManager.popMenu();
}); });
// Callback для отслеживания ввода текста
uiManager.setTextFieldCallback("serverInputField", uiManager.setTextFieldCallback("serverInputField",
[this](const std::string& fieldName, const std::string& newText) { [this](const std::string& fieldName, const std::string& newText) {
std::cout << "Server input field changed to: " << newText << std::endl; std::cout << "Server input field changed to: " << newText << std::endl;

View File

@ -35,7 +35,7 @@ namespace ZL {
std::function<void()> onFirePressed; std::function<void()> onFirePressed;
std::function<void(const std::string&, int)> onSingleplayerPressed; std::function<void(const std::string&, int)> onSingleplayerPressed;
std::function<void()> onMultiplayerPressed; std::function<void(const std::string&, int)> onMultiplayerPressed;
}; };
}; };

View File

@ -267,6 +267,16 @@ namespace ZL
Environment::zoom = DEFAULT_ZOOM; Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false; Environment::tapDownHold = false;
if (networkClient) {
try {
networkClient->Send(std::string("RESPAWN"));
std::cout << "Client: Sent RESPAWN to server\n";
}
catch (...) {
std::cerr << "Client: Failed to send RESPAWN\n";
}
}
std::cerr << "Game restarted\n"; std::cerr << "Game restarted\n";
}; };
@ -677,8 +687,6 @@ namespace ZL
{ {
if (!textRenderer) return; if (!textRenderer) return;
//#ifdef NETWORK
// 2D поверх 3D
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -688,18 +696,12 @@ namespace ZL
if (deadRemotePlayers.count(id)) continue; if (deadRemotePlayers.count(id)) continue;
const ClientState& st = remotePlayer; const ClientState& st = remotePlayer;
// Позиция корабля в мире
Vector3f shipWorld = st.position; Vector3f shipWorld = st.position;
float distSq = (Environment::shipState.position - shipWorld).squaredNorm(); float distSq = (Environment::shipState.position - shipWorld).squaredNorm();
/*if (distSq > MAX_DIST_SQ) // дальность прорисовки никнейма
continue;*/
float dist = sqrt(distSq); float dist = sqrt(distSq);
float alpha = 1.0f; // постоянная видимость float alpha = 1.0f;
/*float alpha = std::clamp(1.f - (dist - FADE_START) / FADE_RANGE, 0.f, 1.f); // дальность прорисовки никнейма Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f };
if (alpha < 0.01f)
continue; */
Vector3f labelWorld = shipWorld + Vector3f{ 0.f, -4.f, 0.f }; // регулировка высоты
float sx, sy, depth; float sx, sy, depth;
if (!worldToScreen(labelWorld, sx, sy, depth)) if (!worldToScreen(labelWorld, sx, sy, depth))
continue; continue;
@ -707,18 +709,21 @@ namespace ZL
float uiX = sx, uiY = sy; float uiX = sx, uiY = sy;
float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE); float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);
// Дефолтный лейбл std::string displayName;
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m"; if (!st.nickname.empty() && st.nickname != "Player") {
displayName = st.nickname;
}
else {
displayName = "Player (" + std::to_string(st.id) + ")";
}
std::string label = displayName + " " + std::to_string((int)dist) + "m";
// TODO: nickname sync textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha });
textRenderer->drawText(label, uiX + 1.f, uiY + 1.f, scale, true, { 0.f, 0.f, 0.f, alpha }); // color param
textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha }); textRenderer->drawText(label, uiX, uiY, scale, true, { 1.f, 1.f, 1.f, alpha });
} }
glDisable(GL_BLEND); glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
//#endif
} }
int Space::pickTargetId() const int Space::pickTargetId() const
@ -1288,6 +1293,10 @@ namespace ZL
for (auto const& [id, remotePlayer] : latestRemotePlayers) { for (auto const& [id, remotePlayer] : latestRemotePlayers) {
if (networkClient && id == networkClient->GetClientId()) {
continue;
}
if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay)) if (!remotePlayer.canFetchClientStateAtTime(nowRoundedWithDelay))
{ {
continue; continue;
@ -1296,7 +1305,6 @@ namespace ZL
ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay); ClientState playerState = remotePlayer.fetchClientStateAtTime(nowRoundedWithDelay);
remotePlayerStates[id] = playerState; remotePlayerStates[id] = playerState;
} }
for (auto& p : projectiles) { for (auto& p : projectiles) {

View File

@ -79,7 +79,6 @@ namespace ZL {
try { try {
int respawnedPlayerId = std::stoi(parts[1]); int respawnedPlayerId = std::stoi(parts[1]);
pendingRespawns_.push_back(respawnedPlayerId); pendingRespawns_.push_back(respawnedPlayerId);
remotePlayers.erase(respawnedPlayerId);
std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl; std::cout << "Client: Received RESPAWN_ACK for player " << respawnedPlayerId << std::endl;
} }
catch (...) {} catch (...) {}
@ -202,6 +201,11 @@ namespace ZL {
{ {
auto& rp = remotePlayers[remoteId]; 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); rp.add_state(remoteState);
} }
} }
@ -218,7 +222,7 @@ namespace ZL {
if (playerParts.size() < 15) return; // ID + 14 полей ClientState if (playerParts.size() < 15) return; // ID + 14 полей ClientState
int rId = std::stoi(playerParts[0]); int rId = std::stoi(playerParts[0]);
if (rId == clientId) return; // Свое состояние игрок знает лучше всех (Client Side Prediction) if (rId == clientId) return; // Свое состояние игрок знает лучше всех, (Client Side Prediction)
ClientState remoteState; ClientState remoteState;
remoteState.id = rId; remoteState.id = rId;
@ -230,6 +234,40 @@ namespace ZL {
remotePlayers[rId].add_state(remoteState); 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) { std::string WebSocketClientBase::SignMessage(const std::string& msg) {