Merge branch 'main' into linux

This commit is contained in:
Vladislav Khorev committed 2026-03-06 17:06:42 +00:00
commit d151d4bfaf
64 files changed
+959 -652

No files matched your search

+6 -1
View File
@@ -1,9 +1,14 @@
# how to build # how to build
If emsdk is not installed, you need to clone it from here: https://github.com/emscripten-core/emsdk
Activate the environment: and install:
``` ```
C:\Work\Projects\emsdk\emsdk.bat install latest C:\Work\Projects\emsdk\emsdk.bat install latest
```
Then activate the environment:
```
C:\Work\Projects\emsdk\emsdk.bat activate latest C:\Work\Projects\emsdk\emsdk.bat activate latest
C:\Work\Projects\emsdk\emsdk_env.bat C:\Work\Projects\emsdk\emsdk_env.bat
``` ```
-24
View File
@@ -17,23 +17,10 @@
width: 100vw; height: 100vh; width: 100vw; height: 100vh;
border: none; border: none;
} }
/* Кнопка Fullscreen */
#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%); }
</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>
@@ -49,17 +36,6 @@
} }
}; };
// Кнопка Fullscreen
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 не мгновенно. // Chrome на Android обновляет innerWidth/Height не мгновенно.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -2
View File
@@ -65,8 +65,8 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/game_over/Filledbuttons.png", "normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Filledbuttons.png", "hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Filledbuttons.png" "pressed": "resources/game_over/Variant6.png"
} }
}, },
{ {
+4 -4
View File
@@ -50,8 +50,8 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/main_menu/single.png", "normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/single.png", "hover": "resources/main_menu/Variant5.png",
"pressed": "resources/main_menu/single.png" "pressed": "resources/main_menu/Variant6.png"
} }
}, },
{ {
@@ -61,8 +61,8 @@
"height": 56, "height": 56,
"textures": { "textures": {
"normal": "resources/main_menu/multi.png", "normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/multi.png", "hover": "resources/main_menu/Variant7.png",
"pressed": "resources/main_menu/multi.png" "pressed": "resources/main_menu/Variant8.png"
} }
}, },
{ {
+3 -3
View File
@@ -1,14 +1,14 @@
{ {
"emissionRate": 100.0, "emissionRate": 10.0,
"maxParticles": 200, "maxParticles": 200,
"particleSize": 0.3, "particleSize": 0.3,
"biasX": 0.3, "biasX": 0.3,
"emissionPoints": [ "emissionPoints": [
{ {
"position": [-1.3, 0, 0.0] "position": [-1.0, 1.4, -3.5]
}, },
{ {
"position": [1.3, 0.0, 0.0] "position": [1.0, 1.4, -3.5]
} }
], ],
"speedRange": [0.5, 2.0], "speedRange": [0.5, 2.0],
+20
View File
@@ -0,0 +1,20 @@
{
"emissionRate": 10.0,
"maxParticles": 200,
"particleSize": 0.3,
"biasX": 0.3,
"emissionPoints": [
{
"position": [0.0, 2.8, -3.5]
},
{
"position": [0.0, 1.5, -3.5]
}
],
"speedRange": [0.5, 2.0],
"zSpeedRange": [1.0, 3.0],
"scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0],
"texture": "resources/spark.png",
"shaderProgramName": "spark"
}
@@ -2,14 +2,14 @@
"emissionPoints": [ "emissionPoints": [
{ "position": [0.0, 0.0, 0.0] } { "position": [0.0, 0.0, 0.0] }
], ],
"texture": "resources/spark_white.png", "texture": "resources/spark2.png",
"speedRange": [10.0, 30.0], "speedRange": [5.0, 10.0],
"zSpeedRange": [-1.0, 1.0], "zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 1.0], "scaleRange": [0.5, 2.0],
"lifeTimeRange": [200.0, 800.0], "lifeTimeRange": [200.0, 800.0],
"emissionRate": 50.0, "emissionRate": 30.0,
"maxParticles": 10, "maxParticles": 150,
"particleSize": 0.5, "particleSize": 1.0,
"biasX": 0.1, "biasX": 0.1,
"shaderProgramName": "default" "shaderProgramName": "default"
} }
+48 -16
View File
@@ -6,6 +6,20 @@
"width": "match_parent", "width": "match_parent",
"height": "match_parent", "height": "match_parent",
"children": [ "children": [
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 40,
"horizontal_gravity": "left",
"vertical_gravity": "top",
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"centered": false
},
{ {
"type": "Button", "type": "Button",
"name": "shootButton", "name": "shootButton",
@@ -16,9 +30,10 @@
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/shoot_normal.png", "normal": "resources/fire.png",
"hover": "resources/shoot_hover.png", "hover": "resources/fire2.png",
"pressed": "resources/shoot_pressed.png" "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
@@ -31,25 +46,42 @@
"horizontal_gravity": "left", "horizontal_gravity": "left",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"normal": "resources/shoot_normal.png", "normal": "resources/fire.png",
"hover": "resources/shoot_hover.png", "hover": "resources/fire2.png",
"pressed": "resources/shoot_pressed.png" "pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
} }
}, },
{ {
"type": "Slider", "type": "Button",
"name": "velocitySlider", "name": "minusButton",
"x": 10, "x": -20,
"y": 200, "y": 110,
"width": 80, "width": 150,
"height": 300, "height": 150,
"value": 0.0, "border" : 20,
"orientation": "vertical",
"horizontal_gravity": "right", "horizontal_gravity": "right",
"vertical_gravity": "bottom", "vertical_gravity": "bottom",
"textures": { "textures": {
"track": "resources/velocitySliderTexture.png", "normal": "resources/button_minus.png",
"knob": "resources/velocitySliderButton.png" "hover": "resources/button_minus_pressed.png",
"pressed": "resources/button_minus_pressed.png"
}
},
{
"type": "Button",
"name": "plusButton",
"x": -20,
"y": 220,
"width": 150,
"height": 150,
"border" : 20,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/button_plus.png",
"hover": "resources/button_plus_pressed.png",
"pressed": "resources/button_plus_pressed.png"
} }
} }
] ]
BIN
View File
Binary file not shown.
BIN
View File
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.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+1
View File
@@ -26,6 +26,7 @@ add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_
# Исполняемый файл сервера # Исполняемый файл сервера
add_executable(Server add_executable(Server
server.h
server.cpp server.cpp
../src/network/ClientState.h ../src/network/ClientState.h
../src/network/ClientState.cpp ../src/network/ClientState.cpp
+402 -494
View File
@@ -1,44 +1,11 @@
#include <boost/beast/core.hpp> #include "server.h"
#include <boost/beast/websocket.hpp> #include <boost/beast/websocket.hpp>
#include <boost/asio/ip/tcp.hpp> #include <boost/asio/ip/tcp.hpp>
#include <iostream> #include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <map>
#include <queue>
#include <sstream> #include <sstream>
#include <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
#include "../src/network/ClientState.h"
#include <random> #include <random>
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <unordered_set>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
struct DeathInfo {
int targetId = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int killerId = -1;
};
std::vector<std::string> split(const std::string& s, char delimiter) { std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens; std::vector<std::string> tokens;
@@ -50,279 +17,309 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
return tokens; return tokens;
} }
struct ServerBox { Session::Session(Server& server, tcp::socket&& socket, int id)
Eigen::Vector3f position; : server_(server)
Eigen::Matrix3f rotation; , ws_(std::move(socket))
float collisionRadius = 2.0f; , id_(id)
bool destroyed = false; , lastReceivedTime_(std::chrono::system_clock::now()) {
}; }
struct Projectile { bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
int shooterId = -1; if (!joined_) return false;
uint64_t spawnMs = 0; auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceivedTime_).count();
Eigen::Vector3f pos; return elapsed > PLAYER_TIMEOUT_MS;
Eigen::Vector3f vel; }
float lifeMs = PROJECTILE_LIFE;
};
struct BoxDestroyedInfo { void Session::force_disconnect() {
int boxIndex = -1; ws_.async_close(websocket::close_code::normal,
uint64_t serverTime = 0; [self = shared_from_this()](beast::error_code) {});
Eigen::Vector3f position = Eigen::Vector3f::Zero(); }
int destroyedBy = -1;
};
std::vector<BoxDestroyedInfo> g_boxDestructions; int Session::get_id() const { return id_; }
std::mutex g_boxDestructions_mutex;
std::vector<ServerBox> g_serverBoxes; bool Session::hasSpawnReserved() const { return hasReservedSpawn_; }
std::mutex g_boxes_mutex; const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; }
std::vector<std::shared_ptr<class Session>> g_sessions; bool Session::fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
std::mutex g_sessions_mutex; if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
outState = timedClientStates.fetchClientStateAtTime(targetTime);
std::vector<Projectile> g_projectiles; return true;
std::mutex g_projectiles_mutex;
std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;
class Session;
void broadcastToAll(const std::string& message);
class Session : public std::enable_shared_from_this<Session> {
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
bool is_writing_ = false;
std::queue<std::shared_ptr<std::string>> writeQueue_;
std::mutex writeMutex_;
public:
ClientStateInterval timedClientStates;
bool joined_ = false;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player";
int shipType = 0;
explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) {
} }
return false;
}
int get_id() const { return id_; } void Session::send_message(const std::string& msg) {
auto ss = std::make_shared<std::string>(msg);
bool hasSpawnReserved() const { return hasReservedSpawn_; }
const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; }
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
outState = timedClientStates.fetchClientStateAtTime(targetTime);
return true;
}
return false;
}
void send_message(const std::string& msg) {
auto ss = std::make_shared<std::string>(msg);
{
std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.push(ss);
}
doWrite();
}
void run() {
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.push_back(shared_from_this());
}
ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
if (ec) return;
std::cout << "Client " << self->id_ << " connected\n";
self->init();
});
}
bool IsMessageValid(const std::string& fullMessage) {
#ifdef ENABLE_NETWORK_CHECKSUM
size_t hashPos = fullMessage.find("#hash:");
if (hashPos == std::string::npos) {
return false; // Хеша нет, хотя он ожидался
}
std::string originalContent = fullMessage.substr(0, hashPos);
std::string receivedHashStr = fullMessage.substr(hashPos + 6); // 6 — длина "#hash:"
// Вычисляем ожидаемый хеш от контента
size_t expectedHash = fnv1a_hash(originalContent + NET_SECRET);
std::stringstream ss;
ss << std::hex << expectedHash;
return ss.str() == receivedHashStr;
#else
return true; // В режиме отладки пропускаем всё
#endif
}
private:
void sendBoxesToClient() {
std::lock_guard<std::mutex> lock(g_boxes_mutex);
std::string boxMsg = "BOXES:";
bool first = true;
for (size_t i = 0; i < g_serverBoxes.size(); ++i) {
const auto& box = g_serverBoxes[i];
if (box.destroyed) continue;
Eigen::Quaternionf q(box.rotation);
if (!first) boxMsg += "|";
first = false;
boxMsg += std::to_string(i) + ":" +
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()) + ":" +
"0";
}
// Если все коробки уничтожены — отправится просто "BOXES:" (это нормально)
send_message(boxMsg);
}
public:
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) {
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
self->do_read();
}
});
}
ClientState get_latest_state(std::chrono::system_clock::time_point now) {
if (timedClientStates.timedStates.empty()) {
return {};
}
// 1. Берем самое последнее известное состояние
ClientState latest = timedClientStates.timedStates.back();
// 3. Применяем компенсацию лага (экстраполяцию).
// Функция внутри использует simulate_physics, чтобы переместить объект
// из точки lastUpdateServerTime в точку now.
latest.apply_lag_compensation(now);
return latest;
}
void doWrite() {
std::lock_guard<std::mutex> lock(writeMutex_); std::lock_guard<std::mutex> lock(writeMutex_);
if (is_writing_ || writeQueue_.empty()) { writeQueue_.push(ss);
return;
}
is_writing_ = true;
auto message = writeQueue_.front();
ws_.async_write(net::buffer(*message),
[self = shared_from_this(), message](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
return;
}
{
std::lock_guard<std::mutex> lock(self->writeMutex_);
self->writeQueue_.pop();
self->is_writing_ = false;
}
self->doWrite();
});
} }
private: doWrite();
}
void do_read() { void Session::run() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { {
if (ec) { std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
std::lock_guard<std::mutex> lock(g_sessions_mutex); server_.g_sessions.push_back(shared_from_this());
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
[self](const std::shared_ptr<Session>& session) {
return session.get() == self.get();
}), g_sessions.end());
std::cout << "Client " << self->id_ << " disconnected\n";
return;
}
std::string msg = beast::buffers_to_string(self->buffer_.data());
self->process_message(msg);
self->buffer_.consume(self->buffer_.size());
self->do_read();
});
} }
void process_message(const std::string& msg) { ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
if (!IsMessageValid(msg)) { if (ec) return;
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; std::cout << "Client " << self->id_ << " connected\n";
return; self->init();
} });
std::string cleanMessage = msg.substr(0, msg.find("#hash:")); }
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl; bool Session::IsMessageValid(const std::string& fullMessage) {
auto parts = split(cleanMessage, ':'); #ifdef ENABLE_NETWORK_CHECKSUM
size_t hashPos = fullMessage.find("#hash:");
if (hashPos == std::string::npos) {
return false; // Хеша нет, хотя он ожидался
}
if (parts.empty()) return; std::string originalContent = fullMessage.substr(0, hashPos);
std::string receivedHashStr = fullMessage.substr(hashPos + 6); // 6 — длина "#hash:"
std::string type = parts[0]; // Вычисляем ожидаемый хеш от контента
size_t expectedHash = fnv1a_hash(originalContent + NET_SECRET);
if (type == "JOIN") { std::stringstream ss;
std::string nick = "Player"; ss << std::hex << expectedHash;
int sType = 0;
if (parts.size() >= 2) nick = parts[1];
if (parts.size() >= 3) {
try { sType = std::stoi(parts[2]); }
catch (...) { sType = 0; }
}
this->nickname = nick; return ss.str() == receivedHashStr;
this->shipType = sType; #else
this->joined_ = true; return true; // В режиме отладки пропускаем всё
#endif
}
void Session::sendBoxesToClient() {
std::lock_guard<std::mutex> lock(server_.g_boxes_mutex);
std::string boxMsg = "BOXES:";
bool first = true;
for (size_t i = 0; i < server_.g_serverBoxes.size(); ++i) {
const auto& box = server_.g_serverBoxes[i];
if (box.destroyed) continue;
Eigen::Quaternionf q(box.rotation);
if (!first) boxMsg += "|";
first = false;
boxMsg += std::to_string(i) + ":" +
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()) + ":" +
"0";
}
send_message(boxMsg);
}
void Session::init()
{
sendBoxesToClient();
auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
if (!ec) {
auto now_tp = std::chrono::system_clock::now(); auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>( uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_); self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
this->hasReservedSpawn_ = true; self->do_read();
this->reservedSpawn_ = spawnPos; }
});
}
ClientState Session::get_latest_state(std::chrono::system_clock::time_point now) {
if (timedClientStates.timedStates.empty()) {
return {};
}
ClientState latest = timedClientStates.timedStates.back();
latest.apply_lag_compensation(now);
return latest;
}
void Session::doWrite() {
std::lock_guard<std::mutex> lock(writeMutex_);
if (is_writing_ || writeQueue_.empty()) {
return;
}
is_writing_ = true;
auto message = writeQueue_.front();
ws_.async_write(net::buffer(*message),
[self = shared_from_this(), message](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
return;
}
{
std::lock_guard<std::mutex> lock(self->writeMutex_);
self->writeQueue_.pop();
self->is_writing_ = false;
}
self->doWrite();
});
}
void Session::do_read() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
if (ec) {
if (self->joined_) {
self->server_.broadcastToAllExceptId("PLAYER_LEFT:" + std::to_string(self->id_), self->id_);
std::cout << "Client " << self->id_ << " disconnected, broadcasting PLAYER_LEFT\n";
}
std::lock_guard<std::mutex> lock(self->server_.g_sessions_mutex);
self->server_.g_sessions.erase(std::remove_if(self->server_.g_sessions.begin(), self->server_.g_sessions.end(),
[self](const std::shared_ptr<Session>& session) {
return session.get() == self.get();
}), self->server_.g_sessions.end());
std::cout << "Client " << self->id_ << " removed from session list\n";
return;
}
std::string msg = beast::buffers_to_string(self->buffer_.data());
self->process_message(msg);
self->buffer_.consume(self->buffer_.size());
self->do_read();
});
}
void Session::process_message(const std::string& msg) {
if (!IsMessageValid(msg)) {
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return;
}
lastReceivedTime_ = std::chrono::system_clock::now();
std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl;
auto parts = split(cleanMessage, ':');
if (parts.empty()) return;
std::string type = parts[0];
if (type == "JOIN") {
std::string nick = "Player";
int sType = 0;
if (parts.size() >= 2) nick = parts[1];
if (parts.size() >= 3) {
try { sType = std::stoi(parts[2]); }
catch (...) { sType = 0; }
}
this->nickname = nick;
this->shipType = sType;
this->joined_ = true;
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
ClientState st;
st.id = id_;
st.position = spawnPos;
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
);
std::string eventMsg =
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
server_.broadcastToAllExceptId(eventMsg, id_);
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
server_.broadcastToAllExceptId(info, id_);
{
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
for (auto& session : server_.g_sessions) {
if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
this->send_message(otherInfo);
}
}
}
else if (type == "UPD") {
if (!joined_) {
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
return;
}
{
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
if (server_.g_dead_players.find(id_) != server_.g_dead_players.end()) {
std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
return;
}
}
if (parts.size() < 16) return;
uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{
std::chrono::milliseconds(clientTimestamp)
};
receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2);
receivedState.nickname = this->nickname;
receivedState.shipType = this->shipType;
timedClientStates.add_state(receivedState);
}
else if (type == "RESPAWN") {
{
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
server_.g_dead_players.erase(id_);
}
{
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
ClientState st; ClientState st;
st.id = id_; st.id = id_;
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
st.position = spawnPos; st.position = spawnPos;
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
st.rotation = Eigen::Matrix3f::Identity(); st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero(); st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f; st.velocity = 0.0f;
@@ -334,188 +331,53 @@ private:
st.shipType = this->shipType; st.shipType = this->shipType;
timedClientStates.add_state(st); timedClientStates.add_state(st);
this->send_message( this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent() "SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
); );
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
server_.broadcastToAll(respawnMsg);
std::string eventMsg = std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); server_.broadcastToAll(playerInfo);
{ std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
std::lock_guard<std::mutex> lock(g_sessions_mutex); server_.broadcastToAll(eventMsg);
for (auto& session : g_sessions) {
if (session->get_id() == id_) continue;
session->send_message(eventMsg);
}
}
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType); std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
session->send_message(info);
}
}
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
this->send_message(otherInfo);
}
}
}
else if (type == "UPD") {
if (!joined_) {
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
return;
}
{
std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(id_) != g_dead_players.end()) {
std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
return;
}
}
if (parts.size() < 16) return;
uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{
std::chrono::milliseconds(clientTimestamp)
};
receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2);
receivedState.nickname = this->nickname;
receivedState.shipType = this->shipType;
timedClientStates.add_state(receivedState);
}
else if (type == "RESPAWN") {
{
std::lock_guard<std::mutex> gd(g_dead_mutex);
g_dead_players.erase(id_);
}
{
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
ClientState st;
st.id = id_;
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
st.position = spawnPos;
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
);
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
broadcastToAll(respawnMsg);
std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
broadcastToAll(playerInfo);
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
broadcastToAll(eventMsg);
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
}
}
else if (parts[0] == "FIRE") {
if (parts.size() < 10) return;
uint64_t clientTime = std::stoull(parts[1]);
Eigen::Vector3f pos{
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
};
Eigen::Quaternionf 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::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) {
session->send_message(broadcast);
}
}
}
{
const std::vector<Eigen::Vector3f> localOffsets = {
Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f),
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
};
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < std::min(shotCount, (int)localOffsets.size()); ++i) {
Projectile pr;
pr.shooterId = id_;
pr.spawnMs = now_ms;
Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
pr.pos = shotPos;
Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
pr.vel = worldForward * velocity;
pr.lifeMs = 15000.0f;
g_projectiles.push_back(pr);
std::cout << "Server: Created projectile from player " << id_
<< " at pos (" << shotPos.x() << ", " << shotPos.y() << ", " << shotPos.z()
<< ") vel (" << pr.vel.x() << ", " << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
}
}
} }
} }
else if (parts[0] == "FIRE") {
if (parts.size() < 10) return;
}; uint64_t clientTime = std::stoull(parts[1]);
Eigen::Vector3f pos{
std::stof(parts[2]), std::stof(parts[3]), std::stof(parts[4])
};
Eigen::Quaternionf dir(
std::stof(parts[5]), std::stof(parts[6]), std::stof(parts[7]), std::stof(parts[8])
);
float velocity = std::stof(parts[9]);
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId) 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);
server_.broadcastToAllExceptId(broadcast, id_);
server_.createProjectile(id_, pos, dir, velocity);
}
}
Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId)
{ {
static thread_local std::mt19937 rng{ std::random_device{}() }; static thread_local std::mt19937 rng{ std::random_device{}() };
@@ -580,14 +442,54 @@ Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset); return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset);
} }
void broadcastToAll(const std::string& message) { void Server::broadcastToAll(const std::string& message) {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (const auto& session : g_sessions) { for (const auto& session : g_sessions) {
session->send_message(message); session->send_message(message);
} }
} }
void update_world(net::steady_timer& timer, net::io_context& ioc) { void Server::broadcastToAllExceptId(const std::string& message, int id)
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() == id) continue;
session->send_message(message);
}
}
void Server::createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity)
{
const std::vector<Eigen::Vector3f> localOffsets = {
Eigen::Vector3f(-1.5f, 0.9f - 6.f, 5.0f),
Eigen::Vector3f(1.5f, 0.9f - 6.f, 5.0f)
};
uint64_t now_ms = std::chrono::duration_cast<std::chrono::milliseconds>((
std::chrono::system_clock::now().time_since_epoch())).count();
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
for (int i = 0; i < localOffsets.size(); ++i) {
Projectile pr;
pr.shooterId = id;
pr.spawnMs = now_ms;
Eigen::Vector3f shotPos = pos + dir.toRotationMatrix() * localOffsets[i];
pr.pos = shotPos;
Eigen::Vector3f localForward(0.0f, 0.0f, -1.0f);
Eigen::Vector3f worldForward = dir.toRotationMatrix() * localForward;
float len = worldForward.norm();
if (len > 1e-6f) worldForward /= len;
pr.vel = worldForward * velocity;
pr.lifeMs = 15000.0f;
g_projectiles.push_back(pr);
std::cout << "Server: Created projectile from player " << id
<< " at pos (" << shotPos.x() << ", " << shotPos.y() << ", " << shotPos.z()
<< ") vel (" << pr.vel.x() << ", " << pr.vel.y() << ", " << pr.vel.z() << ")" << std::endl;
}
}
void Server::update_world() {
static auto last_snapshot_time = std::chrono::system_clock::now(); static auto last_snapshot_time = std::chrono::system_clock::now();
@@ -595,26 +497,24 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
uint64_t now_ms = static_cast<uint64_t>( uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count()); std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
// --- Snapshot every 500ms --- // --- Detect and force-disconnect timed-out players ---
/*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) { {
last_snapshot_time = now; std::vector<std::shared_ptr<Session>> timedOut;
{
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
std::lock_guard<std::mutex> lock(g_sessions_mutex); if (session->is_timed_out(now)) {
for (auto& session : g_sessions) { timedOut.push_back(session);
ClientState st = session->get_latest_state(now); }
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent(); }
} }
for (auto& session : g_sessions) { for (auto& session : timedOut) {
session->send_message(snapshot_msg); std::cout << "Server: Player " << session->get_id()
<< " timed out after " << PLAYER_TIMEOUT_MS << "ms, forcing disconnect\n";
session->force_disconnect();
} }
}*/ }
// --- Tick: broadcast each player's latest state to all others (20Hz) ---
// Send the raw last-known state with its original timestamp, NOT an extrapolated one.
// Extrapolating here causes snap-back: if a player stops rotating, the server would
// keep sending over-rotated positions until the new state arrives, then B snaps back.
{ {
std::lock_guard<std::mutex> lock(g_sessions_mutex); std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& sender : g_sessions) { for (auto& sender : g_sessions) {
@@ -834,13 +734,13 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
// --- 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([&timer, &ioc](const boost::system::error_code& ec) { timer.async_wait([this](const boost::system::error_code& ec) {
if (ec) return; if (ec) return;
update_world(timer, ioc); update_world();
}); });
} }
std::vector<ServerBox> generateServerBoxes(int count) { std::vector<ServerBox> Server::generateServerBoxes(int count) {
std::vector<ServerBox> boxes; std::vector<ServerBox> boxes;
std::random_device rd; std::random_device rd;
std::mt19937 gen(rd()); std::mt19937 gen(rd());
@@ -889,34 +789,42 @@ std::vector<ServerBox> generateServerBoxes(int count) {
return boxes; return boxes;
} }
Server::Server(tcp::acceptor& acceptor, net::io_context& ioc)
: acceptor_(acceptor)
, ioc_(ioc)
, timer(ioc_)
{
}
void Server::init()
{
std::lock_guard<std::mutex> lock(g_boxes_mutex);
g_serverBoxes = generateServerBoxes(50);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
}
void Server::accept()
{
acceptor_.async_accept([&](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(*this, std::move(socket), next_id++)->run();
}
accept();
});
}
int main() { int main() {
try { try {
{
std::lock_guard<std::mutex> lock(g_boxes_mutex);
g_serverBoxes = generateServerBoxes(50);
//g_serverBoxes = generateServerBoxes(1);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
}
net::io_context ioc; net::io_context ioc;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8010} }; tcp::acceptor acceptor{ ioc, {tcp::v4(), 8010} };
int next_id = 1000; Server server(acceptor, ioc);
server.accept();
std::cout << "Server started on port 8010...\n"; std::cout << "Server started on port 8010...\n";
auto do_accept = [&](auto& self_fn) -> void { server.update_world();
acceptor.async_accept([&, self_fn](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket), next_id++)->run();
}
self_fn(self_fn);
});
};
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept);
ioc.run(); ioc.run();
} }
catch (std::exception const& e) { catch (std::exception const& e) {
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <queue>
#include <unordered_set>
#include <Eigen/Dense>
#include "../src/network/ClientState.h"
#define _USE_MATH_DEFINES
#include <math.h>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
std::vector<std::string> split(const std::string& s, char delimiter);
struct DeathInfo {
int targetId = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int killerId = -1;
};
struct ServerBox {
Eigen::Vector3f position;
Eigen::Matrix3f rotation;
float collisionRadius = 2.0f;
bool destroyed = false;
};
struct Projectile {
int shooterId = -1;
uint64_t spawnMs = 0;
Eigen::Vector3f pos;
Eigen::Vector3f vel;
float lifeMs = PROJECTILE_LIFE;
};
struct BoxDestroyedInfo {
int boxIndex = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int destroyedBy = -1;
};
class Server;
class Session : public std::enable_shared_from_this<Session> {
Server& server_;
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
bool is_writing_ = false;
std::queue<std::shared_ptr<std::string>> writeQueue_;
std::mutex writeMutex_;
public:
ClientStateInterval timedClientStates;
bool joined_ = false;
std::chrono::system_clock::time_point lastReceivedTime_;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player";
int shipType = 0;
Session(Server& server, tcp::socket&& socket, int id);
int get_id() const;
bool hasSpawnReserved() const;
const Eigen::Vector3f& reservedSpawn() const;
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const;
void send_message(const std::string& msg);
void run();
bool IsMessageValid(const std::string& fullMessage);
bool is_timed_out(std::chrono::system_clock::time_point now) const;
void force_disconnect();
private:
void sendBoxesToClient();
public:
void init();
ClientState get_latest_state(std::chrono::system_clock::time_point now);
void doWrite();
private:
void do_read();
void process_message(const std::string& msg);
};
class Server
{
public:
tcp::acceptor& acceptor_;
net::io_context& ioc_;
net::steady_timer timer;
std::vector<BoxDestroyedInfo> g_boxDestructions;
std::mutex g_boxDestructions_mutex;
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex;
std::vector<Projectile> g_projectiles;
std::mutex g_projectiles_mutex;
std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;
int next_id = 1000;
std::vector<ServerBox> generateServerBoxes(int count);
public:
Server(tcp::acceptor& acceptor, net::io_context& ioc);
void broadcastToAll(const std::string& message);
void broadcastToAllExceptId(const std::string& message, int id);
void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
void update_world();
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
void init();
void accept();
};
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef AUDIO #ifdef AUDIO
#include "AudioPlayerAsync.h" #include "AudioPlayerAsync.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#ifdef AUDIO #ifdef AUDIO
+1 -1
View File
@@ -1,4 +1,4 @@
#include "BoneAnimatedModel.h" #include "BoneAnimatedModel.h"
#include <regex> #include <regex>
#include <string> #include <string>
#include <fstream> #include <fstream>
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include <unordered_map> #include <unordered_map>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "Environment.h" #include "Environment.h"
#include "utils/Utils.h" #include "utils/Utils.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#ifdef __linux__ #ifdef __linux__
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#endif #endif
+16
View File
@@ -150,6 +150,18 @@ namespace ZL
Environment::shipState.nickname = nickname; Environment::shipState.nickname = nickname;
Environment::shipState.shipType = shipType; Environment::shipState.shipType = shipType;
if (Environment::shipState.shipType == 1)
{
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else
{
menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
auto localClient = new LocalClient; auto localClient = new LocalClient;
ClientState st = Environment::shipState; ClientState st = Environment::shipState;
st.id = localClient->GetClientId(); st.id = localClient->GetClientId();
@@ -230,6 +242,10 @@ namespace ZL
} }
void Game::drawScene() { void Game::drawScene() {
//For low quality:
glViewport(0, 0, Environment::width, Environment::height);
//For high quality:
//glViewport(0, 0, Environment::projectionWidth, Environment::projectionHeight);
if (!loadingCompleted) { if (!loadingCompleted) {
drawLoading(); drawLoading();
} }
+17 -1
View File
@@ -1,4 +1,4 @@
#include "MenuManager.h" #include "MenuManager.h"
namespace ZL { namespace ZL {
@@ -103,6 +103,22 @@ namespace ZL {
uiManager.setButtonCallback("shootButton2", [this](const std::string& name) { uiManager.setButtonCallback("shootButton2", [this](const std::string& name) {
onFirePressed(); onFirePressed();
}); });
uiManager.setButtonCallback("plusButton", [this](const std::string& name) {
int newVel = Environment::shipState.selectedVelocity+1;
if (newVel > 4)
{
newVel = 4;
}
onVelocityChanged(newVel);
});
uiManager.setButtonCallback("minusButton", [this](const std::string& name) {
int newVel = Environment::shipState.selectedVelocity-1;
if (newVel < 0)
{
newVel = 0;
}
onVelocityChanged(newVel);
});
uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) { uiManager.setSliderCallback("velocitySlider", [this](const std::string& name, float value) {
int newVel = roundf(value * 10); int newVel = roundf(value * 10);
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#include "Projectile.h" #include "Projectile.h"
namespace ZL { namespace ZL {
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
+88 -16
View File
@@ -1,4 +1,4 @@
#include "Space.h" #include "Space.h"
#include "AnimatedModel.h" #include "AnimatedModel.h"
#include "BoneAnimatedModel.h" #include "BoneAnimatedModel.h"
#include "planet/PlanetData.h" #include "planet/PlanetData.h"
@@ -282,6 +282,20 @@ namespace ZL
menuManager.onVelocityChanged = [this](float newVelocity) { menuManager.onVelocityChanged = [this](float newVelocity) {
newShipVelocity = newVelocity; newShipVelocity = newVelocity;
if (Environment::shipState.shipType == 0)
{
if (newVelocity > 2)
{
this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Disabled;
this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Disabled;
}
else
{
this->menuManager.uiManager.findButton("shootButton")->state = ButtonState::Normal;
this->menuManager.uiManager.findButton("shootButton2")->state = ButtonState::Normal;
}
}
}; };
menuManager.onFirePressed = [this]() { menuManager.onFirePressed = [this]() {
@@ -289,10 +303,13 @@ namespace ZL
}; };
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 projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE); bool projCfgLoaded = projectileEmitter.loadFromJsonFile("resources/config/spark_projectile_config.json", renderer, CONST_ZIP_FILE);
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({ Vector3f{0,0,45000} });
//projectileEmitter.setUseWorldSpace(true);
cubemapTexture = std::make_shared<Texture>( cubemapTexture = std::make_shared<Texture>(
std::array<TextureDataStruct, 6>{ std::array<TextureDataStruct, 6>{
@@ -315,7 +332,9 @@ namespace ZL
spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE); spaceshipBase = LoadFromTextFile02("resources/spaceshipnew001.txt", CONST_ZIP_FILE);
spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix()); spaceshipBase.RotateByMatrix(Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())).toRotationMatrix());// QuatFromRotateAroundY(M_PI / 2.0).toRotationMatrix());
spaceshipBase.Move(Vector3f{ 1.2, 0, -5 }); spaceshipBase.Move(Vector3f{ 1.2, 0, -5 });
spaceshipBase.Scale(0.4f);
spaceship.AssignFrom(spaceshipBase); spaceship.AssignFrom(spaceshipBase);
spaceship.RefreshVBO(); spaceship.RefreshVBO();
@@ -501,12 +520,18 @@ namespace ZL
renderer.EnableVertexAttribArray(vTexCoordName); renderer.EnableVertexAttribArray(vTexCoordName);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendFunc(GL_SRC_ALPHA, GL_ONE);
renderer.PushMatrix();
renderer.LoadIdentity();
renderer.TranslateMatrix({ 0,0, -1.0f * Environment::zoom });
renderer.RotateMatrix(Environment::inverseShipMatrix);
renderer.TranslateMatrix(-Environment::shipState.position);
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
p->draw(renderer); //p->draw(renderer);
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
} }
} }
renderer.PopMatrix();
glDisable(GL_BLEND); glDisable(GL_BLEND);
renderer.DisableVertexAttribArray(vPositionName); renderer.DisableVertexAttribArray(vPositionName);
@@ -519,12 +544,17 @@ namespace ZL
renderer.PushMatrix(); renderer.PushMatrix();
renderer.TranslateMatrix({ 0, 0, 16 }); renderer.TranslateMatrix({ 0, 0, 16 });
renderer.TranslateMatrix({ 0, -6.f, 0 }); renderer.TranslateMatrix({ 0, -6.f, 0 });
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); if (Environment::shipState.shipType == 1) {
sparkEmitterCargo.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
}
else {
sparkEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
}
renderer.PopMatrix(); renderer.PopMatrix();
} }
if (showExplosion) { if (showExplosion) {
explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height); explosionEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height, false);
} }
//glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID()); //glBindTexture(GL_TEXTURE_2D, basePlatformTexture->getTexID());
@@ -1450,7 +1480,17 @@ namespace ZL
auto now_ms = newTickCount; auto now_ms = newTickCount;
sparkEmitter.update(static_cast<float>(delta)); SparkEmitter* sparkEmitterPtr;
if (Environment::shipState.shipType == 1) {
sparkEmitterPtr = &sparkEmitterCargo;
}
else
{
sparkEmitterPtr = &sparkEmitter;
}
sparkEmitterPtr->update(static_cast<float>(delta));
planetObject.update(static_cast<float>(delta)); planetObject.update(static_cast<float>(delta));
if (firePressed) if (firePressed)
@@ -1594,9 +1634,9 @@ namespace ZL
for (const auto& p : projectiles) { for (const auto& p : projectiles) {
if (p && p->isActive()) { if (p && p->isActive()) {
Vector3f worldPos = p->getPosition(); Vector3f worldPos = p->getPosition();
Vector3f rel = worldPos - Environment::shipState.position; //Vector3f rel = worldPos - Environment::shipState.position;
Vector3f camPos = Environment::inverseShipMatrix * rel; //Vector3f camPos = Environment::inverseShipMatrix * rel;
p->projectileEmitter.setEmissionPoints({ camPos }); p->projectileEmitter.setEmissionPoints({ worldPos });
p->projectileEmitter.emit(); p->projectileEmitter.emit();
p->projectileEmitter.update(static_cast<float>(delta)); p->projectileEmitter.update(static_cast<float>(delta));
} }
@@ -1610,16 +1650,25 @@ namespace ZL
projectileEmitter.setEmissionPoints(std::vector<Vector3f>()); projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
}*/ }*/
std::vector<Vector3f> shipCameraPoints; if (Environment::shipState.velocity > 0.1f) {
for (const auto& lp : shipLocalEmissionPoints) {
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f }; sparkEmitterPtr->setIsActive(true);
shipCameraPoints.push_back(adjusted);
std::vector<Vector3f> shipCameraPoints;
for (const auto& lp : shipLocalEmissionPoints) {
Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f };
shipCameraPoints.push_back(adjusted);
}
if (!shipCameraPoints.empty()) {
sparkEmitterPtr->setEmissionPoints(shipCameraPoints);
}
} }
if (!shipCameraPoints.empty()) { else
sparkEmitter.setEmissionPoints(shipCameraPoints); {
sparkEmitterPtr->setIsActive(false);
} }
sparkEmitter.update(static_cast<float>(delta)); sparkEmitterPtr->update(static_cast<float>(delta));
//projectileEmitter.update(static_cast<float>(delta)); //projectileEmitter.update(static_cast<float>(delta));
explosionEmitter.update(static_cast<float>(delta)); explosionEmitter.update(static_cast<float>(delta));
@@ -1773,6 +1822,18 @@ namespace ZL
void Space::update() { void Space::update() {
if (networkClient) { if (networkClient) {
if (networkClient->IsConnected()) {
wasConnectedToServer = true;
}
else if (wasConnectedToServer && shipAlive && !gameOver) {
wasConnectedToServer = false;
shipAlive = false;
gameOver = true;
Environment::shipState.velocity = 0.0f;
std::cout << "Client: Lost connection to server\n";
menuManager.showGameOver(this->playerScore);
}
auto pending = networkClient->getPendingProjectiles(); auto pending = networkClient->getPendingProjectiles();
if (!pending.empty()) { if (!pending.empty()) {
const float projectileSpeed = PROJECTILE_VELOCITY; const float projectileSpeed = PROJECTILE_VELOCITY;
@@ -1862,6 +1923,17 @@ namespace ZL
} }
} }
auto disconnects = networkClient->getPendingDisconnects();
for (int pid : disconnects) {
remotePlayerStates.erase(pid);
deadRemotePlayers.erase(pid);
if (trackedTargetId == pid) {
trackedTargetId = -1;
targetAcquireAnim = 0.f;
}
std::cout << "Client: Remote player " << pid << " left the game, removed from scene\n";
}
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;
+3 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "Environment.h" #include "Environment.h"
@@ -88,6 +88,7 @@ namespace ZL {
VertexDataStruct boxBase; VertexDataStruct boxBase;
SparkEmitter sparkEmitter; SparkEmitter sparkEmitter;
SparkEmitter sparkEmitterCargo;
SparkEmitter projectileEmitter; SparkEmitter projectileEmitter;
SparkEmitter explosionEmitter; SparkEmitter explosionEmitter;
PlanetObject planetObject; PlanetObject planetObject;
@@ -123,6 +124,7 @@ namespace ZL {
std::unordered_set<int> deadRemotePlayers; std::unordered_set<int> deadRemotePlayers;
int playerScore = 0; int playerScore = 0;
bool wasConnectedToServer = false;
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;
+47 -17
View File
@@ -74,7 +74,7 @@ namespace ZL {
texture = tex; texture = tex;
} }
void SparkEmitter::prepareDrawData() { void SparkEmitter::prepareDrawData(bool withRotation) {
if (!drawDataDirty) return; if (!drawDataDirty) return;
drawPositions.clear(); drawPositions.clear();
@@ -91,10 +91,10 @@ namespace ZL {
for (const auto& particle : particles) { for (const auto& particle : particles) {
if (particle.active) { if (particle.active) {
Vector3f posCam; Vector3f posCam;
if (useWorldSpace) { if (withRotation) {
Vector3f rel = particle.position - Environment::shipState.position; Vector3f rel = particle.position - Environment::shipState.position;
posCam = Environment::inverseShipMatrix * rel; posCam = Environment::inverseShipMatrix * rel;
} }
else { else {
posCam = particle.position; posCam = particle.position;
} }
@@ -120,29 +120,59 @@ namespace ZL {
float size = particleSize * particle.scale; float size = particleSize * particle.scale;
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); if (withRotation)
drawTexCoords.push_back({ 0.0f, 0.0f }); {
drawPositions.push_back({ posCam(0) - size, posCam(1) + size, posCam(2) }); drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 1.0f }); drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) }); drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f }); drawTexCoords.push_back({ 0.0f, 1.0f });
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam);
drawTexCoords.push_back({ 0.0f, 0.0f }); drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) }); drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 1.0f }); drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) }); drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam);
drawTexCoords.push_back({ 1.0f, 0.0f }); drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, -size, 0 } + posCam);
drawTexCoords.push_back({ 1.0f, 0.0f });
}
else
{
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) - size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 1.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 0.0f, 0.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) + size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 1.0f });
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
drawTexCoords.push_back({ 1.0f, 0.0f });
}
} }
drawDataDirty = false; drawDataDirty = false;
} }
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight) { void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight)
{
draw(renderer, zoom, screenWidth, screenHeight, true);
}
void SparkEmitter::draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation) {
if (!configured) { if (!configured) {
throw std::runtime_error("Failed to load spark emitter config file 1!"); throw std::runtime_error("Failed to load spark emitter config file 1!");
} }
@@ -155,7 +185,7 @@ 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(); prepareDrawData(withRotation);
if (drawPositions.empty()) { if (drawPositions.empty()) {
return; return;
+7 -1
View File
@@ -50,7 +50,7 @@ namespace ZL {
std::string shaderProgramName; std::string shaderProgramName;
bool configured; bool configured;
void prepareDrawData(); void prepareDrawData(bool withRotation);
bool useWorldSpace; bool useWorldSpace;
public: public:
@@ -76,11 +76,17 @@ namespace ZL {
void emit(); void emit();
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight); void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight);
void draw(Renderer& renderer, float zoom, int screenWidth, int screenHeight, bool withRotation);
const std::vector<SparkParticle>& getParticles() const; const std::vector<SparkParticle>& getParticles() const;
size_t getActiveParticleCount() const; size_t getActiveParticleCount() const;
std::shared_ptr<Texture> getTexture() const { return texture; } std::shared_ptr<Texture> getTexture() const { return texture; }
void setIsActive(bool newActive)
{
isActive = newActive;
}
private: private:
void initParticle(SparkParticle& particle, int emitterIndex); void initParticle(SparkParticle& particle, int emitterIndex);
Vector3f getRandomVelocity(int emitterIndex); Vector3f getRandomVelocity(int emitterIndex);
+1 -1
View File
@@ -1,4 +1,4 @@
#include "TextModel.h" #include "TextModel.h"
#include <regex> #include <regex>
#include <string> #include <string>
#include <fstream> #include <fstream>
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include <unordered_map> #include <unordered_map>
+20 -9
View File
@@ -1,4 +1,4 @@
#include "UiManager.h" #include "UiManager.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include "render/TextRenderer.h" #include "render/TextRenderer.h"
#include <fstream> #include <fstream>
@@ -57,6 +57,7 @@ namespace ZL {
case ButtonState::Normal: tex = &texNormal; break; case ButtonState::Normal: tex = &texNormal; break;
case ButtonState::Hover: tex = &texHover; break; case ButtonState::Hover: tex = &texHover; break;
case ButtonState::Pressed: tex = &texPressed; break; case ButtonState::Pressed: tex = &texPressed; break;
case ButtonState::Disabled: tex = &texDisabled; break;
} }
if (!(*tex)) return; if (!(*tex)) return;
@@ -290,6 +291,9 @@ namespace ZL {
btn->texNormal = loadTex("normal"); btn->texNormal = loadTex("normal");
btn->texHover = loadTex("hover"); btn->texHover = loadTex("hover");
btn->texPressed = loadTex("pressed"); btn->texPressed = loadTex("pressed");
btn->texDisabled = loadTex("disabled");
btn->border = j.value("border", 0.0f);
node->button = btn; node->button = btn;
} }
@@ -1049,11 +1053,14 @@ namespace ZL {
void UiManager::onMouseMove(int x, int y) { void UiManager::onMouseMove(int x, int y) {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->rect.contains((float)x, (float)y)) { if (b->state != ButtonState::Disabled)
if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover; {
} if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
else { if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover;
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; }
else {
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal;
}
} }
} }
@@ -1074,11 +1081,15 @@ namespace ZL {
} }
} }
void UiManager::onMouseDown(int x, int y) { void UiManager::onMouseDown(int x, int y) {
for (auto& b : buttons) { for (auto& b : buttons) {
if (b->rect.contains((float)x, (float)y)) { if (b->state != ButtonState::Disabled)
b->state = ButtonState::Pressed; {
pressedButton = b; if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
b->state = ButtonState::Pressed;
pressedButton = b;
}
} }
} }
+9 -2
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/Renderer.h" #include "render/Renderer.h"
#include "render/TextureManager.h" #include "render/TextureManager.h"
@@ -23,12 +23,17 @@ namespace ZL {
bool contains(float px, float py) const { bool contains(float px, float py) const {
return px >= x && px <= x + w && py >= y && py <= y + h; return px >= x && px <= x + w && py >= y && py <= y + h;
} }
bool containsConsideringBorder(float px, float py, float border) const {
return px >= x+border && px <= x + w-border && py >= y+border && py <= y + h-border;
}
}; };
enum class ButtonState { enum class ButtonState {
Normal, Normal,
Hover, Hover,
Pressed Pressed,
Disabled
}; };
enum class LayoutType { enum class LayoutType {
@@ -76,9 +81,11 @@ namespace ZL {
struct UiButton { struct UiButton {
std::string name; std::string name;
UiRect rect; UiRect rect;
float border = 0;
std::shared_ptr<Texture> texNormal; std::shared_ptr<Texture> texNormal;
std::shared_ptr<Texture> texHover; std::shared_ptr<Texture> texHover;
std::shared_ptr<Texture> texPressed; std::shared_ptr<Texture> texPressed;
std::shared_ptr<Texture> texDisabled;
ButtonState state = ButtonState::Normal; ButtonState state = ButtonState::Normal;
VertexRenderStruct mesh; VertexRenderStruct mesh;
+7 -7
View File
@@ -1,4 +1,4 @@
#include "Game.h" #include "Game.h"
#include "Environment.h" #include "Environment.h"
#include <iostream> #include <iostream>
#ifdef __ANDROID__ #ifdef __ANDROID__
@@ -26,10 +26,6 @@ ZL::Game game;
#ifdef EMSCRIPTEN #ifdef EMSCRIPTEN
void MainLoop() { void MainLoop() {
// SDL_GL_MakeCurrent тут не нужен каждый раз
/*glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
SDL_GL_SwapWindow(ZL::Environment::window);*/
g_game->update(); g_game->update();
} }
#else #else
@@ -55,8 +51,8 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
static void applyResize(int logicalW, int logicalH) { static void applyResize(int logicalW, int logicalH) {
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung) // Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
//double dpr = emscripten_get_device_pixel_ratio(); double dpr = emscripten_get_device_pixel_ratio();
double dpr = 1; // low quality //double dpr = 1; // low quality
// Вычисляем реальные физические пиксели // Вычисляем реальные физические пиксели
int physicalW = static_cast<int>(logicalW * dpr); int physicalW = static_cast<int>(logicalW * dpr);
@@ -83,6 +79,10 @@ static void applyResize(int logicalW, int logicalH) {
e.window.data1 = physicalW; e.window.data1 = physicalW;
e.window.data2 = physicalH; e.window.data2 = physicalH;
SDL_PushEvent(&e); SDL_PushEvent(&e);
std::cout << "Resized, new size: " << logicalW << "x" << logicalH
<< " (physical: " << physicalW << "x" << physicalH
<< ", DPR: " << dpr << ")" << std::endl;
} }
EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) { EM_BOOL onWindowResized(int /*eventType*/, const EmscriptenUiEvent* e, void* /*userData*/) {
+2 -1
View File
@@ -14,7 +14,7 @@ constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f";
#define ENABLE_NETWORK_CHECKSUM #define ENABLE_NETWORK_CHECKSUM
constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f; constexpr float ANGULAR_ACCEL = 0.005f * 1000.0f;
constexpr float SHIP_ACCEL = 1.0f * 1000.0f; constexpr float SHIP_ACCEL = 1.0f * 200.0f;
constexpr float ROTATION_SENSITIVITY = 0.002f; constexpr float ROTATION_SENSITIVITY = 0.002f;
constexpr float PLANET_RADIUS = 20000.f; constexpr float PLANET_RADIUS = 20000.f;
@@ -26,6 +26,7 @@ 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 = 500; //ms constexpr long long CLIENT_DELAY = 500; //ms
constexpr long long CUTOFF_TIME = 5000; //ms constexpr long long CUTOFF_TIME = 5000; //ms
constexpr long long PLAYER_TIMEOUT_MS = 10000; //ms — disconnect if no UPD received
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
+1
View File
@@ -50,6 +50,7 @@ namespace ZL {
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 int64_t getTimeOffset() const { return 0; } virtual int64_t getTimeOffset() const { return 0; }
virtual std::vector<int> getPendingDisconnects() { return {}; }
}; };
} }
+19
View File
@@ -103,6 +103,19 @@ namespace ZL {
return; return;
} }
if (msg.rfind("PLAYER_LEFT:", 0) == 0) {
if (parts.size() >= 2) {
try {
int pid = std::stoi(parts[1]);
remotePlayers.erase(pid);
pendingDisconnects_.push_back(pid);
std::cout << "Client: Player " << pid << " disconnected (PLAYER_LEFT)\n";
}
catch (...) {}
}
return;
}
if (msg.rfind("RESPAWN_ACK:", 0) == 0) { if (msg.rfind("RESPAWN_ACK:", 0) == 0) {
//auto parts = split(msg, ':'); //auto parts = split(msg, ':');
if (parts.size() >= 2) { if (parts.size() >= 2) {
@@ -356,6 +369,12 @@ namespace ZL {
return copy; return copy;
} }
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
std::vector<int> copy;
copy.swap(pendingDisconnects_);
return copy;
}
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() { std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
std::vector<ClientState> copy; std::vector<ClientState> copy;
copy.swap(pendingSpawns_); copy.swap(pendingSpawns_);
+3 -1
View File
@@ -20,6 +20,7 @@ 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<int> pendingDisconnects_;
int clientId = -1; int clientId = -1;
int64_t timeOffset = 0; int64_t timeOffset = 0;
std::vector<ClientState> pendingSpawns_; std::vector<ClientState> pendingSpawns_;
@@ -41,7 +42,7 @@ namespace ZL {
return serverBoxes_; return serverBoxes_;
} }
std::vector<bool> getServerBoxDestroyedFlags() { std::vector<bool> getServerBoxDestroyedFlags() override {
return serverBoxesDestroyed_; return serverBoxesDestroyed_;
} }
@@ -49,6 +50,7 @@ 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<int> getPendingDisconnects() override;
std::vector<ClientState> getPendingSpawns(); std::vector<ClientState> getPendingSpawns();
int getClientId() const { return clientId; } int getClientId() const { return clientId; }
}; };
+6 -6
View File
@@ -1,4 +1,4 @@
#include "FrameBuffer.h" #include "FrameBuffer.h"
#include <iostream> #include <iostream>
#include "Environment.h" #include "Environment.h"
@@ -15,10 +15,10 @@ namespace ZL {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
// Настраиваем фильтрацию // Настраиваем фильтрацию
if (useMipmaps) { if (useMipmaps) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
// Сразу генерируем пустые уровни, чтобы текстура считалась "полной" // Сразу генерируем пустые уровни, чтобы текстура считалась "полной"
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
} }
else { else {
@@ -49,13 +49,13 @@ namespace ZL {
void FrameBuffer::Bind() { void FrameBuffer::Bind() {
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры
} }
void FrameBuffer::Unbind() { void FrameBuffer::Unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
// Здесь желательно возвращать вьюпорт к размерам экрана, // Здесь желательно возвращать вьюпорт к размерам экрана,
// например, через Environment::width/height // например, через Environment::width/height
glViewport(0, 0, Environment::width, Environment::height); glViewport(0, 0, Environment::width, Environment::height);
} }
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
#include <memory> #include <memory>
+1 -1
View File
@@ -1,4 +1,4 @@
#include "OpenGlExtensions.h" #include "OpenGlExtensions.h"
#include "utils/Utils.h" #include "utils/Utils.h"
#include <iostream> #include <iostream>
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "SDL.h" #include "SDL.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#include "render/Renderer.h" #include "render/Renderer.h"
#include <cmath> #include <cmath>
namespace ZL { namespace ZL {
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include "render/OpenGlExtensions.h" #include "render/OpenGlExtensions.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#include "ShaderManager.h" #include "ShaderManager.h"
#include <iostream> #include <iostream>
#include <SDL.h> #include <SDL.h>
+8 -8
View File
@@ -1,4 +1,4 @@
#include "Perlin.h" #include "Perlin.h"
#include <cmath> #include <cmath>
#include <numeric> #include <numeric>
#include <random> #include <random>
@@ -9,19 +9,19 @@ namespace ZL {
PerlinNoise::PerlinNoise() { PerlinNoise::PerlinNoise() {
p.resize(256); p.resize(256);
std::iota(p.begin(), p.end(), 0); std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (можно задать seed) // Перемешиваем для случайности (можно задать seed)
std::default_random_engine engine(77777); std::default_random_engine engine(77777);
std::shuffle(p.begin(), p.end(), engine); std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
} }
PerlinNoise::PerlinNoise(uint64_t seed) { PerlinNoise::PerlinNoise(uint64_t seed) {
p.resize(256); p.resize(256);
std::iota(p.begin(), p.end(), 0); std::iota(p.begin(), p.end(), 0);
// Перемешиваем для случайности (используем переданный seed) // Перемешиваем для случайности (используем переданный seed)
std::default_random_engine engine(static_cast<unsigned int>(seed)); std::default_random_engine engine(static_cast<unsigned int>(seed));
std::shuffle(p.begin(), p.end(), engine); std::shuffle(p.begin(), p.end(), engine);
p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения p.insert(p.end(), p.begin(), p.end()); // Дублируем для переполнения
} }
float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); } float PerlinNoise::fade(float t) { return t * t * t * (t * (t * 6 - 15) + 10); }
@@ -58,13 +58,13 @@ namespace ZL {
} }
float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) { float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) {
// Частота шума (чем больше, тем больше "холмов") // Частота шума (чем больше, тем больше "холмов")
float frequency = 7.0f; float frequency = 7.0f;
// Получаем значение шума (обычно от -1 до 1) // Получаем значение шума (обычно от -1 до 1)
float noiseValue = noise(pos(0) * frequency, pos(1) * frequency, pos(2) * frequency); float noiseValue = noise(pos(0) * frequency, pos(1) * frequency, pos(2) * frequency);
// Масштабируем: хотим отклонение от 1.0 до 1.1 (примерно) // Масштабируем: хотим отклонение от 1.0 до 1.1 (примерно)
float height = 1.0f + (noiseValue * noiseCoeff); float height = 1.0f + (noiseValue * noiseCoeff);
return height; return height;
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <cstdint> #include <cstdint>
+5 -5
View File
@@ -1,4 +1,4 @@
#include "TaskManager.h" #include "TaskManager.h"
namespace ZL namespace ZL
@@ -27,8 +27,8 @@ namespace ZL
TaskManager::~TaskManager() { TaskManager::~TaskManager() {
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется
ioContext.stop(); // Опционально: немедленная остановка ioContext.stop(); // Опционально: немедленная остановка
for (auto& t : workers) { for (auto& t : workers) {
if (t.joinable()) t.join(); if (t.joinable()) t.join();
} }
@@ -51,7 +51,7 @@ namespace ZL
mainThreadTasks.pop(); mainThreadTasks.pop();
} }
#else #else
// Извлекаем только одну задачу, чтобы не блокировать update надолго // Извлекаем только одну задачу, чтобы не блокировать update надолго
{ {
std::lock_guard<std::mutex> lock(mainThreadMutex); std::lock_guard<std::mutex> lock(mainThreadMutex);
if (!mainThreadTasks.empty()) { if (!mainThreadTasks.empty()) {
@@ -62,7 +62,7 @@ namespace ZL
#endif #endif
if (task) { if (task) {
task(); // Здесь выполняется RefreshVBO или загрузка текстуры task(); // Здесь выполняется RefreshVBO или загрузка текстуры
} }
} }
} }
+3 -3
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#ifndef EMSCRIPTEN #ifndef EMSCRIPTEN
#include <boost/asio.hpp> #include <boost/asio.hpp>
@@ -23,7 +23,7 @@ namespace ZL {
public: public:
TaskManager(size_t threadCount = 2); TaskManager(size_t threadCount = 2);
// Метод для добавления фоновой задачи // ћетод дл¤ добавлени¤ фоновой задачи
void EnqueueBackgroundTask(std::function<void()> task); void EnqueueBackgroundTask(std::function<void()> task);
// Graceful shutdown // Graceful shutdown
@@ -49,7 +49,7 @@ namespace ZL {
public: public:
void EnqueueMainThreadTask(std::function<void()> task); void EnqueueMainThreadTask(std::function<void()> task);
// Выполнение задач по одной (или пачкой) за кадр // ¬ыполнение задач по одной (или пачкой) за кадр
void processMainThreadTasks(); void processMainThreadTasks();
}; };
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once #pragma once
#include <string> #include <string>
#include <vector> #include <vector>