From 3e2632c5b5c4e6faad3ec536fe5cc849afa70f24 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 5 Mar 2026 20:14:02 +0600 Subject: [PATCH 1/6] added textures --- resources/config/game_over.json | 4 ++-- resources/config/main_menu.json | 8 ++++---- resources/config/ui.json | 12 ++++++------ resources/fire.png | 3 +++ resources/fire2.png | 3 +++ resources/game_over/Variant5.png | 3 +++ resources/game_over/Variant6.png | 3 +++ resources/main_menu/Variant5.png | 3 +++ resources/main_menu/Variant6.png | 3 +++ resources/main_menu/Variant7.png | 3 +++ resources/main_menu/Variant8.png | 3 +++ 11 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 resources/fire.png create mode 100644 resources/fire2.png create mode 100644 resources/game_over/Variant5.png create mode 100644 resources/game_over/Variant6.png create mode 100644 resources/main_menu/Variant5.png create mode 100644 resources/main_menu/Variant6.png create mode 100644 resources/main_menu/Variant7.png create mode 100644 resources/main_menu/Variant8.png diff --git a/resources/config/game_over.json b/resources/config/game_over.json index 6248579..9d43223 100644 --- a/resources/config/game_over.json +++ b/resources/config/game_over.json @@ -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" } }, { diff --git a/resources/config/main_menu.json b/resources/config/main_menu.json index 55bbb1f..3d1c80b 100644 --- a/resources/config/main_menu.json +++ b/resources/config/main_menu.json @@ -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" } }, { diff --git a/resources/config/ui.json b/resources/config/ui.json index fa27292..100b161 100644 --- a/resources/config/ui.json +++ b/resources/config/ui.json @@ -16,9 +16,9 @@ "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/fire.png" } }, { @@ -31,9 +31,9 @@ "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/fire.png" } }, { diff --git a/resources/fire.png b/resources/fire.png new file mode 100644 index 0000000..4ca886d --- /dev/null +++ b/resources/fire.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9eec617d529a9631ceea24c40ffd8665f15f987920bba501ba5234379c56ed2c +size 71713 diff --git a/resources/fire2.png b/resources/fire2.png new file mode 100644 index 0000000..1760044 --- /dev/null +++ b/resources/fire2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6201f627673caf52c13fdaf2303374cb704d1ed8e700d5f6d65cb04a743f3201 +size 91233 diff --git a/resources/game_over/Variant5.png b/resources/game_over/Variant5.png new file mode 100644 index 0000000..705ffcb --- /dev/null +++ b/resources/game_over/Variant5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:76b4ab0bcfedfad0110e907430d31db71fbc61680e767c4e3263ead2afe478ba +size 5729 diff --git a/resources/game_over/Variant6.png b/resources/game_over/Variant6.png new file mode 100644 index 0000000..d18f3bc --- /dev/null +++ b/resources/game_over/Variant6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:119e152699afe2d610ad0f73dafde75269bd8ea0d6973f2e10db20f3c592c213 +size 5653 diff --git a/resources/main_menu/Variant5.png b/resources/main_menu/Variant5.png new file mode 100644 index 0000000..f52915a --- /dev/null +++ b/resources/main_menu/Variant5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f07e77372d0a131772303b7cb0c89d34570cbc4d1822378f3565e724594636b +size 4866 diff --git a/resources/main_menu/Variant6.png b/resources/main_menu/Variant6.png new file mode 100644 index 0000000..c5fc531 --- /dev/null +++ b/resources/main_menu/Variant6.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:15efda31a108efcb4cbf3d7d5e4d4f99c96f2464390f3a9b01c5aa310580938b +size 4427 diff --git a/resources/main_menu/Variant7.png b/resources/main_menu/Variant7.png new file mode 100644 index 0000000..fd6bcf3 --- /dev/null +++ b/resources/main_menu/Variant7.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59d1bec35c88c068e4874824b3fcb60b50f38565f6fa5d3df5660c2b718ec1ca +size 3536 diff --git a/resources/main_menu/Variant8.png b/resources/main_menu/Variant8.png new file mode 100644 index 0000000..d72ae82 --- /dev/null +++ b/resources/main_menu/Variant8.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:02e7369f9ba5bf7f152ea3f62353da7b3c2d75e52f93bd99f5f03e8f2bb76821 +size 3451 From b0b7391939b6123e8f57f7f5720d5cc88b44c7f9 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 5 Mar 2026 19:53:27 +0300 Subject: [PATCH 2/6] Fixed low quality --- src/Game.cpp | 4 ++++ src/Space.cpp | 2 +- src/Space.h | 2 +- src/UiManager.cpp | 2 +- src/UiManager.h | 2 +- src/main.cpp | 14 +++++++------- src/network/WebSocketClientBase.h | 2 +- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/Game.cpp b/src/Game.cpp index 014c42e..6893d5c 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -230,6 +230,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(); } diff --git a/src/Space.cpp b/src/Space.cpp index 4285f90..54c945d 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -1,4 +1,4 @@ -#include "Space.h" +#include "Space.h" #include "AnimatedModel.h" #include "BoneAnimatedModel.h" #include "planet/PlanetData.h" diff --git a/src/Space.h b/src/Space.h index b38b98e..9008a69 100644 --- a/src/Space.h +++ b/src/Space.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "Environment.h" diff --git a/src/UiManager.cpp b/src/UiManager.cpp index f06e47f..1359250 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -1,4 +1,4 @@ -#include "UiManager.h" +#include "UiManager.h" #include "utils/Utils.h" #include "render/TextRenderer.h" #include diff --git a/src/UiManager.h b/src/UiManager.h index 59743d2..56f95ad 100644 --- a/src/UiManager.h +++ b/src/UiManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "render/TextureManager.h" diff --git a/src/main.cpp b/src/main.cpp index dd7224b..6dda7cb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,4 @@ -#include "Game.h" +#include "Game.h" #include "Environment.h" #include #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(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*/) { diff --git a/src/network/WebSocketClientBase.h b/src/network/WebSocketClientBase.h index 915952a..eb8cc8c 100644 --- a/src/network/WebSocketClientBase.h +++ b/src/network/WebSocketClientBase.h @@ -41,7 +41,7 @@ namespace ZL { return serverBoxes_; } - std::vector getServerBoxDestroyedFlags() { + std::vector getServerBoxDestroyedFlags() override { return serverBoxesDestroyed_; } From a8c76dee3cff94d4911bb3e6bb46db59d332898a Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Thu, 5 Mar 2026 23:58:35 +0300 Subject: [PATCH 3/6] Working on fixing minor bugs --- proj-web/space-game001plain.html | 24 ------ resources/button_minus.png | 3 + resources/button_minus_pressed.png | 3 + resources/button_plus.png | 3 + resources/button_plus_pressed.png | 3 + resources/config/spark_config.json | 6 +- resources/config/spark_config_cargo.json | 20 +++++ resources/config/spark_projectile_config.json | 12 +-- resources/config/ui.json | 56 ++++++++++--- resources/fire.png | 4 +- resources/fire2.png | 4 +- resources/fire_disabled.png | 3 + resources/game_over/Variant5.png | 4 +- resources/game_over/Variant6.png | 4 +- resources/main_menu/Variant5.png | 4 +- resources/main_menu/Variant6.png | 4 +- resources/main_menu/Variant7.png | 4 +- resources/main_menu/Variant8.png | 4 +- resources/spark.png | 4 +- resources/spark2.png | 4 +- resources/spark3.png | 3 + src/Game.cpp | 12 +++ src/MenuManager.cpp | 16 ++++ src/Projectile.cpp | 2 +- src/Projectile.h | 2 +- src/Space.cpp | 79 +++++++++++++++---- src/Space.h | 1 + src/SparkEmitter.cpp | 64 +++++++++++---- src/SparkEmitter.h | 8 +- src/UiManager.cpp | 27 +++++-- src/UiManager.h | 9 ++- src/network/ClientState.h | 2 +- 32 files changed, 288 insertions(+), 110 deletions(-) create mode 100644 resources/button_minus.png create mode 100644 resources/button_minus_pressed.png create mode 100644 resources/button_plus.png create mode 100644 resources/button_plus_pressed.png create mode 100644 resources/config/spark_config_cargo.json create mode 100644 resources/fire_disabled.png create mode 100644 resources/spark3.png diff --git a/proj-web/space-game001plain.html b/proj-web/space-game001plain.html index 72892cd..d3c1953 100644 --- a/proj-web/space-game001plain.html +++ b/proj-web/space-game001plain.html @@ -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%); } -
Downloading...
@@ -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 не мгновенно. diff --git a/resources/button_minus.png b/resources/button_minus.png new file mode 100644 index 0000000..8fd6fec --- /dev/null +++ b/resources/button_minus.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8425f4b75b63b138810c92449c93baffd39acef9f5c9ead69687c3e8b0c577c6 +size 14922 diff --git a/resources/button_minus_pressed.png b/resources/button_minus_pressed.png new file mode 100644 index 0000000..7cd9a42 --- /dev/null +++ b/resources/button_minus_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:729fc37ad29bd53b13232a0242b89142cbf29c08bfbce8834a403b265ff5a700 +size 28545 diff --git a/resources/button_plus.png b/resources/button_plus.png new file mode 100644 index 0000000..4d48ebc --- /dev/null +++ b/resources/button_plus.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5551fbec116add9541e797b53928e36c8fe8835ad2e2e118241b4148a2ccb4c1 +size 15058 diff --git a/resources/button_plus_pressed.png b/resources/button_plus_pressed.png new file mode 100644 index 0000000..b5e51ce --- /dev/null +++ b/resources/button_plus_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ad20d915c39c968d29d94a3e30099e40ea5a7da8f82d4775a0976fdfacadf93e +size 27821 diff --git a/resources/config/spark_config.json b/resources/config/spark_config.json index cbe6c47..d7ec09e 100644 --- a/resources/config/spark_config.json +++ b/resources/config/spark_config.json @@ -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], diff --git a/resources/config/spark_config_cargo.json b/resources/config/spark_config_cargo.json new file mode 100644 index 0000000..1fbe1a0 --- /dev/null +++ b/resources/config/spark_config_cargo.json @@ -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" +} \ No newline at end of file diff --git a/resources/config/spark_projectile_config.json b/resources/config/spark_projectile_config.json index 7fbe467..9e303e9 100644 --- a/resources/config/spark_projectile_config.json +++ b/resources/config/spark_projectile_config.json @@ -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" } \ No newline at end of file diff --git a/resources/config/ui.json b/resources/config/ui.json index 100b161..06e895a 100644 --- a/resources/config/ui.json +++ b/resources/config/ui.json @@ -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", @@ -18,7 +32,8 @@ "textures": { "normal": "resources/fire.png", "hover": "resources/fire2.png", - "pressed": "resources/fire.png" + "pressed": "resources/fire2.png", + "disabled": "resources/fire_disabled.png" } }, { @@ -33,23 +48,40 @@ "textures": { "normal": "resources/fire.png", "hover": "resources/fire2.png", - "pressed": "resources/fire.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" } } ] diff --git a/resources/fire.png b/resources/fire.png index 4ca886d..07e9346 100644 --- a/resources/fire.png +++ b/resources/fire.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9eec617d529a9631ceea24c40ffd8665f15f987920bba501ba5234379c56ed2c -size 71713 +oid sha256:5efff9f73d98fa230bfa247b3302c530275aba103638d5a220aa09f94bc629df +size 100748 diff --git a/resources/fire2.png b/resources/fire2.png index 1760044..cc9b372 100644 --- a/resources/fire2.png +++ b/resources/fire2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6201f627673caf52c13fdaf2303374cb704d1ed8e700d5f6d65cb04a743f3201 -size 91233 +oid sha256:1a329b0c8847a33061e769891cb04d82544d573fcd836514eb98021340e975a1 +size 116746 diff --git a/resources/fire_disabled.png b/resources/fire_disabled.png new file mode 100644 index 0000000..008d469 --- /dev/null +++ b/resources/fire_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b78ea265d945ccdea12a319ee288f9e99cc472c745300ee1a5f3393726082710 +size 111366 diff --git a/resources/game_over/Variant5.png b/resources/game_over/Variant5.png index 705ffcb..08c15b5 100644 --- a/resources/game_over/Variant5.png +++ b/resources/game_over/Variant5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76b4ab0bcfedfad0110e907430d31db71fbc61680e767c4e3263ead2afe478ba -size 5729 +oid sha256:44b90a4684f39ad8498194a8e130a02a4b5bb241364c8db05a069a05349eb547 +size 10072 diff --git a/resources/game_over/Variant6.png b/resources/game_over/Variant6.png index d18f3bc..58beb4f 100644 --- a/resources/game_over/Variant6.png +++ b/resources/game_over/Variant6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:119e152699afe2d610ad0f73dafde75269bd8ea0d6973f2e10db20f3c592c213 -size 5653 +oid sha256:c2d3e1fb2e45fe0a5641784a65606114a8a0343a3e45a11141f331a38e1bdd88 +size 9597 diff --git a/resources/main_menu/Variant5.png b/resources/main_menu/Variant5.png index f52915a..54c357d 100644 --- a/resources/main_menu/Variant5.png +++ b/resources/main_menu/Variant5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f07e77372d0a131772303b7cb0c89d34570cbc4d1822378f3565e724594636b -size 4866 +oid sha256:a2ef90493bbdbaf7779bb091d49c90613e79c25c4d3b553927d69312b944e0cc +size 8311 diff --git a/resources/main_menu/Variant6.png b/resources/main_menu/Variant6.png index c5fc531..8978164 100644 --- a/resources/main_menu/Variant6.png +++ b/resources/main_menu/Variant6.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:15efda31a108efcb4cbf3d7d5e4d4f99c96f2464390f3a9b01c5aa310580938b -size 4427 +oid sha256:69b42cd78a376d55a0f17dfa83906cd09c264d04832e8f1991f773bb7756b22f +size 7445 diff --git a/resources/main_menu/Variant7.png b/resources/main_menu/Variant7.png index fd6bcf3..2bc3793 100644 --- a/resources/main_menu/Variant7.png +++ b/resources/main_menu/Variant7.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:59d1bec35c88c068e4874824b3fcb60b50f38565f6fa5d3df5660c2b718ec1ca -size 3536 +oid sha256:780846ab6b6487b01054e6a624352fe7fbe1db67c0dfd4c29994ffc45b978239 +size 6593 diff --git a/resources/main_menu/Variant8.png b/resources/main_menu/Variant8.png index d72ae82..17e4e40 100644 --- a/resources/main_menu/Variant8.png +++ b/resources/main_menu/Variant8.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02e7369f9ba5bf7f152ea3f62353da7b3c2d75e52f93bd99f5f03e8f2bb76821 -size 3451 +oid sha256:636a46a4b822d4aad03481c58929bca46c8513ad1e502a48446fd682888699b5 +size 6266 diff --git a/resources/spark.png b/resources/spark.png index 4ed63fd..9f78b0d 100644 --- a/resources/spark.png +++ b/resources/spark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8885a2b593c5dc0e8ac5cf5204bf3d82eb45ad6dae9b56e2bc88a41e61144c89 -size 2222 +oid sha256:fe73ae8d4ac8d878a65a3d84b16075582223bb028d1a431b5c6af257811850c4 +size 2742 diff --git a/resources/spark2.png b/resources/spark2.png index b0e806f..dcc9896 100644 --- a/resources/spark2.png +++ b/resources/spark2.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11cde0c85c95f91eb9ace65cead22a37ba80d215897f32a2d6be1410210c1acf -size 2656 +oid sha256:7a22261dfa7a9f14f79bcac6af502c7092bdebc1d30f3cbd2df4c8e3ba4ac99e +size 2700 diff --git a/resources/spark3.png b/resources/spark3.png new file mode 100644 index 0000000..32049bc --- /dev/null +++ b/resources/spark3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2017634739f985c00a681d1623c7fe37dbeaa52dbc4665b5593e59a9d7e2e753 +size 2656 diff --git a/src/Game.cpp b/src/Game.cpp index 6893d5c..4aa8d80 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -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(); diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index c5b072d..80643fe 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -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); diff --git a/src/Projectile.cpp b/src/Projectile.cpp index 9f98d4f..ee2eaba 100644 --- a/src/Projectile.cpp +++ b/src/Projectile.cpp @@ -1,4 +1,4 @@ -#include "Projectile.h" +#include "Projectile.h" namespace ZL { diff --git a/src/Projectile.h b/src/Projectile.h index e72552e..2f3b4cc 100644 --- a/src/Projectile.h +++ b/src/Projectile.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "render/TextureManager.h" diff --git a/src/Space.cpp b/src/Space.cpp index 54c945d..9b22995 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -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()); - projectileEmitter.setEmissionPoints(std::vector()); + //projectileEmitter.setEmissionPoints({ Vector3f{0,0,45000} }); + //projectileEmitter.setUseWorldSpace(true); + cubemapTexture = std::make_shared( std::array{ @@ -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(delta)); + SparkEmitter* sparkEmitterPtr; + + if (Environment::shipState.shipType == 1) { + sparkEmitterPtr = &sparkEmitterCargo; + } + else + { + sparkEmitterPtr = &sparkEmitter; + + } + sparkEmitterPtr->update(static_cast(delta)); planetObject.update(static_cast(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(delta)); } @@ -1610,16 +1650,25 @@ namespace ZL projectileEmitter.setEmissionPoints(std::vector()); }*/ - std::vector shipCameraPoints; - for (const auto& lp : shipLocalEmissionPoints) { - Vector3f adjusted = lp + Vector3f{ 0.0f, -Environment::zoom * 0.03f, 0.0f }; - shipCameraPoints.push_back(adjusted); + if (Environment::shipState.velocity > 0.1f) { + + sparkEmitterPtr->setIsActive(true); + + std::vector 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()) { - sparkEmitter.setEmissionPoints(shipCameraPoints); + else + { + sparkEmitterPtr->setIsActive(false); } - sparkEmitter.update(static_cast(delta)); + sparkEmitterPtr->update(static_cast(delta)); //projectileEmitter.update(static_cast(delta)); explosionEmitter.update(static_cast(delta)); diff --git a/src/Space.h b/src/Space.h index 9008a69..94f50c6 100644 --- a/src/Space.h +++ b/src/Space.h @@ -88,6 +88,7 @@ namespace ZL { VertexDataStruct boxBase; SparkEmitter sparkEmitter; + SparkEmitter sparkEmitterCargo; SparkEmitter projectileEmitter; SparkEmitter explosionEmitter; PlanetObject planetObject; diff --git a/src/SparkEmitter.cpp b/src/SparkEmitter.cpp index 5f70710..748afd5 100644 --- a/src/SparkEmitter.cpp +++ b/src/SparkEmitter.cpp @@ -74,7 +74,7 @@ namespace ZL { texture = tex; } - void SparkEmitter::prepareDrawData() { + void SparkEmitter::prepareDrawData(bool withRotation) { if (!drawDataDirty) return; drawPositions.clear(); @@ -91,10 +91,10 @@ 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; - } + } else { posCam = particle.position; } @@ -120,29 +120,59 @@ namespace ZL { float size = particleSize * particle.scale; - drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); - drawTexCoords.push_back({ 0.0f, 0.0f }); + if (withRotation) + { - drawPositions.push_back({ posCam(0) - size, posCam(1) + size, posCam(2) }); - drawTexCoords.push_back({ 0.0f, 1.0f }); + drawPositions.push_back(Environment::shipState.rotation * Vector3f{ -size, -size, 0 } + posCam); + 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(Environment::shipState.rotation * Vector3f{ -size, size,0 } + posCam); + drawTexCoords.push_back({ 0.0f, 1.0f }); - drawPositions.push_back({ posCam(0) - size, posCam(1) - size, posCam(2) }); - 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({ posCam(0) + size, posCam(1) + size, posCam(2) }); - 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({ posCam(0) + size, posCam(1) - size, posCam(2) }); - drawTexCoords.push_back({ 1.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 }); + + 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; } - 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; diff --git a/src/SparkEmitter.h b/src/SparkEmitter.h index 812b828..744c32f 100644 --- a/src/SparkEmitter.h +++ b/src/SparkEmitter.h @@ -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& getParticles() const; size_t getActiveParticleCount() const; std::shared_ptr getTexture() const { return texture; } + void setIsActive(bool newActive) + { + isActive = newActive; + } + private: void initParticle(SparkParticle& particle, int emitterIndex); Vector3f getRandomVelocity(int emitterIndex); diff --git a/src/UiManager.cpp b/src/UiManager.cpp index 1359250..8a7bcf9 100644 --- a/src/UiManager.cpp +++ b/src/UiManager.cpp @@ -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,11 +1053,14 @@ 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::Pressed) b->state = ButtonState::Hover; - } - else { - if (b->state != ButtonState::Pressed) b->state = ButtonState::Normal; + 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; + } } } @@ -1074,11 +1081,15 @@ namespace ZL { } } + void UiManager::onMouseDown(int x, int y) { for (auto& b : buttons) { - if (b->rect.contains((float)x, (float)y)) { - b->state = ButtonState::Pressed; - pressedButton = b; + if (b->state != ButtonState::Disabled) + { + if (b->rect.containsConsideringBorder((float)x, (float)y, b->border)) { + b->state = ButtonState::Pressed; + pressedButton = b; + } } } diff --git a/src/UiManager.h b/src/UiManager.h index 56f95ad..a03fa85 100644 --- a/src/UiManager.h +++ b/src/UiManager.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 texNormal; std::shared_ptr texHover; std::shared_ptr texPressed; + std::shared_ptr texDisabled; ButtonState state = ButtonState::Normal; VertexRenderStruct mesh; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 5bbd8b3..0812892 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -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; From c35e093cc5cd3e98561f7cbccb8dd8555175eabc Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 6 Mar 2026 17:51:46 +0300 Subject: [PATCH 4/6] Working on encoding --- src/AnimatedModel.h | 2 +- src/AudioPlayerAsync.cpp | 2 +- src/AudioPlayerAsync.h | 2 +- src/BoneAnimatedModel.cpp | 2 +- src/BoneAnimatedModel.h | 2 +- src/Environment.cpp | 2 +- src/Environment.h | 2 +- src/MenuManager.cpp | 2 +- src/MenuManager.h | 2 +- src/TextModel.cpp | 2 +- src/TextModel.h | 2 +- src/render/FrameBuffer.cpp | 12 ++++++------ src/render/FrameBuffer.h | 2 +- src/render/OpenGlExtensions.cpp | 2 +- src/render/OpenGlExtensions.h | 2 +- src/render/Renderer.cpp | 2 +- src/render/Renderer.h | 2 +- src/render/ShaderManager.cpp | 2 +- src/utils/Perlin.cpp | 16 ++++++++-------- src/utils/Perlin.h | 2 +- src/utils/TaskManager.cpp | 10 +++++----- src/utils/TaskManager.h | 6 +++--- src/utils/Utils.h | 2 +- 23 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/AnimatedModel.h b/src/AnimatedModel.h index 895987d..c11937a 100644 --- a/src/AnimatedModel.h +++ b/src/AnimatedModel.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" diff --git a/src/AudioPlayerAsync.cpp b/src/AudioPlayerAsync.cpp index bb4ed01..879e82a 100644 --- a/src/AudioPlayerAsync.cpp +++ b/src/AudioPlayerAsync.cpp @@ -1,4 +1,4 @@ -#ifdef AUDIO +#ifdef AUDIO #include "AudioPlayerAsync.h" diff --git a/src/AudioPlayerAsync.h b/src/AudioPlayerAsync.h index b034d96..131d6ea 100644 --- a/src/AudioPlayerAsync.h +++ b/src/AudioPlayerAsync.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifdef AUDIO diff --git a/src/BoneAnimatedModel.cpp b/src/BoneAnimatedModel.cpp index 8c32b2b..e3070f4 100644 --- a/src/BoneAnimatedModel.cpp +++ b/src/BoneAnimatedModel.cpp @@ -1,4 +1,4 @@ -#include "BoneAnimatedModel.h" +#include "BoneAnimatedModel.h" #include #include #include diff --git a/src/BoneAnimatedModel.h b/src/BoneAnimatedModel.h index b760546..4c19d7c 100644 --- a/src/BoneAnimatedModel.h +++ b/src/BoneAnimatedModel.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include diff --git a/src/Environment.cpp b/src/Environment.cpp index 9c41b4d..09ab631 100644 --- a/src/Environment.cpp +++ b/src/Environment.cpp @@ -1,4 +1,4 @@ -#include "Environment.h" +#include "Environment.h" #include "utils/Utils.h" diff --git a/src/Environment.h b/src/Environment.h index 17125ac..00fd608 100644 --- a/src/Environment.h +++ b/src/Environment.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifdef __linux__ #include #endif diff --git a/src/MenuManager.cpp b/src/MenuManager.cpp index 80643fe..fcd47f5 100644 --- a/src/MenuManager.cpp +++ b/src/MenuManager.cpp @@ -1,4 +1,4 @@ -#include "MenuManager.h" +#include "MenuManager.h" namespace ZL { diff --git a/src/MenuManager.h b/src/MenuManager.h index 0b33f2b..88757be 100644 --- a/src/MenuManager.h +++ b/src/MenuManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include "Environment.h" #include "render/TextureManager.h" diff --git a/src/TextModel.cpp b/src/TextModel.cpp index 1ee7b98..4896034 100644 --- a/src/TextModel.cpp +++ b/src/TextModel.cpp @@ -1,4 +1,4 @@ -#include "TextModel.h" +#include "TextModel.h" #include #include #include diff --git a/src/TextModel.h b/src/TextModel.h index 12fb91a..d72002e 100644 --- a/src/TextModel.h +++ b/src/TextModel.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/Renderer.h" #include diff --git a/src/render/FrameBuffer.cpp b/src/render/FrameBuffer.cpp index 2216fc5..74d17a1 100644 --- a/src/render/FrameBuffer.cpp +++ b/src/render/FrameBuffer.cpp @@ -1,4 +1,4 @@ -#include "FrameBuffer.h" +#include "FrameBuffer.h" #include #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); } diff --git a/src/render/FrameBuffer.h b/src/render/FrameBuffer.h index 9d4cd04..2c35f4d 100644 --- a/src/render/FrameBuffer.h +++ b/src/render/FrameBuffer.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/OpenGlExtensions.h" #include diff --git a/src/render/OpenGlExtensions.cpp b/src/render/OpenGlExtensions.cpp index e4b3677..f57e1c5 100644 --- a/src/render/OpenGlExtensions.cpp +++ b/src/render/OpenGlExtensions.cpp @@ -1,4 +1,4 @@ -#include "OpenGlExtensions.h" +#include "OpenGlExtensions.h" #include "utils/Utils.h" #include diff --git a/src/render/OpenGlExtensions.h b/src/render/OpenGlExtensions.h index fbb0908..a80ddcb 100644 --- a/src/render/OpenGlExtensions.h +++ b/src/render/OpenGlExtensions.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "SDL.h" diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 721ae54..748b7f3 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1,4 +1,4 @@ -#include "render/Renderer.h" +#include "render/Renderer.h" #include namespace ZL { diff --git a/src/render/Renderer.h b/src/render/Renderer.h index e95adb4..656752a 100644 --- a/src/render/Renderer.h +++ b/src/render/Renderer.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "render/OpenGlExtensions.h" diff --git a/src/render/ShaderManager.cpp b/src/render/ShaderManager.cpp index a45cf64..45c8140 100644 --- a/src/render/ShaderManager.cpp +++ b/src/render/ShaderManager.cpp @@ -1,4 +1,4 @@ -#include "ShaderManager.h" +#include "ShaderManager.h" #include #include diff --git a/src/utils/Perlin.cpp b/src/utils/Perlin.cpp index d03be20..e986aa9 100644 --- a/src/utils/Perlin.cpp +++ b/src/utils/Perlin.cpp @@ -1,4 +1,4 @@ -#include "Perlin.h" +#include "Perlin.h" #include #include #include @@ -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(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; diff --git a/src/utils/Perlin.h b/src/utils/Perlin.h index 5dbf711..4135c6f 100644 --- a/src/utils/Perlin.h +++ b/src/utils/Perlin.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include diff --git a/src/utils/TaskManager.cpp b/src/utils/TaskManager.cpp index c4fe7f1..5670795 100644 --- a/src/utils/TaskManager.cpp +++ b/src/utils/TaskManager.cpp @@ -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 lock(mainThreadMutex); if (!mainThreadTasks.empty()) { @@ -62,7 +62,7 @@ namespace ZL #endif if (task) { - task(); // RefreshVBO + task(); // Здесь выполняется RefreshVBO или загрузка текстуры } } } \ No newline at end of file diff --git a/src/utils/TaskManager.h b/src/utils/TaskManager.h index afb18df..4ea1f08 100644 --- a/src/utils/TaskManager.h +++ b/src/utils/TaskManager.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #ifndef EMSCRIPTEN #include @@ -23,7 +23,7 @@ namespace ZL { public: TaskManager(size_t threadCount = 2); - // + // ћетод дл¤ добавлени¤ фоновой задачи void EnqueueBackgroundTask(std::function task); // Graceful shutdown @@ -49,7 +49,7 @@ namespace ZL { public: void EnqueueMainThreadTask(std::function task); - // ( ) + // ¬ыполнение задач по одной (или пачкой) за кадр void processMainThreadTasks(); }; diff --git a/src/utils/Utils.h b/src/utils/Utils.h index 6ddd132..9b88b58 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include #include From 8528b4dc901bb484d4ff1370775ab00634b4bc0e Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 6 Mar 2026 19:08:55 +0300 Subject: [PATCH 5/6] Clean up server --- server/CMakeLists.txt | 1 + server/server.cpp | 871 ++++++++++++++++++------------------------ server/server.h | 137 +++++++ 3 files changed, 510 insertions(+), 499 deletions(-) create mode 100644 server/server.h diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 641ed8d..2880449 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -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 diff --git a/server/server.cpp b/server/server.cpp index 2a498b0..54168e2 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -1,44 +1,11 @@ -#include +#include "server.h" #include #include #include -#include -#include -#include -#include -#include -#include #include -#include -#define _USE_MATH_DEFINES -#include -#include "../src/network/ClientState.h" #include #include #include -#include - -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 split(const std::string& s, char delimiter) { std::vector tokens; @@ -50,279 +17,292 @@ std::vector split(const std::string& s, char delimiter) { return tokens; } -struct ServerBox { - Eigen::Vector3f position; - Eigen::Matrix3f rotation; - float collisionRadius = 2.0f; - bool destroyed = false; -}; +Session::Session(Server& server, tcp::socket&& socket, int id) + : server_(server) + , ws_(std::move(socket)) + , id_(id) { +} -struct Projectile { - int shooterId = -1; - uint64_t spawnMs = 0; - Eigen::Vector3f pos; - Eigen::Vector3f vel; - float lifeMs = PROJECTILE_LIFE; -}; +int Session::get_id() const { return id_; } -struct BoxDestroyedInfo { - int boxIndex = -1; - uint64_t serverTime = 0; - Eigen::Vector3f position = Eigen::Vector3f::Zero(); - int destroyedBy = -1; -}; +bool Session::hasSpawnReserved() const { return hasReservedSpawn_; } +const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; } -std::vector g_boxDestructions; -std::mutex g_boxDestructions_mutex; - -std::vector g_serverBoxes; -std::mutex g_boxes_mutex; - -std::vector> g_sessions; -std::mutex g_sessions_mutex; - -std::vector g_projectiles; -std::mutex g_projectiles_mutex; - -std::unordered_set g_dead_players; -std::mutex g_dead_mutex; - -class Session; - -void broadcastToAll(const std::string& message); - -class Session : public std::enable_shared_from_this { - websocket::stream ws_; - beast::flat_buffer buffer_; - int id_; - bool is_writing_ = false; - std::queue> 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) { +bool Session::fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const { + if (timedClientStates.canFetchClientStateAtTime(targetTime)) { + outState = timedClientStates.fetchClientStateAtTime(targetTime); + return true; } + return false; +} - int get_id() const { return id_; } - - 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(msg); - { - std::lock_guard lock(writeMutex_); - writeQueue_.push(ss); - } - doWrite(); - } - - void run() { - { - std::lock_guard 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 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() +void Session::send_message(const std::string& msg) { + auto ss = std::make_shared(msg); { - sendBoxesToClient(); - - auto timer = std::make_shared(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( - std::chrono::duration_cast(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 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 lock(self->writeMutex_); - self->writeQueue_.pop(); - self->is_writing_ = false; - } - self->doWrite(); - }); + writeQueue_.push(ss); } -private: + doWrite(); +} - void do_read() { - ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) { - if (ec) { - std::lock_guard lock(g_sessions_mutex); - g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(), - [self](const std::shared_ptr& 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 Session::run() { + { + std::lock_guard lock(server_.g_sessions_mutex); + server_.g_sessions.push_back(shared_from_this()); } - void process_message(const std::string& msg) { - if (!IsMessageValid(msg)) { - std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; - return; - } - std::string cleanMessage = msg.substr(0, msg.find("#hash:")); + ws_.async_accept([self = shared_from_this()](beast::error_code ec) { + if (ec) return; + std::cout << "Client " << self->id_ << " connected\n"; + self->init(); + }); +} - std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl; - auto parts = split(cleanMessage, ':'); +bool Session::IsMessageValid(const std::string& fullMessage) { +#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::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; } - } + std::stringstream ss; + ss << std::hex << expectedHash; - this->nickname = nick; - this->shipType = sType; - this->joined_ = true; + return ss.str() == receivedHashStr; +#else + return true; // В режиме отладки пропускаем всё +#endif +} +void Session::sendBoxesToClient() { + std::lock_guard 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(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( std::chrono::duration_cast(now_tp.time_since_epoch()).count()); - Eigen::Vector3f spawnPos = PickSafeSpawnPos(id_); - this->hasReservedSpawn_ = true; - this->reservedSpawn_ = spawnPos; + self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms)); + self->do_read(); + } + }); +} + +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 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 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) { + std::lock_guard 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) { + return session.get() == self.get(); + }), self->server_.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 Session::process_message(const std::string& msg) { + if (!IsMessageValid(msg)) { + std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl; + return; + } + 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( + std::chrono::duration_cast(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 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 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 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(std::chrono::duration_cast(now_tp.time_since_epoch()).count()); ClientState st; st.id = id_; + + Eigen::Vector3f spawnPos = server_.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; @@ -334,188 +314,53 @@ private: 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_); + server_.broadcastToAll(respawnMsg); - std::string eventMsg = - "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); + std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType); + server_.broadcastToAll(playerInfo); - { - std::lock_guard lock(g_sessions_mutex); - 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 eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent(); + server_.broadcastToAll(eventMsg); - std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType); - - { - std::lock_guard lock(g_sessions_mutex); - for (auto& session : g_sessions) { - if (session->get_id() == this->id_) continue; - session->send_message(info); - } - } - { - std::lock_guard 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 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 gd(g_dead_mutex); - g_dead_players.erase(id_); - } - - { - auto now_tp = std::chrono::system_clock::now(); - uint64_t now_ms = static_cast(std::chrono::duration_cast(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 lock(g_sessions_mutex); - for (auto& session : g_sessions) { - if (session->get_id() != id_) { - session->send_message(broadcast); - } - } - } - - { - const std::vector 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::system_clock::now().time_since_epoch())).count(); - - std::lock_guard 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; - } - } + 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]); -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{}() }; @@ -580,14 +425,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 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 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 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::system_clock::now().time_since_epoch())).count(); + + std::lock_guard 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 +480,6 @@ void update_world(net::steady_timer& timer, net::io_context& ioc) { uint64_t now_ms = static_cast( std::chrono::duration_cast(now.time_since_epoch()).count()); - // --- Snapshot every 500ms --- - /*if (std::chrono::duration_cast(now - last_snapshot_time).count() >= 500) { - last_snapshot_time = now; - - std::string snapshot_msg = "SNAPSHOT:" + std::to_string(now_ms); - - std::lock_guard 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(); - } - 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 lock(g_sessions_mutex); for (auto& sender : g_sessions) { @@ -834,13 +699,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 generateServerBoxes(int count) { +std::vector Server::generateServerBoxes(int count) { std::vector boxes; std::random_device rd; std::mt19937 gen(rd()); @@ -889,34 +754,42 @@ std::vector generateServerBoxes(int count) { return boxes; } +Server::Server(tcp::acceptor& acceptor, net::io_context& ioc) + : acceptor_(acceptor) + , ioc_(ioc) + , timer(ioc_) +{ +} + +void Server::init() +{ + std::lock_guard 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(*this, std::move(socket), next_id++)->run(); + } + accept(); + }); +} + int main() { try { - { - std::lock_guard 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; tcp::acceptor acceptor{ ioc, {tcp::v4(), 8081} }; - int next_id = 1000; + Server server(acceptor, ioc); + + server.accept(); std::cout << "Server started on port 8081...\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(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) { diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..fbeca04 --- /dev/null +++ b/server/server.h @@ -0,0 +1,137 @@ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../src/network/ClientState.h" +#define _USE_MATH_DEFINES +#include + +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 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 { + Server& server_; + websocket::stream ws_; + beast::flat_buffer buffer_; + int id_; + bool is_writing_ = false; + std::queue> 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; + + 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); + +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 g_boxDestructions; + std::mutex g_boxDestructions_mutex; + + std::vector g_serverBoxes; + std::mutex g_boxes_mutex; + + std::vector> g_sessions; + std::mutex g_sessions_mutex; + + std::vector g_projectiles; + std::mutex g_projectiles_mutex; + + std::unordered_set g_dead_players; + std::mutex g_dead_mutex; + + int next_id = 1000; + + std::vector 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(); +}; From ac551122d98446b37cfc996df586d5d0ca220eb2 Mon Sep 17 00:00:00 2001 From: Vladislav Khorev Date: Fri, 6 Mar 2026 20:05:55 +0300 Subject: [PATCH 6/6] Added force disconnect from server --- proj-web/README.md | 7 +++++- server/server.cpp | 39 +++++++++++++++++++++++++++-- server/server.h | 3 +++ src/Space.cpp | 23 +++++++++++++++++ src/Space.h | 1 + src/network/ClientState.h | 1 + src/network/NetworkInterface.h | 1 + src/network/WebSocketClientBase.cpp | 19 ++++++++++++++ src/network/WebSocketClientBase.h | 2 ++ 9 files changed, 93 insertions(+), 3 deletions(-) diff --git a/proj-web/README.md b/proj-web/README.md index 7969215..d3706c8 100644 --- a/proj-web/README.md +++ b/proj-web/README.md @@ -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 ``` diff --git a/server/server.cpp b/server/server.cpp index 54168e2..2f1da7d 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -20,7 +20,19 @@ std::vector split(const std::string& s, char delimiter) { Session::Session(Server& server, tcp::socket&& socket, int id) : server_(server) , ws_(std::move(socket)) - , id_(id) { + , id_(id) + , lastReceivedTime_(std::chrono::system_clock::now()) { +} + +bool Session::is_timed_out(std::chrono::system_clock::time_point now) const { + if (!joined_) return false; + auto elapsed = std::chrono::duration_cast(now - lastReceivedTime_).count(); + return elapsed > PLAYER_TIMEOUT_MS; +} + +void Session::force_disconnect() { + ws_.async_close(websocket::close_code::normal, + [self = shared_from_this()](beast::error_code) {}); } int Session::get_id() const { return id_; } @@ -166,12 +178,16 @@ void Session::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 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) { return session.get() == self.get(); }), self->server_.g_sessions.end()); - std::cout << "Client " << self->id_ << " disconnected\n"; + std::cout << "Client " << self->id_ << " removed from session list\n"; return; } @@ -188,6 +204,7 @@ void Session::process_message(const std::string& 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; @@ -480,6 +497,24 @@ void Server::update_world() { uint64_t now_ms = static_cast( std::chrono::duration_cast(now.time_since_epoch()).count()); + // --- Detect and force-disconnect timed-out players --- + { + std::vector> timedOut; + { + std::lock_guard lock(g_sessions_mutex); + for (auto& session : g_sessions) { + 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(); + } + } + { std::lock_guard lock(g_sessions_mutex); for (auto& sender : g_sessions) { diff --git a/server/server.h b/server/server.h index fbeca04..485f35c 100644 --- a/server/server.h +++ b/server/server.h @@ -72,6 +72,7 @@ class Session : public std::enable_shared_from_this { 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); @@ -87,6 +88,8 @@ public: 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(); diff --git a/src/Space.cpp b/src/Space.cpp index 9b22995..b079041 100644 --- a/src/Space.cpp +++ b/src/Space.cpp @@ -1822,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; @@ -1911,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; diff --git a/src/Space.h b/src/Space.h index 94f50c6..54f54d5 100644 --- a/src/Space.h +++ b/src/Space.h @@ -124,6 +124,7 @@ namespace ZL { std::unordered_set 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; diff --git a/src/network/ClientState.h b/src/network/ClientState.h index 0812892..d78903d 100644 --- a/src/network/ClientState.h +++ b/src/network/ClientState.h @@ -26,6 +26,7 @@ constexpr float PITCH_LIMIT = static_cast(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 diff --git a/src/network/NetworkInterface.h b/src/network/NetworkInterface.h index 2c7e704..191445d 100644 --- a/src/network/NetworkInterface.h +++ b/src/network/NetworkInterface.h @@ -50,6 +50,7 @@ namespace ZL { virtual int GetClientId() const { return -1; } virtual std::vector getPendingBoxDestructions() = 0; virtual int64_t getTimeOffset() const { return 0; } + virtual std::vector getPendingDisconnects() { return {}; } }; } diff --git a/src/network/WebSocketClientBase.cpp b/src/network/WebSocketClientBase.cpp index 710bf7d..6be0c77 100644 --- a/src/network/WebSocketClientBase.cpp +++ b/src/network/WebSocketClientBase.cpp @@ -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 WebSocketClientBase::getPendingDisconnects() { + std::vector copy; + copy.swap(pendingDisconnects_); + return copy; + } + std::vector WebSocketClientBase::getPendingSpawns() { std::vector copy; copy.swap(pendingSpawns_); diff --git a/src/network/WebSocketClientBase.h b/src/network/WebSocketClientBase.h index eb8cc8c..f74c9b5 100644 --- a/src/network/WebSocketClientBase.h +++ b/src/network/WebSocketClientBase.h @@ -20,6 +20,7 @@ namespace ZL { std::vector pendingDeaths_; std::vector pendingRespawns_; std::vector pendingBoxDestructions_; + std::vector pendingDisconnects_; int clientId = -1; int64_t timeOffset = 0; std::vector pendingSpawns_; @@ -49,6 +50,7 @@ namespace ZL { std::vector getPendingDeaths() override; std::vector getPendingRespawns() override; std::vector getPendingBoxDestructions() override; + std::vector getPendingDisconnects() override; std::vector getPendingSpawns(); int getClientId() const { return clientId; } };