Compare commits

..

No commits in common. "main" and "ShipSpawn" have entirely different histories.

39 changed files with 152 additions and 1484 deletions

View File

@ -8,7 +8,7 @@
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed;
position: fixed; /* Предотвращает pull-to-refresh на Android */
}
#canvas {
display: block;
@ -17,65 +17,13 @@
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 */
#nickOverlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0,0,0,0.85);
z-index: 9999;
}
#nickBox {
background: #111;
border: 1px solid #444;
padding: 24px;
width: 320px;
box-shadow: 0 8px 24px rgba(0,0,0,0.6);
text-align: center;
}
#nickBox h2 { margin: 0 0 12px 0; font-size: 18px; color: #eee; }
#nickBox input[type="text"] {
width: 100%;
padding: 10px;
font-size: 16px;
box-sizing: border-box;
margin-bottom: 12px;
border: 1px solid #333;
background: #000;
color: #fff;
}
#nickBox button {
padding: 10px 16px;
font-size: 16px;
background: #2a9fd6;
border: none;
color: #fff;
cursor: pointer;
}
#nickSkip { margin-left: 8px; background: #666; }
</style>
</head>
<body>
<button id="fs-button">Fullscreen</button>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<!--
<script>
var statusElement = document.getElementById("status");
var canvas = document.getElementById("canvas");
@ -88,119 +36,7 @@
}
};
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() {
setTimeout(() => {
window.dispatchEvent(new Event('resize'));
}, 200);
});
</script>
<script async src="space-game001.js"></script>-->
<div id="nickOverlay" style="display:none;">
<div id="nickBox">
<h2>Enter your nickname</h2>
<input id="nickInput" type="text" maxlength="32" placeholder="Player" />
<div>
<button id="nickSubmit">Start</button>
</div>
</div>
</div>
<script>
// Utility: подготовить глобальный Module до загрузки Emscripten-скрипта
function prepareModuleEnvironment() {
window.Module = window.Module || {};
var canvasEl = document.getElementById('canvas');
// Устанавливаем canvas для Emscripten, чтобы createContext не падал
window.Module.canvas = canvasEl;
// Подготовим заглушку setStatus, если ещё нет
window.Module.setStatus = window.Module.setStatus || function (text) {
var statusElement = document.getElementById("status");
statusElement.innerHTML = text;
statusElement.style.display = text ? 'block' : 'none';
};
}
// Show overlay only if no nickname saved.
function loadGameScript() {
var s = document.createElement('script');
s.src = 'space-game001.js';
s.async = true;
document.body.appendChild(s);
}
function showNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'flex';
var input = document.getElementById('nickInput');
input.focus();
}
function hideNickOverlay() {
var overlay = document.getElementById('nickOverlay');
overlay.style.display = 'none';
}
function saveNickAndStart(nick) {
try {
if (!nick || nick.trim() === '') nick = 'Player';
localStorage.setItem('spacegame_nick', nick);
} catch (e) {
console.warn('localStorage not available', e);
}
hideNickOverlay();
// перед загрузкой скрипта гарантируем, что Module.canvas задан
prepareModuleEnvironment();
loadGameScript();
}
document.addEventListener('DOMContentLoaded', function() {
// Готовим Module сразу — даже если откроется модалка, поле canvas будет доступно для скрипта (если он загружается позже)
prepareModuleEnvironment();
var stored = null;
try {
stored = localStorage.getItem('spacegame_nick');
} catch (e) {
console.warn('localStorage not available', e);
}
if (stored && stored.trim() !== '') {
// Nick is present — start immediately
loadGameScript();
} else {
// Show modal to request nickname before loading WASM
showNickOverlay();
var submit = document.getElementById('nickSubmit');
var skip = document.getElementById('nickSkip');
var input = document.getElementById('nickInput');
submit.addEventListener('click', function() {
saveNickAndStart(input.value);
});
skip.addEventListener('click', function() {
saveNickAndStart('Player');
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') {
saveNickAndStart(input.value);
}
});
}
});
// Обработка ориентации
window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
@ -211,6 +47,6 @@
});
</script>
<script async src="space-game001.js"></script>
</body>
</html>

BIN
resources/black.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/blue_transparent.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_minus_disabled.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_players.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_plus_disabled.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_take.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_take_disabled.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/button_take_pressed.png (Stored with Git LFS)

