Merge remote-tracking branch 'origin/main' into spark

This commit is contained in:
Vlad committed 2026-03-10 14:26:51 +06:00
commit 6fe10eacfc
38 files changed
+1231 -147

No files matched your search

+24 -5
View File
@@ -8,7 +8,7 @@
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed; /* Предотвращает pull-to-refresh на Android */
position: fixed;
}
#canvas {
display: block;
@@ -17,6 +17,18 @@
width: 100vw; height: 100vh;
border: none;
}
#fs-button {
position: absolute;
top: 10px; right: 10px;
padding: 10px;
z-index: 10;
background: rgba(255,255,255,0.3);
color: white; border: 1px solid white;
cursor: pointer;
font-family: sans-serif;
border-radius: 5px;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
/* Nick modal */
@@ -60,6 +72,7 @@
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<!--
@@ -75,12 +88,18 @@
}
};
// Обработка ориентации
document.getElementById('fs-button').addEventListener('click', function() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(e => {
console.error(`Error attempting to enable full-screen mode: ${e.message}`);
});
} else {
document.exitFullscreen();
}
});
window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3 -7
View File
@@ -13,7 +13,7 @@
{
"type": "StaticImage",
"name": "titleBtn",
"width": 254,
"width": 434,
"height": 35,
"texture": "resources/main_menu/title.png"
},
@@ -25,15 +25,11 @@
"texture": "resources/main_menu/line.png"
},
{
"type": "Button",
"type": "StaticImage",
"name": "subtitleBtn",
"width": 144,
"height": 11,
"textures": {
"normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png"
}
"texture": "resources/main_menu/subtitle.png"
},
{
"type": "Button",
@@ -10,6 +10,13 @@
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "titleBtn",
"width": 266,
"height": 66,
"texture": "resources/select_your_ship.png"
},
{
"type": "LinearLayout",
"orientation": "horizontal",
+1 -1
View File
@@ -1,5 +1,5 @@
{
"emissionRate": 0.4,
"emissionRate": 1.2,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
+1 -1
View File
@@ -1,5 +1,5 @@
{
"emissionRate": 0.4,
"emissionRate": 1.2,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
+51 -11
View File
@@ -8,18 +8,39 @@
"children": [
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"name": "gameScoreText",
"x": 0,
"y": 30,
"width": 200,
"height": 40,
"height": 60,
"horizontal_gravity": "left",
"vertical_gravity": "top",
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"text": "Score: 0",
"fontSize": 36,
"color": [
0,
217,
255,
1
],
"centered": false
},
{
"type": "Button",
"name": "showPlayersButton",
"x": 0,
"y": 100,
"width": 150,
"height": 150,
"horizontal_gravity": "left",
"vertical_gravity": "top",
"textures": {
"normal": "resources/button_players.png",
"hover": "resources/button_players.png",
"pressed": "resources/button_players.png",
"disabled": "resources/button_players.png"
}
},
{
"type": "Button",
"name": "shootButton",
@@ -64,8 +85,9 @@
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_minus.png",
"hover": "resources/button_minus_pressed.png",
"pressed": "resources/button_minus_pressed.png"
"hover": "resources/button_minus.png",
"pressed": "resources/button_minus_pressed.png",
"disabled" : "resources/button_minus_disabled.png"
}
},
{
@@ -80,8 +102,26 @@
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_plus.png",
"hover": "resources/button_plus_pressed.png",
"pressed": "resources/button_plus_pressed.png"
"hover": "resources/button_plus.png",
"pressed": "resources/button_plus_pressed.png",
"disabled" : "resources/button_plus_disabled.png"
}
},
{
"type": "Button",
"name": "takeButton",
"x": -20,
"y": 320,
"width": 150,
"height": 150,
"border" : 20,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_take.png",
"hover": "resources/button_take.png",
"pressed": "resources/button_take_pressed.png",
"disabled" : "resources/button_take_disabled.png"
}
}
]
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+125 -2
View File
@@ -31,8 +31,8 @@ bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
}
void Session::force_disconnect() {
ws_.async_close(websocket::close_code::normal,
[self = shared_from_this()](beast::error_code) {});
beast::error_code ec;
ws_.next_layer().socket().close(ec);
}
int Session::get_id() const { return id_; }
@@ -346,6 +346,64 @@ void Session::process_message(const std::string& msg) {
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
}
}
else if (parts[0] == "BOX_PICKUP") {
if (parts.size() < 2) return;
if (this->shipType != 1) {
std::cout << "Server: Player " << id_ << " tried BOX_PICKUP but is not a cargo ship\n";
return;
}
int boxIdx = -1;
try { boxIdx = std::stoi(parts[1]); } catch (...) { return; }
std::lock_guard<std::mutex> bm(server_.g_boxes_mutex);
if (boxIdx < 0 || boxIdx >= (int)server_.g_serverBoxes.size()) return;
if (server_.g_serverBoxes[boxIdx].destroyed) return;
if (timedClientStates.timedStates.empty()) return;
const ClientState& playerState = timedClientStates.timedStates.back();
Eigen::Vector3f boxWorld = server_.g_serverBoxes[boxIdx].position + kWorldOffset;
float distSq = (playerState.position - boxWorld).squaredNorm();
if (distSq > BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) {
std::cout << "Server: Player " << id_ << " too far to pick up box " << boxIdx << "\n";
return;
}
server_.g_serverBoxes[boxIdx].destroyed = true;
std::string pickedUpMsg = "BOX_PICKED_UP:" + std::to_string(boxIdx) + ":" + std::to_string(id_);
server_.broadcastToAll(pickedUpMsg);
std::cout << "Server: Box " << boxIdx << " picked up by player " << id_ << "\n";
// Respawn box
{
static thread_local std::mt19937 rng{ std::random_device{}() };
static thread_local std::uniform_real_distribution<float> angleDist(0.f, static_cast<float>(M_PI * 2.0));
Eigen::Vector3f newPos = server_.PickSafeBoxPos(boxIdx);
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(rng), axis).toRotationMatrix();
server_.g_serverBoxes[boxIdx].position = newPos;
server_.g_serverBoxes[boxIdx].rotation = newRot;
server_.g_serverBoxes[boxIdx].destroyed = false;
Eigen::Quaternionf q(newRot);
std::string respawnMsg = "BOX_RESPAWN:" +
std::to_string(boxIdx) + ":" +
std::to_string(newPos.x()) + ":" +
std::to_string(newPos.y()) + ":" +
std::to_string(newPos.z()) + ":" +
std::to_string(q.w()) + ":" +
std::to_string(q.x()) + ":" +
std::to_string(q.y()) + ":" +
std::to_string(q.z());
server_.broadcastToAll(respawnMsg);
std::cout << "Server: Box " << boxIdx << " respawned after pickup\n";
}
}
else if (parts[0] == "FIRE") {
if (parts.size() < 10) return;
@@ -377,6 +435,28 @@ void Session::process_message(const std::string& msg) {
}
}
Eigen::Vector3f Server::PickSafeBoxPos(int skipIdx)
{
// Assumes g_boxes_mutex is already held by the caller
static thread_local std::mt19937 rng{ std::random_device{}() };
std::uniform_real_distribution<float> dist(-1000.f, 1000.f);
for (int attempt = 0; attempt < 500; ++attempt) {
Eigen::Vector3f cand(dist(rng), dist(rng), dist(rng));
bool safe = true;
for (int i = 0; i < (int)g_serverBoxes.size(); ++i) {
if (i == skipIdx) continue;
if (g_serverBoxes[i].destroyed) continue;
if ((cand - g_serverBoxes[i].position).squaredNorm() < 9.f) {
safe = false;
break;
}
}
if (safe) return cand;
}
return Eigen::Vector3f(dist(rng), dist(rng), dist(rng));
}
Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId)
{
static thread_local std::mt19937 rng{ std::random_device{}() };
@@ -608,6 +688,8 @@ void Server::update_world() {
std::vector<int> boxesToRespawn;
// --- Tick: box-projectile collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
@@ -632,6 +714,7 @@ void Server::update_world() {
}
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
if (g_serverBoxes[boxIdx].destroyed) continue;
g_serverBoxes[boxIdx].destroyed = true;
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
@@ -647,6 +730,8 @@ void Server::update_world() {
g_boxDestructions.push_back(destruction);
}
boxesToRespawn.push_back(static_cast<int>(boxIdx));
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
<< g_projectiles[projIdx].shooterId << std::endl;
}
@@ -690,6 +775,8 @@ void Server::update_world() {
g_boxDestructions.push_back(destruction);
}
boxesToRespawn.push_back(static_cast<int>(bi));
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
<< session->get_id() << std::endl;
break;
@@ -732,6 +819,41 @@ void Server::update_world() {
g_boxDestructions.clear();
}
// --- Respawn destroyed boxes ---
if (!boxesToRespawn.empty()) {
static thread_local std::mt19937 rng{ std::random_device{}() };
static thread_local std::uniform_real_distribution<float> angleDist(0.f, static_cast<float>(M_PI * 2.0));
std::vector<std::string> respawnMsgs;
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
for (int idx : boxesToRespawn) {
if (idx < 0 || idx >= (int)g_serverBoxes.size()) continue;
Eigen::Vector3f newPos = PickSafeBoxPos(idx);
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
Eigen::Matrix3f newRot = Eigen::AngleAxisf(angleDist(rng), axis).toRotationMatrix();
g_serverBoxes[idx].position = newPos;
g_serverBoxes[idx].rotation = newRot;
g_serverBoxes[idx].destroyed = false;
Eigen::Quaternionf q(newRot);
std::string respawnMsg = "BOX_RESPAWN:" +
std::to_string(idx) + ":" +
std::to_string(newPos.x()) + ":" +
std::to_string(newPos.y()) + ":" +
std::to_string(newPos.z()) + ":" +
std::to_string(q.w()) + ":" +
std::to_string(q.x()) + ":" +
std::to_string(q.y()) + ":" +
std::to_string(q.z());
respawnMsgs.push_back(respawnMsg);
std::cout << "Server: Box " << idx << " respawned" << std::endl;
}
}
for (const auto& msg : respawnMsgs) {
broadcastToAll(msg);
}
}
// --- Schedule next tick in 50ms ---
timer.expires_after(std::chrono::milliseconds(50));
timer.async_wait([this](const boost::system::error_code& ec) {
@@ -820,6 +942,7 @@ int main() {
Server server(acceptor, ioc);
server.init();
server.accept();
std::cout << "Server started on port 8081...\n";
+2
View File
@@ -135,6 +135,8 @@ public:
void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
void update_world();
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
// Caller must hold g_boxes_mutex
Eigen::Vector3f PickSafeBoxPos(int skipIdx);
void init();
void accept();
};
+25 -24
View File
@@ -41,6 +41,10 @@ namespace ZL
const char* CONST_ZIP_FILE = "";
#endif
float x = 0;
float y = 0;
float z = 0;
#ifdef EMSCRIPTEN
Game* Game::s_instance = nullptr;
@@ -106,7 +110,20 @@ namespace ZL
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif
loadingMesh.data = CreateRect2D({ 0.5f, 0.5f }, { 0.5f, 0.5f }, 3);
float minDimension;
float width = Environment::projectionWidth;
float height = Environment::projectionHeight;
if (width >= height)
{
minDimension = height;
}
else
{
minDimension = width;
}
loadingMesh.data = CreateRect2D({ 0.0f, 0.0f }, { minDimension*0.5f, minDimension*0.5f }, 3);
loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN
@@ -264,12 +281,12 @@ namespace ZL
renderer.shaderManager.PushShader(defaultShaderName);
renderer.RenderUniform1i(textureUniformName, 0);
//float width = Environment::projectionWidth;
//float height = Environment::projectionHeight;
float width = Environment::projectionWidth;
float height = Environment::projectionHeight;
renderer.PushProjectionMatrix(
0, 1,
0, 1,
-width * 0.5f, width*0.5f,
-height * 0.5f, height * 0.5f,
-10, 10);
renderer.PushMatrix();
@@ -331,7 +348,7 @@ namespace ZL
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError();
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//processTickCount();
@@ -420,22 +437,6 @@ namespace ZL
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
}
/*if (event.type == SDL_MOUSEBUTTONDOWN) {
int mx = event.button.x;
int my = event.button.y;
handleDown(mx, my);
}
if (event.type == SDL_MOUSEBUTTONUP) {
int mx = event.button.x;
int my = event.button.y;
handleUp(mx, my);
}
if (event.type == SDL_MOUSEMOTION) {
int mx = event.motion.x;
int my = event.motion.y;
handleMotion(mx, my);
}*/
if (event.type == SDL_MOUSEWHEEL) {
static const float zoomstep = 2.0f;
if (event.wheel.y > 0) {
@@ -464,8 +465,8 @@ namespace ZL
}
if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_a) {
//Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
if (event.key.keysym.sym == SDLK_r) {
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
}
}
#endif
+36 -4
View File
@@ -188,13 +188,16 @@ namespace ZL {
state = GameState::Gameplay;
uiManager.replaceRoot(gameplayRoot);
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
/*
auto velocityTv = uiManager.findTextView("velocityText");
if (velocityTv) {
velocityTv->rect.x = 10.0f;
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 10.0f;
}
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
}*/
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
if (onFirePressed) onFirePressed();
@@ -205,20 +208,49 @@ namespace ZL {
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity + 1;
if (newVel > 4) newVel = 4;
uiManager.findButton("minusButton")->state = ButtonState::Normal;
if (newVel == 4)
{
uiManager.findButton("plusButton")->state = ButtonState::Disabled;
}
else
{
uiManager.findButton("plusButton")->state = ButtonState::Normal;
}
if (onVelocityChanged) onVelocityChanged(newVel);
});
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
int newVel = Environment::shipState.selectedVelocity - 1;
if (newVel < 0) newVel = 0;
uiManager.findButton("plusButton")->state = ButtonState::Normal;
if (newVel == 0)
{
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
}
else
{
uiManager.findButton("minusButton")->state = ButtonState::Normal;
}
if (onVelocityChanged) onVelocityChanged(newVel);
});
uiManager.setButtonPressCallback("takeButton", [this](const std::string&) {
if (onTakeButtonPressed) onTakeButtonPressed();
});
uiManager.setButtonCallback("showPlayersButton", [this](const std::string&) {
if (onShowPlayersPressed) onShowPlayersPressed();
});
/*
uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) {
int newVel = static_cast<int>(roundf(value * 10));
if (newVel > 2) newVel = 2;
if (newVel != Environment::shipState.selectedVelocity) {
if (onVelocityChanged) onVelocityChanged(newVel);
}
});
});*/
}
// ── State: GameOver ──────────────────────────────────────────────────────
+2
View File
@@ -70,8 +70,10 @@ namespace ZL {
std::function<void()> onRestartPressed;
std::function<void(float)> onVelocityChanged;
std::function<void()> onFirePressed;
std::function<void()> onTakeButtonPressed;
std::function<void(const std::string&, int)> onSingleplayerPressed;
std::function<void(const std::string&, int)> onMultiplayerPressed;
std::function<void()> onShowPlayersPressed;
};
} // namespace ZL
+377 -66
View File
@@ -33,6 +33,8 @@ namespace ZL
extern const char* CONST_ZIP_FILE;
extern float x;
extern float y;
extern float z;
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
{
@@ -262,11 +264,32 @@ namespace ZL
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
Environment::shipState.velocity = 0.0f;
Environment::shipState.selectedVelocity = 0;
newShipVelocity = 0;
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
Environment::zoom = DEFAULT_ZOOM;
Environment::tapDownHold = false;
playerScore = 0;
if (menuManager.uiManager.findButton("minusButton"))
{
menuManager.uiManager.findButton("minusButton")->state = ButtonState::Disabled;
}
if (menuManager.uiManager.findButton("plusButton"))
{
menuManager.uiManager.findButton("plusButton")->state = ButtonState::Normal;
}
if (Environment::shipState.shipType == 0)
{
if (menuManager.uiManager.findButton("shootButton"))
{
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
}
if (menuManager.uiManager.findButton("shootButton2"))
{
menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
}
}
void Space::setup() {
@@ -309,6 +332,31 @@ namespace ZL
firePressed = true;
};
menuManager.onShowPlayersPressed = [this]() {
buildAndShowPlayerList();
};
menuManager.onTakeButtonPressed = [this]() {
if (Environment::shipState.shipType != 1) return;
if (!networkClient) return;
int bestIdx = -1;
float bestDistSq = BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS;
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
if (i >= boxAlive.size() || !boxAlive[i]) continue;
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.f, 0.f, 45000.f };
float distSq = (Environment::shipState.position - boxWorld).squaredNorm();
if (distSq <= bestDistSq) {
bestDistSq = distSq;
bestIdx = static_cast<int>(i);
}
}
if (bestIdx >= 0) {
networkClient->Send("BOX_PICKUP:" + std::to_string(bestIdx));
this->playerScore += 1;
}
};
bool cfgLoaded = sparkEmitter.loadFromJsonFile("resources/config/spark_config.json", renderer, CONST_ZIP_FILE);
bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE);
sparkEmitter.setIsActive(false);
@@ -387,7 +435,7 @@ namespace ZL
boxLabels.clear();
boxLabels.reserve(boxCoordsArr.size());
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
boxLabels.push_back("Box " + std::to_string(i + 1));
boxLabels.push_back("Box " + std::to_string(i));
}
if (!cfgLoaded)
@@ -507,17 +555,7 @@ namespace ZL
}
renderer.PushMatrix();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(- /*0.5 * */ Environment::shipState.position);
std::cout << "Ship pos draw: " << Environment::shipState.position.transpose() << "\n";
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
renderer.PopMatrix();
drawShipSparkEmitters();
}
renderer.PopMatrix();
@@ -591,16 +629,7 @@ namespace ZL
glViewport(0, 0, Environment::width, Environment::height);
// Готовим данные всех эмиттеров (CPU + VBO upload) до начала отрисовки,
// чтобы draw() делал только GPU-вызовы без пауз между кораблём и частицами.
sparkEmitter.prepareForDraw(true);
sparkEmitterCargo.prepareForDraw(true);
explosionEmitter.prepareForDraw(false);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->projectileEmitter.prepareForDraw(true);
}
}
prepareSparkEmittersForDraw();
CheckGlError();
@@ -669,6 +698,11 @@ namespace ZL
for (size_t i = 0; i < n; ++i) {
if (destroyedFlags[i]) boxAlive[i] = false; // destroyed => не рисуем
}
boxLabels.clear();
boxLabels.resize(boxCoordsArr.size());
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
boxLabels[i] = "Box " + std::to_string(i);
}
serverBoxesApplied = true;
}
}
@@ -971,6 +1005,16 @@ namespace ZL
int Space::pickTargetId() const
{
// Use manually selected target if it's still alive and in range
if (manualTrackedTargetId >= 0) {
auto it = remotePlayerStates.find(manualTrackedTargetId);
if (it != remotePlayerStates.end() && !deadRemotePlayers.count(manualTrackedTargetId)) {
float d2 = (Environment::shipState.position - it->second.position).squaredNorm();
if (d2 <= TARGET_MAX_DIST_SQ) return manualTrackedTargetId;
}
// Target no longer valid — fall through to auto-pick
}
int bestId = -1;
float bestDistSq = 1e30f;
@@ -978,8 +1022,8 @@ namespace ZL
if (deadRemotePlayers.count(id)) continue;
float d2 = (Environment::shipState.position - st.position).squaredNorm();
if (d2 > TARGET_MAX_DIST_SQ) continue; // слишком далеко
if (d2 > TARGET_MAX_DIST_SQ) continue;
if (d2 < bestDistSq) {
bestDistSq = d2;
@@ -1431,6 +1475,89 @@ namespace ZL
targetWasVisible = false;
}
void Space::updateSparkEmitters(float deltaMs)
{
// Local ship
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else {
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
sparkEmitterPtr->setIsActive(Environment::shipState.velocity > 0.1f);
sparkEmitterPtr->update(deltaMs);
// Remote ships
for (auto const& [id, playerState] : remotePlayerStates) {
if (deadRemotePlayers.count(id)) continue;
if (!remoteShipSparkEmitters.count(id)) {
remoteShipSparkEmitters.emplace(id, playerState.shipType == 1 ? sparkEmitterCargo : sparkEmitter);
}
auto& remEmitter = remoteShipSparkEmitters.at(id);
std::vector<Vector3f> remEmitPts(2);
if (playerState.shipType == 1) {
remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+2.8f, 8.4f };
remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.0f, -0.4f+1.5f, 8.4f };
} else {
remEmitPts[0] = playerState.position + playerState.rotation * Vector3f{ -0.9f, -0.2,5.6 };
remEmitPts[1] = playerState.position + playerState.rotation * Vector3f{ 0.9f,-0.2,5.6 };
}
remEmitter.setEmissionPoints(remEmitPts);
remEmitter.setIsActive(playerState.velocity > 0.1f);
remEmitter.update(deltaMs);
}
}
void Space::prepareSparkEmittersForDraw()
{
sparkEmitter.prepareForDraw(true);
sparkEmitterCargo.prepareForDraw(true);
for (auto& [id, emitter] : remoteShipSparkEmitters) {
if (!deadRemotePlayers.count(id)) emitter.prepareForDraw(true);
}
explosionEmitter.prepareForDraw(false);
for (const auto& p : projectiles) {
if (p && p->isActive()) {
p->projectileEmitter.prepareForDraw(true);
}
}
}
void Space::drawShipSparkEmitters()
{
renderer.PushMatrix();
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
}
for (auto& [id, emitter] : remoteShipSparkEmitters) {
if (!deadRemotePlayers.count(id)) {
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
emitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
renderer.PopMatrix();
}
}
renderer.PopMatrix();
}
void Space::processTickCount(int64_t newTickCount, int64_t delta) {
auto now_ms = newTickCount;
@@ -1522,7 +1649,7 @@ namespace ZL
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
networkClient->Send(msg);
std::cout << "Sending: " << msg << std::endl;
//std::cout << "Sending: " << msg << std::endl;
}
long long leftoverDelta = delta;
@@ -1545,41 +1672,6 @@ namespace ZL
}
//--------------
SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 2.8, -6.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.0, 1.5, -6.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
}
else
{
sparkEmitterPtr = &sparkEmitter;
static std::vector<Vector3f> emissionPoints = { Vector3f(0, 0, 0), Vector3f(0, 0, 0) };
emissionPoints[0] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ -0.9, 1.4 - 1.0, -8.5 + 16.0 };
emissionPoints[1] = Environment::shipState.position + Environment::shipState.rotation * Vector3f{ 0.9, 1.4 - 1.0, -8.5 + 16.0 };
sparkEmitterPtr->setEmissionPoints(emissionPoints);
//sparkEmitterPtr->setEmissionPoints({ /*0.5* */Environment::shipState.position });
//sparkEmitterPtr->setEmissionPoints({ Vector3f(0, 0, 0) });
std::cout << "Ship pos empo: " << Environment::shipState.position.transpose() << "\n";
}
if (Environment::shipState.velocity > 0.1f)
{
sparkEmitterPtr->setIsActive(true);
}
else
{
sparkEmitterPtr->setIsActive(false);
}
sparkEmitterPtr->update(static_cast<float>(delta));
auto latestRemotePlayers = networkClient->getRemotePlayers();
std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) };
@@ -1601,6 +1693,8 @@ namespace ZL
remotePlayerStates[id] = playerState;
}
updateSparkEmitters(static_cast<float>(delta));
for (auto& p : projectiles) {
if (p && p->isActive()) {
p->update(static_cast<float>(delta), renderer);
@@ -1636,7 +1730,9 @@ namespace ZL
shipAlive = false;
gameOver = true;
Environment::shipState.selectedVelocity = 0;
Environment::shipState.velocity = 0.0f;
newShipVelocity = 0;
showExplosion = true;
explosionEmitter.setUseWorldSpace(true);
@@ -1646,6 +1742,7 @@ namespace ZL
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
clearPlayerListIfVisible();
menuManager.showGameOver(this->playerScore);
}
else {
@@ -1721,6 +1818,7 @@ namespace ZL
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
}
clearPlayerListIfVisible();
menuManager.showGameOver(this->playerScore);
}
}
@@ -1736,8 +1834,31 @@ namespace ZL
std::string velocityStr = "Velocity: " + std::to_string(static_cast<int>(Environment::shipState.velocity));
menuManager.uiManager.setText("velocityText", velocityStr);
}
bool canPickup = false;
if (Environment::shipState.shipType == 1 && Environment::shipState.velocity < 0.1f) {
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
if (i >= boxAlive.size() || !boxAlive[i]) continue;
Vector3f boxWorld = boxCoordsArr[i].pos + Vector3f{ 0.f, 0.f, 45000.f };
float distSq = (Environment::shipState.position - boxWorld).squaredNorm();
if (distSq <= BOX_PICKUP_RADIUS * BOX_PICKUP_RADIUS) {
canPickup = true;
break;
}
}
}
if (canPickup != nearPickupBox) {
nearPickupBox = canPickup;
if (auto btn = menuManager.uiManager.findButton("takeButton"))
btn->state = canPickup ? ButtonState::Normal : ButtonState::Disabled;
}
}
if (playerScore != prevPlayerScore)
{
prevPlayerScore = playerScore;
menuManager.uiManager.setText("gameScoreText", "Score: " + std::to_string(playerScore));
}
}
void Space::fireProjectiles() {
@@ -1746,6 +1867,7 @@ namespace ZL
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
};
const float projectileSpeed = PROJECTILE_VELOCITY;
const float lifeMs = PROJECTILE_LIFE;
const float size = 0.5f;
@@ -1779,6 +1901,7 @@ namespace ZL
gameOver = true;
Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n";
clearPlayerListIfVisible();
menuManager.showConnectionLost();
}
@@ -1817,6 +1940,7 @@ namespace ZL
}
}
}
// Обработка событий смерти, присланных сервером
auto deaths = networkClient->getPendingDeaths();
if (!deaths.empty()) {
@@ -1841,18 +1965,21 @@ namespace ZL
shipAlive = false;
gameOver = true;
Environment::shipState.velocity = 0.0f;
clearPlayerListIfVisible();
menuManager.showGameOver(this->playerScore);
}
else {
deadRemotePlayers.insert(d.targetId);
if (d.targetId == manualTrackedTargetId) manualTrackedTargetId = -1;
std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
}
if (d.killerId == localId) {
this->playerScore += 1;
std::cout << "Client: Increased local score to " << this->playerScore << std::endl;
}
}
rebuildPlayerListIfVisible();
}
auto respawns = networkClient->getPendingRespawns();
@@ -1870,18 +1997,22 @@ namespace ZL
std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
}
}
rebuildPlayerListIfVisible();
auto disconnects = networkClient->getPendingDisconnects();
for (int pid : disconnects) {
remotePlayerStates.erase(pid);
deadRemotePlayers.erase(pid);
remoteShipSparkEmitters.erase(pid);
if (trackedTargetId == pid) {
trackedTargetId = -1;
targetAcquireAnim = 0.f;
}
if (pid == manualTrackedTargetId) manualTrackedTargetId = -1;
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
}
rebuildPlayerListIfVisible();
auto boxDestructions = networkClient->getPendingBoxDestructions();
if (!boxDestructions.empty()) {
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
@@ -1911,17 +2042,48 @@ namespace ZL
}
}
}
auto boxPickups = networkClient->getPendingBoxPickups();
for (const auto& pickup : boxPickups) {
int idx = pickup.boxIndex;
if (idx >= 0 && idx < (int)boxCoordsArr.size() && idx < (int)boxAlive.size()) {
if (boxAlive[idx]) {
boxAlive[idx] = false;
boxRenderArr[idx].data.PositionData.clear();
boxRenderArr[idx].vao.reset();
boxRenderArr[idx].positionVBO.reset();
boxRenderArr[idx].texCoordVBO.reset();
std::cout << "Client: Box " << idx << " picked up by player " << pickup.pickedUpBy << "\n";
}
}
}
auto boxRespawns = networkClient->getPendingBoxRespawns();
for (const auto& respawn : boxRespawns) {
int idx = respawn.boxIndex;
if (idx >= 0 && idx < (int)boxCoordsArr.size()) {
boxCoordsArr[idx].pos = respawn.position;
boxCoordsArr[idx].m = respawn.rotation;
boxAlive[idx] = true;
boxRenderArr[idx].AssignFrom(boxBase);
boxRenderArr[idx].RefreshVBO();
std::cout << "Client: Box " << idx << " respawned" << std::endl;
}
}
}
void Space::handleDown(int mx, int my)
{
Environment::tapDownHold = true;
if (playerListVisible) return;
Environment::tapDownStartPos(0) = mx;
Environment::tapDownStartPos(1) = my;
Environment::tapDownHold = true;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
Environment::tapDownStartPos(0) = mx;
Environment::tapDownStartPos(1) = my;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
void Space::handleUp(int mx, int my)
@@ -1932,6 +2094,8 @@ namespace ZL
void Space::handleMotion(int mx, int my)
{
if (playerListVisible) return;
if (Environment::tapDownHold) {
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
@@ -1969,4 +2133,151 @@ namespace ZL
}*/
std::shared_ptr<UiNode> Space::buildPlayerListRoot()
{
const float btnW = 400;
const float btnH = 50.0f;
// Collect alive remote players
std::vector<std::pair<int, std::string>> players;
for (auto& kv : remotePlayerStates) {
if (!deadRemotePlayers.count(kv.first))
players.push_back({ kv.first, kv.second.nickname });
}
// Root: FrameLayout match_parent x match_parent
auto root = std::make_shared<UiNode>();
root->name = "playerListRoot";
root->layoutType = LayoutType::Frame;
root->width = -1.0f; // match_parent
root->height = -1.0f;
// List container: LinearLayout vertical, centered
float listH = btnH * (float)players.size();
auto listNode = std::make_shared<UiNode>();
listNode->name = "playerList";
listNode->layoutType = LayoutType::Linear;
listNode->orientation = Orientation::Vertical;
listNode->width = btnW;
listNode->height = listH;
listNode->layoutSettings.hGravity = HorizontalGravity::Center;
listNode->layoutSettings.vGravity = VerticalGravity::Center;
for (auto& [pid, nick] : players) {
auto btnNode = std::make_shared<UiNode>();
btnNode->name = "playerBtn_" + std::to_string(pid);
btnNode->layoutType = LayoutType::Frame;
btnNode->width = btnW;
btnNode->height = btnH;
auto tb = std::make_shared<UiTextButton>();
tb->name = btnNode->name;
tb->text = nick;
tb->fontSize = 20;
tb->color = { 1.f, 1.f, 1.f, 1.f };
tb->textCentered = true;
tb->textRenderer = std::make_unique<TextRenderer>();
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, CONST_ZIP_FILE)) {
std::cerr << "Failed to init TextRenderer for TextField: " << tb->name << std::endl;
}
//tb->texNormal = std::make_unique<Texture>(CreateTextureDataFromPng("resources/black.png", ""));
btnNode->textButton = tb;
/*auto button = std::make_shared<UiButton>();
button->name = "Hello";
button->texNormal = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", ""));
btnNode->button = button;*/
listNode->children.push_back(btnNode);
}
// Backdrop: invisible full-screen TextButton — placed LAST so player buttons get priority
auto backdropNode = std::make_shared<UiNode>();
backdropNode->name = "playerListBackdrop";
backdropNode->layoutType = LayoutType::Frame;
backdropNode->width = -1.0f;
backdropNode->height = -1.0f;
auto backdropTb = std::make_shared<UiTextButton>();
backdropTb->name = "playerListBackdrop";
backdropNode->textButton = backdropTb;
/*
auto backgroundNode = std::make_shared<UiNode>();
backgroundNode->name = "playerListBackground";
backgroundNode->layoutType = LayoutType::Frame;
backgroundNode->width = btnW;
backgroundNode->height = listH;
backgroundNode->layoutSettings.hGravity = HorizontalGravity::Center;
backgroundNode->layoutSettings.vGravity = VerticalGravity::Center;
auto backdropImage = std::make_shared<UiStaticImage>();
backdropImage->name = "playerListBackgroundImage";
backdropImage->texture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/blue_transparent.png", ""));
backgroundNode->staticImage = backdropImage;
*/
root->children.push_back(listNode);
root->children.push_back(backdropNode);
//root->children.push_back(backgroundNode);
return root;
}
void Space::buildAndShowPlayerList()
{
auto listRoot = buildPlayerListRoot();
menuManager.uiManager.pushMenuFromSavedRoot(listRoot);
menuManager.uiManager.updateAllLayouts();
playerListVisible = true;
for (auto& kv : remotePlayerStates) {
if (deadRemotePlayers.count(kv.first)) continue;
int pid = kv.first;
std::string btnName = "playerBtn_" + std::to_string(pid);
menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) {
manualTrackedTargetId = pid;
closePlayerList();
});
}
menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) {
closePlayerList();
});
}
void Space::closePlayerList()
{
menuManager.uiManager.popMenu();
menuManager.uiManager.updateAllLayouts();
playerListVisible = false;
}
void Space::rebuildPlayerListIfVisible()
{
if (!playerListVisible) return;
auto listRoot = buildPlayerListRoot();
menuManager.uiManager.replaceRoot(listRoot);
for (auto& kv : remotePlayerStates) {
if (deadRemotePlayers.count(kv.first)) continue;
int pid = kv.first;
std::string btnName = "playerBtn_" + std::to_string(pid);
menuManager.uiManager.setTextButtonCallback(btnName, [this, pid](const std::string&) {
manualTrackedTargetId = pid;
closePlayerList();
});
}
menuManager.uiManager.setTextButtonCallback("playerListBackdrop", [this](const std::string&) {
closePlayerList();
});
}
void Space::clearPlayerListIfVisible()
{
if (!playerListVisible) return;
menuManager.uiManager.clearMenuStack();
playerListVisible = false;
}
} // namespace ZL
+17
View File
@@ -66,6 +66,7 @@ namespace ZL {
std::unique_ptr<TextRenderer> textRenderer;
std::unordered_map<int, ClientState> remotePlayerStates;
std::unordered_map<int, SparkEmitter> remoteShipSparkEmitters;
float newShipVelocity = 0;
@@ -112,6 +113,7 @@ namespace ZL {
const uint64_t explosionDurationMs = 500;
bool serverBoxesApplied = false;
bool nearPickupBox = false;
static constexpr float MAX_DIST_SQ = 10000.f * 10000.f;
static constexpr float FADE_START = 6000.f;
@@ -124,8 +126,12 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers;
int playerScore = 0;
int prevPlayerScore = 0;
bool wasConnectedToServer = false;
bool playerListVisible = false;
int manualTrackedTargetId = -1;
static constexpr float TARGET_MAX_DIST = 50000.0f;
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
@@ -144,6 +150,17 @@ namespace ZL {
void resetPlayerState();
void clearTextRendererCache();
// Player list overlay
void buildAndShowPlayerList();
void closePlayerList();
void rebuildPlayerListIfVisible();
void clearPlayerListIfVisible();
std::shared_ptr<UiNode> buildPlayerListRoot();
void updateSparkEmitters(float deltaMs);
void prepareSparkEmittersForDraw();
void drawShipSparkEmitters();
// Crosshair HUD
struct CrosshairConfig {
bool enabled = true;
+6 -10
View File
@@ -26,12 +26,12 @@ namespace ZL {
sparkQuad.data = VertexDataStruct();
}
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true),
texture(copyFrom.texture),
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
zSpeedRange(copyFrom.zSpeedRange),
@@ -40,6 +40,8 @@ namespace ZL {
shaderProgramName(copyFrom.shaderProgramName),
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
{
// Each copy gets its own GPU buffers; only copy CPU-side data
sparkQuad.data = copyFrom.sparkQuad.data;
}
@@ -198,16 +200,10 @@ namespace ZL {
throw std::runtime_error("Failed to load spark emitter config file 2!");
}
//prepareDrawData(withRotation);
if (drawPositions.empty()) {
return;
}
/*
sparkQuad.data.PositionData = drawPositions;
sparkQuad.data.TexCoordData = drawTexCoords;
sparkQuad.RefreshVBO();
*/
renderer.shaderManager.PushShader(shaderProgramName);
renderer.RenderUniform1i(textureUniformName, 0);
renderer.SetMatrix();
+262 -2
View File
@@ -72,6 +72,65 @@ namespace ZL {
renderer.PopMatrix();
}
void UiTextButton::buildMesh() {
mesh.data.PositionData.clear();
mesh.data.TexCoordData.clear();
float x0 = rect.x;
float y0 = rect.y;
float x1 = rect.x + rect.w;
float y1 = rect.y + rect.h;
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x0, y1, 0 });
mesh.data.TexCoordData.push_back({ 0, 1 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x0, y0, 0 });
mesh.data.TexCoordData.push_back({ 0, 0 });
mesh.data.PositionData.push_back({ x1, y1, 0 });
mesh.data.TexCoordData.push_back({ 1, 1 });
mesh.data.PositionData.push_back({ x1, y0, 0 });
mesh.data.TexCoordData.push_back({ 1, 0 });
mesh.RefreshVBO();
}
void UiTextButton::draw(Renderer& renderer) const {
renderer.PushMatrix();
renderer.TranslateMatrix({ animOffsetX, animOffsetY, 0.0f });
renderer.ScaleMatrix({ animScaleX, animScaleY, 1.0f });
// Draw background texture (optional)
const std::shared_ptr<Texture>* tex = nullptr;
switch (state) {
case ButtonState::Normal: if (texNormal) tex = &texNormal; break;
case ButtonState::Hover: tex = texHover ? &texHover : (texNormal ? &texNormal : nullptr); break;
case ButtonState::Pressed: tex = texPressed ? &texPressed : (texNormal ? &texNormal : nullptr); break;
case ButtonState::Disabled: tex = texDisabled ? &texDisabled : (texNormal ? &texNormal : nullptr); break;
}
if (tex && *tex) {
renderer.RenderUniform1i(textureUniformName, 0);
glBindTexture(GL_TEXTURE_2D, (*tex)->getTexID());
renderer.DrawVertexRenderStruct(mesh);
}
renderer.PopMatrix();
// Draw text on top (uses absolute coords, add anim offset manually)
if (textRenderer && !text.empty()) {
float cx = rect.x + rect.w / 2.0f + animOffsetX;
float cy = rect.y + rect.h / 2.0f + animOffsetY;
textRenderer->drawText(text, cx, cy, 1.0f, textCentered, color);
}
}
void UiSlider::buildTrackMesh() {
trackMesh.data.PositionData.clear();
trackMesh.data.TexCoordData.clear();
@@ -393,6 +452,49 @@ namespace ZL {
node->textField = tf;
}
if (typeStr == "TextButton") {
auto tb = std::make_shared<UiTextButton>();
tb->name = node->name;
tb->rect = initialRect;
tb->border = j.value("border", 0.0f);
// Textures are optional
if (j.contains("textures") && j["textures"].is_object()) {
auto t = j["textures"];
auto loadTex = [&](const std::string& key) -> std::shared_ptr<Texture> {
if (!t.contains(key) || !t[key].is_string()) return nullptr;
std::string path = t[key].get<std::string>();
try {
auto data = CreateTextureDataFromPng(path.c_str(), zipFile.c_str());
return std::make_shared<Texture>(data);
}
catch (const std::exception& e) {
std::cerr << "UiManager: TextButton '" << tb->name << "' failed to load texture " << path << ": " << e.what() << std::endl;
return nullptr;
}
};
tb->texNormal = loadTex("normal");
tb->texHover = loadTex("hover");
tb->texPressed = loadTex("pressed");
tb->texDisabled = loadTex("disabled");
}
if (j.contains("text")) tb->text = j["text"].get<std::string>();
if (j.contains("fontPath")) tb->fontPath = j["fontPath"].get<std::string>();
if (j.contains("fontSize")) tb->fontSize = j["fontSize"].get<int>();
if (j.contains("textCentered")) tb->textCentered = j["textCentered"].get<bool>();
if (j.contains("color") && j["color"].is_array() && j["color"].size() == 4) {
for (int i = 0; i < 4; ++i) tb->color[i] = j["color"][i].get<float>();
}
tb->textRenderer = std::make_unique<TextRenderer>();
if (!tb->textRenderer->init(renderer, tb->fontPath, tb->fontSize, zipFile)) {
std::cerr << "UiManager: Failed to init TextRenderer for TextButton: " << tb->name << std::endl;
}
node->textButton = tb;
}
if (j.contains("animations") && j["animations"].is_object()) {
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
std::string animName = it.key();
@@ -536,6 +638,7 @@ namespace ZL {
root->localY // finalLocalY
);
buttons.clear();
textButtons.clear();
sliders.clear();
textViews.clear();
textFields.clear();
@@ -547,6 +650,9 @@ namespace ZL {
for (auto& b : buttons) {
b->buildMesh();
}
for (auto& tb : textButtons) {
tb->buildMesh();
}
for (auto& s : sliders) {
s->buildTrackMesh();
s->buildKnobMesh();
@@ -694,11 +800,14 @@ namespace ZL {
// 1. Обновляем кнопку
if (node->button) {
node->button->rect = node->screenRect;
// Если у кнопки есть анимационные смещения, они учитываются внутри buildMesh
// или при рендеринге через Uniform-переменные матрицы модели.
node->button->buildMesh();
}
if (node->textButton) {
node->textButton->rect = node->screenRect;
node->textButton->buildMesh();
}
// 2. Обновляем слайдер
if (node->slider) {
node->slider->rect = node->screenRect;
@@ -744,6 +853,9 @@ namespace ZL {
if (node->button) {
buttons.push_back(node->button);
}
if (node->textButton) {
textButtons.push_back(node->textButton);
}
if (node->slider) {
sliders.push_back(node->slider);
}
@@ -862,10 +974,13 @@ namespace ZL {
MenuState prev;
prev.root = root;
prev.buttons = buttons;
prev.textButtons = textButtons;
prev.sliders = sliders;
prev.textViews = textViews;
prev.textFields = textFields;
prev.staticImages = staticImages;
prev.pressedButtons = pressedButtons;
prev.pressedTextButtons = pressedTextButtons;
prev.pressedSliders = pressedSliders;
prev.focusedTextField = focusedTextField;
prev.path = "";
@@ -884,6 +999,14 @@ namespace ZL {
b->animScaleY = 1.0f;
}
}
for (auto& tb : textButtons) {
if (tb) {
tb->animOffsetX = 0.0f;
tb->animOffsetY = 0.0f;
tb->animScaleX = 1.0f;
tb->animScaleY = 1.0f;
}
}
replaceRoot(newRoot);
menuStack.push_back(std::move(prev));
@@ -913,10 +1036,13 @@ namespace ZL {
root = s.root;
buttons = s.buttons;
textButtons = s.textButtons;
sliders = s.sliders;
textViews = s.textViews;
textFields = s.textFields;
staticImages = s.staticImages;
pressedButtons = s.pressedButtons;
pressedTextButtons = s.pressedTextButtons;
pressedSliders = s.pressedSliders;
focusedTextField = s.focusedTextField;
@@ -931,6 +1057,15 @@ namespace ZL {
b->buildMesh();
}
}
for (auto& tb : textButtons) {
if (tb) {
tb->animOffsetX = 0.0f;
tb->animOffsetY = 0.0f;
tb->animScaleX = 1.0f;
tb->animScaleY = 1.0f;
tb->buildMesh();
}
}
for (auto& sl : sliders) {
if (sl) {
@@ -956,6 +1091,9 @@ namespace ZL {
for (const auto& b : buttons) {
b->draw(renderer);
}
for (const auto& tb : textButtons) {
tb->draw(renderer);
}
for (const auto& s : sliders) {
s->draw(renderer);
}
@@ -1007,6 +1145,12 @@ namespace ZL {
node->button->animScaleX = act.origScaleX;
node->button->animScaleY = act.origScaleY;
}
if (node->textButton) {
node->textButton->animOffsetX = act.origOffsetX;
node->textButton->animOffsetY = act.origOffsetY;
node->textButton->animScaleX = act.origScaleX;
node->textButton->animScaleY = act.origScaleY;
}
act.stepIndex = 0;
act.elapsedMs = 0.0f;
act.stepStarted = false;
@@ -1028,12 +1172,20 @@ namespace ZL {
node->button->animOffsetX = step.toX;
node->button->animOffsetY = step.toY;
}
if (node->textButton) {
node->textButton->animOffsetX = step.toX;
node->textButton->animOffsetY = step.toY;
}
}
else if (step.type == "scale") {
if (node->button) {
node->button->animScaleX = step.toX;
node->button->animScaleY = step.toY;
}
if (node->textButton) {
node->textButton->animScaleX = step.toX;
node->textButton->animScaleY = step.toY;
}
}
act.stepIndex++;
act.elapsedMs = 0.0f;
@@ -1048,6 +1200,12 @@ namespace ZL {
act.origScaleX = node->button->animScaleX;
act.origScaleY = node->button->animScaleY;
}
else if (node->textButton) {
act.origOffsetX = node->textButton->animOffsetX;
act.origOffsetY = node->textButton->animOffsetY;
act.origScaleX = node->textButton->animScaleX;
act.origScaleY = node->textButton->animScaleY;
}
else {
act.origOffsetX = act.origOffsetY = 0.0f;
act.origScaleX = act.origScaleY = 1.0f;
@@ -1064,6 +1222,12 @@ namespace ZL {
act.startScaleX = node->button->animScaleX;
act.startScaleY = node->button->animScaleY;
}
else if (node->textButton) {
act.startOffsetX = node->textButton->animOffsetX;
act.startOffsetY = node->textButton->animOffsetY;
act.startScaleX = node->textButton->animScaleX;
act.startScaleY = node->textButton->animScaleY;
}
else {
act.startOffsetX = act.startOffsetY = 0.0f;
act.startScaleX = act.startScaleY = 1.0f;
@@ -1089,6 +1253,10 @@ namespace ZL {
node->button->animOffsetX = nx;
node->button->animOffsetY = ny;
}
if (node->textButton) {
node->textButton->animOffsetX = nx;
node->textButton->animOffsetY = ny;
}
}
else if (step.type == "scale") {
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
@@ -1097,6 +1265,10 @@ namespace ZL {
node->button->animScaleX = sx;
node->button->animScaleY = sy;
}
if (node->textButton) {
node->textButton->animScaleX = sx;
node->textButton->animScaleY = sy;
}
}
else if (step.type == "wait") {
//wait
@@ -1146,6 +1318,17 @@ namespace ZL {
}
}
}
for (auto& tb : textButtons) {
if (tb->state != ButtonState::Disabled)
{
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) {
if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Hover;
}
else {
if (tb->state != ButtonState::Pressed) tb->state = ButtonState::Normal;
}
}
}
}
auto it = pressedSliders.find(fingerId);
@@ -1180,6 +1363,18 @@ namespace ZL {
}
}
for (auto& tb : textButtons) {
if (tb->state != ButtonState::Disabled)
{
if (tb->rect.containsConsideringBorder((float)x, (float)y, tb->border)) {
tb->state = ButtonState::Pressed;
pressedTextButtons[fingerId] = tb;
if (tb->onPress) tb->onPress(tb->name);
break;
}
}
}
for (auto& s : sliders) {
if (s->rect.contains((float)x, (float)y)) {
pressedSliders[fingerId] = s;
@@ -1212,6 +1407,7 @@ namespace ZL {
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
std::vector<std::shared_ptr<UiButton>> clicked;
std::vector<std::shared_ptr<UiTextButton>> clickedText;
auto btnIt = pressedButtons.find(fingerId);
if (btnIt != pressedButtons.end()) {
@@ -1229,6 +1425,21 @@ namespace ZL {
pressedButtons.erase(btnIt);
}
auto tbIt = pressedTextButtons.find(fingerId);
if (tbIt != pressedTextButtons.end()) {
auto tb = tbIt->second;
if (tb) {
bool contains = tb->rect.contains((float)x, (float)y);
if (tb->state == ButtonState::Pressed) {
if (contains) {
clickedText.push_back(tb);
}
tb->state = (contains && fingerId == MOUSE_FINGER_ID) ? ButtonState::Hover : ButtonState::Normal;
}
}
pressedTextButtons.erase(tbIt);
}
pressedSliders.erase(fingerId);
for (auto& b : clicked) {
@@ -1236,6 +1447,11 @@ namespace ZL {
b->onClick(b->name);
}
}
for (auto& tb : clickedText) {
if (tb->onClick) {
tb->onClick(tb->name);
}
}
}
void UiManager::onKeyPress(unsigned char key) {
@@ -1287,6 +1503,12 @@ namespace ZL {
aa.origScaleX = node->button->animScaleX;
aa.origScaleY = node->button->animScaleY;
}
else if (node->textButton) {
aa.origOffsetX = node->textButton->animOffsetX;
aa.origOffsetY = node->textButton->animOffsetY;
aa.origScaleX = node->textButton->animScaleX;
aa.origScaleY = node->textButton->animScaleY;
}
auto cbIt = animCallbacks.find({ nodeName, animName });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[node].push_back(std::move(aa));
@@ -1337,6 +1559,12 @@ namespace ZL {
aa.origScaleX = n->button->animScaleX;
aa.origScaleY = n->button->animScaleY;
}
else if (n->textButton) {
aa.origOffsetX = n->textButton->animOffsetX;
aa.origOffsetY = n->textButton->animOffsetY;
aa.origScaleX = n->textButton->animScaleX;
aa.origScaleY = n->textButton->animScaleY;
}
auto cbIt = animCallbacks.find({ n->name, animName });
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
nodeActiveAnims[n].push_back(std::move(aa));
@@ -1374,4 +1602,36 @@ namespace ZL {
return true;
}
std::shared_ptr<UiTextButton> UiManager::findTextButton(const std::string& name) {
for (auto& tb : textButtons) if (tb->name == name) return tb;
return nullptr;
}
bool UiManager::setTextButtonCallback(const std::string& name, std::function<void(const std::string&)> cb) {
auto tb = findTextButton(name);
if (!tb) {
std::cerr << "UiManager: setTextButtonCallback failed, textButton not found: " << name << std::endl;
return false;
}
tb->onClick = std::move(cb);
return true;
}
bool UiManager::setTextButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb) {
auto tb = findTextButton(name);
if (!tb) {
std::cerr << "UiManager: setTextButtonPressCallback failed, textButton not found: " << name << std::endl;
return false;
}
tb->onPress = std::move(cb);
return true;
}
bool UiManager::setTextButtonText(const std::string& name, const std::string& newText) {
auto tb = findTextButton(name);
if (!tb) return false;
tb->text = newText;
return true;
}
} // namespace ZL
+57 -2
View File
@@ -125,6 +125,42 @@ namespace ZL {
void draw(Renderer& renderer) const;
};
struct UiTextButton {
std::string name;
UiRect rect;
float border = 0;
// Textures are optional — button can be text-only
std::shared_ptr<Texture> texNormal;
std::shared_ptr<Texture> texHover;
std::shared_ptr<Texture> texPressed;
std::shared_ptr<Texture> texDisabled;
ButtonState state = ButtonState::Normal;
VertexRenderStruct mesh;
// Text drawn on top of the button
std::string text;
std::string fontPath = "resources/fonts/DroidSans.ttf";
int fontSize = 32;
std::array<float, 4> color = { 1.f, 1.f, 1.f, 1.f };
bool textCentered = true;
std::unique_ptr<TextRenderer> textRenderer;
std::function<void(const std::string&)> onClick;
std::function<void(const std::string&)> onPress;
// Animation runtime
float animOffsetX = 0.0f;
float animOffsetY = 0.0f;
float animScaleX = 1.0f;
float animScaleY = 1.0f;
void buildMesh();
void draw(Renderer& renderer) const;
};
struct UiTextView {
std::string name;
UiRect rect;
@@ -197,6 +233,7 @@ namespace ZL {
// Компоненты (только один из них обычно активен для ноды)
std::shared_ptr<UiButton> button;
std::shared_ptr<UiTextButton> textButton;
std::shared_ptr<UiSlider> slider;
std::shared_ptr<UiTextView> textView;
std::shared_ptr<UiTextField> textField;
@@ -249,12 +286,12 @@ namespace ZL {
// Returns true if any finger is currently interacting with UI
bool isUiInteraction() const {
return !pressedButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
return !pressedButtons.empty() || !pressedTextButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
}
// Returns true if this specific finger is currently interacting with UI
bool isUiInteractionForFinger(int64_t fingerId) const {
return pressedButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
return pressedButtons.count(fingerId) > 0 || pressedTextButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
}
void stopAllAnimations() {
@@ -268,6 +305,14 @@ namespace ZL {
b->animScaleY = 1.0f;
}
}
for (auto& tb : textButtons) {
if (tb) {
tb->animOffsetX = 0.0f;
tb->animOffsetY = 0.0f;
tb->animScaleX = 1.0f;
tb->animScaleY = 1.0f;
}
}
}
std::shared_ptr<UiButton> findButton(const std::string& name);
@@ -275,6 +320,11 @@ namespace ZL {
bool setButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
bool setButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
std::shared_ptr<UiTextButton> findTextButton(const std::string& name);
bool setTextButtonCallback(const std::string& name, std::function<void(const std::string&)> cb);
bool setTextButtonPressCallback(const std::string& name, std::function<void(const std::string&)> cb);
bool setTextButtonText(const std::string& name, const std::string& newText);
bool addSlider(const std::string& name, const UiRect& rect, Renderer& renderer, const std::string& zipFile,
const std::string& trackPath, const std::string& knobPath, float initialValue = 0.0f, bool vertical = true);
@@ -333,6 +383,7 @@ namespace ZL {
std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiTextButton>> textButtons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields;
@@ -343,16 +394,20 @@ namespace ZL {
// Per-finger tracking for multi-touch support
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
std::shared_ptr<UiTextField> focusedTextField;
struct MenuState {
std::shared_ptr<UiNode> root;
std::vector<std::shared_ptr<UiButton>> buttons;
std::vector<std::shared_ptr<UiTextButton>> textButtons;
std::vector<std::shared_ptr<UiSlider>> sliders;
std::vector<std::shared_ptr<UiTextView>> textViews;
std::vector<std::shared_ptr<UiTextField>> textFields;
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
std::map<int64_t, std::shared_ptr<UiTextButton>> pressedTextButtons;
std::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
std::shared_ptr<UiTextField> focusedTextField;
std::string path;
+5 -4
View File
@@ -31,10 +31,11 @@ constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD rec
constexpr float PROJECTILE_VELOCITY = 600.f;
constexpr float PROJECTILE_LIFE = 15000.f; //ms
const float projectileHitRadius = 1.5f * 5;
const float boxCollisionRadius = 2.0f * 5;
const float shipCollisionRadius = 15.0f * 5;
const float npcCollisionRadius = 5.0f * 5;
const float projectileHitRadius = 1.5f * 4;
const float boxCollisionRadius = 2.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);
+114
View File
@@ -274,6 +274,24 @@ namespace ZL {
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));
@@ -352,11 +370,62 @@ namespace ZL {
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;
@@ -372,6 +441,51 @@ namespace ZL {
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;
+5
View File
@@ -42,6 +42,8 @@ namespace ZL {
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;
@@ -56,6 +58,7 @@ namespace ZL {
void initializeNPCs();
void updateNPCs();
Eigen::Vector3f generateRandomPosition();
Eigen::Vector3f generateRespawnBoxPos(int skipIdx);
public:
void Connect(const std::string& host, uint16_t port) override;
@@ -79,6 +82,8 @@ namespace ZL {
}
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
void setLocalPlayerState(const ClientState& state) {
localPlayerState = state;
+13
View File
@@ -30,6 +30,17 @@ namespace ZL {
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;
@@ -50,6 +61,8 @@ namespace ZL {
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 {}; }
+51
View File
@@ -129,6 +129,45 @@ namespace ZL {
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) {
@@ -369,6 +408,18 @@ namespace ZL {
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_);
+4
View File
@@ -20,6 +20,8 @@ namespace ZL {
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;
@@ -50,6 +52,8 @@ namespace ZL {
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 -2
View File
@@ -617,6 +617,7 @@ namespace ZL {
{
throw std::runtime_error("Modelview matrix stack overflow!!!!");
}
SetMatrix();
}
void Renderer::LoadIdentity()
@@ -922,8 +923,6 @@ namespace ZL {
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
VertexAttribPointer3fv(vPosition, 0, NULL);
//EnableVertexAttribArray(vPosition);
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
}