Merge branch 'main' into linux
This commit is contained in:
commit
d151d4bfaf
64 files changed
+679
-372
No files matched your search
+6
-1
@@ -1,9 +1,14 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
Then activate the environment:
|
||||
```
|
||||
C:\Work\Projects\emsdk\emsdk.bat activate latest
|
||||
C:\Work\Projects\emsdk\emsdk_env.bat
|
||||
```
|
||||
|
||||
@@ -17,23 +17,10 @@
|
||||
width: 100vw; height: 100vh;
|
||||
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%); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button id="fs-button">Fullscreen</button>
|
||||
<div id="status">Downloading...</div>
|
||||
<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() {
|
||||
// Chrome на Android обновляет innerWidth/Height не мгновенно.
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -65,8 +65,8 @@
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/game_over/Filledbuttons.png",
|
||||
"hover": "resources/game_over/Filledbuttons.png",
|
||||
"pressed": "resources/game_over/Filledbuttons.png"
|
||||
"hover": "resources/game_over/Variant5.png",
|
||||
"pressed": "resources/game_over/Variant6.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -50,8 +50,8 @@
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/single.png",
|
||||
"hover": "resources/main_menu/single.png",
|
||||
"pressed": "resources/main_menu/single.png"
|
||||
"hover": "resources/main_menu/Variant5.png",
|
||||
"pressed": "resources/main_menu/Variant6.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -61,8 +61,8 @@
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/main_menu/multi.png",
|
||||
"hover": "resources/main_menu/multi.png",
|
||||
"pressed": "resources/main_menu/multi.png"
|
||||
"hover": "resources/main_menu/Variant7.png",
|
||||
"pressed": "resources/main_menu/Variant8.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"emissionRate": 100.0,
|
||||
"emissionRate": 10.0,
|
||||
"maxParticles": 200,
|
||||
"particleSize": 0.3,
|
||||
"biasX": 0.3,
|
||||
"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],
|
||||
|
||||
@@ -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": [
|
||||
{ "position": [0.0, 0.0, 0.0] }
|
||||
],
|
||||
"texture": "resources/spark_white.png",
|
||||
"speedRange": [10.0, 30.0],
|
||||
"texture": "resources/spark2.png",
|
||||
"speedRange": [5.0, 10.0],
|
||||
"zSpeedRange": [-1.0, 1.0],
|
||||
"scaleRange": [0.5, 1.0],
|
||||
"scaleRange": [0.5, 2.0],
|
||||
"lifeTimeRange": [200.0, 800.0],
|
||||
"emissionRate": 50.0,
|
||||
"maxParticles": 10,
|
||||
"particleSize": 0.5,
|
||||
"emissionRate": 30.0,
|
||||
"maxParticles": 150,
|
||||
"particleSize": 1.0,
|
||||
"biasX": 0.1,
|
||||
"shaderProgramName": "default"
|
||||
}
|
||||
+48
-16
@@ -6,6 +6,20 @@
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"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",
|
||||
"name": "shootButton",
|
||||
@@ -16,9 +30,10 @@
|
||||
"horizontal_gravity": "right",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/shoot_normal.png",
|
||||
"hover": "resources/shoot_hover.png",
|
||||
"pressed": "resources/shoot_pressed.png"
|
||||
"normal": "resources/fire.png",
|
||||
"hover": "resources/fire2.png",
|
||||
"pressed": "resources/fire2.png",
|
||||
"disabled": "resources/fire_disabled.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -31,25 +46,42 @@
|
||||
"horizontal_gravity": "left",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"normal": "resources/shoot_normal.png",
|
||||
"hover": "resources/shoot_hover.png",
|
||||
"pressed": "resources/shoot_pressed.png"
|
||||
"normal": "resources/fire.png",
|
||||
"hover": "resources/fire2.png",
|
||||
"pressed": "resources/fire2.png",
|
||||
"disabled": "resources/fire_disabled.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Slider",
|
||||
"name": "velocitySlider",
|
||||
"x": 10,
|
||||
"y": 200,
|
||||
"width": 80,
|
||||
"height": 300,
|
||||
"value": 0.0,
|
||||
"orientation": "vertical",
|
||||
"type": "Button",
|
||||
"name": "minusButton",
|
||||
"x": -20,
|
||||
"y": 110,
|
||||
"width": 150,
|
||||
"height": 150,
|
||||
"border" : 20,
|
||||
"horizontal_gravity": "right",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"track": "resources/velocitySliderTexture.png",
|
||||
"knob": "resources/velocitySliderButton.png"
|
||||
"normal": "resources/button_minus.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
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -26,6 +26,7 @@ add_subdirectory("${BOOST_SRC_DIR}/libs/predef" boost-predef-build EXCLUDE_FROM_
|
||||
|
||||
# Исполняемый файл сервера
|
||||
add_executable(Server
|
||||
server.h
|
||||
server.cpp
|
||||
../src/network/ClientState.h
|
||||
../src/network/ClientState.cpp
|
||||
|
||||
+146
-238
@@ -1,44 +1,11 @@
|
||||
#include <boost/beast/core.hpp>
|
||||
#include "server.h"
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <Eigen/Dense>
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
#include "../src/network/ClientState.h"
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#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> tokens;
|
||||
@@ -50,75 +17,30 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
|
||||
return tokens;
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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<class 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;
|
||||
|
||||
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) {
|
||||
Session::Session(Server& server, tcp::socket&& socket, int id)
|
||||
: server_(server)
|
||||
, ws_(std::move(socket))
|
||||
, id_(id)
|
||||
, lastReceivedTime_(std::chrono::system_clock::now()) {
|
||||
}
|
||||
|
||||
int get_id() const { return id_; }
|
||||
bool Session::is_timed_out(std::chrono::system_clock::time_point now) const {
|
||||
if (!joined_) return false;
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastReceivedTime_).count();
|
||||
return elapsed > PLAYER_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool hasSpawnReserved() const { return hasReservedSpawn_; }
|
||||
const Eigen::Vector3f& reservedSpawn() const { return reservedSpawn_; }
|
||||
void Session::force_disconnect() {
|
||||
ws_.async_close(websocket::close_code::normal,
|
||||
[self = shared_from_this()](beast::error_code) {});
|
||||
}
|
||||
|
||||
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
|
||||
int Session::get_id() const { return id_; }
|
||||
|
||||
bool Session::hasSpawnReserved() const { return hasReservedSpawn_; }
|
||||
const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; }
|
||||
|
||||
bool Session::fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const {
|
||||
if (timedClientStates.canFetchClientStateAtTime(targetTime)) {
|
||||
outState = timedClientStates.fetchClientStateAtTime(targetTime);
|
||||
return true;
|
||||
@@ -126,7 +48,7 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void send_message(const std::string& msg) {
|
||||
void Session::send_message(const std::string& msg) {
|
||||
auto ss = std::make_shared<std::string>(msg);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(writeMutex_);
|
||||
@@ -135,10 +57,10 @@ public:
|
||||
doWrite();
|
||||
}
|
||||
|
||||
void run() {
|
||||
void Session::run() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
g_sessions.push_back(shared_from_this());
|
||||
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
|
||||
server_.g_sessions.push_back(shared_from_this());
|
||||
}
|
||||
|
||||
ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
|
||||
@@ -148,7 +70,7 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
bool IsMessageValid(const std::string& fullMessage) {
|
||||
bool Session::IsMessageValid(const std::string& fullMessage) {
|
||||
#ifdef ENABLE_NETWORK_CHECKSUM
|
||||
size_t hashPos = fullMessage.find("#hash:");
|
||||
if (hashPos == std::string::npos) {
|
||||
@@ -170,16 +92,14 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void sendBoxesToClient() {
|
||||
std::lock_guard<std::mutex> lock(g_boxes_mutex);
|
||||
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 < g_serverBoxes.size(); ++i) {
|
||||
const auto& box = g_serverBoxes[i];
|
||||
for (size_t i = 0; i < server_.g_serverBoxes.size(); ++i) {
|
||||
const auto& box = server_.g_serverBoxes[i];
|
||||
|
||||
if (box.destroyed) continue;
|
||||
|
||||
@@ -199,14 +119,11 @@ private:
|
||||
"0";
|
||||
}
|
||||
|
||||
// Если все коробки уничтожены — отправится просто "BOXES:" (это нормально)
|
||||
send_message(boxMsg);
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
|
||||
void init()
|
||||
void Session::init()
|
||||
{
|
||||
sendBoxesToClient();
|
||||
|
||||
@@ -224,23 +141,17 @@ public:
|
||||
});
|
||||
}
|
||||
|
||||
ClientState get_latest_state(std::chrono::system_clock::time_point now) {
|
||||
ClientState Session::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() {
|
||||
void Session::doWrite() {
|
||||
std::lock_guard<std::mutex> lock(writeMutex_);
|
||||
if (is_writing_ || writeQueue_.empty()) {
|
||||
return;
|
||||
@@ -263,17 +174,20 @@ public:
|
||||
self->doWrite();
|
||||
});
|
||||
}
|
||||
private:
|
||||
|
||||
void do_read() {
|
||||
void Session::do_read() {
|
||||
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
||||
if (ec) {
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
|
||||
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();
|
||||
}), g_sessions.end());
|
||||
std::cout << "Client " << self->id_ << " disconnected\n";
|
||||
}), self->server_.g_sessions.end());
|
||||
std::cout << "Client " << self->id_ << " removed from session list\n";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,11 +199,12 @@ private:
|
||||
});
|
||||
}
|
||||
|
||||
void process_message(const std::string& msg) {
|
||||
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;
|
||||
@@ -316,7 +231,7 @@ private:
|
||||
uint64_t now_ms = static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
|
||||
|
||||
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
|
||||
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
|
||||
this->hasReservedSpawn_ = true;
|
||||
this->reservedSpawn_ = spawnPos;
|
||||
|
||||
@@ -342,27 +257,16 @@ private:
|
||||
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);
|
||||
for (auto& session : g_sessions) {
|
||||
if (session->get_id() == id_) continue;
|
||||
session->send_message(eventMsg);
|
||||
}
|
||||
}
|
||||
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(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) {
|
||||
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);
|
||||
|
||||
@@ -376,8 +280,8 @@ private:
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
||||
if (g_dead_players.find(id_) != g_dead_players.end()) {
|
||||
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;
|
||||
}
|
||||
@@ -399,8 +303,8 @@ private:
|
||||
}
|
||||
else if (type == "RESPAWN") {
|
||||
{
|
||||
std::lock_guard<std::mutex> gd(g_dead_mutex);
|
||||
g_dead_players.erase(id_);
|
||||
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
|
||||
server_.g_dead_players.erase(id_);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -410,7 +314,7 @@ private:
|
||||
ClientState st;
|
||||
st.id = id_;
|
||||
|
||||
Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_);
|
||||
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
|
||||
st.position = spawnPos;
|
||||
|
||||
this->hasReservedSpawn_ = true;
|
||||
@@ -431,13 +335,13 @@ private:
|
||||
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
|
||||
);
|
||||
std::string respawnMsg = "RESPAWN_ACK:" + std::to_string(id_);
|
||||
broadcastToAll(respawnMsg);
|
||||
server_.broadcastToAll(respawnMsg);
|
||||
|
||||
std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
|
||||
broadcastToAll(playerInfo);
|
||||
server_.broadcastToAll(playerInfo);
|
||||
|
||||
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
|
||||
broadcastToAll(eventMsg);
|
||||
server_.broadcastToAll(eventMsg);
|
||||
|
||||
std::cout << "Server: Player " << id_ << " respawned, broadcasted RESPAWN_ACK, PLAYERINFO and initial UPD\n";
|
||||
}
|
||||
@@ -454,12 +358,6 @@ private:
|
||||
);
|
||||
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) + ":" +
|
||||
@@ -472,50 +370,14 @@ private:
|
||||
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);
|
||||
}
|
||||
|
||||
server_.broadcastToAllExceptId(broadcast, id_);
|
||||
|
||||
server_.createProjectile(id_, pos, dir, 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 < 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId)
|
||||
Eigen::Vector3f Server::PickSafeSpawnPos(int forPlayerId)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
void broadcastToAll(const std::string& message) {
|
||||
void Server::broadcastToAll(const std::string& message) {
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
for (const auto& session : g_sessions) {
|
||||
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();
|
||||
|
||||
@@ -595,26 +497,24 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
||||
uint64_t now_ms = static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
|
||||
|
||||
// --- Snapshot every 500ms ---
|
||||
/*if (std::chrono::duration_cast<std::chrono::milliseconds>(now - last_snapshot_time).count() >= 500) {
|
||||
last_snapshot_time = now;
|
||||
|
||||
std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms);
|
||||
|
||||
// --- Detect and force-disconnect timed-out players ---
|
||||
{
|
||||
std::vector<std::shared_ptr<Session>> timedOut;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
for (auto& session : g_sessions) {
|
||||
ClientState st = session->get_latest_state(now);
|
||||
snapshot_msg += "|" + std::to_string(session->get_id()) + ":" + st.formPingMessageContent();
|
||||
if (session->is_timed_out(now)) {
|
||||
timedOut.push_back(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& session : timedOut) {
|
||||
std::cout << "Server: Player " << session->get_id()
|
||||
<< " timed out after " << PLAYER_TIMEOUT_MS << "ms, forcing disconnect\n";
|
||||
session->force_disconnect();
|
||||
}
|
||||
for (auto& session : g_sessions) {
|
||||
session->send_message(snapshot_msg);
|
||||
}
|
||||
}*/
|
||||
|
||||
// --- 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);
|
||||
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 ---
|
||||
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;
|
||||
update_world(timer, ioc);
|
||||
update_world();
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<ServerBox> generateServerBoxes(int count) {
|
||||
std::vector<ServerBox> Server::generateServerBoxes(int count) {
|
||||
std::vector<ServerBox> boxes;
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
@@ -889,34 +789,42 @@ std::vector<ServerBox> generateServerBoxes(int count) {
|
||||
return boxes;
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
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);
|
||||
//g_serverBoxes = generateServerBoxes(1);
|
||||
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() {
|
||||
try {
|
||||
net::io_context ioc;
|
||||
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";
|
||||
|
||||
auto do_accept = [&](auto& self_fn) -> void {
|
||||
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);
|
||||
server.update_world();
|
||||
ioc.run();
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
|
||||
+140
@@ -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
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "render/Renderer.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef AUDIO
|
||||
#ifdef AUDIO
|
||||
|
||||
#include "AudioPlayerAsync.h"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#ifdef AUDIO
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "BoneAnimatedModel.h"
|
||||
#include "BoneAnimatedModel.h"
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "render/Renderer.h"
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "Environment.h"
|
||||
#include "Environment.h"
|
||||
|
||||
#include "utils/Utils.h"
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#ifdef __linux__
|
||||
#include <SDL2/SDL.h>
|
||||
#endif
|
||||
|
||||
@@ -150,6 +150,18 @@ namespace ZL
|
||||
Environment::shipState.nickname = nickname;
|
||||
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;
|
||||
ClientState st = Environment::shipState;
|
||||
st.id = localClient->GetClientId();
|
||||
@@ -230,6 +242,10 @@ namespace ZL
|
||||
}
|
||||
|
||||
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) {
|
||||
drawLoading();
|
||||
}
|
||||
|
||||
+17
-1
@@ -1,4 +1,4 @@
|
||||
#include "MenuManager.h"
|
||||
#include "MenuManager.h"
|
||||
|
||||
|
||||
namespace ZL {
|
||||
@@ -103,6 +103,22 @@ namespace ZL {
|
||||
uiManager.setButtonCallback("shootButton2", [this](const std::string& name) {
|
||||
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) {
|
||||
|
||||
int newVel = roundf(value * 10);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "render/Renderer.h"
|
||||
#include "Environment.h"
|
||||
#include "render/TextureManager.h"
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "Projectile.h"
|
||||
#include "Projectile.h"
|
||||
|
||||
namespace ZL {
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
|
||||
+83
-11
@@ -1,4 +1,4 @@
|
||||
#include "Space.h"
|
||||
#include "Space.h"
|
||||
#include "AnimatedModel.h"
|
||||
#include "BoneAnimatedModel.h"
|
||||
#include "planet/PlanetData.h"
|
||||
@@ -282,6 +282,20 @@ namespace ZL
|
||||
|
||||
menuManager.onVelocityChanged = [this](float 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]() {
|
||||
@@ -289,10 +303,13 @@ namespace ZL
|
||||
};
|
||||
|
||||
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 explosionCfgLoaded = explosionEmitter.loadFromJsonFile("resources/config/explosion_config.json", renderer, CONST_ZIP_FILE);
|
||||
explosionEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
//projectileEmitter.setEmissionPoints({ Vector3f{0,0,45000} });
|
||||
//projectileEmitter.setUseWorldSpace(true);
|
||||
|
||||
|
||||
cubemapTexture = std::make_shared<Texture>(
|
||||
std::array<TextureDataStruct, 6>{
|
||||
@@ -315,7 +332,9 @@ namespace ZL
|
||||
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.Move(Vector3f{ 1.2, 0, -5 });
|
||||
spaceshipBase.Scale(0.4f);
|
||||
|
||||
spaceship.AssignFrom(spaceshipBase);
|
||||
spaceship.RefreshVBO();
|
||||
@@ -501,12 +520,18 @@ namespace ZL
|
||||
renderer.EnableVertexAttribArray(vTexCoordName);
|
||||
glEnable(GL_BLEND);
|
||||
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) {
|
||||
if (p && p->isActive()) {
|
||||
p->draw(renderer);
|
||||
//p->draw(renderer);
|
||||
p->projectileEmitter.draw(renderer, Environment::zoom, Environment::width, Environment::height);
|
||||
}
|
||||
}
|
||||
renderer.PopMatrix();
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
renderer.DisableVertexAttribArray(vPositionName);
|
||||
@@ -519,12 +544,17 @@ namespace ZL
|
||||
renderer.PushMatrix();
|
||||
renderer.TranslateMatrix({ 0, 0, 16 });
|
||||
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();
|
||||
}
|
||||
|
||||
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());
|
||||
@@ -1450,7 +1480,17 @@ namespace ZL
|
||||
|
||||
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));
|
||||
|
||||
if (firePressed)
|
||||
@@ -1594,9 +1634,9 @@ namespace ZL
|
||||
for (const auto& p : projectiles) {
|
||||
if (p && p->isActive()) {
|
||||
Vector3f worldPos = p->getPosition();
|
||||
Vector3f rel = worldPos - Environment::shipState.position;
|
||||
Vector3f camPos = Environment::inverseShipMatrix * rel;
|
||||
p->projectileEmitter.setEmissionPoints({ camPos });
|
||||
//Vector3f rel = worldPos - Environment::shipState.position;
|
||||
//Vector3f camPos = Environment::inverseShipMatrix * rel;
|
||||
p->projectileEmitter.setEmissionPoints({ worldPos });
|
||||
p->projectileEmitter.emit();
|
||||
p->projectileEmitter.update(static_cast<float>(delta));
|
||||
}
|
||||
@@ -1610,16 +1650,25 @@ namespace ZL
|
||||
projectileEmitter.setEmissionPoints(std::vector<Vector3f>());
|
||||
}*/
|
||||
|
||||
if (Environment::shipState.velocity > 0.1f) {
|
||||
|
||||
sparkEmitterPtr->setIsActive(true);
|
||||
|
||||
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()) {
|
||||
sparkEmitter.setEmissionPoints(shipCameraPoints);
|
||||
sparkEmitterPtr->setEmissionPoints(shipCameraPoints);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sparkEmitterPtr->setIsActive(false);
|
||||
}
|
||||
|
||||
sparkEmitter.update(static_cast<float>(delta));
|
||||
sparkEmitterPtr->update(static_cast<float>(delta));
|
||||
//projectileEmitter.update(static_cast<float>(delta));
|
||||
|
||||
explosionEmitter.update(static_cast<float>(delta));
|
||||
@@ -1773,6 +1822,18 @@ namespace ZL
|
||||
|
||||
void Space::update() {
|
||||
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();
|
||||
if (!pending.empty()) {
|
||||
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();
|
||||
if (!boxDestructions.empty()) {
|
||||
std::cout << "Game: Received " << boxDestructions.size() << " box destruction events" << std::endl;
|
||||
|
||||
+3
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "render/Renderer.h"
|
||||
#include "Environment.h"
|
||||
@@ -88,6 +88,7 @@ namespace ZL {
|
||||
VertexDataStruct boxBase;
|
||||
|
||||
SparkEmitter sparkEmitter;
|
||||
SparkEmitter sparkEmitterCargo;
|
||||
SparkEmitter projectileEmitter;
|
||||
SparkEmitter explosionEmitter;
|
||||
PlanetObject planetObject;
|
||||
@@ -123,6 +124,7 @@ namespace ZL {
|
||||
|
||||
std::unordered_set<int> deadRemotePlayers;
|
||||
int playerScore = 0;
|
||||
bool wasConnectedToServer = false;
|
||||
|
||||
static constexpr float TARGET_MAX_DIST = 50000.0f;
|
||||
static constexpr float TARGET_MAX_DIST_SQ = TARGET_MAX_DIST * TARGET_MAX_DIST;
|
||||
|
||||
+34
-4
@@ -74,7 +74,7 @@ namespace ZL {
|
||||
texture = tex;
|
||||
}
|
||||
|
||||
void SparkEmitter::prepareDrawData() {
|
||||
void SparkEmitter::prepareDrawData(bool withRotation) {
|
||||
if (!drawDataDirty) return;
|
||||
|
||||
drawPositions.clear();
|
||||
@@ -91,7 +91,7 @@ namespace ZL {
|
||||
for (const auto& particle : particles) {
|
||||
if (particle.active) {
|
||||
Vector3f posCam;
|
||||
if (useWorldSpace) {
|
||||
if (withRotation) {
|
||||
Vector3f rel = particle.position - Environment::shipState.position;
|
||||
posCam = Environment::inverseShipMatrix * rel;
|
||||
}
|
||||
@@ -120,6 +120,29 @@ namespace ZL {
|
||||
|
||||
float size = particleSize * particle.scale;
|
||||
|
||||
if (withRotation)
|
||||
{
|
||||
|
||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
|
||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||
|
||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam);
|
||||
drawTexCoords.push_back({ 0.0f, 1.0f });
|
||||
|
||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size,size, 0 } + posCam);
|
||||
drawTexCoords.push_back({ 1.0f, 1.0f });
|
||||
|
||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam);
|
||||
drawTexCoords.push_back({ 0.0f, 0.0f });
|
||||
|
||||
drawPositions.push_back(Environment::shipState.rotation * Vector3f{ size, size,0 } + posCam);
|
||||
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 });
|
||||
|
||||
@@ -137,12 +160,19 @@ namespace ZL {
|
||||
|
||||
drawPositions.push_back({ posCam(0) + size, posCam(1) - size, posCam(2) });
|
||||
drawTexCoords.push_back({ 1.0f, 0.0f });
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
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!");
|
||||
}
|
||||
|
||||
prepareDrawData();
|
||||
prepareDrawData(withRotation);
|
||||
|
||||
if (drawPositions.empty()) {
|
||||
return;
|
||||
|
||||
+7
-1
@@ -50,7 +50,7 @@ namespace ZL {
|
||||
std::string shaderProgramName;
|
||||
|
||||
bool configured;
|
||||
void prepareDrawData();
|
||||
void prepareDrawData(bool withRotation);
|
||||
bool useWorldSpace;
|
||||
|
||||
public:
|
||||
@@ -76,11 +76,17 @@ namespace ZL {
|
||||
void emit();
|
||||
|
||||
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;
|
||||
size_t getActiveParticleCount() const;
|
||||
std::shared_ptr<Texture> getTexture() const { return texture; }
|
||||
|
||||
void setIsActive(bool newActive)
|
||||
{
|
||||
isActive = newActive;
|
||||
}
|
||||
|
||||
private:
|
||||
void initParticle(SparkParticle& particle, int emitterIndex);
|
||||
Vector3f getRandomVelocity(int emitterIndex);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#include "TextModel.h"
|
||||
#include "TextModel.h"
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "render/Renderer.h"
|
||||
#include <unordered_map>
|
||||
|
||||
+14
-3
@@ -1,4 +1,4 @@
|
||||
#include "UiManager.h"
|
||||
#include "UiManager.h"
|
||||
#include "utils/Utils.h"
|
||||
#include "render/TextRenderer.h"
|
||||
#include <fstream>
|
||||
@@ -57,6 +57,7 @@ namespace ZL {
|
||||
case ButtonState::Normal: tex = &texNormal; break;
|
||||
case ButtonState::Hover: tex = &texHover; break;
|
||||
case ButtonState::Pressed: tex = &texPressed; break;
|
||||
case ButtonState::Disabled: tex = &texDisabled; break;
|
||||
}
|
||||
if (!(*tex)) return;
|
||||
|
||||
@@ -290,6 +291,9 @@ namespace ZL {
|
||||
btn->texNormal = loadTex("normal");
|
||||
btn->texHover = loadTex("hover");
|
||||
btn->texPressed = loadTex("pressed");
|
||||
btn->texDisabled = loadTex("disabled");
|
||||
|
||||
btn->border = j.value("border", 0.0f);
|
||||
|
||||
node->button = btn;
|
||||
}
|
||||
@@ -1049,13 +1053,16 @@ namespace ZL {
|
||||
|
||||
void UiManager::onMouseMove(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->rect.contains((float)x, (float)y)) {
|
||||
if (b->state != ButtonState::Disabled)
|
||||
{
|
||||
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
|
||||
if (b->state != ButtonState::Pressed) b->state = ButtonState::Hover;
|
||||
}
|
||||
else {
|
||||
if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pressedSlider) {
|
||||
auto s = pressedSlider;
|
||||
@@ -1074,13 +1081,17 @@ namespace ZL {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void UiManager::onMouseDown(int x, int y) {
|
||||
for (auto& b : buttons) {
|
||||
if (b->rect.contains((float)x, (float)y)) {
|
||||
if (b->state != ButtonState::Disabled)
|
||||
{
|
||||
if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) {
|
||||
b->state = ButtonState::Pressed;
|
||||
pressedButton = b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& s : sliders) {
|
||||
if (s->rect.contains((float)x, (float)y)) {
|
||||
|
||||
+9
-2
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
@@ -23,12 +23,17 @@ namespace ZL {
|
||||
bool contains(float px, float py) const {
|
||||
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 {
|
||||
Normal,
|
||||
Hover,
|
||||
Pressed
|
||||
Pressed,
|
||||
Disabled
|
||||
};
|
||||
|
||||
enum class LayoutType {
|
||||
@@ -76,9 +81,11 @@ namespace ZL {
|
||||
struct UiButton {
|
||||
std::string name;
|
||||
UiRect rect;
|
||||
float border = 0;
|
||||
std::shared_ptr<Texture> texNormal;
|
||||
std::shared_ptr<Texture> texHover;
|
||||
std::shared_ptr<Texture> texPressed;
|
||||
std::shared_ptr<Texture> texDisabled;
|
||||
ButtonState state = ButtonState::Normal;
|
||||
|
||||
VertexRenderStruct mesh;
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
#include "Game.h"
|
||||
#include "Game.h"
|
||||
#include "Environment.h"
|
||||
#include <iostream>
|
||||
#ifdef __ANDROID__
|
||||
@@ -26,10 +26,6 @@ ZL::Game game;
|
||||
|
||||
#ifdef EMSCRIPTEN
|
||||
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();
|
||||
}
|
||||
#else
|
||||
@@ -55,8 +51,8 @@ EM_BOOL onWebGLContextRestored(int /*eventType*/, const void* /*reserved*/, void
|
||||
|
||||
static void applyResize(int logicalW, int logicalH) {
|
||||
// Получаем коэффициент плотности пикселей (например, 2.625 на Pixel или 3.0 на Samsung)
|
||||
//double dpr = emscripten_get_device_pixel_ratio();
|
||||
double dpr = 1; // low quality
|
||||
double dpr = emscripten_get_device_pixel_ratio();
|
||||
//double dpr = 1; // low quality
|
||||
|
||||
// Вычисляем реальные физические пиксели
|
||||
int physicalW = static_cast<int>(logicalW * dpr);
|
||||
@@ -83,6 +79,10 @@ static void applyResize(int logicalW, int logicalH) {
|
||||
e.window.data1 = physicalW;
|
||||
e.window.data2 = physicalH;
|
||||
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*/) {
|
||||
|
||||
@@ -14,7 +14,7 @@ constexpr auto NET_SECRET = "880b3713b9ff3e7a94b2712d54679e1f";
|
||||
#define ENABLE_NETWORK_CHECKSUM
|
||||
|
||||
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 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 CLIENT_DELAY = 500; //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_LIFE = 15000.f; //ms
|
||||
|
||||
@@ -50,6 +50,7 @@ namespace ZL {
|
||||
virtual int GetClientId() const { return -1; }
|
||||
virtual std::vector<BoxDestroyedInfo> getPendingBoxDestructions() = 0;
|
||||
virtual int64_t getTimeOffset() const { return 0; }
|
||||
virtual std::vector<int> getPendingDisconnects() { return {}; }
|
||||
|
||||
};
|
||||
}
|
||||
@@ -103,6 +103,19 @@ namespace ZL {
|
||||
|
||||
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) {
|
||||
//auto parts = split(msg, ':');
|
||||
if (parts.size() >= 2) {
|
||||
@@ -356,6 +369,12 @@ namespace ZL {
|
||||
return copy;
|
||||
}
|
||||
|
||||
std::vector<int> WebSocketClientBase::getPendingDisconnects() {
|
||||
std::vector<int> copy;
|
||||
copy.swap(pendingDisconnects_);
|
||||
return copy;
|
||||
}
|
||||
|
||||
std::vector<ClientState> WebSocketClientBase::getPendingSpawns() {
|
||||
std::vector<ClientState> copy;
|
||||
copy.swap(pendingSpawns_);
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace ZL {
|
||||
std::vector<DeathInfo> pendingDeaths_;
|
||||
std::vector<int> pendingRespawns_;
|
||||
std::vector<BoxDestroyedInfo> pendingBoxDestructions_;
|
||||
std::vector<int> pendingDisconnects_;
|
||||
int clientId = -1;
|
||||
int64_t timeOffset = 0;
|
||||
std::vector<ClientState> pendingSpawns_;
|
||||
@@ -41,7 +42,7 @@ namespace ZL {
|
||||
return serverBoxes_;
|
||||
}
|
||||
|
||||
std::vector<bool> getServerBoxDestroyedFlags() {
|
||||
std::vector<bool> getServerBoxDestroyedFlags() override {
|
||||
return serverBoxesDestroyed_;
|
||||
}
|
||||
|
||||
@@ -49,6 +50,7 @@ namespace ZL {
|
||||
std::vector<DeathInfo> getPendingDeaths() override;
|
||||
std::vector<int> getPendingRespawns() override;
|
||||
std::vector<BoxDestroyedInfo> getPendingBoxDestructions() override;
|
||||
std::vector<int> getPendingDisconnects() override;
|
||||
std::vector<ClientState> getPendingSpawns();
|
||||
int getClientId() const { return clientId; }
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "FrameBuffer.h"
|
||||
#include "FrameBuffer.h"
|
||||
#include <iostream>
|
||||
#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);
|
||||
|
||||
// Настраиваем фильтрацию
|
||||
// Настраиваем фильтрацию
|
||||
if (useMipmaps) {
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
// Сразу генерируем пустые уровни, чтобы текстура считалась "полной"
|
||||
// Сразу генерируем пустые уровни, чтобы текстура считалась "полной"
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
}
|
||||
else {
|
||||
@@ -49,13 +49,13 @@ namespace ZL {
|
||||
|
||||
void FrameBuffer::Bind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры
|
||||
glViewport(0, 0, width, height); // Важно: устанавливаем вьюпорт под размер текстуры
|
||||
}
|
||||
|
||||
void FrameBuffer::Unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
// Здесь желательно возвращать вьюпорт к размерам экрана,
|
||||
// например, через Environment::width/height
|
||||
// Здесь желательно возвращать вьюпорт к размерам экрана,
|
||||
// например, через Environment::width/height
|
||||
glViewport(0, 0, Environment::width, Environment::height);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "render/OpenGlExtensions.h"
|
||||
#include <memory>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "OpenGlExtensions.h"
|
||||
#include "OpenGlExtensions.h"
|
||||
|
||||
#include "utils/Utils.h"
|
||||
#include <iostream>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "render/Renderer.h"
|
||||
#include "render/Renderer.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace ZL {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "render/OpenGlExtensions.h"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "ShaderManager.h"
|
||||
#include "ShaderManager.h"
|
||||
#include <iostream>
|
||||
#include <SDL.h>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Perlin.h"
|
||||
#include "Perlin.h"
|
||||
#include <cmath>
|
||||
#include <numeric>
|
||||
#include <random>
|
||||
@@ -9,19 +9,19 @@ namespace ZL {
|
||||
PerlinNoise::PerlinNoise() {
|
||||
p.resize(256);
|
||||
std::iota(p.begin(), p.end(), 0);
|
||||
// Перемешиваем для случайности (можно задать seed)
|
||||
// Перемешиваем для случайности (можно задать seed)
|
||||
std::default_random_engine engine(77777);
|
||||
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) {
|
||||
p.resize(256);
|
||||
std::iota(p.begin(), p.end(), 0);
|
||||
// Перемешиваем для случайности (используем переданный seed)
|
||||
// Перемешиваем для случайности (используем переданный seed)
|
||||
std::default_random_engine engine(static_cast<unsigned int>(seed));
|
||||
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); }
|
||||
@@ -58,13 +58,13 @@ namespace ZL {
|
||||
}
|
||||
|
||||
float PerlinNoise::getSurfaceHeight(Eigen::Vector3f pos, float noiseCoeff) {
|
||||
// Частота шума (чем больше, тем больше "холмов")
|
||||
// Частота шума (чем больше, тем больше "холмов")
|
||||
float frequency = 7.0f;
|
||||
|
||||
// Получаем значение шума (обычно от -1 до 1)
|
||||
// Получаем значение шума (обычно от -1 до 1)
|
||||
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);
|
||||
|
||||
return height;
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "TaskManager.h"
|
||||
#include "TaskManager.h"
|
||||
|
||||
|
||||
namespace ZL
|
||||
@@ -27,8 +27,8 @@ namespace ZL
|
||||
|
||||
TaskManager::~TaskManager() {
|
||||
#ifndef EMSCRIPTEN
|
||||
workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется
|
||||
ioContext.stop(); // Опционально: немедленная остановка
|
||||
workGuard.reset(); // Разрешаем ioContext.run() завершиться, когда задач не останется
|
||||
ioContext.stop(); // Опционально: немедленная остановка
|
||||
for (auto& t : workers) {
|
||||
if (t.joinable()) t.join();
|
||||
}
|
||||
@@ -51,7 +51,7 @@ namespace ZL
|
||||
mainThreadTasks.pop();
|
||||
}
|
||||
#else
|
||||
// Извлекаем только одну задачу, чтобы не блокировать update надолго
|
||||
// Извлекаем только одну задачу, чтобы не блокировать update надолго
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mainThreadMutex);
|
||||
if (!mainThreadTasks.empty()) {
|
||||
@@ -62,7 +62,7 @@ namespace ZL
|
||||
#endif
|
||||
|
||||
if (task) {
|
||||
task(); // Здесь выполняется RefreshVBO или загрузка текстуры
|
||||
task(); // Здесь выполняется RefreshVBO или загрузка текстуры
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#ifndef EMSCRIPTEN
|
||||
#include <boost/asio.hpp>
|
||||
@@ -23,7 +23,7 @@ namespace ZL {
|
||||
public:
|
||||
TaskManager(size_t threadCount = 2);
|
||||
|
||||
// Метод для добавления фоновой задачи
|
||||
// ћетод дл¤ добавлени¤ фоновой задачи
|
||||
void EnqueueBackgroundTask(std::function<void()> task);
|
||||
|
||||
// Graceful shutdown
|
||||
@@ -49,7 +49,7 @@ namespace ZL {
|
||||
public:
|
||||
void EnqueueMainThreadTask(std::function<void()> task);
|
||||
|
||||
// Выполнение задач по одной (или пачкой) за кадр
|
||||
// ¬ыполнение задач по одной (или пачкой) за кадр
|
||||
void processMainThreadTasks();
|
||||
};
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
Reference in new issue
Block a user