Merge remote-tracking branch 'origin/main' into spark
This commit is contained in:
commit
6fe10eacfc
38 files changed
+1231
-147
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
body, html {
|
body, html {
|
||||||
margin: 0; padding: 0; width: 100%; height: 100%;
|
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||||
overflow: hidden; background-color: #000;
|
overflow: hidden; background-color: #000;
|
||||||
position: fixed; /* Предотвращает pull-to-refresh на Android */
|
position: fixed;
|
||||||
}
|
}
|
||||||
#canvas {
|
#canvas {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -17,6 +17,18 @@
|
|||||||
width: 100vw; height: 100vh;
|
width: 100vw; height: 100vh;
|
||||||
border: none;
|
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%); }
|
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
|
||||||
|
|
||||||
/* Nick modal */
|
/* Nick modal */
|
||||||
@@ -60,6 +72,7 @@
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<button id="fs-button">Fullscreen</button>
|
||||||
<div id="status">Downloading...</div>
|
<div id="status">Downloading...</div>
|
||||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
<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() {
|
window.addEventListener("orientationchange", function() {
|
||||||
// Chrome на Android обновляет innerWidth/Height не мгновенно.
|
|
||||||
// Ждем завершения анимации поворота.
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// В Emscripten это вызовет ваш onWindowResized в C++
|
|
||||||
window.dispatchEvent(new Event('resize'));
|
window.dispatchEvent(new Event('resize'));
|
||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
|
|||||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -13,7 +13,7 @@
|
|||||||
{
|
{
|
||||||
"type": "StaticImage",
|
"type": "StaticImage",
|
||||||
"name": "titleBtn",
|
"name": "titleBtn",
|
||||||
"width": 254,
|
"width": 434,
|
||||||
"height": 35,
|
"height": 35,
|
||||||
"texture": "resources/main_menu/title.png"
|
"texture": "resources/main_menu/title.png"
|
||||||
},
|
},
|
||||||
@@ -25,15 +25,11 @@
|
|||||||
"texture": "resources/main_menu/line.png"
|
"texture": "resources/main_menu/line.png"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Button",
|
"type": "StaticImage",
|
||||||
"name": "subtitleBtn",
|
"name": "subtitleBtn",
|
||||||
"width": 144,
|
"width": 144,
|
||||||
"height": 11,
|
"height": 11,
|
||||||
"textures": {
|
"texture": "resources/main_menu/subtitle.png"
|
||||||
"normal": "resources/main_menu/subtitle.png",
|
|
||||||
"hover": "resources/main_menu/subtitle.png",
|
|
||||||
"pressed": "resources/main_menu/subtitle.png"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "Button",
|
"type": "Button",
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
"width": "match_parent",
|
"width": "match_parent",
|
||||||
"height": "match_parent",
|
"height": "match_parent",
|
||||||
"children": [
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "StaticImage",
|
||||||
|
"name": "titleBtn",
|
||||||
|
"width": 266,
|
||||||
|
"height": 66,
|
||||||
|
"texture": "resources/select_your_ship.png"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "LinearLayout",
|
"type": "LinearLayout",
|
||||||
"orientation": "horizontal",
|
"orientation": "horizontal",
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emissionRate": 0.4,
|
"emissionRate": 1.2,
|
||||||
"maxParticles": 400,
|
"maxParticles": 400,
|
||||||
"particleSize": 0.3,
|
"particleSize": 0.3,
|
||||||
"biasX": 0.3,
|
"biasX": 0.3,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"emissionRate": 0.4,
|
"emissionRate": 1.2,
|
||||||
"maxParticles": 400,
|
"maxParticles": 400,
|
||||||
"particleSize": 0.3,
|
"particleSize": 0.3,
|
||||||
"biasX": 0.3,
|
"biasX": 0.3,
|
||||||
|
|||||||
+51
-11
@@ -8,18 +8,39 @@
|
|||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"type": "TextView",
|
"type": "TextView",
|
||||||
"name": "velocityText",
|
"name": "gameScoreText",
|
||||||
"x": 10,
|
"x": 0,
|
||||||
"y": 10,
|
"y": 30,
|
||||||
"width": 200,
|
"width": 200,
|
||||||
"height": 40,
|
"height": 60,
|
||||||
"horizontal_gravity": "left",
|
"horizontal_gravity": "left",
|
||||||
"vertical_gravity": "top",
|
"vertical_gravity": "top",
|
||||||
"text": "Velocity: 0",
|
"text": "Score: 0",
|
||||||
"fontSize": 24,
|
"fontSize": 36,
|
||||||
"color": [1.0, 1.0, 1.0, 1.0],
|
"color": [
|
||||||
|
0,
|
||||||
|
217,
|
||||||
|
255,
|
||||||
|
1
|
||||||
|
],
|
||||||
"centered": false
|
"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",
|
"type": "Button",
|
||||||
"name": "shootButton",
|
"name": "shootButton",
|
||||||
@@ -64,8 +85,9 @@
|
|||||||
"vertical_gravity": "bottom",
|
"vertical_gravity": "bottom",
|
||||||
"textures": {
|
"textures": {
|
||||||
"normal": "resources/button_minus.png",
|
"normal": "resources/button_minus.png",
|
||||||
"hover": "resources/button_minus_pressed.png",
|
"hover": "resources/button_minus.png",
|
||||||
"pressed": "resources/button_minus_pressed.png"
|
"pressed": "resources/button_minus_pressed.png",
|
||||||
|
"disabled" : "resources/button_minus_disabled.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -80,8 +102,26 @@
|
|||||||
"vertical_gravity": "bottom",
|
"vertical_gravity": "bottom",
|
||||||
"textures": {
|
"textures": {
|
||||||
"normal": "resources/button_plus.png",
|
"normal": "resources/button_plus.png",
|
||||||
"hover": "resources/button_plus_pressed.png",
|
"hover": "resources/button_plus.png",
|
||||||
"pressed": "resources/button_plus_pressed.png"
|
"pressed": "resources/button_plus_pressed.png",
|
||||||
|
"disabled" : "resources/button_plus_disabled.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "takeButton",
|
||||||
|
"x": -20,
|
||||||
|
"y": 320,
|
||||||
|
"width": 150,
|
||||||
|
"height": 150,
|
||||||
|
"border" : 20,
|
||||||
|
"horizontal_gravity": "right",
|
||||||
|
"vertical_gravity": "bottom",
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/button_take.png",
|
||||||
|
"hover": "resources/button_take.png",
|
||||||
|
"pressed": "resources/button_take_pressed.png",
|
||||||
|
"disabled" : "resources/button_take_disabled.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+125
-2
@@ -31,8 +31,8 @@ bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Session::force_disconnect() {
|
void Session::force_disconnect() {
|
||||||
ws_.async_close(websocket::close_code::normal,
|
beast::error_code ec;
|
||||||
[self = shared_from_this()](beast::error_code) {});
|
ws_.next_layer().socket().close(ec);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Session::get_id() const { return id_; }
|
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";
|
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") {
|
else if (parts[0] == "FIRE") {
|
||||||
if (parts.size() < 10) return;
|
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)
|
Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId)
|
||||||
{
|
{
|
||||||
static thread_local std::mt19937 rng{ std::random_device{}() };
|
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 ---
|
// --- Tick: box-projectile collisions ---
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
std::lock_guard<std::mutex> bm(g_boxes_mutex);
|
||||||
@@ -632,6 +714,7 @@ void Server::update_world() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
|
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
|
||||||
|
if (g_serverBoxes[boxIdx].destroyed) continue;
|
||||||
g_serverBoxes[boxIdx].destroyed = true;
|
g_serverBoxes[boxIdx].destroyed = true;
|
||||||
|
|
||||||
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
|
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);
|
g_boxDestructions.push_back(destruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boxesToRespawn.push_back(static_cast<int>(boxIdx));
|
||||||
|
|
||||||
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
|
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
|
||||||
<< g_projectiles[projIdx].shooterId << std::endl;
|
<< g_projectiles[projIdx].shooterId << std::endl;
|
||||||
}
|
}
|
||||||
@@ -690,6 +775,8 @@ void Server::update_world() {
|
|||||||
g_boxDestructions.push_back(destruction);
|
g_boxDestructions.push_back(destruction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boxesToRespawn.push_back(static_cast<int>(bi));
|
||||||
|
|
||||||
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
|
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
|
||||||
<< session->get_id() << std::endl;
|
<< session->get_id() << std::endl;
|
||||||
break;
|
break;
|
||||||
@@ -732,6 +819,41 @@ void Server::update_world() {
|
|||||||
g_boxDestructions.clear();
|
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 ---
|
// --- Schedule next tick in 50ms ---
|
||||||
timer.expires_after(std::chrono::milliseconds(50));
|
timer.expires_after(std::chrono::milliseconds(50));
|
||||||
timer.async_wait([this](const boost::system::error_code& ec) {
|
timer.async_wait([this](const boost::system::error_code& ec) {
|
||||||
@@ -820,6 +942,7 @@ int main() {
|
|||||||
|
|
||||||
Server server(acceptor, ioc);
|
Server server(acceptor, ioc);
|
||||||
|
|
||||||
|
server.init();
|
||||||
server.accept();
|
server.accept();
|
||||||
|
|
||||||
std::cout << "Server started on port 8081...\n";
|
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 createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
|
||||||
void update_world();
|
void update_world();
|
||||||
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
|
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
|
||||||
|
// Caller must hold g_boxes_mutex
|
||||||
|
Eigen::Vector3f PickSafeBoxPos(int skipIdx);
|
||||||
void init();
|
void init();
|
||||||
void accept();
|
void accept();
|
||||||
};
|
};
|
||||||
+25
-24
@@ -41,6 +41,10 @@ namespace ZL
|
|||||||
const char* CONST_ZIP_FILE = "";
|
const char* CONST_ZIP_FILE = "";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
float x = 0;
|
||||||
|
float y = 0;
|
||||||
|
float z = 0;
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
Game* Game::s_instance = nullptr;
|
Game* Game::s_instance = nullptr;
|
||||||
|
|
||||||
@@ -106,7 +110,20 @@ namespace ZL
|
|||||||
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
loadingTexture = std::make_unique<Texture>(CreateTextureDataFromPng("resources/loading.png", CONST_ZIP_FILE));
|
||||||
#endif
|
#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();
|
loadingMesh.RefreshVBO();
|
||||||
|
|
||||||
#ifdef EMSCRIPTEN
|
#ifdef EMSCRIPTEN
|
||||||
@@ -264,12 +281,12 @@ namespace ZL
|
|||||||
renderer.shaderManager.PushShader(defaultShaderName);
|
renderer.shaderManager.PushShader(defaultShaderName);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
|
|
||||||
//float width = Environment::projectionWidth;
|
float width = Environment::projectionWidth;
|
||||||
//float height = Environment::projectionHeight;
|
float height = Environment::projectionHeight;
|
||||||
|
|
||||||
renderer.PushProjectionMatrix(
|
renderer.PushProjectionMatrix(
|
||||||
0, 1,
|
-width * 0.5f, width*0.5f,
|
||||||
0, 1,
|
-height * 0.5f, height * 0.5f,
|
||||||
-10, 10);
|
-10, 10);
|
||||||
|
|
||||||
renderer.PushMatrix();
|
renderer.PushMatrix();
|
||||||
@@ -331,7 +348,7 @@ namespace ZL
|
|||||||
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
//SDL_GL_MakeCurrent(ZL::Environment::window, glContext);
|
||||||
ZL::CheckGlError();
|
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);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
//processTickCount();
|
//processTickCount();
|
||||||
@@ -420,22 +437,6 @@ namespace ZL
|
|||||||
handleMotion(ZL::UiManager::MOUSE_FINGER_ID, mx, my);
|
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) {
|
if (event.type == SDL_MOUSEWHEEL) {
|
||||||
static const float zoomstep = 2.0f;
|
static const float zoomstep = 2.0f;
|
||||||
if (event.wheel.y > 0) {
|
if (event.wheel.y > 0) {
|
||||||
@@ -464,8 +465,8 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_KEYUP) {
|
if (event.type == SDL_KEYUP) {
|
||||||
if (event.key.keysym.sym == SDLK_a) {
|
if (event.key.keysym.sym == SDLK_r) {
|
||||||
//Environment::shipState.position = { 9466.15820, 1046.00159, 18531.2090 };
|
std::cout << "Camera position: x=" << x << " y=" << y << " z=" << z << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+36
-4
@@ -188,13 +188,16 @@ namespace ZL {
|
|||||||
state = GameState::Gameplay;
|
state = GameState::Gameplay;
|
||||||
uiManager.replaceRoot(gameplayRoot);
|
uiManager.replaceRoot(gameplayRoot);
|
||||||
|
|
||||||
|
uiManager.findButton("minusButton")->state = ButtonState::Disabled;
|
||||||
|
if (auto btn = uiManager.findButton("takeButton")) btn->state = ButtonState::Disabled;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
auto velocityTv = uiManager.findTextView("velocityText");
|
auto velocityTv = uiManager.findTextView("velocityText");
|
||||||
if (velocityTv) {
|
if (velocityTv) {
|
||||||
velocityTv->rect.x = 10.0f;
|
velocityTv->rect.x = 10.0f;
|
||||||
velocityTv->rect.y = static_cast<float>(Environment::height) - velocityTv->rect.h - 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&) {
|
uiManager.setButtonPressCallback("shootButton", [this](const std::string&) {
|
||||||
if (onFirePressed) onFirePressed();
|
if (onFirePressed) onFirePressed();
|
||||||
@@ -205,20 +208,49 @@ namespace ZL {
|
|||||||
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
|
uiManager.setButtonPressCallback("plusButton", [this](const std::string&) {
|
||||||
int newVel = Environment::shipState.selectedVelocity + 1;
|
int newVel = Environment::shipState.selectedVelocity + 1;
|
||||||
if (newVel > 4) newVel = 4;
|
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);
|
if (onVelocityChanged) onVelocityChanged(newVel);
|
||||||
});
|
});
|
||||||
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
|
uiManager.setButtonPressCallback("minusButton", [this](const std::string&) {
|
||||||
int newVel = Environment::shipState.selectedVelocity - 1;
|
int newVel = Environment::shipState.selectedVelocity - 1;
|
||||||
if (newVel < 0) newVel = 0;
|
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);
|
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) {
|
uiManager.setSliderCallback("velocitySlider", [this](const std::string&, float value) {
|
||||||
int newVel = static_cast<int>(roundf(value * 10));
|
int newVel = static_cast<int>(roundf(value * 10));
|
||||||
if (newVel > 2) newVel = 2;
|
if (newVel > 2) newVel = 2;
|
||||||
if (newVel != Environment::shipState.selectedVelocity) {
|
if (newVel != Environment::shipState.selectedVelocity) {
|
||||||
if (onVelocityChanged) onVelocityChanged(newVel);
|
if (onVelocityChanged) onVelocityChanged(newVel);
|
||||||
}
|
}
|
||||||
});
|
});*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── State: GameOver ──────────────────────────────────────────────────────
|
// ── State: GameOver ──────────────────────────────────────────────────────
|
||||||
|
|||||||
@@ -70,8 +70,10 @@ namespace ZL {
|
|||||||
std::function<void()> onRestartPressed;
|
std::function<void()> onRestartPressed;
|
||||||
std::function<void(float)> onVelocityChanged;
|
std::function<void(float)> onVelocityChanged;
|
||||||
std::function<void()> onFirePressed;
|
std::function<void()> onFirePressed;
|
||||||
|
std::function<void()> onTakeButtonPressed;
|
||||||
std::function<void(const std::string&, int)> onSingleplayerPressed;
|
std::function<void(const std::string&, int)> onSingleplayerPressed;
|
||||||
std::function<void(const std::string&, int)> onMultiplayerPressed;
|
std::function<void(const std::string&, int)> onMultiplayerPressed;
|
||||||
|
std::function<void()> onShowPlayersPressed;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZL
|
} // namespace ZL
|
||||||
+377
-66
@@ -33,6 +33,8 @@ namespace ZL
|
|||||||
extern const char* CONST_ZIP_FILE;
|
extern const char* CONST_ZIP_FILE;
|
||||||
|
|
||||||
extern float x;
|
extern float x;
|
||||||
|
extern float y;
|
||||||
|
extern float z;
|
||||||
|
|
||||||
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
|
Eigen::Quaternionf generateRandomQuaternion(std::mt19937& gen)
|
||||||
{
|
{
|
||||||
@@ -262,11 +264,32 @@ namespace ZL
|
|||||||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||||
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
|
Environment::shipState.position = Vector3f{ 0, 0, 45000.f };
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
|
Environment::shipState.selectedVelocity = 0;
|
||||||
|
newShipVelocity = 0;
|
||||||
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
|
Environment::shipState.rotation = Eigen::Matrix3f::Identity();
|
||||||
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
|
Environment::inverseShipMatrix = Eigen::Matrix3f::Identity();
|
||||||
Environment::zoom = DEFAULT_ZOOM;
|
Environment::zoom = DEFAULT_ZOOM;
|
||||||
Environment::tapDownHold = false;
|
Environment::tapDownHold = false;
|
||||||
playerScore = 0;
|
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() {
|
void Space::setup() {
|
||||||
@@ -309,6 +332,31 @@ namespace ZL
|
|||||||
firePressed = true;
|
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 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);
|
bool cfgLoaded2 = sparkEmitterCargo.loadFromJsonFile("resources/config/spark_config_cargo.json", renderer, CONST_ZIP_FILE);
|
||||||
sparkEmitter.setIsActive(false);
|
sparkEmitter.setIsActive(false);
|
||||||
@@ -387,7 +435,7 @@ namespace ZL
|
|||||||
boxLabels.clear();
|
boxLabels.clear();
|
||||||
boxLabels.reserve(boxCoordsArr.size());
|
boxLabels.reserve(boxCoordsArr.size());
|
||||||
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
|
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)
|
if (!cfgLoaded)
|
||||||
@@ -507,17 +555,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
renderer.PushMatrix();
|
drawShipSparkEmitters();
|
||||||
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();
|
renderer.PopMatrix();
|
||||||
@@ -591,16 +629,7 @@ namespace ZL
|
|||||||
|
|
||||||
glViewport(0, 0, Environment::width, Environment::height);
|
glViewport(0, 0, Environment::width, Environment::height);
|
||||||
|
|
||||||
// Готовим данные всех эмиттеров (CPU + VBO upload) до начала отрисовки,
|
prepareSparkEmittersForDraw();
|
||||||
// чтобы 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();
|
CheckGlError();
|
||||||
|
|
||||||
@@ -669,6 +698,11 @@ namespace ZL
|
|||||||
for (size_t i = 0; i < n; ++i) {
|
for (size_t i = 0; i < n; ++i) {
|
||||||
if (destroyedFlags[i]) boxAlive[i] = false; // destroyed => не рисуем
|
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;
|
serverBoxesApplied = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -971,6 +1005,16 @@ namespace ZL
|
|||||||
|
|
||||||
int Space::pickTargetId() const
|
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;
|
int bestId = -1;
|
||||||
float bestDistSq = 1e30f;
|
float bestDistSq = 1e30f;
|
||||||
|
|
||||||
@@ -978,8 +1022,8 @@ namespace ZL
|
|||||||
if (deadRemotePlayers.count(id)) continue;
|
if (deadRemotePlayers.count(id)) continue;
|
||||||
|
|
||||||
float d2 = (Environment::shipState.position - st.position).squaredNorm();
|
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) {
|
if (d2 < bestDistSq) {
|
||||||
bestDistSq = d2;
|
bestDistSq = d2;
|
||||||
@@ -1431,6 +1475,89 @@ namespace ZL
|
|||||||
targetWasVisible = false;
|
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) {
|
void Space::processTickCount(int64_t newTickCount, int64_t delta) {
|
||||||
|
|
||||||
auto now_ms = newTickCount;
|
auto now_ms = newTickCount;
|
||||||
@@ -1522,7 +1649,7 @@ namespace ZL
|
|||||||
|
|
||||||
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
|
std::string msg = "UPD:" + std::to_string(now_ms) + ":" + Environment::shipState.formPingMessageContent();
|
||||||
networkClient->Send(msg);
|
networkClient->Send(msg);
|
||||||
std::cout << "Sending: " << msg << std::endl;
|
//std::cout << "Sending: " << msg << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
long long leftoverDelta = delta;
|
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();
|
auto latestRemotePlayers = networkClient->getRemotePlayers();
|
||||||
|
|
||||||
std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) };
|
std::chrono::system_clock::time_point nowRoundedWithDelay{ std::chrono::milliseconds(newTickCount - CLIENT_DELAY) };
|
||||||
@@ -1601,6 +1693,8 @@ namespace ZL
|
|||||||
remotePlayerStates[id] = playerState;
|
remotePlayerStates[id] = playerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSparkEmitters(static_cast<float>(delta));
|
||||||
|
|
||||||
for (auto& p : projectiles) {
|
for (auto& p : projectiles) {
|
||||||
if (p && p->isActive()) {
|
if (p && p->isActive()) {
|
||||||
p->update(static_cast<float>(delta), renderer);
|
p->update(static_cast<float>(delta), renderer);
|
||||||
@@ -1636,7 +1730,9 @@ namespace ZL
|
|||||||
|
|
||||||
shipAlive = false;
|
shipAlive = false;
|
||||||
gameOver = true;
|
gameOver = true;
|
||||||
|
Environment::shipState.selectedVelocity = 0;
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
|
newShipVelocity = 0;
|
||||||
showExplosion = true;
|
showExplosion = true;
|
||||||
|
|
||||||
explosionEmitter.setUseWorldSpace(true);
|
explosionEmitter.setUseWorldSpace(true);
|
||||||
@@ -1646,6 +1742,7 @@ namespace ZL
|
|||||||
|
|
||||||
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
|
std::cerr << "GAME OVER: collision with planet (moved back and exploded)\n";
|
||||||
|
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1721,6 +1818,7 @@ namespace ZL
|
|||||||
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
|
planetObject.planetStones.statuses[collidedTriIdx] = ChunkStatus::Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1736,8 +1834,31 @@ namespace ZL
|
|||||||
std::string velocityStr = "Velocity: " + std::to_string(static_cast<int>(Environment::shipState.velocity));
|
std::string velocityStr = "Velocity: " + std::to_string(static_cast<int>(Environment::shipState.velocity));
|
||||||
menuManager.uiManager.setText("velocityText", velocityStr);
|
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() {
|
void Space::fireProjectiles() {
|
||||||
@@ -1746,6 +1867,7 @@ namespace ZL
|
|||||||
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
Vector3f{ 1.5f, 0.9f - 6.f, 5.0f }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const float projectileSpeed = PROJECTILE_VELOCITY;
|
const float projectileSpeed = PROJECTILE_VELOCITY;
|
||||||
const float lifeMs = PROJECTILE_LIFE;
|
const float lifeMs = PROJECTILE_LIFE;
|
||||||
const float size = 0.5f;
|
const float size = 0.5f;
|
||||||
@@ -1779,6 +1901,7 @@ namespace ZL
|
|||||||
gameOver = true;
|
gameOver = true;
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
std::cout << "Client: Lost connection to server\n";
|
std::cout << "Client: Lost connection to server\n";
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showConnectionLost();
|
menuManager.showConnectionLost();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1817,6 +1940,7 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка событий смерти, присланных сервером
|
// Обработка событий смерти, присланных сервером
|
||||||
auto deaths = networkClient->getPendingDeaths();
|
auto deaths = networkClient->getPendingDeaths();
|
||||||
if (!deaths.empty()) {
|
if (!deaths.empty()) {
|
||||||
@@ -1841,18 +1965,21 @@ namespace ZL
|
|||||||
shipAlive = false;
|
shipAlive = false;
|
||||||
gameOver = true;
|
gameOver = true;
|
||||||
Environment::shipState.velocity = 0.0f;
|
Environment::shipState.velocity = 0.0f;
|
||||||
|
clearPlayerListIfVisible();
|
||||||
menuManager.showGameOver(this->playerScore);
|
menuManager.showGameOver(this->playerScore);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
deadRemotePlayers.insert(d.targetId);
|
deadRemotePlayers.insert(d.targetId);
|
||||||
|
if (d.targetId == manualTrackedTargetId) manualTrackedTargetId = -1;
|
||||||
std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
|
std::cout << "Marked remote player " << d.targetId << " as dead" << std::endl;
|
||||||
}
|
}
|
||||||
if (d.killerId == localId) {
|
if (d.killerId == localId) {
|
||||||
this->playerScore += 1;
|
this->playerScore += 1;
|
||||||
std::cout << "Client: Increased local score to " << this->playerScore << std::endl;
|
std::cout << "Client: Increased local score to " << this->playerScore << std::endl;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto respawns = networkClient->getPendingRespawns();
|
auto respawns = networkClient->getPendingRespawns();
|
||||||
@@ -1870,18 +1997,22 @@ namespace ZL
|
|||||||
std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
|
std::cout << "Client: Remote player " << respawnId << " respawned, removed from dead list" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
|
|
||||||
auto disconnects = networkClient->getPendingDisconnects();
|
auto disconnects = networkClient->getPendingDisconnects();
|
||||||
for (int pid : disconnects) {
|
for (int pid : disconnects) {
|
||||||
remotePlayerStates.erase(pid);
|
remotePlayerStates.erase(pid);
|
||||||
deadRemotePlayers.erase(pid);
|
deadRemotePlayers.erase(pid);
|
||||||
|
remoteShipSparkEmitters.erase(pid);
|
||||||
if (trackedTargetId == pid) {
|
if (trackedTargetId == pid) {
|
||||||
trackedTargetId = -1;
|
trackedTargetId = -1;
|
||||||
targetAcquireAnim = 0.f;
|
targetAcquireAnim = 0.f;
|
||||||
}
|
}
|
||||||
|
if (pid == manualTrackedTargetId) manualTrackedTargetId = -1;
|
||||||
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
|
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rebuildPlayerListIfVisible();
|
||||||
auto boxDestructions = networkClient->getPendingBoxDestructions();
|
auto boxDestructions = networkClient->getPendingBoxDestructions();
|
||||||
if (!boxDestructions.empty()) {
|
if (!boxDestructions.empty()) {
|
||||||
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
|
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)
|
void Space::handleDown(int mx, int my)
|
||||||
{
|
{
|
||||||
Environment::tapDownHold = true;
|
if (playerListVisible) return;
|
||||||
|
|
||||||
Environment::tapDownStartPos(0) = mx;
|
Environment::tapDownHold = true;
|
||||||
Environment::tapDownStartPos(1) = my;
|
|
||||||
|
|
||||||
Environment::tapDownCurrentPos(0) = mx;
|
Environment::tapDownStartPos(0) = mx;
|
||||||
Environment::tapDownCurrentPos(1) = my;
|
Environment::tapDownStartPos(1) = my;
|
||||||
|
|
||||||
|
Environment::tapDownCurrentPos(0) = mx;
|
||||||
|
Environment::tapDownCurrentPos(1) = my;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Space::handleUp(int mx, int my)
|
void Space::handleUp(int mx, int my)
|
||||||
@@ -1932,6 +2094,8 @@ namespace ZL
|
|||||||
|
|
||||||
void Space::handleMotion(int mx, int my)
|
void Space::handleMotion(int mx, int my)
|
||||||
{
|
{
|
||||||
|
if (playerListVisible) return;
|
||||||
|
|
||||||
if (Environment::tapDownHold) {
|
if (Environment::tapDownHold) {
|
||||||
Environment::tapDownCurrentPos(0) = mx;
|
Environment::tapDownCurrentPos(0) = mx;
|
||||||
Environment::tapDownCurrentPos(1) = my;
|
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
|
} // namespace ZL
|
||||||
+17
@@ -66,6 +66,7 @@ namespace ZL {
|
|||||||
std::unique_ptr<TextRenderer> textRenderer;
|
std::unique_ptr<TextRenderer> textRenderer;
|
||||||
|
|
||||||
std::unordered_map<int, ClientState> remotePlayerStates;
|
std::unordered_map<int, ClientState> remotePlayerStates;
|
||||||
|
std::unordered_map<int, SparkEmitter> remoteShipSparkEmitters;
|
||||||
|
|
||||||
float newShipVelocity = 0;
|
float newShipVelocity = 0;
|
||||||
|
|
||||||
@@ -112,6 +113,7 @@ namespace ZL {
|
|||||||
const uint64_t explosionDurationMs = 500;
|
const uint64_t explosionDurationMs = 500;
|
||||||
|
|
||||||
bool serverBoxesApplied = false;
|
bool serverBoxesApplied = false;
|
||||||
|
bool nearPickupBox = false;
|
||||||
|
|
||||||
static constexpr float MAX_DIST_SQ = 10000.f * 10000.f;
|
static constexpr float MAX_DIST_SQ = 10000.f * 10000.f;
|
||||||
static constexpr float FADE_START = 6000.f;
|
static constexpr float FADE_START = 6000.f;
|
||||||
@@ -124,8 +126,12 @@ namespace ZL {
|
|||||||
|
|
||||||
std::unordered_set<int> deadRemotePlayers;
|
std::unordered_set<int> deadRemotePlayers;
|
||||||
int playerScore = 0;
|
int playerScore = 0;
|
||||||
|
int prevPlayerScore = 0;
|
||||||
bool wasConnectedToServer = false;
|
bool wasConnectedToServer = false;
|
||||||
|
|
||||||
|
bool playerListVisible = false;
|
||||||
|
int manualTrackedTargetId = -1;
|
||||||
|
|
||||||
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
||||||
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
||||||
|
|
||||||
@@ -144,6 +150,17 @@ namespace ZL {
|
|||||||
void resetPlayerState();
|
void resetPlayerState();
|
||||||
void clearTextRendererCache();
|
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
|
// Crosshair HUD
|
||||||
struct CrosshairConfig {
|
struct CrosshairConfig {
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|||||||
+6
-10
@@ -26,12 +26,12 @@ namespace ZL {
|
|||||||
sparkQuad.data = VertexDataStruct();
|
sparkQuad.data = VertexDataStruct();
|
||||||
}
|
}
|
||||||
|
|
||||||
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
|
SparkEmitter::SparkEmitter(const SparkEmitter& copyFrom)
|
||||||
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
|
: particles(copyFrom.particles), emissionPoints(copyFrom.emissionPoints),
|
||||||
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
|
lastEmissionTime(copyFrom.lastEmissionTime), emissionRate(copyFrom.emissionRate),
|
||||||
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
|
isActive(copyFrom.isActive), drawPositions(copyFrom.drawPositions),
|
||||||
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(copyFrom.drawDataDirty),
|
drawTexCoords(copyFrom.drawTexCoords), drawDataDirty(true),
|
||||||
sparkQuad(copyFrom.sparkQuad), texture(copyFrom.texture),
|
texture(copyFrom.texture),
|
||||||
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
|
maxParticles(copyFrom.maxParticles), particleSize(copyFrom.particleSize),
|
||||||
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
|
biasX(copyFrom.biasX), speedRange(copyFrom.speedRange),
|
||||||
zSpeedRange(copyFrom.zSpeedRange),
|
zSpeedRange(copyFrom.zSpeedRange),
|
||||||
@@ -40,6 +40,8 @@ namespace ZL {
|
|||||||
shaderProgramName(copyFrom.shaderProgramName),
|
shaderProgramName(copyFrom.shaderProgramName),
|
||||||
configured(copyFrom.configured), useWorldSpace(copyFrom.useWorldSpace)
|
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!");
|
throw std::runtime_error("Failed to load spark emitter config file 2!");
|
||||||
}
|
}
|
||||||
|
|
||||||
//prepareDrawData(withRotation);
|
|
||||||
|
|
||||||
if (drawPositions.empty()) {
|
if (drawPositions.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
sparkQuad.data.PositionData = drawPositions;
|
|
||||||
sparkQuad.data.TexCoordData = drawTexCoords;
|
|
||||||
sparkQuad.RefreshVBO();
|
|
||||||
*/
|
|
||||||
renderer.shaderManager.PushShader(shaderProgramName);
|
renderer.shaderManager.PushShader(shaderProgramName);
|
||||||
renderer.RenderUniform1i(textureUniformName, 0);
|
renderer.RenderUniform1i(textureUniformName, 0);
|
||||||
renderer.SetMatrix();
|
renderer.SetMatrix();
|
||||||
|
|||||||
+262
-2
@@ -72,6 +72,65 @@ namespace ZL {
|
|||||||
renderer.PopMatrix();
|
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() {
|
void UiSlider::buildTrackMesh() {
|
||||||
trackMesh.data.PositionData.clear();
|
trackMesh.data.PositionData.clear();
|
||||||
trackMesh.data.TexCoordData.clear();
|
trackMesh.data.TexCoordData.clear();
|
||||||
@@ -393,6 +452,49 @@ namespace ZL {
|
|||||||
node->textField = tf;
|
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()) {
|
if (j.contains("animations") && j["animations"].is_object()) {
|
||||||
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
|
for (auto it = j["animations"].begin(); it != j["animations"].end(); ++it) {
|
||||||
std::string animName = it.key();
|
std::string animName = it.key();
|
||||||
@@ -536,6 +638,7 @@ namespace ZL {
|
|||||||
root->localY // finalLocalY
|
root->localY // finalLocalY
|
||||||
);
|
);
|
||||||
buttons.clear();
|
buttons.clear();
|
||||||
|
textButtons.clear();
|
||||||
sliders.clear();
|
sliders.clear();
|
||||||
textViews.clear();
|
textViews.clear();
|
||||||
textFields.clear();
|
textFields.clear();
|
||||||
@@ -547,6 +650,9 @@ namespace ZL {
|
|||||||
for (auto& b : buttons) {
|
for (auto& b : buttons) {
|
||||||
b->buildMesh();
|
b->buildMesh();
|
||||||
}
|
}
|
||||||
|
for (auto& tb : textButtons) {
|
||||||
|
tb->buildMesh();
|
||||||
|
}
|
||||||
for (auto& s : sliders) {
|
for (auto& s : sliders) {
|
||||||
s->buildTrackMesh();
|
s->buildTrackMesh();
|
||||||
s->buildKnobMesh();
|
s->buildKnobMesh();
|
||||||
@@ -694,11 +800,14 @@ namespace ZL {
|
|||||||
// 1. Обновляем кнопку
|
// 1. Обновляем кнопку
|
||||||
if (node->button) {
|
if (node->button) {
|
||||||
node->button->rect = node->screenRect;
|
node->button->rect = node->screenRect;
|
||||||
// Если у кнопки есть анимационные смещения, они учитываются внутри buildMesh
|
|
||||||
// или при рендеринге через Uniform-переменные матрицы модели.
|
|
||||||
node->button->buildMesh();
|
node->button->buildMesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->rect = node->screenRect;
|
||||||
|
node->textButton->buildMesh();
|
||||||
|
}
|
||||||
|
|
||||||
// 2. Обновляем слайдер
|
// 2. Обновляем слайдер
|
||||||
if (node->slider) {
|
if (node->slider) {
|
||||||
node->slider->rect = node->screenRect;
|
node->slider->rect = node->screenRect;
|
||||||
@@ -744,6 +853,9 @@ namespace ZL {
|
|||||||
if (node->button) {
|
if (node->button) {
|
||||||
buttons.push_back(node->button);
|
buttons.push_back(node->button);
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
textButtons.push_back(node->textButton);
|
||||||
|
}
|
||||||
if (node->slider) {
|
if (node->slider) {
|
||||||
sliders.push_back(node->slider);
|
sliders.push_back(node->slider);
|
||||||
}
|
}
|
||||||
@@ -862,10 +974,13 @@ namespace ZL {
|
|||||||
MenuState prev;
|
MenuState prev;
|
||||||
prev.root = root;
|
prev.root = root;
|
||||||
prev.buttons = buttons;
|
prev.buttons = buttons;
|
||||||
|
prev.textButtons = textButtons;
|
||||||
prev.sliders = sliders;
|
prev.sliders = sliders;
|
||||||
|
prev.textViews = textViews;
|
||||||
prev.textFields = textFields;
|
prev.textFields = textFields;
|
||||||
prev.staticImages = staticImages;
|
prev.staticImages = staticImages;
|
||||||
prev.pressedButtons = pressedButtons;
|
prev.pressedButtons = pressedButtons;
|
||||||
|
prev.pressedTextButtons = pressedTextButtons;
|
||||||
prev.pressedSliders = pressedSliders;
|
prev.pressedSliders = pressedSliders;
|
||||||
prev.focusedTextField = focusedTextField;
|
prev.focusedTextField = focusedTextField;
|
||||||
prev.path = "";
|
prev.path = "";
|
||||||
@@ -884,6 +999,14 @@ namespace ZL {
|
|||||||
b->animScaleY = 1.0f;
|
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);
|
replaceRoot(newRoot);
|
||||||
menuStack.push_back(std::move(prev));
|
menuStack.push_back(std::move(prev));
|
||||||
@@ -913,10 +1036,13 @@ namespace ZL {
|
|||||||
|
|
||||||
root = s.root;
|
root = s.root;
|
||||||
buttons = s.buttons;
|
buttons = s.buttons;
|
||||||
|
textButtons = s.textButtons;
|
||||||
sliders = s.sliders;
|
sliders = s.sliders;
|
||||||
|
textViews = s.textViews;
|
||||||
textFields = s.textFields;
|
textFields = s.textFields;
|
||||||
staticImages = s.staticImages;
|
staticImages = s.staticImages;
|
||||||
pressedButtons = s.pressedButtons;
|
pressedButtons = s.pressedButtons;
|
||||||
|
pressedTextButtons = s.pressedTextButtons;
|
||||||
pressedSliders = s.pressedSliders;
|
pressedSliders = s.pressedSliders;
|
||||||
focusedTextField = s.focusedTextField;
|
focusedTextField = s.focusedTextField;
|
||||||
|
|
||||||
@@ -931,6 +1057,15 @@ namespace ZL {
|
|||||||
b->buildMesh();
|
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) {
|
for (auto& sl : sliders) {
|
||||||
if (sl) {
|
if (sl) {
|
||||||
@@ -956,6 +1091,9 @@ namespace ZL {
|
|||||||
for (const auto& b : buttons) {
|
for (const auto& b : buttons) {
|
||||||
b->draw(renderer);
|
b->draw(renderer);
|
||||||
}
|
}
|
||||||
|
for (const auto& tb : textButtons) {
|
||||||
|
tb->draw(renderer);
|
||||||
|
}
|
||||||
for (const auto& s : sliders) {
|
for (const auto& s : sliders) {
|
||||||
s->draw(renderer);
|
s->draw(renderer);
|
||||||
}
|
}
|
||||||
@@ -1007,6 +1145,12 @@ namespace ZL {
|
|||||||
node->button->animScaleX = act.origScaleX;
|
node->button->animScaleX = act.origScaleX;
|
||||||
node->button->animScaleY = act.origScaleY;
|
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.stepIndex = 0;
|
||||||
act.elapsedMs = 0.0f;
|
act.elapsedMs = 0.0f;
|
||||||
act.stepStarted = false;
|
act.stepStarted = false;
|
||||||
@@ -1028,12 +1172,20 @@ namespace ZL {
|
|||||||
node->button->animOffsetX = step.toX;
|
node->button->animOffsetX = step.toX;
|
||||||
node->button->animOffsetY = step.toY;
|
node->button->animOffsetY = step.toY;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animOffsetX = step.toX;
|
||||||
|
node->textButton->animOffsetY = step.toY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "scale") {
|
else if (step.type == "scale") {
|
||||||
if (node->button) {
|
if (node->button) {
|
||||||
node->button->animScaleX = step.toX;
|
node->button->animScaleX = step.toX;
|
||||||
node->button->animScaleY = step.toY;
|
node->button->animScaleY = step.toY;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animScaleX = step.toX;
|
||||||
|
node->textButton->animScaleY = step.toY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
act.stepIndex++;
|
act.stepIndex++;
|
||||||
act.elapsedMs = 0.0f;
|
act.elapsedMs = 0.0f;
|
||||||
@@ -1048,6 +1200,12 @@ namespace ZL {
|
|||||||
act.origScaleX = node->button->animScaleX;
|
act.origScaleX = node->button->animScaleX;
|
||||||
act.origScaleY = node->button->animScaleY;
|
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 {
|
else {
|
||||||
act.origOffsetX = act.origOffsetY = 0.0f;
|
act.origOffsetX = act.origOffsetY = 0.0f;
|
||||||
act.origScaleX = act.origScaleY = 1.0f;
|
act.origScaleX = act.origScaleY = 1.0f;
|
||||||
@@ -1064,6 +1222,12 @@ namespace ZL {
|
|||||||
act.startScaleX = node->button->animScaleX;
|
act.startScaleX = node->button->animScaleX;
|
||||||
act.startScaleY = node->button->animScaleY;
|
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 {
|
else {
|
||||||
act.startOffsetX = act.startOffsetY = 0.0f;
|
act.startOffsetX = act.startOffsetY = 0.0f;
|
||||||
act.startScaleX = act.startScaleY = 1.0f;
|
act.startScaleX = act.startScaleY = 1.0f;
|
||||||
@@ -1089,6 +1253,10 @@ namespace ZL {
|
|||||||
node->button->animOffsetX = nx;
|
node->button->animOffsetX = nx;
|
||||||
node->button->animOffsetY = ny;
|
node->button->animOffsetY = ny;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animOffsetX = nx;
|
||||||
|
node->textButton->animOffsetY = ny;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "scale") {
|
else if (step.type == "scale") {
|
||||||
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
|
float sx = act.startScaleX + (act.endScaleX - act.startScaleX) * te;
|
||||||
@@ -1097,6 +1265,10 @@ namespace ZL {
|
|||||||
node->button->animScaleX = sx;
|
node->button->animScaleX = sx;
|
||||||
node->button->animScaleY = sy;
|
node->button->animScaleY = sy;
|
||||||
}
|
}
|
||||||
|
if (node->textButton) {
|
||||||
|
node->textButton->animScaleX = sx;
|
||||||
|
node->textButton->animScaleY = sy;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (step.type == "wait") {
|
else if (step.type == "wait") {
|
||||||
//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);
|
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) {
|
for (auto& s : sliders) {
|
||||||
if (s->rect.contains((float)x, (float)y)) {
|
if (s->rect.contains((float)x, (float)y)) {
|
||||||
pressedSliders[fingerId] = s;
|
pressedSliders[fingerId] = s;
|
||||||
@@ -1212,6 +1407,7 @@ namespace ZL {
|
|||||||
|
|
||||||
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
|
void UiManager::onTouchUp(int64_t fingerId, int x, int y) {
|
||||||
std::vector<std::shared_ptr<UiButton>> clicked;
|
std::vector<std::shared_ptr<UiButton>> clicked;
|
||||||
|
std::vector<std::shared_ptr<UiTextButton>> clickedText;
|
||||||
|
|
||||||
auto btnIt = pressedButtons.find(fingerId);
|
auto btnIt = pressedButtons.find(fingerId);
|
||||||
if (btnIt != pressedButtons.end()) {
|
if (btnIt != pressedButtons.end()) {
|
||||||
@@ -1229,6 +1425,21 @@ namespace ZL {
|
|||||||
pressedButtons.erase(btnIt);
|
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);
|
pressedSliders.erase(fingerId);
|
||||||
|
|
||||||
for (auto& b : clicked) {
|
for (auto& b : clicked) {
|
||||||
@@ -1236,6 +1447,11 @@ namespace ZL {
|
|||||||
b->onClick(b->name);
|
b->onClick(b->name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (auto& tb : clickedText) {
|
||||||
|
if (tb->onClick) {
|
||||||
|
tb->onClick(tb->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UiManager::onKeyPress(unsigned char key) {
|
void UiManager::onKeyPress(unsigned char key) {
|
||||||
@@ -1287,6 +1503,12 @@ namespace ZL {
|
|||||||
aa.origScaleX = node->button->animScaleX;
|
aa.origScaleX = node->button->animScaleX;
|
||||||
aa.origScaleY = node->button->animScaleY;
|
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 });
|
auto cbIt = animCallbacks.find({ nodeName, animName });
|
||||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||||
nodeActiveAnims[node].push_back(std::move(aa));
|
nodeActiveAnims[node].push_back(std::move(aa));
|
||||||
@@ -1337,6 +1559,12 @@ namespace ZL {
|
|||||||
aa.origScaleX = n->button->animScaleX;
|
aa.origScaleX = n->button->animScaleX;
|
||||||
aa.origScaleY = n->button->animScaleY;
|
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 });
|
auto cbIt = animCallbacks.find({ n->name, animName });
|
||||||
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
if (cbIt != animCallbacks.end()) aa.onComplete = cbIt->second;
|
||||||
nodeActiveAnims[n].push_back(std::move(aa));
|
nodeActiveAnims[n].push_back(std::move(aa));
|
||||||
@@ -1374,4 +1602,36 @@ namespace ZL {
|
|||||||
return true;
|
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
|
} // namespace ZL
|
||||||
+57
-2
@@ -125,6 +125,42 @@ namespace ZL {
|
|||||||
void draw(Renderer& renderer) const;
|
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 {
|
struct UiTextView {
|
||||||
std::string name;
|
std::string name;
|
||||||
UiRect rect;
|
UiRect rect;
|
||||||
@@ -197,6 +233,7 @@ namespace ZL {
|
|||||||
|
|
||||||
// Компоненты (только один из них обычно активен для ноды)
|
// Компоненты (только один из них обычно активен для ноды)
|
||||||
std::shared_ptr<UiButton> button;
|
std::shared_ptr<UiButton> button;
|
||||||
|
std::shared_ptr<UiTextButton> textButton;
|
||||||
std::shared_ptr<UiSlider> slider;
|
std::shared_ptr<UiSlider> slider;
|
||||||
std::shared_ptr<UiTextView> textView;
|
std::shared_ptr<UiTextView> textView;
|
||||||
std::shared_ptr<UiTextField> textField;
|
std::shared_ptr<UiTextField> textField;
|
||||||
@@ -249,12 +286,12 @@ namespace ZL {
|
|||||||
|
|
||||||
// Returns true if any finger is currently interacting with UI
|
// Returns true if any finger is currently interacting with UI
|
||||||
bool isUiInteraction() const {
|
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
|
// Returns true if this specific finger is currently interacting with UI
|
||||||
bool isUiInteractionForFinger(int64_t fingerId) const {
|
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() {
|
void stopAllAnimations() {
|
||||||
@@ -268,6 +305,14 @@ namespace ZL {
|
|||||||
b->animScaleY = 1.0f;
|
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);
|
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 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);
|
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,
|
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);
|
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::shared_ptr<UiNode> root;
|
||||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
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<UiSlider>> sliders;
|
||||||
std::vector<std::shared_ptr<UiTextView>> textViews;
|
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||||
@@ -343,16 +394,20 @@ namespace ZL {
|
|||||||
|
|
||||||
// Per-finger tracking for multi-touch support
|
// Per-finger tracking for multi-touch support
|
||||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
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::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||||
std::shared_ptr<UiTextField> focusedTextField;
|
std::shared_ptr<UiTextField> focusedTextField;
|
||||||
|
|
||||||
struct MenuState {
|
struct MenuState {
|
||||||
std::shared_ptr<UiNode> root;
|
std::shared_ptr<UiNode> root;
|
||||||
std::vector<std::shared_ptr<UiButton>> buttons;
|
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<UiSlider>> sliders;
|
||||||
|
std::vector<std::shared_ptr<UiTextView>> textViews;
|
||||||
std::vector<std::shared_ptr<UiTextField>> textFields;
|
std::vector<std::shared_ptr<UiTextField>> textFields;
|
||||||
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
std::vector<std::shared_ptr<UiStaticImage>> staticImages;
|
||||||
std::map<int64_t, std::shared_ptr<UiButton>> pressedButtons;
|
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::map<int64_t, std::shared_ptr<UiSlider>> pressedSliders;
|
||||||
std::shared_ptr<UiTextField> focusedTextField;
|
std::shared_ptr<UiTextField> focusedTextField;
|
||||||
std::string path;
|
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_VELOCITY = 600.f;
|
||||||
constexpr float PROJECTILE_LIFE = 15000.f; //ms
|
constexpr float PROJECTILE_LIFE = 15000.f; //ms
|
||||||
|
|
||||||
const float projectileHitRadius = 1.5f * 5;
|
const float projectileHitRadius = 1.5f * 4;
|
||||||
const float boxCollisionRadius = 2.0f * 5;
|
const float boxCollisionRadius = 2.0f * 4;
|
||||||
const float shipCollisionRadius = 15.0f * 5;
|
const float shipCollisionRadius = 15.0f * 3;
|
||||||
const float npcCollisionRadius = 5.0f * 5;
|
const float BOX_PICKUP_RADIUS = shipCollisionRadius * 3;
|
||||||
|
const float npcCollisionRadius = 5.0f * 3;
|
||||||
|
|
||||||
uint32_t fnv1a_hash(const std::string& data);
|
uint32_t fnv1a_hash(const std::string& data);
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,24 @@ namespace ZL {
|
|||||||
std::cout << "LocalClient: Box " << boxIdx << " destroyed by projectile from player "
|
std::cout << "LocalClient: Box " << boxIdx << " destroyed by projectile from player "
|
||||||
<< projectiles[projIdx].shooterId << std::endl;
|
<< 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)
|
if (std::find(projIndicesToRemove.begin(), projIndicesToRemove.end(), (int)projIdx)
|
||||||
== projIndicesToRemove.end()) {
|
== projIndicesToRemove.end()) {
|
||||||
projIndicesToRemove.push_back(static_cast<int>(projIdx));
|
projIndicesToRemove.push_back(static_cast<int>(projIdx));
|
||||||
@@ -352,11 +370,62 @@ namespace ZL {
|
|||||||
|
|
||||||
std::cout << "LocalClient: Box " << bi << " destroyed by ship collision with player "
|
std::cout << "LocalClient: Box " << bi << " destroyed by ship collision with player "
|
||||||
<< GetClientId() << std::endl;
|
<< 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) {
|
void LocalClient::Send(const std::string& message) {
|
||||||
auto parts = [](const std::string& s, char delimiter) {
|
auto parts = [](const std::string& s, char delimiter) {
|
||||||
std::vector<std::string> tokens;
|
std::vector<std::string> tokens;
|
||||||
@@ -372,6 +441,51 @@ namespace ZL {
|
|||||||
|
|
||||||
std::string type = parts[0];
|
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 (type == "FIRE") {
|
||||||
if (parts.size() < 10) return;
|
if (parts.size() < 10) return;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ namespace ZL {
|
|||||||
std::vector<ProjectileInfo> pendingProjectiles;
|
std::vector<ProjectileInfo> pendingProjectiles;
|
||||||
std::vector<DeathInfo> pendingDeaths;
|
std::vector<DeathInfo> pendingDeaths;
|
||||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions;
|
std::vector<BoxDestroyedInfo> pendingBoxDestructions;
|
||||||
|
std::vector<BoxPickedUpInfo> pendingBoxPickups;
|
||||||
|
std::vector<BoxRespawnInfo> pendingBoxRespawns;
|
||||||
std::vector<int> pendingRespawns;
|
std::vector<int> pendingRespawns;
|
||||||
|
|
||||||
uint64_t lastUpdateMs = 0;
|
uint64_t lastUpdateMs = 0;
|
||||||
@@ -56,6 +58,7 @@ namespace ZL {
|
|||||||
void initializeNPCs();
|
void initializeNPCs();
|
||||||
void updateNPCs();
|
void updateNPCs();
|
||||||
Eigen::Vector3f generateRandomPosition();
|
Eigen::Vector3f generateRandomPosition();
|
||||||
|
Eigen::Vector3f generateRespawnBoxPos(int skipIdx);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Connect(const std::string& host, uint16_t port) override;
|
void Connect(const std::string& host, uint16_t port) override;
|
||||||
@@ -79,6 +82,8 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||||
|
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
|
||||||
|
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
|
||||||
|
|
||||||
void setLocalPlayerState(const ClientState& state) {
|
void setLocalPlayerState(const ClientState& state) {
|
||||||
localPlayerState = state;
|
localPlayerState = state;
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ namespace ZL {
|
|||||||
int destroyedBy = -1;
|
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 {
|
class INetworkClient {
|
||||||
public:
|
public:
|
||||||
virtual ~INetworkClient() = default;
|
virtual ~INetworkClient() = default;
|
||||||
@@ -50,6 +61,8 @@ namespace ZL {
|
|||||||
virtual std::vector<int> getPendingRespawns() = 0;
|
virtual std::vector<int> getPendingRespawns() = 0;
|
||||||
virtual int GetClientId() const { return -1; }
|
virtual int GetClientId() const { return -1; }
|
||||||
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
|
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 int64_t getTimeOffset() const { return 0; }
|
||||||
virtual std::vector<int> getPendingDisconnects() { return {}; }
|
virtual std::vector<int> getPendingDisconnects() { return {}; }
|
||||||
|
|
||||||
|
|||||||
@@ -129,6 +129,45 @@ namespace ZL {
|
|||||||
return;
|
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) {
|
if (msg.rfind("BOX_DESTROYED:", 0) == 0) {
|
||||||
//auto parts = split(msg, ':');
|
//auto parts = split(msg, ':');
|
||||||
if (parts.size() >= 7) {
|
if (parts.size() >= 7) {
|
||||||
@@ -369,6 +408,18 @@ namespace ZL {
|
|||||||
return copy;
|
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> WebSocketClientBase::getPendingDisconnects() {
|
||||||
std::vector<int> copy;
|
std::vector<int> copy;
|
||||||
copy.swap(pendingDisconnects_);
|
copy.swap(pendingDisconnects_);
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ namespace ZL {
|
|||||||
std::vector<DeathInfo> pendingDeaths_;
|
std::vector<DeathInfo> pendingDeaths_;
|
||||||
std::vector<int> pendingRespawns_;
|
std::vector<int> pendingRespawns_;
|
||||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
||||||
|
std::vector<BoxPickedUpInfo> pendingBoxPickups_;
|
||||||
|
std::vector<BoxRespawnInfo> pendingBoxRespawns_;
|
||||||
std::vector<int> pendingDisconnects_;
|
std::vector<int> pendingDisconnects_;
|
||||||
int clientId = -1;
|
int clientId = -1;
|
||||||
int64_t timeOffset = 0;
|
int64_t timeOffset = 0;
|
||||||
@@ -50,6 +52,8 @@ namespace ZL {
|
|||||||
std::vector<DeathInfo> getPendingDeaths() override;
|
std::vector<DeathInfo> getPendingDeaths() override;
|
||||||
std::vector<int> getPendingRespawns() override;
|
std::vector<int> getPendingRespawns() override;
|
||||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||||
|
std::vector<BoxPickedUpInfo> getPendingBoxPickups() override;
|
||||||
|
std::vector<BoxRespawnInfo> getPendingBoxRespawns() override;
|
||||||
std::vector<int> getPendingDisconnects() override;
|
std::vector<int> getPendingDisconnects() override;
|
||||||
std::vector<ClientState> getPendingSpawns();
|
std::vector<ClientState> getPendingSpawns();
|
||||||
int getClientId() const { return clientId; }
|
int getClientId() const { return clientId; }
|
||||||
|
|||||||
@@ -617,6 +617,7 @@ namespace ZL {
|
|||||||
{
|
{
|
||||||
throw std::runtime_error("Modelview matrix stack overflow!!!!");
|
throw std::runtime_error("Modelview matrix stack overflow!!!!");
|
||||||
}
|
}
|
||||||
|
SetMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::LoadIdentity()
|
void Renderer::LoadIdentity()
|
||||||
@@ -922,8 +923,6 @@ namespace ZL {
|
|||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
|
glBindBuffer(GL_ARRAY_BUFFER, VertexRenderStruct.positionVBO->getBuffer());
|
||||||
VertexAttribPointer3fv(vPosition, 0, NULL);
|
VertexAttribPointer3fv(vPosition, 0, NULL);
|
||||||
//EnableVertexAttribArray(vPosition);
|
|
||||||
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
|
glDrawArrays(GL_TRIANGLES, 0, static_cast<GLsizei>(VertexRenderStruct.data.PositionData.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user