Merge remote-tracking branch 'origin/main' into spark
This commit is contained in:
commit
6fe10eacfc
@ -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
resources/black.png
(Stored with Git LFS)
Normal file
BIN
resources/black.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/blue_transparent.png
(Stored with Git LFS)
Normal file
BIN
resources/blue_transparent.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_minus_disabled.png
(Stored with Git LFS)
Normal file
BIN
resources/button_minus_disabled.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_players.png
(Stored with Git LFS)
Normal file
BIN
resources/button_players.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_plus_disabled.png
(Stored with Git LFS)
Normal file
BIN
resources/button_plus_disabled.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_take.png
(Stored with Git LFS)
Normal file
BIN
resources/button_take.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_take_disabled.png
(Stored with Git LFS)
Normal file
BIN
resources/button_take_disabled.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/button_take_pressed.png
(Stored with Git LFS)
Normal file
BIN
resources/button_take_pressed.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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,5 +1,5 @@
|
||||
{
|
||||
"emissionRate": 0.4,
|
||||
"emissionRate": 1.2,
|
||||
"maxParticles": 400,
|
||||
"particleSize": 0.3,
|
||||
"biasX": 0.3,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"emissionRate": 0.4,
|
||||
"emissionRate": 1.2,
|
||||
"maxParticles": 400,
|
||||
"particleSize": 0.3,
|
||||
"biasX": 0.3,
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
BIN
resources/connection_failed.png
(Stored with Git LFS)
BIN
resources/connection_failed.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/loading.png
(Stored with Git LFS)
BIN
resources/loading.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/main_menu/about.png
(Stored with Git LFS)
Normal file
BIN
resources/main_menu/about.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/main_menu/about_hover.png
(Stored with Git LFS)
Normal file
BIN
resources/main_menu/about_hover.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/main_menu/about_pressed.png
(Stored with Git LFS)
Normal file
BIN
resources/main_menu/about_pressed.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
resources/main_menu/title.png
(Stored with Git LFS)
BIN
resources/main_menu/title.png
(Stored with Git LFS)
Binary file not shown.
BIN
resources/select_your_ship.png
(Stored with Git LFS)
Normal file
BIN
resources/select_your_ship.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -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";
|
||||
|
||||
@ -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();
|
||||
};
|
||||
|
||||
49
src/Game.cpp
49
src/Game.cpp
@ -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
|
||||
|
||||
@ -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 ──────────────────────────────────────────────────────
|
||||
|
||||
@ -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
|
||||
439
src/Space.cpp
439
src/Space.cpp
@ -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;
|
||||
|
||||
@ -979,7 +1023,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;
|
||||
@ -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,10 +1965,12 @@ 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) {
|
||||
@ -1853,6 +1979,7 @@ namespace ZL
|
||||
|
||||
}
|
||||
}
|
||||
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
src/Space.h
17
src/Space.h
@ -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;
|
||||
|
||||
@ -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(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();
|
||||
|
||||
@ -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
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {}; }
|
||||
|
||||
|
||||
@ -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_);
|
||||
|
||||
@ -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; }
|
||||
|
||||
@ -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()));
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user