merge
This commit is contained in:
commit
0d2f705d42
18 files changed
+602
-23
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.
+191
-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,88 @@ 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() < 10) {
|
||||||
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::Quaternion dir{
|
||||||
|
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])
|
||||||
|
};
|
||||||
|
|
||||||
|
float velocity = std::stof(parts[9]);
|
||||||
|
|
||||||
|
int shotCount = 2;
|
||||||
|
if (parts.size() >= 11) {
|
||||||
|
try {
|
||||||
|
shotCount = std::stoi(parts[10]);
|
||||||
|
}
|
||||||
|
catch (...) {
|
||||||
|
shotCount = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.w()) + ":" +
|
||||||
|
std::to_string(dir.x()) + ":" +
|
||||||
|
std::to_string(dir.y()) + ":" +
|
||||||
|
std::to_string(dir.z()) + ":" +
|
||||||
|
std::to_string(velocity) + ":" +
|
||||||
|
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 +146,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 +200,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 +223,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 +269,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 = 1000;
|
int next_id = 1000;
|
||||||
|
|||||||
+149
-3
@@ -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
|
||||||
@@ -164,6 +165,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");
|
||||||
@@ -192,10 +197,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) {
|
||||||
@@ -228,11 +231,38 @@ 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();
|
const float projectileSpeed = 60.0f;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
Eigen::Quaternionf q(Environment::shipState.rotation);
|
||||||
|
float speedToSend = projectileSpeed + Environment::shipState.velocity;
|
||||||
|
int shotCount = 2;
|
||||||
|
|
||||||
|
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(q.w()) + ":" +
|
||||||
|
std::to_string(q.x()) + ":" +
|
||||||
|
std::to_string(q.y()) + ":" +
|
||||||
|
std::to_string(q.z()) + ":" +
|
||||||
|
std::to_string(speedToSend) + ":" +
|
||||||
|
std::to_string(shotCount);
|
||||||
|
|
||||||
|
networkClient->Send(fireMsg);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -242,6 +272,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>{
|
||||||
@@ -580,6 +624,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) {
|
||||||
|
|
||||||
@@ -1095,6 +1160,87 @@ 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;
|
||||||
|
for (const auto& pi : pending) {
|
||||||
|
const std::vector<Vector3f> localOffsets = {
|
||||||
|
Vector3f{ -1.5f, 0.9f, 5.0f },
|
||||||
|
Vector3f{ 1.5f, 0.9f, 5.0f }
|
||||||
|
//Vector3f{},
|
||||||
|
//Vector3f{}
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector3f localForward = { 0, 0, -1 };
|
||||||
|
Vector3f worldForward = pi.rotation * localForward;
|
||||||
|
|
||||||
|
float len = worldForward.norm();
|
||||||
|
if (len <= 1e-6f) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
worldForward /= len;
|
||||||
|
|
||||||
|
Vector3f baseVel = worldForward * pi.velocity;
|
||||||
|
|
||||||
|
for (const auto& off : localOffsets) {
|
||||||
|
Vector3f shotPos = pi.position + (pi.rotation * off);
|
||||||
|
|
||||||
|
for (auto& p : projectiles) {
|
||||||
|
if (!p->isActive()) {
|
||||||
|
p->init(shotPos, baseVel, lifeMs, size, projectileTexture, renderer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
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)
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ namespace ZL {
|
|||||||
uint64_t lastExplosionTime = 0;
|
uint64_t lastExplosionTime = 0;
|
||||||
const uint64_t explosionDurationMs = 500;
|
const uint64_t explosionDurationMs = 500;
|
||||||
|
|
||||||
|
bool serverBoxesApplied = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ constexpr float PLANET_MAX_ANGULAR_VELOCITY = 10.f;
|
|||||||
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
|
constexpr float PITCH_LIMIT = static_cast<float>(M_PI) / 9.f;//18.0f;
|
||||||
|
|
||||||
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,15 @@
|
|||||||
// 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();
|
||||||
|
Eigen::Matrix3f rotation = Eigen::Matrix3f::Identity();
|
||||||
|
float velocity = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class INetworkClient {
|
class INetworkClient {
|
||||||
public:
|
public:
|
||||||
virtual ~INetworkClient() = default;
|
virtual ~INetworkClient() = default;
|
||||||
@@ -15,5 +24,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,72 @@ 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() >= 10) {
|
||||||
|
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])
|
||||||
|
);
|
||||||
|
Eigen::Quaternionf q(
|
||||||
|
std::stof(parts[6]),
|
||||||
|
std::stof(parts[7]),
|
||||||
|
std::stof(parts[8]),
|
||||||
|
std::stof(parts[9])
|
||||||
|
);
|
||||||
|
pi.rotation = q.toRotationMatrix();
|
||||||
|
|
||||||
|
pi.velocity = std::stof(parts[10]);
|
||||||
|
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