adjust label scale: constant far, smooth growth near
This commit is contained in:
commit
c8076b6018
18 files changed
+567
-25
No files matched your search
@@ -0,0 +1,128 @@
|
|||||||
|
{
|
||||||
|
"root": {
|
||||||
|
"type": "FrameLayout",
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 1280,
|
||||||
|
"height": 720,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "LinearLayout",
|
||||||
|
"name": "settingsButtons",
|
||||||
|
"orientation": "vertical",
|
||||||
|
"spacing": 10,
|
||||||
|
"x": 0,
|
||||||
|
"y": 0,
|
||||||
|
"width": 300,
|
||||||
|
"height": 300,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "langButton",
|
||||||
|
"x": 1100,
|
||||||
|
"y": 580,
|
||||||
|
"width": 142,
|
||||||
|
"height": 96,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/lang.png",
|
||||||
|
"hover": "resources/main_menu/lang.png",
|
||||||
|
"pressed": "resources/main_menu/lang.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "titleBtn",
|
||||||
|
"x": 473,
|
||||||
|
"y": 500,
|
||||||
|
"width": 254,
|
||||||
|
"height": 35,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/title.png",
|
||||||
|
"hover": "resources/main_menu/title.png",
|
||||||
|
"pressed": "resources/main_menu/title.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "underlineBtn",
|
||||||
|
"x": 516,
|
||||||
|
"y": 465,
|
||||||
|
"width": 168,
|
||||||
|
"height": 44,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/line.png",
|
||||||
|
"hover": "resources/main_menu/line.png",
|
||||||
|
"pressed": "resources/main_menu/line.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "subtitleBtn",
|
||||||
|
"x": 528,
|
||||||
|
"y": 455,
|
||||||
|
"width": 144,
|
||||||
|
"height": 11,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/subtitle.png",
|
||||||
|
"hover": "resources/main_menu/subtitle.png",
|
||||||
|
"pressed": "resources/main_menu/subtitle.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "singleButton",
|
||||||
|
"x": 409,
|
||||||
|
"y": 360,
|
||||||
|
"width": 382,
|
||||||
|
"height": 56,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/single.png",
|
||||||
|
"hover": "resources/main_menu/single.png",
|
||||||
|
"pressed": "resources/main_menu/single.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "multiplayerButton",
|
||||||
|
"x": 409,
|
||||||
|
"y": 289,
|
||||||
|
"width": 382,
|
||||||
|
"height": 56,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/multi.png",
|
||||||
|
"hover": "resources/main_menu/multi.png",
|
||||||
|
"pressed": "resources/main_menu/multi.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "exitButton",
|
||||||
|
"x": 409,
|
||||||
|
"y": 218,
|
||||||
|
"width": 382,
|
||||||
|
"height": 56,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/exit.png",
|
||||||
|
"hover": "resources/main_menu/exit.png",
|
||||||
|
"pressed": "resources/main_menu/exit.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "Button",
|
||||||
|
"name": "versionLabel",
|
||||||
|
"x": 559.5,
|
||||||
|
"y": 170,
|
||||||
|
"width": 81,
|
||||||
|
"height": 9,
|
||||||
|
"textures": {
|
||||||
|
"normal": "resources/main_menu/version.png",
|
||||||
|
"hover": "resources/main_menu/version.png",
|
||||||
|
"pressed": "resources/main_menu/version.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+187
-18
@@ -11,6 +11,8 @@
|
|||||||
#define _USE_MATH_DEFINES
|
#define _USE_MATH_DEFINES
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include "../src/network/ClientState.h"
|
#include "../src/network/ClientState.h"
|
||||||
|
#include <random>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
// Вспомогательный split
|
// Вспомогательный split
|
||||||
std::vector<std::string> split(const std::string& s, char delimiter) {
|
std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||||
@@ -31,6 +33,16 @@ using tcp = net::ip::tcp;
|
|||||||
|
|
||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
|
struct ServerBox {
|
||||||
|
Eigen::Vector3f position;
|
||||||
|
Eigen::Matrix3f rotation;
|
||||||
|
float collisionRadius = 2.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<ServerBox> g_serverBoxes;
|
||||||
|
std::mutex g_boxes_mutex;
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Session>> g_sessions;
|
std::vector<std::shared_ptr<Session>> g_sessions;
|
||||||
std::mutex g_sessions_mutex;
|
std::mutex g_sessions_mutex;
|
||||||
|
|
||||||
@@ -38,38 +50,84 @@ class Session : public std::enable_shared_from_this<Session> {
|
|||||||
websocket::stream<beast::tcp_stream> ws_;
|
websocket::stream<beast::tcp_stream> ws_;
|
||||||
beast::flat_buffer buffer_;
|
beast::flat_buffer buffer_;
|
||||||
int id_;
|
int id_;
|
||||||
|
bool is_writing_ = false;
|
||||||
|
|
||||||
ClientStateInterval timedClientStates;
|
ClientStateInterval timedClientStates;
|
||||||
|
|
||||||
void process_message(const std::string& msg) {
|
void process_message(const std::string& msg) {
|
||||||
auto now_server = std::chrono::system_clock::now();
|
auto now_server = std::chrono::system_clock::now();
|
||||||
|
|
||||||
auto parts = split(msg, ':');
|
auto parts = split(msg, ':');
|
||||||
|
|
||||||
if (parts.size() < 16)
|
if (parts.empty()) {
|
||||||
{
|
std::cerr << "Empty message received\n";
|
||||||
throw std::runtime_error("Unknown message type received, too small");
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string type = parts[0];
|
||||||
|
|
||||||
|
if (type == "UPD") {
|
||||||
|
if (parts.size() < 16) {
|
||||||
|
std::cerr << "Invalid UPD message: too few parts (" << parts.size() << ")\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
uint64_t clientTimestamp = std::stoull(parts[1]);
|
uint64_t clientTimestamp = std::stoull(parts[1]);
|
||||||
|
|
||||||
ClientState receivedState;
|
ClientState receivedState;
|
||||||
|
|
||||||
receivedState.id = id_;
|
receivedState.id = id_;
|
||||||
|
std::chrono::system_clock::time_point uptime_timepoint{
|
||||||
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) };
|
std::chrono::milliseconds(clientTimestamp)
|
||||||
|
};
|
||||||
receivedState.lastUpdateServerTime = uptime_timepoint;
|
receivedState.lastUpdateServerTime = uptime_timepoint;
|
||||||
|
|
||||||
if (parts[0] == "UPD") {
|
|
||||||
receivedState.handle_full_sync(parts, 2);
|
receivedState.handle_full_sync(parts, 2);
|
||||||
retranslateMessage(msg);
|
retranslateMessage(msg);
|
||||||
|
timedClientStates.add_state(receivedState);
|
||||||
}
|
}
|
||||||
else
|
else if (parts[0] == "FIRE") {
|
||||||
{
|
if (parts.size() < 8) {
|
||||||
throw std::runtime_error("Unknown message type received: " + parts[0]);
|
std::cerr << "Invalid FIRE: too few parts\n";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
timedClientStates.add_state(receivedState);
|
uint64_t clientTime = std::stoull(parts[1]);
|
||||||
|
Eigen::Vector3f pos{
|
||||||
|
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
|
||||||
|
};
|
||||||
|
Eigen::Vector3f dir{
|
||||||
|
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7])
|
||||||
|
};
|
||||||
|
|
||||||
|
int shotCount = 1;
|
||||||
|
if (parts.size() >= 9) {
|
||||||
|
try {
|
||||||
|
shotCount = std::stoi(parts[8]);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
shotCount = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string broadcast = "PROJECTILE:" +
|
||||||
|
std::to_string(id_) + ":" +
|
||||||
|
std::to_string(clientTime) + ":" +
|
||||||
|
std::to_string(pos.x()) + ":" +
|
||||||
|
std::to_string(pos.y()) + ":" +
|
||||||
|
std::to_string(pos.z()) + ":" +
|
||||||
|
std::to_string(dir.x()) + ":" +
|
||||||
|
std::to_string(dir.y()) + ":" +
|
||||||
|
std::to_string(dir.z()) + ":" +
|
||||||
|
std::to_string(shotCount);
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||||
|
std::cout << "Player " << id_ << " fired " << shotCount << " shots → broadcasting\n";
|
||||||
|
|
||||||
|
for (auto& session : g_sessions) {
|
||||||
|
if (session->get_id() != id_) {
|
||||||
|
session->send_message(broadcast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
std::cerr << "Unknown message type: " << type << "\n";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void retranslateMessage(const std::string& msg)
|
void retranslateMessage(const std::string& msg)
|
||||||
@@ -84,6 +142,52 @@ class Session : public std::enable_shared_from_this<Session> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sendBoxesToClient() {
|
||||||
|
std::lock_guard<std::mutex> lock(g_boxes_mutex);
|
||||||
|
|
||||||
|
std::string boxMsg = "BOXES:";
|
||||||
|
for (const auto& box : g_serverBoxes) {
|
||||||
|
Eigen::Quaternionf q(box.rotation);
|
||||||
|
boxMsg += std::to_string(box.position.x()) + ":" +
|
||||||
|
std::to_string(box.position.y()) + ":" +
|
||||||
|
std::to_string(box.position.z()) + ":" +
|
||||||
|
std::to_string(q.w()) + ":" +
|
||||||
|
std::to_string(q.x()) + ":" +
|
||||||
|
std::to_string(q.y()) + ":" +
|
||||||
|
std::to_string(q.z()) + "|";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!boxMsg.empty() && boxMsg.back() == '|') {
|
||||||
|
boxMsg.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
send_message(boxMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send_message(std::string msg) {
|
||||||
|
auto ss = std::make_shared<std::string>(std::move(msg));
|
||||||
|
|
||||||
|
if (is_writing_) {
|
||||||
|
|
||||||
|
ws_.async_write(net::buffer(*ss),
|
||||||
|
[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
|
||||||
|
if (ec) {
|
||||||
|
std::cerr << "Write error: " << ec.message() << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
is_writing_ = true;
|
||||||
|
ws_.async_write(net::buffer(*ss),
|
||||||
|
[self = shared_from_this(), ss](beast::error_code ec, std::size_t) {
|
||||||
|
self->is_writing_ = false;
|
||||||
|
if (ec) {
|
||||||
|
std::cerr << "Write error: " << ec.message() << std::endl;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Session(tcp::socket&& socket, int id)
|
explicit Session(tcp::socket&& socket, int id)
|
||||||
@@ -92,6 +196,16 @@ public:
|
|||||||
|
|
||||||
void init()
|
void init()
|
||||||
{
|
{
|
||||||
|
sendBoxesToClient();
|
||||||
|
|
||||||
|
auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
|
||||||
|
timer->expires_after(std::chrono::milliseconds(100));
|
||||||
|
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
|
||||||
|
if (!ec) {
|
||||||
|
self->send_message("ID:" + std::to_string(self->id_));
|
||||||
|
self->do_read();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void run() {
|
void run() {
|
||||||
@@ -105,15 +219,15 @@ public:
|
|||||||
if (ec) return;
|
if (ec) return;
|
||||||
std::cout << "Client " << self->id_ << " connected\n";
|
std::cout << "Client " << self->id_ << " connected\n";
|
||||||
self->init();
|
self->init();
|
||||||
self->send_message("ID:" + std::to_string(self->id_));
|
// self->send_message("ID:" + std::to_string(self->id_));
|
||||||
self->do_read();
|
// self->do_read();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_message(std::string msg) {
|
/*void send_message(std::string msg) {
|
||||||
auto ss = std::make_shared<std::string>(std::move(msg));
|
auto ss = std::make_shared<std::string>(std::move(msg));
|
||||||
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
|
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
|
||||||
}
|
}*/
|
||||||
|
|
||||||
int get_id() const {
|
int get_id() const {
|
||||||
return id_;
|
return id_;
|
||||||
@@ -151,8 +265,63 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ServerBox> generateServerBoxes(int count) {
|
||||||
|
std::vector<ServerBox> boxes;
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
|
const float MIN_COORD = -100.0f;
|
||||||
|
const float MAX_COORD = 100.0f;
|
||||||
|
const float MIN_DISTANCE = 3.0f;
|
||||||
|
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
|
||||||
|
const int MAX_ATTEMPTS = 1000;
|
||||||
|
|
||||||
|
std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
|
||||||
|
std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
bool accepted = false;
|
||||||
|
int attempts = 0;
|
||||||
|
|
||||||
|
while (!accepted && attempts < MAX_ATTEMPTS) {
|
||||||
|
ServerBox box;
|
||||||
|
box.position = Eigen::Vector3f(
|
||||||
|
(float)posDistrib(gen),
|
||||||
|
(float)posDistrib(gen),
|
||||||
|
(float)posDistrib(gen)
|
||||||
|
);
|
||||||
|
|
||||||
|
accepted = true;
|
||||||
|
for (const auto& existingBox : boxes) {
|
||||||
|
Eigen::Vector3f diff = box.position - existingBox.position;
|
||||||
|
if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
|
||||||
|
accepted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accepted) {
|
||||||
|
float randomAngle = (float)angleDistrib(gen);
|
||||||
|
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
|
||||||
|
box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
|
||||||
|
boxes.push_back(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
try {
|
try {
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(g_boxes_mutex);
|
||||||
|
g_serverBoxes = generateServerBoxes(50);
|
||||||
|
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
|
||||||
|
}
|
||||||
net::io_context ioc;
|
net::io_context ioc;
|
||||||
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
|
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
|
||||||
int next_id = 0;
|
int next_id = 0;
|
||||||
|
|||||||
+122
-4
@@ -10,6 +10,7 @@
|
|||||||
#include <random>
|
#include <random>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
#ifdef __ANDROID__
|
#ifdef __ANDROID__
|
||||||
#include <android/log.h>
|
#include <android/log.h>
|
||||||
#endif
|
#endif
|
||||||
@@ -268,6 +269,10 @@ namespace ZL
|
|||||||
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
|
bool explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
|
||||||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||||
|
|
||||||
|
uiManager.loadFromFile("resources/config/main_menu.json", renderer, CONST_ZIP_FILE);
|
||||||
|
std::function<void()> loadGameplayUI;
|
||||||
|
loadGameplayUI = [this]() {
|
||||||
uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
|
uiManager.loadFromFile("resources/config/ui.json", renderer, CONST_ZIP_FILE);
|
||||||
|
|
||||||
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
|
uiManager.startAnimationOnNode("backgroundNode", "bgScroll");
|
||||||
@@ -296,10 +301,8 @@ namespace ZL
|
|||||||
g_exitBgAnimating = false;
|
g_exitBgAnimating = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set UI button callbacks
|
|
||||||
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
|
uiManager.setButtonCallback("playButton", [this](const std::string& name) {
|
||||||
std::cerr << "Play button pressed: " << name << std::endl;
|
std::cerr << "Play button pressed: " << name << std::endl;
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
|
uiManager.setButtonCallback("settingsButton", [this](const std::string& name) {
|
||||||
@@ -332,11 +335,31 @@ namespace ZL
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
|
uiManager.setButtonCallback("shootButton", [this](const std::string& name) {
|
||||||
uint64_t now = SDL_GetTicks64();
|
uint64_t now = SDL_GetTicks64();
|
||||||
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
if (now - lastProjectileFireTime >= static_cast<uint64_t>(projectileCooldownMs)) {
|
||||||
lastProjectileFireTime = now;
|
lastProjectileFireTime = now;
|
||||||
fireProjectiles();
|
|
||||||
|
this->fireProjectiles();
|
||||||
|
|
||||||
|
Eigen::Vector3f localForward = { 0, 0, -1 };
|
||||||
|
Eigen::Vector3f worldForward = (Environment::shipState.rotation * localForward).normalized();
|
||||||
|
|
||||||
|
Eigen::Vector3f centerPos = Environment::shipState.position +
|
||||||
|
Environment::shipState.rotation * Vector3f{ 0, 0.9f, 5.0f };
|
||||||
|
|
||||||
|
std::string fireMsg = "FIRE:" +
|
||||||
|
std::to_string(now) + ":" +
|
||||||
|
std::to_string(centerPos.x()) + ":" +
|
||||||
|
std::to_string(centerPos.y()) + ":" +
|
||||||
|
std::to_string(centerPos.z()) + ":" +
|
||||||
|
std::to_string(worldForward.x()) + ":" +
|
||||||
|
std::to_string(worldForward.y()) + ":" +
|
||||||
|
std::to_string(worldForward.z()) + ":" +
|
||||||
|
"2";
|
||||||
|
|
||||||
|
networkClient->Send(fireMsg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -346,6 +369,20 @@ namespace ZL
|
|||||||
newShipVelocity = newVel;
|
newShipVelocity = newVel;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
uiManager.setButtonCallback("singleButton", [loadGameplayUI](const std::string& name) {
|
||||||
|
std::cerr << "Single button pressed: " << name << " -> load gameplay UI\n";
|
||||||
|
loadGameplayUI();
|
||||||
|
});
|
||||||
|
uiManager.setButtonCallback("multiplayerButton", [loadGameplayUI](const std::string& name) {
|
||||||
|
std::cerr << "Multiplayer button pressed: " << name << " -> load gameplay UI\n";
|
||||||
|
loadGameplayUI();
|
||||||
|
});
|
||||||
|
uiManager.setButtonCallback("exitButton", [](const std::string& name) {
|
||||||
|
std::cerr << "Exit from main menu pressed: " << name << " -> exiting\n";
|
||||||
|
Environment::exitGameLoop = true;
|
||||||
|
});
|
||||||
|
|
||||||
cubemapTexture = std::make_shared<Texture>(
|
cubemapTexture = std::make_shared<Texture>(
|
||||||
std::array<TextureDataStruct, 6>{
|
std::array<TextureDataStruct, 6>{
|
||||||
@@ -692,6 +729,27 @@ namespace ZL
|
|||||||
|
|
||||||
latestRemotePlayers = networkClient->getRemotePlayers();
|
latestRemotePlayers = networkClient->getRemotePlayers();
|
||||||
|
|
||||||
|
// Если сервер прислал коробки, применяем их однократно вместо локальной генерации
|
||||||
|
if (!serverBoxesApplied && networkClient) {
|
||||||
|
auto sboxes = networkClient->getServerBoxes();
|
||||||
|
if (!sboxes.empty()) {
|
||||||
|
boxCoordsArr.clear();
|
||||||
|
for (auto& b : sboxes) {
|
||||||
|
BoxCoords bc;
|
||||||
|
bc.pos = b.first;
|
||||||
|
bc.m = b.second;
|
||||||
|
boxCoordsArr.push_back(bc);
|
||||||
|
}
|
||||||
|
boxRenderArr.resize(boxCoordsArr.size());
|
||||||
|
for (int i = 0; i < (int)boxCoordsArr.size(); ++i) {
|
||||||
|
boxRenderArr[i].AssignFrom(boxBase);
|
||||||
|
boxRenderArr[i].RefreshVBO();
|
||||||
|
}
|
||||||
|
boxAlive.assign(boxCoordsArr.size(), true);
|
||||||
|
serverBoxesApplied = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Итерируемся по актуальным данным из extrapolateRemotePlayers
|
// Итерируемся по актуальным данным из extrapolateRemotePlayers
|
||||||
for (auto const& [id, remotePlayer] : latestRemotePlayers) {
|
for (auto const& [id, remotePlayer] : latestRemotePlayers) {
|
||||||
|
|
||||||
@@ -770,7 +828,16 @@ namespace ZL
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
float uiX = sx, uiY = sy;
|
float uiX = sx, uiY = sy;
|
||||||
float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);
|
//float scale = std::clamp(BASE_SCALE / (dist * PERSPECTIVE_K + 1.f), MIN_SCALE, MAX_SCALE);
|
||||||
|
float scale;
|
||||||
|
if (dist > CLOSE_DIST) {
|
||||||
|
scale = 0.4f;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
float t = 1.0f - (dist / CLOSE_DIST);
|
||||||
|
scale = 0.4f + (MAX_SCALE - 0.4f) * t;
|
||||||
|
}
|
||||||
|
scale = std::clamp(scale, MIN_SCALE, MAX_SCALE);
|
||||||
|
|
||||||
// Дефолтный лейбл
|
// Дефолтный лейбл
|
||||||
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m";
|
std::string label = "Player (" + std::to_string(st.id) + ") " + std::to_string((int)dist) + "m";
|
||||||
@@ -1255,6 +1322,57 @@ namespace ZL
|
|||||||
render();
|
render();
|
||||||
mainThreadHandler.processMainThreadTasks();
|
mainThreadHandler.processMainThreadTasks();
|
||||||
networkClient->Poll();
|
networkClient->Poll();
|
||||||
|
|
||||||
|
if (networkClient) {
|
||||||
|
auto pending = networkClient->getPendingProjectiles();
|
||||||
|
if (!pending.empty()) {
|
||||||
|
const float projectileSpeed = 60.0f;
|
||||||
|
const float lifeMs = 5000.0f;
|
||||||
|
const float size = 0.5f;
|
||||||
|
|
||||||
|
auto remotePlayersSnapshot = networkClient->getRemotePlayers();
|
||||||
|
for (const auto& pi : pending) {
|
||||||
|
Eigen::Vector3f dir = pi.direction;
|
||||||
|
float len = dir.norm();
|
||||||
|
if (len <= 1e-6f) continue;
|
||||||
|
dir /= len;
|
||||||
|
|
||||||
|
Eigen::Matrix3f shooterRot = Eigen::Matrix3f::Identity();
|
||||||
|
float shooterVel = 0.0f;
|
||||||
|
auto it = remotePlayersSnapshot.find(pi.shooterId);
|
||||||
|
if (it != remotePlayersSnapshot.end()) {
|
||||||
|
std::chrono::system_clock::time_point pktTime{ std::chrono::milliseconds(pi.clientTime) };
|
||||||
|
if (it->second.canFetchClientStateAtTime(pktTime)) {
|
||||||
|
ClientState shooterState = it->second.fetchClientStateAtTime(pktTime);
|
||||||
|
shooterRot = shooterState.rotation;
|
||||||
|
shooterVel = shooterState.velocity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float speedWithOwner = projectileSpeed + shooterVel;
|
||||||
|
Eigen::Vector3f baseVel = dir * speedWithOwner;
|
||||||
|
|
||||||
|
int shotCount = 2;
|
||||||
|
|
||||||
|
std::vector<Eigen::Vector3f> localOffsets = {
|
||||||
|
{-1.5f, 0.9f, 5.0f},
|
||||||
|
{ 1.5f, 0.9f, 5.0f}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < shotCount; ++i) {
|
||||||
|
Eigen::Vector3f rotatedOffset = shooterRot * localOffsets[i];
|
||||||
|
Eigen::Vector3f shotPos = pi.position + rotatedOffset;
|
||||||
|
|
||||||
|
for (auto& p : projectiles) {
|
||||||
|
if (!p->isActive()) {
|
||||||
|
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Game::handleDown(int mx, int my)
|
void Game::handleDown(int mx, int my)
|
||||||
|
|||||||
+3
-1
@@ -121,7 +121,9 @@ namespace ZL {
|
|||||||
static constexpr float BASE_SCALE = 140.f;
|
static constexpr float BASE_SCALE = 140.f;
|
||||||
static constexpr float PERSPECTIVE_K = 0.05f; // Tune
|
static constexpr float PERSPECTIVE_K = 0.05f; // Tune
|
||||||
static constexpr float MIN_SCALE = 0.4f;
|
static constexpr float MIN_SCALE = 0.4f;
|
||||||
static constexpr float MAX_SCALE = 1.5f;
|
static constexpr float MAX_SCALE = 0.8f;
|
||||||
|
static constexpr float CLOSE_DIST = 600.0f;
|
||||||
|
bool serverBoxesApplied = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ constexpr float SHIP_ACCEL = 1.0f * 1000.0f;
|
|||||||
constexpr float ROTATION_SENSITIVITY = 0.002f;
|
constexpr float ROTATION_SENSITIVITY = 0.002f;
|
||||||
|
|
||||||
constexpr long long SERVER_DELAY = 0; //ms
|
constexpr long long SERVER_DELAY = 0; //ms
|
||||||
constexpr long long CLIENT_DELAY = 200; //ms
|
constexpr long long CLIENT_DELAY = 1000; //ms
|
||||||
constexpr long long CUTOFF_TIME = 5000; //ms
|
constexpr long long CUTOFF_TIME = 5000; //ms
|
||||||
|
|
||||||
struct ClientState {
|
struct ClientState {
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ namespace ZL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalClient::Send(const std::string& message) {
|
void LocalClient::Send(const std::string& message) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ProjectileInfo> LocalClient::getPendingProjectiles() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -17,9 +17,14 @@ namespace ZL {
|
|||||||
|
|
||||||
bool IsConnected() const override { return true; }
|
bool IsConnected() const override { return true; }
|
||||||
int GetClientId() const { return 1; }
|
int GetClientId() const { return 1; }
|
||||||
|
std::vector<ProjectileInfo> getPendingProjectiles();
|
||||||
|
|
||||||
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
|
std::unordered_map<int, ClientStateInterval> getRemotePlayers() override {
|
||||||
return std::unordered_map<int, ClientStateInterval>();
|
return std::unordered_map<int, ClientStateInterval>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,13 @@
|
|||||||
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
|
// NetworkInterface.h - »нтерфейс дл¤ разных типов соединений
|
||||||
namespace ZL {
|
namespace ZL {
|
||||||
|
|
||||||
|
struct ProjectileInfo {
|
||||||
|
int shooterId = -1;
|
||||||
|
uint64_t clientTime = 0;
|
||||||
|
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||||
|
Eigen::Vector3f direction = Eigen::Vector3f::Zero();
|
||||||
|
};
|
||||||
|
|
||||||
class INetworkClient {
|
class INetworkClient {
|
||||||
public:
|
public:
|
||||||
virtual ~INetworkClient() = default;
|
virtual ~INetworkClient() = default;
|
||||||
@@ -15,5 +22,9 @@ namespace ZL {
|
|||||||
virtual bool IsConnected() const = 0;
|
virtual bool IsConnected() const = 0;
|
||||||
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
|
virtual void Poll() = 0; // ƒл¤ обработки вход¤щих пакетов
|
||||||
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
|
virtual std::unordered_map<int, ClientStateInterval> getRemotePlayers() = 0;
|
||||||
|
|
||||||
|
virtual std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() = 0;
|
||||||
|
|
||||||
|
virtual std::vector<ProjectileInfo> getPendingProjectiles() = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,13 @@ namespace ZL {
|
|||||||
messageQueue.push(msg);
|
messageQueue.push(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ProjectileInfo> WebSocketClient::getPendingProjectiles() {
|
||||||
|
std::lock_guard<std::mutex> lock(projMutex_);
|
||||||
|
auto copy = pendingProjectiles_;
|
||||||
|
pendingProjectiles_.clear();
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
void WebSocketClient::Poll() {
|
void WebSocketClient::Poll() {
|
||||||
std::lock_guard<std::mutex> lock(queueMutex);
|
std::lock_guard<std::mutex> lock(queueMutex);
|
||||||
|
|
||||||
@@ -83,6 +90,68 @@ namespace ZL {
|
|||||||
std::string msg = messageQueue.front();
|
std::string msg = messageQueue.front();
|
||||||
messageQueue.pop();
|
messageQueue.pop();
|
||||||
|
|
||||||
|
// Обработка списка коробок от сервера
|
||||||
|
if (msg.rfind("BOXES:", 0) == 0) {
|
||||||
|
std::string payload = msg.substr(6); // после "BOXES:"
|
||||||
|
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> parsedBoxes;
|
||||||
|
if (!payload.empty()) {
|
||||||
|
auto items = split(payload, '|');
|
||||||
|
for (auto& item : items) {
|
||||||
|
if (item.empty()) continue;
|
||||||
|
auto parts = split(item, ':');
|
||||||
|
if (parts.size() < 7) continue;
|
||||||
|
try {
|
||||||
|
float px = std::stof(parts[0]);
|
||||||
|
float py = std::stof(parts[1]);
|
||||||
|
float pz = std::stof(parts[2]);
|
||||||
|
Eigen::Quaternionf q(
|
||||||
|
std::stof(parts[3]),
|
||||||
|
std::stof(parts[4]),
|
||||||
|
std::stof(parts[5]),
|
||||||
|
std::stof(parts[6])
|
||||||
|
);
|
||||||
|
Eigen::Matrix3f rot = q.toRotationMatrix();
|
||||||
|
parsedBoxes.emplace_back(Eigen::Vector3f{ px, py, pz }, rot);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
// пропускаем некорректную запись
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> bLock(boxesMutex);
|
||||||
|
serverBoxes_ = std::move(parsedBoxes);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.rfind("PROJECTILE:", 0) == 0) {
|
||||||
|
auto parts = split(msg, ':');
|
||||||
|
if (parts.size() >= 9) {
|
||||||
|
try {
|
||||||
|
ProjectileInfo pi;
|
||||||
|
pi.shooterId = std::stoi(parts[1]);
|
||||||
|
pi.clientTime = std::stoull(parts[2]);
|
||||||
|
pi.position = Eigen::Vector3f(
|
||||||
|
std::stof(parts[3]),
|
||||||
|
std::stof(parts[4]),
|
||||||
|
std::stof(parts[5])
|
||||||
|
);
|
||||||
|
pi.direction = Eigen::Vector3f(
|
||||||
|
std::stof(parts[6]),
|
||||||
|
std::stof(parts[7]),
|
||||||
|
std::stof(parts[8])
|
||||||
|
);
|
||||||
|
std::lock_guard<std::mutex> pl(projMutex_);
|
||||||
|
pendingProjectiles_.push_back(pi);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg.rfind("EVENT:", 0) == 0) {
|
if (msg.rfind("EVENT:", 0) == 0) {
|
||||||
auto parts = split(msg, ':');
|
auto parts = split(msg, ':');
|
||||||
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
|
if (parts.size() < 5) continue; // EVENT:ID:TYPE:TIME:DATA...
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ namespace ZL {
|
|||||||
std::unordered_map<int, ClientStateInterval> remotePlayers;
|
std::unordered_map<int, ClientStateInterval> remotePlayers;
|
||||||
std::mutex playersMutex;
|
std::mutex playersMutex;
|
||||||
|
|
||||||
|
// Серверные коробки
|
||||||
|
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> serverBoxes_;
|
||||||
|
std::mutex boxesMutex;
|
||||||
|
|
||||||
|
std::vector<ProjectileInfo> pendingProjectiles_;
|
||||||
|
std::mutex projMutex_;
|
||||||
|
|
||||||
void startAsyncRead();
|
void startAsyncRead();
|
||||||
void processIncomingMessage(const std::string& msg);
|
void processIncomingMessage(const std::string& msg);
|
||||||
|
|
||||||
@@ -54,6 +61,13 @@ namespace ZL {
|
|||||||
std::lock_guard<std::mutex> lock(playersMutex);
|
std::lock_guard<std::mutex> lock(playersMutex);
|
||||||
return remotePlayers;
|
return remotePlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<Eigen::Vector3f, Eigen::Matrix3f>> getServerBoxes() override {
|
||||||
|
std::lock_guard<std::mutex> lock(boxesMutex);
|
||||||
|
return serverBoxes_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ProjectileInfo> getPendingProjectiles() override;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Reference in new issue
Block a user