Binary file not shown.

View File

@ -13,7 +13,7 @@
{
"type": "StaticImage",
"name": "titleBtn",
"width": 434,
"width": 254,
"height": 35,
"texture": "resources/main_menu/title.png"
},
@ -25,11 +25,15 @@
"texture": "resources/main_menu/line.png"
},
{
"type": "StaticImage",
"type": "Button",
"name": "subtitleBtn",
"width": 144,
"height": 11,
"texture": "resources/main_menu/subtitle.png"
"textures": {
"normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png"
}
},
{
"type": "Button",

View File

@ -10,13 +10,6 @@
"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",

View File

@ -1,5 +1,5 @@
{
"emissionRate": 1.2,
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,

View File

@ -1,5 +1,5 @@
{
"emissionRate": 1.2,
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,

View File

@ -8,55 +8,18 @@
"children": [
{
"type": "TextView",
"name": "gameScoreText",
"x": 0,
"y": 30,
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 60,
"height": 40,
"horizontal_gravity": "left",
"vertical_gravity": "top",
"text": "Score: 0",
"fontSize": 36,
"color": [
0,
217,
255,
1
],
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"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": "inverseMouseButton",
"x": 0,
"y": 100,
"width": 150,
"height": 150,
"horizontal_gravity": "right",
"vertical_gravity": "top",
"textures": {
"normal": "resources/fire.png",
"hover": "resources/fire.png",
"pressed": "resources/fire2.png",
"disabled": "resources/fire.png"
}
},
{
"type": "Button",
"name": "shootButton",
@ -101,9 +64,8 @@
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_minus.png",
"hover": "resources/button_minus.png",
"pressed": "resources/button_minus_pressed.png",
"disabled" : "resources/button_minus_disabled.png"
"hover": "resources/button_minus_pressed.png",
"pressed": "resources/button_minus_pressed.png"
}
},
{
@ -118,26 +80,8 @@
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_plus.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"
"hover": "resources/button_plus_pressed.png",
"pressed": "resources/button_plus_pressed.png"
}
}
]

BIN
resources/connection_failed.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/loading.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/about.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/about_hover.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/about_pressed.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/main_menu/title.png (Stored with Git LFS)

Binary file not shown.

BIN
resources/select_your_ship.png (Stored with Git LFS)

Binary file not shown.

View File

@ -31,8 +31,8 @@ bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
}
void Session::force_disconnect() {
beast::error_code ec;
ws_.next_layer().socket().close(ec);
ws_.async_close(websocket::close_code::normal,
[self = shared_from_this()](beast::error_code) {});
}
int Session::get_id() const { return id_; }
@ -125,6 +125,8 @@ void Session::sendBoxesToClient() {
void Session::init()
{
sendBoxesToClient();
auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
@ -133,8 +135,6 @@ void Session::init()
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
self->sendBoxesToClient();
self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
self->do_read();
}
@ -346,64 +346,6 @@ 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;
@ -435,28 +377,6 @@ 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{}() };
@ -688,8 +608,6 @@ void Server::update_world() {
std::vector<int> boxesToRespawn;
// --- Tick: box-projectile collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
@ -714,7 +632,6 @@ 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);
@ -730,8 +647,6 @@ 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;
}
@ -775,8 +690,6 @@ 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;
@ -819,41 +732,6 @@ 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) {
@ -942,7 +820,6 @@ int main() {
Server server(acceptor, ioc);
server.init();
server.accept();
std::cout << "Server started on port 8081...\n";

View File

@ -135,8 +135,6 @@ 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();
};

View File

@ -41,10 +41,6 @@ namespace ZL
const char* CONST_ZIP_FILE = "";
#endif
float x = 0;
float y = 0;
float z = 0;
#ifdef EMSCRIPTEN
Game* Game::s_instance = nullptr;
@ -110,20 +106,7 @@ namespace ZL
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
#endif
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.data = CreateRect2D({ 0.5f, 0.5f }, { 0.5f, 0.5f }, 3);
loadingMesh.RefreshVBO();
#ifdef EMSCRIPTEN
@ -281,12 +264,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(
-width * 0.5f, width*0.5f,
-height * 0.5f, height * 0.5f,
0, 1,
0, 1,
-10, 10);
renderer.PushMatrix();
@ -348,7 +331,7 @@ namespace ZL
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
ZL::CheckGlError();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//processTickCount();
@ -437,6 +420,22 @@ 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) {
@ -465,8 +464,8 @@ namespace ZL
}
if (event.type == SDL_KEYUP) {
if (event.key.keysym.sym == SDLK_r) {
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
if (event.key.keysym.sym == SDLK_a) {
//Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
}
}
#endif

View File

@ -1,15 +1,8 @@
#include "MenuManager.h"
#include <iostream>
#ifdef EMSCRIPTEN
#include <emscripten.h>
#include <cstdlib>
#endif
namespace ZL {
extern bool inverseVertical;
MenuManager::MenuManager(Renderer& iRenderer) :
renderer(iRenderer)
{
@ -60,39 +53,15 @@ namespace ZL {
state = GameState::ShipSelectionSingle;
uiManager.replaceRoot(shipSelectionRoot);
std::string initialNick;
#ifdef EMSCRIPTEN
char* savedNickC = emscripten_run_script_string("localStorage.getItem('spacegame_nick') || ''");
if (savedNickC) {
initialNick = savedNickC;
free(savedNickC);
}
#endif
auto tf = uiManager.findTextField("nicknameInput");
if (tf) {
if (!initialNick.empty()) tf->text = initialNick;
#ifdef EMSCRIPTEN
uiManager.setTextFieldCallback("nicknameInput", [](const std::string&, const std::string& value) {
EM_ASM_({
try { localStorage.setItem('spacegame_nick', UTF8ToString($0)); } catch(e) {}
}, value.c_str());
});
#endif
}
uiManager.setButtonCallback("spaceshipButton", [this, initialNick](const std::string&) {
uiManager.setButtonCallback("spaceshipButton", [this](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 0);
});
uiManager.setButtonCallback("cargoshipButton", [this, initialNick](const std::string&) {
uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
enterGameplay();
if (onSingleplayerPressed) onSingleplayerPressed(nick, 1);
@ -110,31 +79,8 @@ namespace ZL {
state = GameState::ShipSelectionMulti;
uiManager.replaceRoot(shipSelectionRoot);
std::string initialNick;
#ifdef EMSCRIPTEN
char* savedNickC = emscripten_run_script_string("localStorage.getItem('spacegame_nick') || ''");
if (savedNickC) {
initialNick = savedNickC;
free(savedNickC);
}
#endif
auto tf = uiManager.findTextField("nicknameInput");
if (tf) {
if (!initialNick.empty()) tf->text = initialNick;
#ifdef EMSCRIPTEN
uiManager.setTextFieldCallback("nicknameInput", [](const std::string&, const std::string& value) {
EM_ASM_({
try { localStorage.setItem('spacegame_nick', UTF8ToString($0)); } catch(e) {}
}, value.c_str());
});
#endif
}
uiManager.setButtonCallback("spaceshipButton", [this, initialNick](const std::string&) {
uiManager.setButtonCallback("spaceshipButton", [this](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
pendingMultiNick = nick;
pendingMultiShipType = 0;
@ -142,9 +88,8 @@ namespace ZL {
if (onMultiplayerPressed) onMultiplayerPressed(nick, 0);
});
uiManager.setButtonCallback("cargoshipButton", [this, initialNick](const std::string&) {
uiManager.setButtonCallback("cargoshipButton", [this](const std::string&) {
std::string nick = uiManager.getTextFieldValue("nicknameInput");
if (nick.empty()) nick = initialNick;
if (nick.empty()) nick = "Player";
pendingMultiNick = nick;
pendingMultiShipType = 1;
@ -190,16 +135,13 @@ 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();
@ -210,57 +152,20 @@ 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();
});
//inverseMouseButton
uiManager.setButtonPressCallback("inverseMouseButton", [this](const std::string&) {
inverseVertical = !inverseVertical;
std::cout << "Inverse mouse: " << (inverseVertical ? "ON" : "OFF") << std::endl;
});
/*
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 ──────────────────────────────────────────────────────

View File

@ -70,10 +70,8 @@ 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

View File

@ -33,10 +33,6 @@ namespace ZL
extern const char* CONST_ZIP_FILE;
extern float x;
extern float y;
extern float z;
bool inverseVertical = true;
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
{
@ -266,32 +262,11 @@ 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() {
@ -334,31 +309,6 @@ 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);
@ -437,7 +387,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));
boxLabels.push_back("Box " + std::to_string(i + 1));
}
if (!cfgLoaded)
@ -557,7 +507,17 @@ namespace ZL
}
drawShipSparkEmitters();
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();
}
renderer.PopMatrix();
@ -631,7 +591,16 @@ namespace ZL
glViewport(0, 0, Environment::width, Environment::height);
prepareSparkEmittersForDraw();
// Готовим данные всех эмиттеров (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);
}
}
CheckGlError();
@ -700,11 +669,6 @@ 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;
}
}
@ -1007,16 +971,6 @@ 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;
@ -1025,7 +979,7 @@ namespace ZL
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;
@ -1477,89 +1431,6 @@ 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;
@ -1651,7 +1522,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;
@ -1674,6 +1545,41 @@ 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) };
@ -1695,8 +1601,6 @@ 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);
@ -1732,9 +1636,7 @@ namespace ZL
shipAlive = false;
gameOver = true;
Environment::shipState.selectedVelocity = 0;
Environment::shipState.velocity = 0.0f;
newShipVelocity = 0;
showExplosion = true;
explosionEmitter.setUseWorldSpace(true);
@ -1744,7 +1646,6 @@ namespace ZL
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
clearPlayerListIfVisible();
menuManager.showGameOver(this->playerScore);
}
else {
@ -1820,7 +1721,6 @@ namespace ZL
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
}
clearPlayerListIfVisible();
menuManager.showGameOver(this->playerScore);
}
}
@ -1836,31 +1736,8 @@ 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() {
@ -1869,7 +1746,6 @@ 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;
@ -1903,7 +1779,6 @@ namespace ZL
gameOver = true;
Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n";
clearPlayerListIfVisible();
menuManager.showConnectionLost();
}
@ -1942,7 +1817,6 @@ namespace ZL
}
}
}
// Обработка событий смерти, присланных сервером
auto deaths = networkClient->getPendingDeaths();
if (!deaths.empty()) {
@ -1967,12 +1841,10 @@ 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) {
@ -1981,7 +1853,6 @@ namespace ZL
}
}
rebuildPlayerListIfVisible();
}
auto respawns = networkClient->getPendingRespawns();
@ -1999,22 +1870,18 @@ 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;
@ -2044,59 +1911,17 @@ 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)
{
if (playerListVisible) return;
Environment::tapDownHold = true;
Environment::tapDownHold = true;
if (inverseVertical)
{
Environment::tapDownStartPos(0) = mx;
Environment::tapDownStartPos(1) = my;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
else
{
Environment::tapDownStartPos(0) = mx;
Environment::tapDownStartPos(1) = -my;
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = -my;
}
}
void Space::handleUp(int mx, int my)
@ -2107,22 +1932,9 @@ namespace ZL
void Space::handleMotion(int mx, int my)
{
if (playerListVisible) return;
if (inverseVertical)
{
if (Environment::tapDownHold) {
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
}
else
{
if (Environment::tapDownHold) {
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = -my;
}
if (Environment::tapDownHold) {
Environment::tapDownCurrentPos(0) = mx;
Environment::tapDownCurrentPos(1) = my;
}
}
@ -2157,151 +1969,4 @@ 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

View File

@ -66,7 +66,6 @@ namespace ZL {
std::unique_ptr<TextRenderer> textRenderer;
std::unordered_map<int, ClientState> remotePlayerStates;
std::unordered_map<int, SparkEmitter> remoteShipSparkEmitters;
float newShipVelocity = 0;
@ -113,7 +112,6 @@ 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;
@ -126,12 +124,8 @@ 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;
@ -150,17 +144,6 @@ 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;

View File

@ -30,8 +30,8 @@ namespace ZL {
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true),
texture(copyFrom.texture),
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
zSpeedRange(copyFrom.zSpeedRange),
@ -40,8 +40,6 @@ 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;
}
@ -200,10 +198,16 @@ 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();

View File

@ -72,65 +72,6 @@ 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();
@ -452,49 +393,6 @@ 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();
@ -638,7 +536,6 @@ namespace ZL {
root->localY // finalLocalY
);
buttons.clear();
textButtons.clear();
sliders.clear();
textViews.clear();
textFields.clear();
@ -650,9 +547,6 @@ namespace ZL {
for (auto& b : buttons) {
b->buildMesh();
}
for (auto& tb : textButtons) {
tb->buildMesh();
}
for (auto& s : sliders) {
s->buildTrackMesh();
s->buildKnobMesh();
@ -800,14 +694,11 @@ 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;
@ -853,9 +744,6 @@ 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);
}
@ -974,13 +862,10 @@ 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 = "";
@ -999,14 +884,6 @@ 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));
@ -1036,13 +913,10 @@ 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;
@ -1057,15 +931,6 @@ 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) {
@ -1091,9 +956,6 @@ 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);
}
@ -1145,12 +1007,6 @@ 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;
@ -1172,20 +1028,12 @@ 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;
@ -1200,12 +1048,6 @@ 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;
@ -1222,12 +1064,6 @@ 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;
@ -1253,10 +1089,6 @@ 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;
@ -1265,10 +1097,6 @@ 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
@ -1318,17 +1146,6 @@ 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);
@ -1363,18 +1180,6 @@ 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;
@ -1407,7 +1212,6 @@ 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()) {
@ -1425,21 +1229,6 @@ 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) {
@ -1447,11 +1236,6 @@ namespace ZL {
b->onClick(b->name);
}
}
for (auto& tb : clickedText) {
if (tb->onClick) {
tb->onClick(tb->name);
}
}
}
void UiManager::onKeyPress(unsigned char key) {
@ -1503,12 +1287,6 @@ 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));
@ -1559,12 +1337,6 @@ 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));
@ -1602,36 +1374,4 @@ 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

View File

@ -125,42 +125,6 @@ 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;
@ -233,7 +197,6 @@ 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;
@ -286,12 +249,12 @@ namespace ZL {
// Returns true if any finger is currently interacting with UI
bool isUiInteraction() const {
return !pressedButtons.empty() || !pressedTextButtons.empty() || !pressedSliders.empty() || focusedTextField != nullptr;
return !pressedButtons.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 || pressedTextButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
return pressedButtons.count(fingerId) > 0 || pressedSliders.count(fingerId) > 0 || focusedTextField != nullptr;
}
void stopAllAnimations() {
@ -305,14 +268,6 @@ 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);
@ -320,11 +275,6 @@ 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);
@ -383,7 +333,6 @@ 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;
@ -394,20 +343,16 @@ 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;

View File

@ -31,11 +31,10 @@ 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 * 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;
const float projectileHitRadius = 1.5f * 5;
const float boxCollisionRadius = 2.0f * 5;
const float shipCollisionRadius = 15.0f * 5;
const float npcCollisionRadius = 5.0f * 5;
uint32_t fnv1a_hash(const std::string& data);

View File

@ -274,24 +274,6 @@ 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));
@ -370,62 +352,11 @@ 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;
@ -441,51 +372,6 @@ 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;

View File

@ -42,8 +42,6 @@ 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;
@ -58,7 +56,6 @@ 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;
@ -82,8 +79,6 @@ namespace ZL {
}
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
void setLocalPlayerState(const ClientState& state) {
localPlayerState = state;

View File

@ -30,17 +30,6 @@ 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;
@ -61,8 +50,6 @@ 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 {}; }

View File

@ -129,45 +129,6 @@ 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) {
@ -408,18 +369,6 @@ 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_);

View File

@ -20,8 +20,6 @@ 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;
@ -52,8 +50,6 @@ 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; }

View File

@ -9,7 +9,7 @@ namespace ZL {
// Формируем URL. Обратите внимание, что в Web часто лучше использовать ws://localhost
//std::string url = "ws://" + host + ":" + std::to_string(port);
std::string url = "wss://api.spacegame.fishrungames.com";
//std::string url = "ws://localhost:8081";
EmscriptenWebSocketCreateAttributes attr = {
url.c_str(),
nullptr,

View File

@ -617,7 +617,6 @@ namespace ZL {
{
throw std::runtime_error("Modelview matrix stack overflow!!!!");
}
SetMatrix();
}
void Renderer::LoadIdentity()
@ -923,6 +922,8 @@ 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()));
}