Compare commits

..
103 Commits
Author SHA1 Message Date
Vladislav Khorev 59b5bea540 Fixing particles 2026-03-08 19:07:02 +03:00
Vladislav Khorev 9da3cc4401 Fixing bug in particle emitter 2026-03-08 00:22:13 +03:00
Vladislav Khorev 4a542fd6c8 Added multi-touch, added on press down 2026-03-07 22:41:25 +03:00
Vladislav Khorev a06cb77a66 Fixing minor stuff, refactoring 2026-03-07 20:51:34 +03:00
Vladislav Khorev 3c2c06ca40 Working on refactoring 2026-03-07 20:03:16 +03:00
Vladislav Khorev 928600acd4 Refactoring 2026-03-07 19:41:15 +03:00
Vladislav Khorev 7ccda081b0 Fixing bugs 2026-03-06 23:15:56 +03:00
Vladislav Khorev 41f370f2ee Minor fixing 2026-03-06 22:34:03 +03:00
Vladislav Khorev 4af20c65e6 Make menu to state machine 2026-03-06 21:52:00 +03:00
Vladislav Khorev ac551122d9 Added force disconnect from server 2026-03-06 20:05:55 +03:00
Vladislav Khorev 8528b4dc90 Clean up server 2026-03-06 19:08:55 +03:00
Vladislav Khorev c35e093cc5 Working on encoding 2026-03-06 17:51:46 +03:00
Vladislav Khorev a8c76dee3c Working on fixing minor bugs 2026-03-05 23:58:35 +03:00
Vladislav Khorev e84a094ad9 Merge branch 'spark' 2026-03-05 19:54:24 +03:00
Vladislav Khorev b0b7391939 Fixed low quality 2026-03-05 19:53:27 +03:00
Vlad 3e2632c5b5 added textures 2026-03-05 20:14:02 +06:00
Vladislav Khorev 70ef730e86 Merge branch 'main' into ShipSpawn 2026-03-05 10:24:17 +03:00
Vladislav Khorev 92b288d978 fixing bugs 2026-03-05 10:15:55 +03:00
Vladislav Khorev cca1d0ece2 Merge branch 'spark' 2026-03-05 09:10:38 +03:00
Vladislav Khorev 8effdd5dfa Merge branch 'sergey' 2026-03-05 09:10:00 +03:00
Vladislav Khorev e78429b600 Working on running the game even in Telegram 2026-03-04 21:41:51 +03:00
anime 05a13a40e7 fixed: Box destroition 2026-03-04 16:37:00 +06:00
vottozi ef7617701e fix server collision radius mismatch 2026-03-04 03:37:58 +06:00
vottozi 2f50dc1522 fix Lead Indicator rendering when target off-screen 2026-03-04 01:42:57 +06:00
vottozi 9eafcd27fb Fix lead indicator prediction & align projectile lifetime (client/server) 2026-03-03 19:42:08 +06:00
Vlad 3879eb1d75 fixed web ship types boxes / local boxes collision and projectiles 2026-03-03 19:17:07 +06:00
anime 78046e5e2d Merge remote-tracking branch 'origin/main' into ShipSpawn 2026-03-02 17:49:09 +06:00
anime 26233f934f fixed: spawning ships 2026-03-02 17:47:46 +06:00
Vlad 167c04b107 install 2026-03-02 14:05:23 +06:00
Vladislav Khorev 5fcb1d1234 server changes 2026-03-01 22:35:17 +03:00
Vladislav Khorev 70a617b688 More changes 2026-03-01 22:34:12 +03:00
Vladislav Khorev bb0f584bf6 Working on UI and web game 2026-03-01 21:22:57 +03:00
Vladislav Khorev 6b4b549b3c Working on UI for web 2026-02-28 23:01:26 +03:00
Vladislav Khorev ffbecbbcde Working on ui 2026-02-28 22:21:08 +03:00
Vladislav Khorev 2728ca9646 Adapting web version 2026-02-28 20:37:47 +03:00
Vladislav Khorev 7e5aa22fee merge 2026-02-28 13:33:33 +03:00
Vlad c20e82634a fix game over 2026-02-28 12:07:40 +06:00
Vladislav Khorev 9f82e7a2e6 Adapt for web 2026-02-27 22:31:07 +03:00
Vlad 7418bbbe27 fix spaceship rotation fix sync boxes 2026-02-27 17:37:00 +06:00
Vladislav Khorev be2aa76f8b Working on server optimization 2026-02-26 22:26:43 +03:00
Vladislav Khorev e8f1786a2a merge 2026-02-26 21:22:38 +03:00
Vladislav Khorev e30f8a4c70 merge 2026-02-26 21:10:27 +03:00
Vladislav Khorev f8642efa23 merge 2026-02-26 21:02:16 +03:00
vottozi 5da5a754fe add configurable HUD crosshair 2026-02-26 01:49:49 +06:00
vottozi fb1b0a1c2e Lead Indicator fix 2026-02-25 22:18:47 +06:00
Vlad 74c2f786a1 added choising spaceship type for multiplayer and some fix in respawn 2026-02-25 19:17:42 +06:00
Vlad 5216965496 added choosing spaceship in singleplay 2026-02-24 20:34:01 +06:00
Vlad ad7294ceea added random spaceship type 2026-02-24 19:51:33 +06:00
Vlad 3893038d9a Merge remote-tracking branch 'origin/main' into spark 2026-02-23 15:30:01 +06:00
Vlad cb2e8318e7 added multi_menu 2026-02-23 15:29:05 +06:00
Vladislav Khorev 5cca2413fe changes 2026-02-23 11:46:57 +03:00
Vladislav Khorev db4254e205 working on web 2026-02-23 11:45:55 +03:00
vottozi fa3a4c66c9 add lead indicator 2026-02-23 14:43:23 +06:00
Vladislav Khorev 84006380db Added loading screen 2026-02-23 09:55:01 +03:00
Vladislav Khorev f6cc30a30c Try out Cursor 2026-02-22 21:20:39 +03:00
Vladislav Khorev d883b58260 Adapt for web 2026-02-22 20:04:18 +03:00
Vladislav Khorev b59a10b7e6 Major refactoring for game menu 2026-02-22 19:50:13 +03:00
Vladislav Khorev 5b57696acf Refactoring major 2026-02-22 19:15:25 +03:00
Vladislav Khorev 1265d87bc5 Updated UI 2026-02-22 18:23:27 +03:00
Vladislav Khorev 46d7a2a25d Merge branch 'spark' 2026-02-22 17:02:40 +03:00
Vlad 0babfff28b added multiplayer menu 2026-02-22 19:21:12 +06:00
Vladislav Khorev ed6e1bacc7 merge 2026-02-22 16:03:11 +03:00
Vladislav Khorev 9c39782729 Fixing bug with collisions 2026-02-22 15:53:04 +03:00
Vladislav Khorev efad2dde3e Restore as it was before 2026-02-22 14:08:12 +03:00
Vladislav Khorev 84d8ee7eee Merge branch 'spark' 2026-02-22 13:30:33 +03:00
Vladislav Khorev 7693237aa5 Added websocket 2026-02-22 13:29:06 +03:00
vottozi 528c94e921 Add enemy target brackets + off-screen arrows 2026-02-19 01:13:08 +06:00
Vlad a8700af1d0 added npc destroy 2026-02-13 15:40:40 +06:00
Vlad 9793c5bf06 fix npc 2026-02-12 18:13:19 +06:00
vottozi b56fafa0e0 Add mesh caching for unchanging text labels 2026-02-12 13:23:16 +06:00
Vladislav Khorev 22ee99418d Working on web version 2026-02-11 21:48:25 +03:00
vottozi 4eda57b4e4 single texture atlas for all font glyphs in TextRenderer 2026-02-12 00:08:52 +06:00
Vladislav Khorev 2ffd8124f2 Change the way fire happens 2026-02-11 20:02:15 +03:00
Vladislav Khorev 210c191d41 Added different hash func 2026-02-10 21:06:21 +03:00
Vladislav Khorev 251a59ddbe added hash 2026-02-10 11:42:23 +03:00
Vlad 5df2216da6 added npc 2026-02-10 12:42:19 +06:00
Vladislav Khorev 2c1c077611 Added time offset sync, added web version multiplayer 2026-02-09 22:56:48 +03:00
Vlad 629c9ba7b1 add local server 2026-02-09 18:42:07 +06:00
Vladislav Khorev a59bcc0c4b Adapting for web 2026-02-08 22:38:24 +03:00
Vladislav Khorev cf1ec1e76f merge 2026-02-08 15:53:55 +03:00
Vladislav Khorev 8a374a4231 Merge with main 2026-02-08 15:31:25 +03:00
Vlad da2b6e5577 some fix/added sync boxes 2026-02-06 19:16:17 +06:00
vottozi aa9590e2f4 Add velocity HUD display (Velocity: 0) in top-left corner 2026-02-06 18:04:33 +06:00
vottozi a1d54b01b0 Add TextView UI component 2026-02-06 15:11:27 +06:00
Vlad 65a2066727 size 2026-02-05 14:00:20 +06:00
Vladislav Khorev c49c4626d0 merge 2026-02-05 10:43:24 +03:00
Vlad ec156bd679 add sync projectiles 2026-02-05 13:29:21 +06:00
Vladislav Khorev 7b1d9363a0 Fixing lag compensation and make more smooth 2026-02-05 09:00:54 +03:00
Vladislav Khorev 82ca994fac merge 2026-02-05 07:33:13 +03:00
Vladislav Khorev 0d2f705d42 merge 2026-02-05 07:30:12 +03:00
vottozi e799b4ec79 conflict resolution 2026-02-05 01:43:45 +06:00
vottozi c8076b6018 adjust label scale: constant far, smooth growth near 2026-02-05 01:34:14 +06:00
Vlad 88f68b20ea some fix 2026-02-02 14:45:36 +06:00
vottozi d249f17d22 everything uselles 2026-02-02 13:50:43 +06:00
vottozi bee3545196 change to permanent visibility of ship labels 2026-02-02 13:48:28 +06:00
vottozi e050d7d0ec Improve ship nickname rendering 2026-02-02 04:44:18 +06:00
vottozi e18484ea62 add player ID labels for ships 2026-02-01 23:34:21 +06:00
Vlad 204b79bf06 add main menu 2026-01-31 19:25:40 +06:00
Vlad 50232c0816 fix shooting 2026-01-31 16:44:00 +06:00
Vlad e8fb14b809 added sync projectiles/boxes 2026-01-27 19:38:18 +06:00
Vladislav Khorev d425167dce Added planet physics and simple base 2026-01-25 20:55:36 +03:00
vottozi f41228acf8 add freetype-based text rendering and box labels 2026-01-22 15:54:21 +06:00
Vlad cacc18dc7e added boxes from server 2026-01-19 22:04:33 +06:00
159 changed files with 21422 additions and 2867 deletions

No files matched your search

+5 -3
View File
@@ -398,7 +398,9 @@ add_library(freetype_external_lib UNKNOWN IMPORTED GLOBAL)
set_target_properties(freetype_external_lib PROPERTIES
IMPORTED_LOCATION_DEBUG "${_ft_debug_lib}"
IMPORTED_LOCATION_RELEASE "${_ft_release_lib}"
INTERFACE_INCLUDE_DIRECTORIES
)
target_include_directories(freetype_external_lib INTERFACE
"$<IF:$<CONFIG:Debug>,${FREETYPE_BASE_DIR}-Debug/include/freetype2,${FREETYPE_BASE_DIR}-Release/include/freetype2>"
"$<IF:$<CONFIG:Debug>,${FREETYPE_BASE_DIR}-Debug/include,${FREETYPE_BASE_DIR}-Release/include>"
)
@@ -453,8 +455,8 @@ if(NOT _have_sdl2ttf)
-DSDL2_LIBRARY=${_SDL2_LIB}
-DSDL2_INCLUDE_DIR=${SDL2_INSTALL_DIR}/include/SDL2
-DFREETYPE_LIBRARY=${_FT_LIB}
-DFREETYPE_INCLUDE_DIR=${_FT_PREFIX}/include
-DFREETYPE_INCLUDE_DIRS=${_FT_PREFIX}/include
-DFREETYPE_INCLUDE_DIR=${_FT_PREFIX}/include/freetype2
-DFREETYPE_INCLUDE_DIRS=${_FT_PREFIX}/include/freetype2
-DSDL2TTF_VENDORED=OFF
-DSDL2TTF_SAMPLES=OFF
RESULT_VARIABLE _ttf_cfg_res
+60 -20
View File
@@ -21,23 +21,60 @@ include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/FetchDependencies.cmake")
set(SOURCES
../src/main.cpp
../src/Game.cpp
../src/Game.h
../src/Environment.cpp
../src/BoneAnimatedModel.cpp
../src/TextModel.cpp
../src/Projectile.cpp
../src/SparkEmitter.cpp
../src/UiManager.cpp
../src/Environment.h
../src/render/Renderer.cpp
../src/render/Renderer.h
../src/render/ShaderManager.cpp
../src/render/ShaderManager.h
../src/render/TextureManager.cpp
../src/render/FrameBuffer.cpp
../src/render/TextureManager.h
../src/TextModel.cpp
../src/TextModel.h
../src/AudioPlayerAsync.cpp
../src/AudioPlayerAsync.h
../src/BoneAnimatedModel.cpp
../src/BoneAnimatedModel.h
../src/render/OpenGlExtensions.cpp
../src/render/OpenGlExtensions.h
../src/utils/Utils.cpp
../src/utils/TaskManager.cpp
../src/utils/Perlin.cpp
../src/planet/PlanetData.cpp
../src/utils/Utils.h
../src/SparkEmitter.cpp
../src/SparkEmitter.h
../src/planet/PlanetObject.cpp
../src/planet/PlanetObject.h
../src/planet/PlanetData.cpp
../src/planet/PlanetData.h
../src/utils/Perlin.cpp
../src/utils/Perlin.h
../src/utils/TaskManager.cpp
../src/utils/TaskManager.h
../src/planet/StoneObject.cpp
../src/planet/StoneObject.h
../src/render/FrameBuffer.cpp
../src/render/FrameBuffer.h
../src/UiManager.cpp
../src/UiManager.h
../src/Projectile.h
../src/Projectile.cpp
../src/network/NetworkInterface.h
../src/network/LocalClient.h
../src/network/LocalClient.cpp
../src/network/ClientState.h
../src/network/ClientState.cpp
../src/network/WebSocketClientBase.h
../src/network/WebSocketClientBase.cpp
../src/network/WebSocketClientEmscripten.h
../src/network/WebSocketClientEmscripten.cpp
../src/render/TextRenderer.h
../src/render/TextRenderer.cpp
../src/MenuManager.h
../src/MenuManager.cpp
../src/Space.h
../src/Space.cpp
../src/GameConstants.h
../src/GameConstants.cpp
)
add_executable(space-game001 ${SOURCES})
@@ -58,30 +95,33 @@ set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
add_subdirectory("../thirdparty/libzip-1.11.4" libzip-build)
# Линковка:
# 'zip' берется из add_subdirectory
# 'z' - это системный zlib Emscripten-а (флаг -sUSE_ZLIB=1 добавим ниже)
target_link_libraries(space-game001 PRIVATE zip z)
target_link_libraries(space-game001 PRIVATE zip z websocket.js)
# Эмскриптен-флаги
set(EMSCRIPTEN_FLAGS
"-sUSE_SDL=2"
"-sUSE_SDL_IMAGE=2"
"-sUSE_LIBPNG=1"
"-sUSE_ZLIB=1" # Добавили zlib порт
"-pthread"
"-sUSE_PTHREADS=1"
"-sUSE_ZLIB=1"
"-sUSE_SDL_TTF=2"
#"-pthread"
#"-sUSE_PTHREADS=1"
"-fexceptions"
"-DNETWORK"
)
target_compile_options(space-game001 PRIVATE ${EMSCRIPTEN_FLAGS} "-O2")
# Only loading.png and the shaders used before resources.zip is ready are preloaded.
# resources.zip is downloaded asynchronously at runtime and served as a separate file.
set(EMSCRIPTEN_LINK_FLAGS
${EMSCRIPTEN_FLAGS}
"-O2"
"-sPTHREAD_POOL_SIZE=4"
#"-sPTHREAD_POOL_SIZE=4"
"-sALLOW_MEMORY_GROWTH=1"
"--preload-file resources.zip"
"-sFULL_ES3=1"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/loading.png@resources/loading.png"
"--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/../resources/shaders@resources/shaders"
)
# Применяем настройки линковки
@@ -131,8 +171,8 @@ install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/space-game001plain.html"
DESTINATION .
)
# Если вам все еще нужен сам resources.zip отдельно в папке public:
#install(FILES "${RESOURCES_ZIP}" DESTINATION .)
# resources.zip is served separately and downloaded asynchronously at runtime
install(FILES "${RESOURCES_ZIP}" DESTINATION .)
add_custom_command(TARGET space-game001 POST_BUILD
COMMAND ${CMAKE_COMMAND} --install .
+7 -1
View File
@@ -1,7 +1,13 @@
# 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
+2
View File
@@ -0,0 +1,2 @@
<!doctypehtml><html lang=en-us><head><meta charset=utf-8><meta content="text/html; charset=utf-8"http-equiv=Content-Type><title>Emscripten-Generated Code</title><style>body{font-family:arial;margin:0;padding:none}.emscripten{padding-right:0;margin-left:auto;margin-right:auto;display:block}div.emscripten{text-align:center}div.emscripten_border{border:1px solid #000}canvas.emscripten{border:0 none;background-color:#000}#emscripten_logo{display:inline-block;margin:0;padding:6px;width:265px}.spinner{height:30px;width:30px;margin:0;margin-top:20px;margin-left:20px;display:inline-block;vertical-align:top;-webkit-animation:rotation .8s linear infinite;-moz-animation:rotation .8s linear infinite;-o-animation:rotation .8s linear infinite;animation:rotation .8s linear infinite;border-left:5px solid #ebebeb;border-right:5px solid #ebebeb;border-bottom:5px solid #ebebeb;border-top:5px solid #787878;border-radius:100%;background-color:#bdd72e}@-webkit-keyframes rotation{from{-webkit-transform:rotate(0)}to{-webkit-transform:rotate(360deg)}}@-moz-keyframes rotation{from{-moz-transform:rotate(0)}to{-moz-transform:rotate(360deg)}}@-o-keyframes rotation{from{-o-transform:rotate(0)}to{-o-transform:rotate(360deg)}}@keyframes rotation{from{transform:rotate(0)}to{transform:rotate(360deg)}}#status{display:inline-block;vertical-align:top;margin-top:30px;margin-left:20px;font-weight:700;color:#787878}#progress{height:20px;width:300px}#controls{display:inline-block;float:right;vertical-align:top;margin-top:30px;margin-right:20px}#output{width:100%;height:200px;margin:0 auto;margin-top:10px;border-left:0;border-right:0px;padding-left:0;padding-right:0;display:block;background-color:#000;color:#fff;font-family:'Lucida Console',Monaco,monospace;outline:0}</style></head><body><script src="https://cdn.jsdelivr.net/npm/eruda"></script>
<script>eruda.init();</script><a href=http://emscripten.org><img id=emscripten_logo src=""></a><div class=spinner id=spinner></div><div class=emscripten id=status>Downloading...</div><span id=controls><span><input type=checkbox id=resize>Resize canvas</span> <span><input type=checkbox id=pointerLock checked>Lock/hide mouse pointer    </span><span><input type=button onclick='Module.requestFullscreen(document.getElementById("pointerLock").checked,document.getElementById("resize").checked)'value=Fullscreen></span></span><div class=emscripten><progress hidden id=progress max=100 value=0></progress></div><div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault() tabindex=-1></canvas></div><textarea id=output rows=8></textarea><script>var statusElement=document.getElementById("status"),progressElement=document.getElementById("progress"),spinnerElement=document.getElementById("spinner"),canvasElement=document.getElementById("canvas"),outputElement=document.getElementById("output");outputElement&&(outputElement.value=""),canvasElement.addEventListener("webglcontextlost",(e=>{alert("WebGL context lost. You will need to reload the page."),e.preventDefault()}),!1);var Module={print(...e){if(console.log(...e),outputElement){var t=e.join(" ");outputElement.value+=t+"\n",outputElement.scrollTop=outputElement.scrollHeight}},canvas:canvasElement,setStatus(e){if(Module.setStatus.last||(Module.setStatus.last={time:Date.now(),text:""}),e!==Module.setStatus.last.text){var t=e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/),n=Date.now();t&&n-Module.setStatus.last.time<30||(Module.setStatus.last.time=n,Module.setStatus.last.text=e,t?(e=t[1],progressElement.value=100*parseInt(t[2]),progressElement.max=100*parseInt(t[4]),progressElement.hidden=!1,spinnerElement.hidden=!1):(progressElement.value=null,progressElement.max=null,progressElement.hidden=!0,e||(spinnerElement.style.display="none")),statusElement.innerHTML=e)}},totalDependencies:0,monitorRunDependencies(e){this.totalDependencies=Math.max(this.totalDependencies,e),Module.setStatus(e?"Preparing... ("+(this.totalDependencies-e)+"/"+this.totalDependencies+")":"All downloads complete.")}};Module.setStatus("Downloading..."),window.onerror=e=>{Module.setStatus("Exception thrown, see JavaScript console"),spinnerElement.style.display="none",Module.setStatus=e=>{e&&console.error("[post-exception status] "+e)}}</script><script async src="space-game001.js" crossorigin="anonymous"></script></body></html>
+42 -90
View File
@@ -1,100 +1,52 @@
<!doctypehtml>
<html lang=en-us>
<!DOCTYPE html>
<html lang="en-us">
<head>
<meta charset=utf-8>
<meta content="text/html; charset=utf-8" http-equiv=Content-Type>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>Space Game</title>
<style>
body {
font-family: arial;
margin: 0;
overflow: hidden;
padding: 0;
min-height: 100vh;
background-color: #000;
body, html {
margin: 0; padding: 0; width: 100%; height: 100%;
overflow: hidden; background-color: #000;
position: fixed; /* Предотвращает pull-to-refresh на Android */
}
.emscripten {
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block
}
div.emscripten {
text-align: center
}
div.emscripten_border {
border: 1px solid #000
}
canvas.emscripten {
border: 0 none;
background-color: #000;
width: 90%;
height: 100vh;
}
@-webkit-keyframes rotation {
from {
-webkit-transform: rotate(0)
}
to {
-webkit-transform: rotate(360deg)
}
}
@-moz-keyframes rotation {
from {
-moz-transform: rotate(0)
}
to {
-moz-transform: rotate(360deg)
}
}
@-o-keyframes rotation {
from {
-o-transform: rotate(0)
}
to {
-o-transform: rotate(360deg)
}
}
@keyframes rotation {
from {
transform: rotate(0)
}
to {
transform: rotate(360deg)
}
}
#status {
display: none;
}
#progress {
height: 20px;
width: 300px
#canvas {
display: block;
position: absolute;
top: 0; left: 0;
width: 100vw; height: 100vh;
border: none;
}
#status { color: white; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
</style>
</head>
<body>
<div class=emscripten id=status></div>
<div class=emscripten><progress hidden id=progress max=100 value=0></progress></div>
<div class=emscripten_border><canvas class=emscripten id=canvas oncontextmenu=event.preventDefault()
tabindex=-1></canvas></div>
<script>var statusElement = document.getElementById("status"), progressElement = document.getElementById("progress"), spinnerElement = document.getElementById("spinner"), Module = { print: function () { var e = document.getElementById("output"); return e && (e.value = ""), function (t) { arguments.length > 1 && (t = Array.prototype.slice.call(arguments).join(" ")), console.log(t), e && (e.value += t + "\n", e.scrollTop = e.scrollHeight) } }(), canvas: (() => { var e = document.getElementById("canvas"); return e.addEventListener("webglcontextlost", (e => { alert("WebGL context lost. You will need to reload the page."), e.preventDefault() }), !1), e })(), setStatus: e => { if (Module.setStatus.last || (Module.setStatus.last = { time: Date.now(), text: "" }), e !== Module.setStatus.last.text) { var t = e.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/), n = Date.now(); t && n - Module.setStatus.last.time < 30 || (Module.setStatus.last.time = n, Module.setStatus.last.text = e, t ? (e = t[1], progressElement.value = 100 * parseInt(t[2]), progressElement.max = 100 * parseInt(t[4]), progressElement.hidden = !1, spinnerElement.hidden = !1) : (progressElement.value = null, progressElement.max = null, progressElement.hidden = !0, e || (spinnerElement.style.display = "none")), statusElement.innerHTML = e) } }, totalDependencies: 0, monitorRunDependencies: e => { this.totalDependencies = Math.max(this.totalDependencies, e), Module.setStatus(e ? "Preparing... (" + (this.totalDependencies - e) + "/" + this.totalDependencies + ")" : "All downloads complete.") } }; Module.setStatus("Downloading..."), window.onerror = e => { Module.setStatus("Exception thrown, see JavaScript console"), spinnerElement.style.display = "none", Module.setStatus = e => { e && console.error("[post-exception status] " + e) } }</script>
<script async src=space-game001.js></script>
</body>
<div id="status">Downloading...</div>
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
<script>
var statusElement = document.getElementById("status");
var canvas = document.getElementById("canvas");
var Module = {
canvas: canvas,
setStatus: function(text) {
statusElement.innerHTML = text;
statusElement.style.display = text ? 'block' : 'none';
}
};
// Обработка ориентации
window.addEventListener("orientationchange", function() {
// Chrome на Android обновляет innerWidth/Height не мгновенно.
// Ждем завершения анимации поворота.
setTimeout(() => {
// В Emscripten это вызовет ваш onWindowResized в C++
window.dispatchEvent(new Event('resize'));
}, 200);
});
</script>
<script async src="space-game001.js"></script>
</body>
</html>
+13 -3
View File
@@ -18,8 +18,6 @@ add_executable(space-game001
../src/Environment.h
../src/render/Renderer.cpp
../src/render/Renderer.h
../src/render/Camera.cpp
../src/render/Camera.h
../src/render/ShaderManager.cpp
../src/render/ShaderManager.h
../src/render/TextureManager.cpp
@@ -59,6 +57,18 @@ add_executable(space-game001
../src/network/ClientState.cpp
../src/network/WebSocketClient.h
../src/network/WebSocketClient.cpp
../src/network/WebSocketClientBase.h
../src/network/WebSocketClientBase.cpp
../src/network/WebSocketClientEmscripten.h
../src/network/WebSocketClientEmscripten.cpp
../src/render/TextRenderer.h
../src/render/TextRenderer.cpp
../src/MenuManager.h
../src/MenuManager.cpp
../src/Space.h
../src/Space.cpp
../src/GameConstants.h
../src/GameConstants.cpp
)
# Установка проекта по умолчанию для Visual Studio
@@ -80,7 +90,7 @@ target_compile_definitions(space-game001 PRIVATE
WIN32_LEAN_AND_MEAN
PNG_ENABLED
SDL_MAIN_HANDLED
# NETWORK
NETWORK
# SIMPLIFIED
)
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.
File diff suppressed because it is too large. Load diff
+22
View File
@@ -0,0 +1,22 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connecting",
"x" : 0,
"y" : 0,
"width": 488,
"height": 154,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connecting.png"
}
]
}
}
+52
View File
@@ -0,0 +1,52 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connectionFailed",
"x" : 0,
"y" : 0,
"width": 488,
"height": 308,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connection_failed.png"
},
{
"type": "Button",
"name": "connectionFailedReconnectButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -20,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Variant6.png"
}
},
{
"type": "Button",
"name": "connectionFailedGoBack",
"width": 382,
"height": 56,
"x" : 0,
"y" : -86,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}
+52
View File
@@ -0,0 +1,52 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "connectionLost",
"x" : 0,
"y" : 0,
"width": 488,
"height": 308,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"texture": "resources/connection_lost.png"
},
{
"type": "Button",
"name": "reconnectButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -20,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Variant6.png"
}
},
{
"type": "Button",
"name": "exitServerButton",
"width": 382,
"height": 56,
"x" : 0,
"y" : -86,
"horizontal_gravity": "center",
"vertical_gravity": "center",
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}
+21
View File
@@ -0,0 +1,21 @@
{
"enabled": true,
"referenceResolution": [1280, 720],
"color": [1.0, 1.0, 1.0],
"cl_crosshairalpha": 1.0,
"cl_crosshairthickness": 2.0,
"centerGapPx": 10.0,
"top": {
"lengthPx": 14.0,
"angleDeg": 90.0
},
"arms": [
{ "lengthPx": 20.0, "angleDeg": 210.0 },
{ "lengthPx": 20.0, "angleDeg": 330.0 }
]
}
+1 -1
View File
@@ -5,7 +5,7 @@
"texture": "resources/spark_white.png",
"speedRange": [10.0, 30.0],
"zSpeedRange": [-1.0, 1.0],
"scaleRange": [0.5, 1.0],
"scaleRange": [5.0, 10.0],
"lifeTimeRange": [200.0, 800.0],
"emissionRate": 50.0,
"maxParticles": 5,
+58 -28
View File
@@ -2,52 +2,82 @@
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"align": "center",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 10,
"x": 0,
"y": 0,
"width": 1920,
"height": 1080,
"background": {
"color": [0, 0, 0, 0.7]
},
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "Button",
"name": "gameOverText",
"x": 350,
"y": 400,
"width": 600,
"height": 150,
"width": 327,
"height": 26,
"textures": {
"normal": "resources/gameover.png",
"hover": "resources/gameover.png",
"pressed": "resources/gameover.png"
"normal": "resources/game_over/MissionFailed.png",
"hover": "resources/game_over/MissionFailed.png",
"pressed": "resources/game_over/MissionFailed.png"
}
},
{
"type": "Button",
"name": "restartButton",
"x": 350,
"y": 300,
"width": 300,
"height": 80,
"name": "underlineBtn",
"width": 168,
"height": 44,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_normal.png",
"pressed": "resources/shoot_normal.png"
"normal": "resources/game_over/Container.png",
"hover": "resources/game_over/Container.png",
"pressed": "resources/game_over/Container.png"
}
},
{
"type": "Button",
"name": "finalscore",
"width": 87,
"height": 9,
"textures": {
"normal": "resources/game_over/FinalScore.png",
"hover": "resources/game_over/FinalScore.png",
"pressed": "resources/game_over/FinalScore.png"
}
},
{
"type": "TextView",
"name": "scoreText",
"width": 600,
"height": 80,
"text": "0",
"fontSize": 36,
"color": [
0,
217,
255,
1
],
"align": "center"
},
{
"type": "Button",
"name": "restartButton",
"width": 382,
"height": 56,
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Variant5.png",
"pressed": "resources/game_over/Variant6.png"
}
},
{
"type": "Button",
"name": "gameOverExitButton",
"x": 650,
"y": 300,
"width": 300,
"height": 80,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/sand2.png",
"hover": "resources/sand2.png",
"pressed": "resources/sand2.png"
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
+93
View File
@@ -0,0 +1,93 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"align": "center",
"x": 0,
"y": 0,
"width": 1920,
"height": 1080,
"background": {
"color": [0, 0, 0, 0.7]
},
"children": [
{
"type": "Button",
"name": "gameOverText",
"x": 476.5,
"y": 500,
"width": 327,
"height": 26,
"textures": {
"normal": "resources/game_over/MissionFailed.png",
"hover": "resources/game_over/MissionFailed.png",
"pressed": "resources/game_over/MissionFailed.png"
}
},
{
"type": "Button",
"name": "underlineBtn",
"x": 556,
"y": 465,
"width": 168,
"height": 44,
"textures": {
"normal": "resources/game_over/Container.png",
"hover": "resources/game_over/Container.png",
"pressed": "resources/game_over/Container.png"
}
},
{
"type": "Button",
"name": "finalscore",
"x": 596.5,
"y": 436,
"width": 87,
"height": 9,
"textures": {
"normal": "resources/game_over/FinalScore.png",
"hover": "resources/game_over/FinalScore.png",
"pressed": "resources/game_over/FinalScore.png"
}
},
{
"type": "TextView",
"name": "scoreText",
"x": 350,
"y": 356,
"width": 600,
"height": 80,
"text": "0",
"fontSize": 36,
"color": [0, 217, 255, 1],
"align": "center"
},
{
"type": "Button",
"name": "restartButton",
"x": 449,
"y": 308,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/game_over/Filledbuttons.png",
"hover": "resources/game_over/Filledbuttons.png",
"pressed": "resources/game_over/Filledbuttons.png"
}
},
{
"type": "Button",
"name": "gameOverExitButton",
"x": 449,
"y": 240,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/game_over/Secondarybutton.png",
"hover": "resources/game_over/Secondarybutton.png",
"pressed": "resources/game_over/Secondarybutton.png"
}
}
]
}
}
+69
View File
@@ -0,0 +1,69 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 10,
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "StaticImage",
"name": "titleBtn",
"width": 254,
"height": 35,
"texture": "resources/main_menu/title.png"
},
{
"type": "StaticImage",
"name": "underlineBtn",
"width": 168,
"height": 44,
"texture": "resources/main_menu/line.png"
},
{
"type": "Button",
"name": "subtitleBtn",
"width": 144,
"height": 11,
"textures": {
"normal": "resources/main_menu/subtitle.png",
"hover": "resources/main_menu/subtitle.png",
"pressed": "resources/main_menu/subtitle.png"
}
},
{
"type": "Button",
"name": "singleButton",
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/single.png",
"hover": "resources/main_menu/Variant5.png",
"pressed": "resources/main_menu/Variant6.png"
}
},
{
"type": "Button",
"name": "multiplayerButton",
"width": 382,
"height": 56,
"textures": {
"normal": "resources/main_menu/multi.png",
"hover": "resources/main_menu/Variant7.png",
"pressed": "resources/main_menu/Variant8.png"
}
},
{
"type": "StaticImage",
"name": "versionLabel",
"width": 81,
"height": 9,
"texture": "resources/main_menu/version.png"
}
]
}
}
+158
View File
@@ -0,0 +1,158 @@
{
"root": {
"type": "LinearLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"children": [
{
"type": "Button",
"name": "langButton",
"x": 1100,
"y": 580,
"width": 142,
"height": 96,
"textures": {
"normal": "resources/main_menu/lang.png",
"hover": "resources/main_menu/lang.png",
"pressed": "resources/main_menu/lang.png"
}
},
{
"type": "Button",
"name": "titleBtn",
"x": 512,
"y": 500,
"width": 254,
"height": 35,
"textures": {
"normal": "resources/multiplayer_menu/title.png",
"hover": "resources/multiplayer_menu/title.png",
"pressed": "resources/multiplayer_menu/title.png"
}
},
{
"type": "Button",
"name": "subtitle",
"x": 596.5,
"y": 470,
"width": 87,
"height": 11,
"textures": {
"normal": "resources/multiplayer_menu/JoinServer.png",
"hover": "resources/multiplayer_menu/JoinServer.png",
"pressed": "resources/multiplayer_menu/JoinServer.png"
}
},
{
"type": "Button",
"name": "subtitleBtn",
"x": 450,
"y": 445,
"width": 94,
"height": 9,
"textures": {
"normal": "resources/multiplayer_menu/ServerName.png",
"hover": "resources/multiplayer_menu/ServerName.png",
"pressed": "resources/multiplayer_menu/ServerName.png"
}
},
{
"type": "TextField",
"name": "serverInputField",
"x": 449,
"y": 390,
"width": 382,
"height": 56,
"placeholder": "Enter server name or IP",
"fontPath": "resources/fonts/DroidSans.ttf",
"fontSize": 16,
"maxLength": 256,
"color": [122, 156, 198, 1],
"placeholderColor": [122, 156, 198, 1],
"backgroundColor": [15, 29, 51, 1],
"borderColor": [15, 29, 51, 1]
},
{
"type": "Button",
"name": "connectButton",
"x": 449,
"y": 350,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Filledbuttons.png",
"hover": "resources/multiplayer_menu/Filledbuttons.png",
"pressed": "resources/multiplayer_menu/Filledbuttons.png"
}
},
{
"type": "Button",
"name": "backButton",
"x": 449,
"y": 280,
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/multiplayer_menu/Backbutton.png"
}
},
{
"type": "Button",
"name": "AvailableServers",
"x": 450,
"y": 240,
"width": 139,
"height": 9,
"textures": {
"normal": "resources/multiplayer_menu/AvailableServers.png",
"hover": "resources/multiplayer_menu/AvailableServers.png",
"pressed": "resources/multiplayer_menu/AvailableServers.png"
}
},
{
"type": "Button",
"name": "SerButton",
"x": 436.5,
"y": 170,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button.png",
"hover": "resources/multiplayer_menu/Button.png",
"pressed": "resources/multiplayer_menu/Button.png"
}
},
{
"type": "Button",
"name": "SerButton2",
"x": 436.5,
"y": 88,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button2.png",
"hover": "resources/multiplayer_menu/Button2.png",
"pressed": "resources/multiplayer_menu/Button2.png"
}
},
{
"type": "Button",
"name": "SerButton3",
"x": 436.5,
"y": 6,
"width": 407,
"height": 62,
"textures": {
"normal": "resources/multiplayer_menu/Button3.png",
"hover": "resources/multiplayer_menu/Button3.png",
"pressed": "resources/multiplayer_menu/Button3.png"
}
}
]
}
}
+59
View File
@@ -0,0 +1,59 @@
{
"root": {
"type": "LinearLayout",
"orientation": "vertical",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 10,
"x": 0,
"y": 0,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "LinearLayout",
"orientation": "horizontal",
"vertical_align": "center",
"horizontal_align": "center",
"spacing": 10,
"width": "match_parent",
"height": 260,
"children": [
{
"type": "Button",
"name": "spaceshipButton",
"width": 256,
"height": 256,
"textures": {
"normal": "resources/multiplayer_menu/ship_fighter.png",
"hover": "resources/multiplayer_menu/ship_fighter_pressed.png",
"pressed": "resources/multiplayer_menu/ship_fighter_pressed.png"
}
},
{
"type": "Button",
"name": "cargoshipButton",
"width": 256,
"height": 256,
"textures": {
"normal": "resources/multiplayer_menu/ship_cargo.png",
"hover": "resources/multiplayer_menu/ship_cargo_pressed.png",
"pressed": "resources/multiplayer_menu/ship_cargo_pressed.png"
}
}
]
},
{
"type": "Button",
"name": "backButton",
"width": 382,
"height": 56,
"textures": {
"normal": "resources/multiplayer_menu/Backbutton.png",
"hover": "resources/multiplayer_menu/Backbutton.png",
"pressed": "resources/multiplayer_menu/Backbutton.png"
}
}
]
}
}
+4 -12
View File
@@ -1,22 +1,14 @@
{
"emissionRate": 100.0,
"maxParticles": 200,
"emissionRate": 0.4,
"maxParticles": 400,
"particleSize": 0.3,
"biasX": 0.3,
"emissionPoints": [
{
"position": [-1.3, 0, 0.0]
},
{
"position": [1.3, 0.0, 0.0]
}
],
"speedRange": [0.5, 2.0],
"zSpeedRange": [1.0, 3.0],
"scaleRange": [0.8, 1.2],
"lifeTimeRange": [600.0, 1400.0],
"lifeTimeRange": [300.0, 500.0],
"texture": "resources/spark.png",
"shaderProgramName": "default",
"vertexShader": "resources/shaders/spark.vertex",
"fragmentShader": "resources/shaders/spark.fragment"
"shaderProgramName": "spark"
}
+20
View File
@@ -0,0 +1,20 @@
{
"emissionRate": 0.4,
"maxParticles": 400,
"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.09,
"emissionRate": 30.0,
"maxParticles": 150,
"particleSize": 1.0,
"biasX": 0.1,
"shaderProgramName": "default"
}
+70 -150
View File
@@ -3,165 +3,85 @@
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"width": "match_parent",
"height": "match_parent",
"children": [
{
"type": "FrameLayout",
"name": "leftPanel",
"x": 100,
"y": 100,
"width": 320,
"height": 400,
"children": [
{
"type": "LinearLayout",
"name": "mainButtons",
"orientation": "vertical",
"spacing": 10,
"x": 0,
"y": 0,
"width": 300,
"height": 300,
"children": [
{
"type": "Button",
"name": "playButton",
"x": -1000,
"y": 500,
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "settingsButton",
"x": -1000,
"y": 400,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 0.5
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": -1000,
"y": 300,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 1.0
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
},
"bgScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
1280,
0
],
"duration": 5.0,
"easing": "linear"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
}
]
}
]
},
{
"type": "Slider",
"name": "velocitySlider",
"x": 1140,
"y": 100,
"width": 50,
"height": 500,
"value": 0.0,
"orientation": "vertical",
"textures": {
"track": "resources/velocitySliderTexture.png",
"knob": "resources/velocitySliderButton.png"
}
"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",
"x": 1115,
"x": 0,
"y": 0,
"width": 100,
"height": 100,
"width": 150,
"height": 150,
"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/fire.png",
"pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
}
},
{
"type": "Button",
"name": "shootButton2",
"x": 0,
"y": 0,
"width": 150,
"height": 150,
"horizontal_gravity": "left",
"vertical_gravity": "bottom",
"textures": {
"normal": "resources/fire.png",
"hover": "resources/fire.png",
"pressed": "resources/fire2.png",
"disabled": "resources/fire_disabled.png"
}
},
{
"type": "Button",
"name": "minusButton",
"x": -20,
"y": 110,
"width": 150,
"height": 150,
"border" : 20,
"horizontal_gravity": "right",
"vertical_gravity": "bottom",
"textures": {
"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"
}
}
]
+194
View File
@@ -0,0 +1,194 @@
{
"root": {
"type": "FrameLayout",
"x": 0,
"y": 0,
"width": 1280,
"height": 720,
"children": [
{
"type": "FrameLayout",
"name": "leftPanel",
"x": 100,
"y": 100,
"width": 320,
"height": 400,
"children": [
{
"type": "LinearLayout",
"name": "mainButtons",
"orientation": "vertical",
"spacing": 10,
"x": 0,
"y": 0,
"width": 300,
"height": 300,
"children": [
{
"type": "Button",
"name": "playButton",
"x": -1000,
"y": 500,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "settingsButton",
"x": -1000,
"y": 400,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 0.5
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
},
{
"type": "Button",
"name": "exitButton",
"x": -1000,
"y": 300,
"width": 200,
"height": 50,
"animations": {
"buttonsExit": {
"repeat": false,
"steps": [
{
"type": "wait",
"duration": 1.0
},
{
"type": "move",
"to": [
-400,
0
],
"duration": 1.0,
"easing": "easein"
}
]
},
"bgScroll": {
"repeat": true,
"steps": [
{
"type": "move",
"to": [
1280,
0
],
"duration": 5.0,
"easing": "linear"
}
]
}
},
"textures": {
"normal": "./resources/sand2.png",
"hover": "./resources/sand2.png",
"pressed": "./resources/sand2.png"
}
}
]
}
]
},
{
"type": "Slider",
"name": "velocitySlider",
"x": 1140,
"y": 300,
"width": 50,
"height": 300,
"value": 0.0,
"orientation": "vertical",
"textures": {
"track": "resources/velocitySliderTexture.png",
"knob": "resources/velocitySliderButton.png"
}
},
{
"type": "Button",
"name": "shootButton",
"x": 100,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "Button",
"name": "shootButton2",
"x": 1000,
"y": 100,
"width": 100,
"height": 100,
"textures": {
"normal": "resources/shoot_normal.png",
"hover": "resources/shoot_hover.png",
"pressed": "resources/shoot_pressed.png"
}
},
{
"type": "TextView",
"name": "velocityText",
"x": 10,
"y": 10,
"width": 200,
"height": 40,
"text": "Velocity: 0",
"fontSize": 24,
"color": [1.0, 1.0, 1.0, 1.0],
"centered": false
}
]
}
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
@@ -0,0 +1,125 @@
//precisionhighp float;
// Фрагментный шейдер:
uniform vec3 uColor;
uniform float uDistanceToPlanetSurface; // Расстояние корабля до поверхности
// Константы затухания, определенные прямо в шейдере
const float DIST_FOG_MAX = 2000.0;
const float DIST_FOG_MIN = 1000.0;
varying vec3 vWorldNormal;
varying vec3 vViewNormal;
varying vec3 vViewPosition;
// Добавь эти uniform-ы
//uniform float uTime;
uniform vec3 uLightDirView;
uniform vec3 uPlayerDirWorld;
// Простая функция псевдослучайного шума
float hash(float n) { return fract(sin(n) * 43758.5453123); }
float noise(vec3 x) {
vec3 p = floor(x);
vec3 f = fract(x);
f = f * f * (3.0 - 2.0 * f);
float n = p.x + p.y * 57.0 + 113.0 * p.z;
return mix(mix(mix(hash(n + 0.0), hash(n + 1.0), f.x),
mix(hash(n + 57.0), hash(n + 58.0), f.x), f.y),
mix(mix(hash(n + 113.0), hash(n + 114.0), f.x),
mix(hash(n + 170.0), hash(n + 171.0), f.x), f.y), f.z);
}
// Fractal Brownian Motion для "кучевости" облаков
float fbm(vec3 p) {
float f = 0.5000 * noise(p); p *= 2.02;
f += 0.2500 * noise(p); p *= 2.03;
f += 0.1250 * noise(p);
return f;
}
void main()
{
//gl_FragColor = vec4(1.0, 0.0, 0.0, 0.5);
vec3 normal = normalize(vViewNormal);
vec3 viewVector = normalize(-vViewPosition);
float NdotV = dot(normal, viewVector);
// Вектор направления от центра планеты к текущему фрагменту атмосферы (Мировой)
vec3 fragmentDir = normalize(vWorldNormal);
// Вектор направления от центра планеты к игроку (нужно передать как uniform)
// Передай его в C++: renderer.RenderUniform3fv("uPlayerDirWorld", playerDirWorld.data());
// --- 1. Плавное отсечение за горизонтом (Horizon Mask) ---
// Считаем косинус угла между игроком и точкой атмосферы
float cosAngle = dot(fragmentDir, uPlayerDirWorld);
// Плавно затухаем от 0.0 (горизонт) до 0.2 (над головой)
//float horizonMask = smoothstep(0.0, 0.4, cosAngle);
float horizonMask = smoothstep(0.93, 1.0, cosAngle);
// --- 2. Плавный переход при прохождении сквозь слой (Transition Mask) ---
// Определяем "высоту" игрока относительно слоя (напр. слой на 1.03 * R)
// uDistanceToPlanetSurface уже вычислен в PlanetData
float layerHeight = 600.0; // Примерная толщина слоя атмосферы (3% от 20000)
// Делаем прозрачным при приближении к границе (dist около layerHeight)
// и снова видимым, когда спустились ниже
float distToLayer = abs(uDistanceToPlanetSurface - layerHeight);
float transitionMask = smoothstep(0.0, 200.0, distToLayer);
// --- 3. Освещение и облака ---
float diff = max(dot(normal, uLightDirView), 0.0);
if (uDistanceToPlanetSurface < layerHeight) {
diff = max(dot(-normal, uLightDirView), 0.0); // Инверсия для взгляда снизу
}
float lightIntensity = diff + 0.05;
vec3 cloudCoord = fragmentDir * 6.0;
//cloudCoord.x += uTime * 0.03;
float n = fbm(cloudCoord);
float cloudMask = smoothstep(0.4, 0.65, n);
// --- 4. Финальный расчет альфы ---
float atmosphereDensity = pow(1.0 - abs(NdotV), 5.0);
float distanceFactor = clamp((uDistanceToPlanetSurface - DIST_FOG_MIN) / (DIST_FOG_MAX - DIST_FOG_MIN), 0.0, 1.0);
// Базовая прозрачность облаков
float cloudAlpha = cloudMask * 0.8;
// Применяем маски:
// В космосе важен distanceFactor, на планете важен horizonMask
float finalCloudAlpha = cloudAlpha * transitionMask;
if (uDistanceToPlanetSurface < layerHeight) {
finalCloudAlpha *= horizonMask; // На планете скрываем то, что под ногами
} else {
finalCloudAlpha *= distanceFactor; // В космосе скрываем по вашей старой логике
if (NdotV <=0.0)
{
//finalCloudAlpha = 0.0;
discard;
}
}
vec3 currentAtmo = mix(vec3(0.01, 0.02, 0.1), uColor, lightIntensity);
vec3 currentCloud = mix(vec3(0.1, 0.1, 0.15), vec3(1.0, 1.0, 1.0), lightIntensity);
vec3 finalRGB = mix(currentAtmo, currentCloud, cloudMask);
//vec3 finalRGB = currentAtmo;
// Для дымки (atmo) оставляем затухание при посадке
float atmoAlpha = atmosphereDensity * distanceFactor;
//float atmoAlpha = distanceFactor;
float finalAtmoAlpha = atmoAlpha;
if (uDistanceToPlanetSurface < layerHeight) {
finalCloudAlpha *= horizonMask;
finalAtmoAlpha *= horizonMask; // Принудительно гасим дымку под горизонтом
}
gl_FragColor = vec4(finalRGB, max(finalAtmoAlpha, finalCloudAlpha));
}
@@ -0,0 +1,9 @@
//precisionmediump float;
varying vec3 color;
void main()
{
//gl_FragColor = vec4(color, 1.0);
gl_FragColor = vec4(1.0, 1.0, 0.5, 1.0);
}
@@ -0,0 +1,12 @@
//precisionmediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
//gl_FragColor = vec4(color.rgb*0.1 + vec3(0.9, 0.9, 0.9), 1.0);
gl_FragColor = color;
}
@@ -0,0 +1,8 @@
//precisionmediump float;
uniform samplerCube Texture;
varying vec3 dir;
void main(){
gl_FragColor = textureCube(Texture, normalize(dir));
}
@@ -0,0 +1,9 @@
//precisionmediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
gl_FragColor = color;
}
@@ -0,0 +1,37 @@
//precisionmediump float;
uniform samplerCube Texture;
uniform float skyPercent;
uniform float uPlayerLightFactor; // Глобальный фактор дня/ночи для позиции игрока
uniform vec3 uSkyColor;
varying vec3 vViewDir;
void main() {
vec3 viewDir = normalize(vViewDir);
// 1. Получаем звезды
vec4 starsColor = textureCube(Texture, viewDir);
// 2. Цвета атмосферы
vec3 dayColor = uSkyColor;
vec3 nightColor = vec3(0.01, 0.01, 0.04);
// 3. Теперь всё небо окрашивается одинаково в зависимости от положения игрока
// Мы плавно смешиваем ночной и дневной купол
vec3 atmosphericColor = mix(nightColor, dayColor, uPlayerLightFactor);
// 4. Логика видимости звезд
// Звезды видны в космосе (skyPercent=0)
// И в атмосфере, если сейчас ночь (uPlayerLightFactor -> 0)
float starsVisibility = (1.0 - skyPercent) + (skyPercent * (1.0 - uPlayerLightFactor));
// Делаем звезды чуть ярче на ночном небе
starsVisibility = pow(starsVisibility, 1.5);
// 5. Итоговый цвет
// Добавляем слой атмосферы к звездам
vec3 skyLayer = atmosphericColor * skyPercent;
vec3 finalColor = (starsColor.rgb * starsVisibility) + skyLayer;
gl_FragColor = vec4(finalColor, 1.0);
}
@@ -0,0 +1,14 @@
//precisionmediump float;
varying vec2 TexCoord;
varying float vHeight;
uniform sampler2D Texture;
void main()
{
vec4 stoneColor = texture2D(Texture, TexCoord);
gl_FragColor = vec4(stoneColor.rgb, vHeight);
//gl_FragColor = vec4(vHeight, vHeight, vHeight, vHeight);
//gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);
}
+1 -1
View File
@@ -15,7 +15,7 @@ varying vec3 vWorldNormal;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform highp vec3 uViewPos;
uniform vec3 uViewPos;
void main() {
gl_Position = ProjectionModelViewMatrix * vec4(vPosition, 1.0);
@@ -0,0 +1,116 @@
//precisionhighp float;
varying vec2 TexCoord;
varying vec3 vViewDirTangent;
varying vec3 Color;
varying vec3 worldPosition;
varying vec3 vWorldNormal;
uniform sampler2D Texture;
uniform sampler2D BakedTexture;
uniform float uHeightScale;
uniform float uDistanceToPlanetSurface;
uniform float uCurrentZFar;
uniform vec3 uViewPos;
const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман
uniform vec3 uLightDirWorld; // Направление ЛУЧЕЙ (1, -1, -1)
uniform float uPlayerLightFactor;
uniform vec3 uPlayerDirWorld;
void main() {
// --- 1. Расчет освещения поверхности ---
// Направление НА источник света
vec3 lightToSource = normalize(-uLightDirWorld);
vec3 fragmentDir = normalize(vWorldNormal);
// Используем vNormal (нормаль в мировом пространстве, так как vPosition тоже в мировом)
// Важно: если vNormal не нормализована в вершинном, делаем это здесь
vec3 normal = normalize(fragmentDir); // На планете-сфере нормаль совпадает с направлением от центра
float diff = max(dot(normal, lightToSource), 0.0);
float ambient = 0.3; // Базовая освещенность ночной стороны
float surfaceLightIntensity = diff + ambient;
// --- 2. Динамический цвет тумана ---
// Синхронизируем туман с атмосферой: днем голубой, ночью темно-синий
vec3 dayFog = vec3(0.0, 0.5, 1.0);
vec3 nightFog = vec3(0.01, 0.01, 0.04);
vec3 dynamicFogColor = mix(nightFog, dayFog, uPlayerLightFactor);
// --- 3. Основная логика текстур (твой код) ---
vec3 viewDir = normalize(vViewDirTangent);
float height = texture2D(Texture, TexCoord).a;
vec2 p = vec2(viewDir.x, -viewDir.y) * (height * uHeightScale);
vec2 finalTexCoord = TexCoord + p;
float realDist = distance(worldPosition, uViewPos);
float textureMixFactor = clamp((2000.0 - realDist) / 500.0, 0.0, 1.0);
vec4 bakedTextureColor = texture2D(BakedTexture, finalTexCoord);
vec4 textureColor = texture2D(Texture, TexCoord);
vec4 textureFlavoredColor = vec4(textureColor.rgb * Color, 1.0);
if (bakedTextureColor.x < 0.01 && bakedTextureColor.y < 0.01 && bakedTextureColor.z < 0.01) {
textureMixFactor = 1.0;
}
vec4 finalColor = textureMixFactor * textureFlavoredColor + (1.0 - textureMixFactor) * bakedTextureColor;
// --- 4. ПРИМЕНЕНИЕ ОСВЕЩЕНИЯ ---
// Умножаем результат текстурирования на освещенность
finalColor.rgb *= surfaceLightIntensity;
// --- 5. Расчет тумана (с использованием динамического цвета) ---
float h = uDistanceToPlanetSurface;
float fogStart, fogEnd;
if (h >= 1000.0) {
gl_FragColor = vec4(finalColor.rgb, 1.0);
}
else
{
if (h > 100.0) {
// Нормализуем высоту от 100 до 1000 (t: 0 -> 1)
float t = (h - 100.0) / 900.0;
// На высоте 100 туман начинается со 100.
// К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает.
fogStart = mix(1500.0, 15000.0, t);
// Держим ширину зоны тумана в пределах 400-600 единиц
float fogRange = mix(1000.0, 6000.0, t);
fogEnd = fogStart + fogRange;
}
else if (h > 40.0) {
float t = (h - 40.0) / 60.0;
// На высоте 100 туман начинается со 100.
// К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает.
fogStart = mix(800.0, 1500.0, t);
fogEnd = mix(1000.0, 2500.0, t);
}
else if (h > 20.0) {
float t = (h - 20.0) / 20.0;
fogStart = mix(200.0, 800.0, t);
fogEnd = mix(500.0, 1000.0, t);
}
else {
// Минимумы при h = 0: start 25, end 125
float t = clamp(h / 20.0, 0.0, 1.0);
fogStart = mix(100.0, 200.0, t);
fogEnd = mix(250.0, 500.0, t);
}
float fogRange = max(fogEnd - fogStart, 1.0);
float fogFactor = clamp((realDist - fogStart) / fogRange, 0.0, 1.0);
// Смешиваем с динамическим цветом тумана
vec3 mixedColor = mix(finalColor.rgb, dynamicFogColor, fogFactor);
gl_FragColor = vec4(mixedColor, 1.0);
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ uniform float uHeightScale;
uniform float uDistanceToPlanetSurface;
uniform float uCurrentZFar;
uniform vec3 uViewPos;
uniform highp vec3 uViewPos;
const vec4 FOG_COLOR = vec4(0.0, 0.5, 1.0, 1.0); // Синий туман
+1 -3
View File
@@ -14,7 +14,7 @@ varying vec3 vWorldNormal;
uniform mat4 ProjectionModelViewMatrix;
uniform mat4 ModelViewMatrix;
uniform highp vec3 uViewPos;
uniform vec3 uViewPos;
void main() {
vWorldNormal = vNormal;
@@ -23,8 +23,6 @@ void main() {
vec3 viewDirWorld = normalize(uViewPos - vPosition);
// Строим матрицу перехода из атрибутов
// Так как базис ортонормирован, TBN^-1 == TBN_transpose
vViewDirTangent = vec3(
dot(viewDirWorld, vTangent),
dot(viewDirWorld, vBinormal),
@@ -0,0 +1,102 @@
//precisionhighp float;
// planetStone фрагментный шейдер
varying vec2 TexCoord;
varying vec3 worldPosition;
varying vec3 vWorldNormal;
uniform sampler2D Texture;
uniform float uDistanceToPlanetSurface;
uniform vec3 uViewPos;
uniform vec3 uLightDirWorld;
uniform float uPlayerLightFactor;
void main()
{
// --- 1. Подготовка векторов ---
vec3 normal = normalize(vWorldNormal);
vec3 lightToSource = normalize(-uLightDirWorld);
// Вектор от центра планеты к камню (нормаль самой поверхности планеты под камнем)
// Предполагаем, что центр планеты в (0,0,0)
vec3 surfaceNormal = normalize(worldPosition);
// --- 2. Расчет Shadow Mask ---
// Проверяем, освещена ли точка планеты, на которой стоит камень
float shadowMask = clamp(dot(surfaceNormal, lightToSource) * 5.0, 0.0, 1.0);
// Коэффициент 5.0 делает переход на терминаторе более четким
// --- 3. Освещение камня ---
float diff = max(dot(normal, lightToSource), 0.0);
// Применяем shadowMask к диффузной составляющей
// Если точка на планете в тени, то прямой свет (diff) обнуляется
float ambient = 0.3;
float lightIntensity = (diff * shadowMask);
lightIntensity *= mix(0.2, 1.0, shadowMask);
lightIntensity += ambient;
// --- 4. Остальная логика (цвета и туман) ---
vec3 dayFog = vec3(0.0, 0.5, 1.0);
vec3 nightFog = vec3(0.01, 0.01, 0.04);
vec3 dynamicFogColor = mix(nightFog, dayFog, uPlayerLightFactor);
vec4 textureColor = texture2D(Texture, TexCoord);
vec3 litColor = textureColor.rgb * lightIntensity;
float realDist = distance(worldPosition, uViewPos);
float h = uDistanceToPlanetSurface;
float alphaFactor = clamp((2000.0 - realDist) / 500.0, 0.0, 1.0);
float fogStart, fogEnd;
// --- Логика расчета границ тумана ---
if (h >= 1000.0) {
gl_FragColor = vec4(litColor, alphaFactor);
}
else
{
if (h > 100.0) {
// Нормализуем высоту от 100 до 1000 (t: 0 -> 1)
float t = (h - 100.0) / 900.0;
// На высоте 100 туман начинается со 100.
// К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает.
fogStart = mix(1500.0, 15000.0, t);
// Держим ширину зоны тумана в пределах 400-600 единиц
float fogRange = mix(1000.0, 6000.0, t);
fogEnd = fogStart + fogRange;
}
else if (h > 40.0) {
float t = (h - 40.0) / 60.0;
// На высоте 100 туман начинается со 100.
// К высоте 1000 он плавно "убегает" к границе 2500, где объект исчезает.
fogStart = mix(800.0, 1500.0, t);
fogEnd = mix(1000.0, 2500.0, t);
}
else if (h > 20.0) {
float t = (h - 20.0) / 20.0;
fogStart = mix(200.0, 800.0, t);
fogEnd = mix(500.0, 1000.0, t);
}
else {
// Минимумы при h = 0: start 25, end 125
float t = clamp(h / 20.0, 0.0, 1.0);
fogStart = mix(100.0, 200.0, t);
fogEnd = mix(250.0, 500.0, t);
}
// Расчет фактора тумана
float fogRange = max(fogEnd - fogStart, 1.0);
float fogFactor = clamp((realDist - fogStart) / fogRange, 0.0, 1.0);
// Смешивание освещенного камня с динамическим туманом
vec3 mixedColor = mix(litColor, dynamicFogColor, fogFactor);
gl_FragColor = vec4(mixedColor, alphaFactor);
}
}
+9
View File
@@ -0,0 +1,9 @@
//precisionmediump float;
uniform sampler2D Texture;
varying vec2 texCoord;
void main()
{
vec4 color = texture2D(Texture,texCoord).rgba;
gl_FragColor = color;
}
File renamed without changes.
+11
View File
@@ -0,0 +1,11 @@
attribute vec2 vPosition;
attribute vec2 vTexCoord;
varying vec2 TexCoord;
uniform mat4 uProjection;
void main() {
TexCoord = vTexCoord;
gl_Position = uProjection * vec4(vPosition, 0.0, 1.0);
}
+14
View File
@@ -0,0 +1,14 @@
#version 330 core
in vec2 TexCoord;
out vec4 FragColor;
uniform sampler2D uText;
uniform vec4 uColor;
uniform vec4 uOutlineColor;
uniform float uOutlineWidth = 0.7;
void main() {
float dist = texture(uText, TexCoord).r;
float outline = smoothstep(0.5 - uOutlineWidth, 0.5 + uOutlineWidth, dist);
float text = smoothstep(0.5, 0.5 + 0.1, dist);
float glow = exp(-pow(dist - 0.5, 2.0) / 0.02);
FragColor = vec4(uColor.rgb * (text + glow * 0.6), uColor.a * outline);
}
+19
View File
@@ -0,0 +1,19 @@
precision mediump float;
varying vec2 TexCoord;
uniform sampler2D uText;
uniform vec4 uColor;
//uniform float uOutlineWidth; // Убрано дефолтное значение
void main() {
float dist = texture2D(uText, TexCoord).r;
float uOutlineWidth = 0.7;
float outline = smoothstep(0.5 - uOutlineWidth, 0.5 + uOutlineWidth, dist);
float text = smoothstep(0.5, 0.6, dist); // 0.5 + 0.1 вычислено заранее
float glow = exp(-pow(dist - 0.5, 2.0) / 0.02);
gl_FragColor = vec4(uColor.rgb * (text + glow * 0.6), uColor.a * outline);
}
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+2
View File
@@ -26,8 +26,10 @@ 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
)
target_include_directories(Server PRIVATE ${BOOST_SRC_DIR})
+762 -110
View File
@@ -1,18 +1,12 @@
#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 <Eigen/Dense>
#define _USE_MATH_DEFINES
#include <math.h>
#include "../src/network/ClientState.h"
#include <sstream>
#include <random>
#include <algorithm>
#include <chrono>
// Вспомогательный split
std::vector<std::string> split(const std::string& s, char delimiter) {
std::vector<std::string> tokens;
std::string token;
@@ -23,112 +17,177 @@ std::vector<std::string> split(const std::string& s, char delimiter) {
return tokens;
}
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
Session::Session(Server& server, tcp::socket&& socket, int id)
: server_(server)
, ws_(std::move(socket))
, id_(id)
, lastReceivedTime_(std::chrono::system_clock::now()) {
}
class Session;
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;
}
std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex;
void Session::force_disconnect() {
ws_.async_close(websocket::close_code::normal,
[self = shared_from_this()](beast::error_code) {});
}
class Session : public std::enable_shared_from_this<Session> {
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
int Session::get_id() const { return id_; }
ClientStateInterval timedClientStates;
bool Session::hasSpawnReserved() const { return hasReservedSpawn_; }
const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; }
void process_message(const std::string& msg) {
auto now_server = std::chrono::system_clock::now();
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;
}
auto parts = split(msg, ':');
if (parts.size() < 16)
void Session::send_message(const std::string& msg) {
auto ss = std::make_shared<std::string>(msg);
{
throw std::runtime_error("Unknown message type received, too small");
std::lock_guard<std::mutex> lock(writeMutex_);
writeQueue_.push(ss);
}
doWrite();
}
uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{ std::chrono::duration_cast<std::chrono::system_clock::time_point::duration>(std::chrono::milliseconds(clientTimestamp)) };
receivedState.lastUpdateServerTime = uptime_timepoint;
if (parts[0] == "UPD") {
receivedState.handle_full_sync(parts, 2);
retranslateMessage(msg);
}
else
void Session::run() {
{
throw std::runtime_error("Unknown message type received: " + parts[0]);
}
timedClientStates.add_state(receivedState);
}
void retranslateMessage(const std::string& msg)
{
std::string event_msg = "EVENT:" + std::to_string(id_) + ":" + msg;
std::lock_guard<std::mutex> lock(g_sessions_mutex);
for (auto& session : g_sessions) {
if (session->get_id() != id_) { // Не шлем отправителю
session->send_message(event_msg);
}
}
}
public:
explicit Session(tcp::socket&& socket, int id)
: ws_(std::move(socket)), id_(id) {
}
void init()
{
}
void 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) {
if (ec) return;
std::cout << "Client " << self->id_ << " connected\n";
self->init();
self->send_message("ID:" + std::to_string(self->id_));
self->do_read();
});
}
void send_message(std::string msg) {
auto ss = std::make_shared<std::string>(std::move(msg));
ws_.async_write(net::buffer(*ss), [ss](beast::error_code, std::size_t) {});
bool Session::IsMessageValid(const std::string& fullMessage) {
#ifdef ENABLE_NETWORK_CHECKSUM
size_t hashPos = fullMessage.find("#hash:");
if (hashPos == std::string::npos) {
return false; // Хеша нет, хотя он ожидался
}
int get_id() const {
return id_;
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 do_read() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
if (ec)
void Session::sendBoxesToClient() {
std::lock_guard<std::mutex> lock(server_.g_boxes_mutex);
std::string boxMsg = "BOXES:";
bool first = true;
for (size_t i = 0; i < server_.g_serverBoxes.size(); ++i) {
const auto& box = server_.g_serverBoxes[i];
if (box.destroyed) continue;
Eigen::Quaternionf q(box.rotation);
if (!first) boxMsg += "|";
first = false;
boxMsg += std::to_string(i) + ":" +
std::to_string(box.position.x()) + ":" +
std::to_string(box.position.y()) + ":" +
std::to_string(box.position.z()) + ":" +
std::to_string(q.w()) + ":" +
std::to_string(q.x()) + ":" +
std::to_string(q.y()) + ":" +
std::to_string(q.z()) + ":" +
"0";
}
send_message(boxMsg);
}
void Session::init()
{
std::lock_guard<std::mutex> lock(g_sessions_mutex);
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
sendBoxesToClient();
auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
timer->expires_after(std::chrono::milliseconds(100));
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
if (!ec) {
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
self->send_message("ID:" + std::to_string(self->id_) + ":" + std::to_string(now_ms));
self->do_read();
}
});
}
ClientState Session::get_latest_state(std::chrono::system_clock::time_point now) {
if (timedClientStates.timedStates.empty()) {
return {};
}
ClientState latest = timedClientStates.timedStates.back();
latest.apply_lag_compensation(now);
return latest;
}
void Session::doWrite() {
std::lock_guard<std::mutex> lock(writeMutex_);
if (is_writing_ || writeQueue_.empty()) {
return;
}
is_writing_ = true;
auto message = writeQueue_.front();
ws_.async_write(net::buffer(*message),
[self = shared_from_this(), message](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "Write error: " << ec.message() << std::endl;
return;
}
{
std::lock_guard<std::mutex> lock(self->writeMutex_);
self->writeQueue_.pop();
self->is_writing_ = false;
}
self->doWrite();
});
}
void Session::do_read() {
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
if (ec) {
if (self->joined_) {
self->server_.broadcastToAllExceptId("PLAYER_LEFT:" + std::to_string(self->id_), self->id_);
std::cout << "Client " << self->id_ << " disconnected, broadcasting PLAYER_LEFT\n";
}
std::lock_guard<std::mutex> lock(self->server_.g_sessions_mutex);
self->server_.g_sessions.erase(std::remove_if(self->server_.g_sessions.begin(), self->server_.g_sessions.end(),
[self](const std::shared_ptr<Session>& session) {
return session.get() == self.get();
}), g_sessions.end());
}), self->server_.g_sessions.end());
std::cout << "Client " << self->id_ << " removed from session list\n";
return;
}
@@ -139,40 +198,633 @@ private:
self->do_read();
});
}
void Session::process_message(const std::string& msg) {
if (!IsMessageValid(msg)) {
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
return;
}
lastReceivedTime_ = std::chrono::system_clock::now();
std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl;
auto parts = split(cleanMessage, ':');
if (parts.empty()) return;
std::string type = parts[0];
if (type == "JOIN") {
std::string nick = "Player";
int sType = 0;
if (parts.size() >= 2) nick = parts[1];
if (parts.size() >= 3) {
try { sType = std::stoi(parts[2]); }
catch (...) { sType = 0; }
}
this->nickname = nick;
this->shipType = sType;
this->joined_ = true;
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
this->hasReservedSpawn_ = true;
this->reservedSpawn_ = spawnPos;
ClientState st;
st.id = id_;
st.position = spawnPos;
st.rotation = Eigen::Matrix3f::Identity();
st.currentAngularVelocity = Eigen::Vector3f::Zero();
st.velocity = 0.0f;
st.selectedVelocity = 0;
st.discreteMag = 0.0f;
st.discreteAngle = -1;
st.lastUpdateServerTime = now_tp;
st.nickname = this->nickname;
st.shipType = this->shipType;
timedClientStates.add_state(st);
this->send_message(
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
);
std::string eventMsg =
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
server_.broadcastToAllExceptId(eventMsg, id_);
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
server_.broadcastToAllExceptId(info, id_);
{
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
for (auto& session : server_.g_sessions) {
if (session->get_id() == this->id_) continue;
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
this->send_message(otherInfo);
}
}
}
else if (type == "UPD") {
if (!joined_) {
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
return;
}
{
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
if (server_.g_dead_players.find(id_) != server_.g_dead_players.end()) {
std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
return;
}
}
if (parts.size() < 16) return;
uint64_t clientTimestamp = std::stoull(parts[1]);
ClientState receivedState;
receivedState.id = id_;
std::chrono::system_clock::time_point uptime_timepoint{
std::chrono::milliseconds(clientTimestamp)
};
receivedState.lastUpdateServerTime = uptime_timepoint;
receivedState.handle_full_sync(parts, 2);
receivedState.nickname = this->nickname;
receivedState.shipType = this->shipType;
timedClientStates.add_state(receivedState);
}
else if (type == "RESPAWN") {
{
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
server_.g_dead_players.erase(id_);
}
{
auto now_tp = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
ClientState st;
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;
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_);
server_.broadcastToAll(respawnMsg);
std::string playerInfo = "PLAYERINFO:" + std::to_string(id_) + ":" + st.nickname + ":" + std::to_string(st.shipType);
server_.broadcastToAll(playerInfo);
std::string eventMsg = "EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
server_.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]);
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{}() };
std::scoped_lock lock(g_boxes_mutex, g_sessions_mutex, g_dead_mutex);
auto isSafe = [&](const Eigen::Vector3f& pWorld) -> bool
{
for (const auto& box : g_serverBoxes) {
if (box.destroyed) continue;
Eigen::Vector3f boxWorld = box.position + kWorldOffset;
float minDist = kShipRadius + box.collisionRadius + kSpawnBoxMargin;
if ((pWorld - boxWorld).squaredNorm() < minDist * minDist)
return false;
}
for (const auto& s : g_sessions) {
int pid = s->get_id();
if (pid == forPlayerId) continue;
if (g_dead_players.count(pid)) continue;
Eigen::Vector3f otherPos;
if (!s->timedClientStates.timedStates.empty()) {
otherPos = s->timedClientStates.timedStates.back().position;
}
else if (s->hasSpawnReserved()) {
otherPos = s->reservedSpawn();
}
else {
continue;
}
float minDist = (kShipRadius * 2.0f) + kSpawnShipMargin;
if ((pWorld - otherPos).squaredNorm() < minDist * minDist)
return false;
}
return true;
};
void update_world(net::steady_timer& timer, net::io_context& ioc) {
const float radii[] = { 150.f, 250.f, 400.f, 650.f, 1000.f, 1600.f };
// TODO: Renew game state
for (float r : radii) {
std::uniform_real_distribution<float> dxy(-r, r);
std::uniform_real_distribution<float> dz(-kSpawnZJitter, kSpawnZJitter);
for (int attempt = 0; attempt < 250; ++attempt) {
Eigen::Vector3f cand(
dxy(rng),
dxy(rng),
kWorldZOffset + dz(rng)
);
if (isSafe(cand))
return cand;
}
}
int a = (forPlayerId % 10);
int b = ((forPlayerId / 10) % 10);
return Eigen::Vector3f(600.0f + a * 100.0f, -600.0f + b * 100.0f, kWorldZOffset);
}
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 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();
auto now = std::chrono::system_clock::now();
uint64_t now_ms = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count());
// --- 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) {
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<std::mutex> lock(g_sessions_mutex);
for (auto& sender : g_sessions) {
if (sender->timedClientStates.timedStates.empty()) continue;
const ClientState& st = sender->timedClientStates.timedStates.back();
uint64_t stateTime = static_cast<uint64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
st.lastUpdateServerTime.time_since_epoch()).count());
std::string event_msg = "EVENT:" + std::to_string(sender->get_id()) +
":UPD:" + std::to_string(stateTime) + ":" + st.formPingMessageContent();
for (auto& receiver : g_sessions) {
if (receiver->get_id() != sender->get_id()) {
receiver->send_message(event_msg);
}
}
}
}
// --- Tick: projectile movement and hit detection ---
const float dt = 50.0f / 1000.0f;
std::vector<DeathInfo> deathEvents;
{
std::lock_guard<std::mutex> pl(g_projectiles_mutex);
std::vector<int> indicesToRemove;
for (size_t i = 0; i < g_projectiles.size(); ++i) {
auto& pr = g_projectiles[i];
pr.pos += pr.vel * dt;
if (now_ms > pr.spawnMs + static_cast<uint64_t>(pr.lifeMs)) {
indicesToRemove.push_back(static_cast<int>(i));
continue;
}
bool hitDetected = false;
{
std::lock_guard<std::mutex> lm(g_sessions_mutex);
std::lock_guard<std::mutex> gd(g_dead_mutex);
for (auto& session : g_sessions) {
int targetId = session->get_id();
if (targetId == pr.shooterId) continue;
if (g_dead_players.find(targetId) != g_dead_players.end()) continue;
ClientState targetState;
if (!session->fetchStateAtTime(now, targetState)) continue;
Eigen::Vector3f diff = pr.pos - targetState.position;
float combinedRadius = shipCollisionRadius + projectileHitRadius;
if (diff.squaredNorm() <= combinedRadius * combinedRadius) {
DeathInfo death;
death.targetId = targetId;
death.serverTime = now_ms;
death.position = pr.pos;
death.killerId = pr.shooterId;
deathEvents.push_back(death);
g_dead_players.insert(targetId);
indicesToRemove.push_back(static_cast<int>(i));
hitDetected = true;
std::cout << "Server: *** HIT DETECTED! ***" << std::endl;
std::cout << "Server: Projectile at ("
<< pr.pos.x() << ", " << pr.pos.y() << ", " << pr.pos.z()
<< ") hit player " << targetId << std::endl;
break;
}
}
}
if (hitDetected) continue;
}
if (!indicesToRemove.empty()) {
std::sort(indicesToRemove.rbegin(), indicesToRemove.rend());
for (int idx : indicesToRemove) {
if (idx >= 0 && idx < (int)g_projectiles.size()) {
g_projectiles.erase(g_projectiles.begin() + idx);
}
}
}
}
// --- Tick: box-projectile collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
std::vector<std::pair<size_t, size_t>> boxProjectileCollisions;
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue;
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
for (size_t pi = 0; pi < g_projectiles.size(); ++pi) {
const auto& pr = g_projectiles[pi];
Eigen::Vector3f diff = pr.pos - boxWorld;
float thresh = boxCollisionRadius + projectileHitRadius;
if (diff.squaredNorm() <= thresh * thresh) {
boxProjectileCollisions.push_back({ bi, pi });
}
}
}
for (const auto& [boxIdx, projIdx] : boxProjectileCollisions) {
g_serverBoxes[boxIdx].destroyed = true;
Eigen::Vector3f boxWorld = g_serverBoxes[boxIdx].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
BoxDestroyedInfo destruction;
destruction.boxIndex = static_cast<int>(boxIdx);
destruction.serverTime = now_ms;
destruction.position = boxWorld;
destruction.destroyedBy = g_projectiles[projIdx].shooterId;
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
g_boxDestructions.push_back(destruction);
}
std::cout << "Server: Box " << boxIdx << " destroyed by projectile from player "
<< g_projectiles[projIdx].shooterId << std::endl;
}
}
// --- Tick: box-ship collisions ---
{
std::lock_guard<std::mutex> bm(g_boxes_mutex);
std::lock_guard<std::mutex> lm(g_sessions_mutex);
for (size_t bi = 0; bi < g_serverBoxes.size(); ++bi) {
if (g_serverBoxes[bi].destroyed) continue;
Eigen::Vector3f boxWorld = g_serverBoxes[bi].position + Eigen::Vector3f(0.0f, 0.0f, 45000.0f);
for (auto& session : g_sessions) {
{
std::lock_guard<std::mutex> gd(g_dead_mutex);
if (g_dead_players.find(session->get_id()) != g_dead_players.end()) {
continue;
}
}
ClientState shipState;
if (!session->fetchStateAtTime(now, shipState)) continue;
Eigen::Vector3f diff = shipState.position - boxWorld;
float thresh = shipCollisionRadius + boxCollisionRadius;
if (diff.squaredNorm() <= thresh * thresh) {
g_serverBoxes[bi].destroyed = true;
BoxDestroyedInfo destruction;
destruction.boxIndex = static_cast<int>(bi);
destruction.serverTime = now_ms;
destruction.position = boxWorld;
destruction.destroyedBy = session->get_id();
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
g_boxDestructions.push_back(destruction);
}
std::cout << "Server: Box " << bi << " destroyed by ship collision with player "
<< session->get_id() << std::endl;
break;
}
}
}
}
// --- Broadcast deaths ---
for (const auto& death : deathEvents) {
std::string deadMsg = "DEAD:" +
std::to_string(death.serverTime) + ":" +
std::to_string(death.targetId) + ":" +
std::to_string(death.position.x()) + ":" +
std::to_string(death.position.y()) + ":" +
std::to_string(death.position.z()) + ":" +
std::to_string(death.killerId);
broadcastToAll(deadMsg);
std::cout << "Server: Sent DEAD event - Player " << death.targetId
<< " killed by " << death.killerId << std::endl;
}
// --- Broadcast box destructions ---
{
std::lock_guard<std::mutex> dm(g_boxDestructions_mutex);
for (const auto& destruction : g_boxDestructions) {
std::string boxMsg = "BOX_DESTROYED:" +
std::to_string(destruction.boxIndex) + ":" +
std::to_string(destruction.serverTime) + ":" +
std::to_string(destruction.position.x()) + ":" +
std::to_string(destruction.position.y()) + ":" +
std::to_string(destruction.position.z()) + ":" +
std::to_string(destruction.destroyedBy);
broadcastToAll(boxMsg);
std::cout << "Server: Broadcasted BOX_DESTROYED for box " << destruction.boxIndex << std::endl;
}
g_boxDestructions.clear();
}
// --- Schedule next tick in 50ms ---
timer.expires_after(std::chrono::milliseconds(50));
timer.async_wait([&](const boost::system::error_code& ec) {
if (!ec) update_world(timer, ioc);
timer.async_wait([this](const boost::system::error_code& ec) {
if (ec) return;
update_world();
});
}
std::vector<ServerBox> Server::generateServerBoxes(int count) {
std::vector<ServerBox> boxes;
std::random_device rd;
std::mt19937 gen(rd());
const float MIN_COORD = -1000.0f;
const float MAX_COORD = 1000.0f;
const float MIN_DISTANCE = 3.0f;
const float MIN_DISTANCE_SQUARED = MIN_DISTANCE * MIN_DISTANCE;
const int MAX_ATTEMPTS = 1000;
std::uniform_real_distribution<> posDistrib(MIN_COORD, MAX_COORD);
std::uniform_real_distribution<> angleDistrib(0.0, M_PI * 2.0);
for (int i = 0; i < count; i++) {
bool accepted = false;
int attempts = 0;
while (!accepted && attempts < MAX_ATTEMPTS) {
ServerBox box;
box.position = Eigen::Vector3f(
(float)posDistrib(gen),
(float)posDistrib(gen),
(float)posDistrib(gen)
);
accepted = true;
for (const auto& existingBox : boxes) {
Eigen::Vector3f diff = box.position - existingBox.position;
if (diff.squaredNorm() < MIN_DISTANCE_SQUARED) {
accepted = false;
break;
}
}
if (accepted) {
float randomAngle = (float)angleDistrib(gen);
Eigen::Vector3f axis = Eigen::Vector3f::Random().normalized();
box.rotation = Eigen::AngleAxisf(randomAngle, axis).toRotationMatrix();
boxes.push_back(box);
}
attempts++;
}
}
return boxes;
}
Server::Server(tcp::acceptor& acceptor, net::io_context& ioc)
: acceptor_(acceptor)
, ioc_(ioc)
, timer(ioc_)
{
}
void Server::init()
{
std::lock_guard<std::mutex> lock(g_boxes_mutex);
g_serverBoxes = generateServerBoxes(50);
std::cout << "Generated " << g_serverBoxes.size() << " boxes on server\n";
}
void Server::accept()
{
acceptor_.async_accept([&](beast::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(*this, std::move(socket), next_id++)->run();
}
accept();
});
}
int main() {
try {
net::io_context ioc;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8080} };
int next_id = 1000;
tcp::acceptor acceptor{ ioc, {tcp::v4(), 8081} };
std::cout << "Server started on port 8080...\n";
Server server(acceptor, ioc);
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);
});
};
server.accept();
std::cout << "Server started on port 8081...\n";
net::steady_timer timer(ioc);
update_world(timer, ioc);
do_accept(do_accept);
server.update_world();
ioc.run();
}
catch (std::exception const& e) {
+140
View File
@@ -0,0 +1,140 @@
#pragma once
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <string>
#include <memory>
#include <vector>
#include <mutex>
#include <queue>
#include <unordered_set>
#include <Eigen/Dense>
#include "../src/network/ClientState.h"
#define _USE_MATH_DEFINES
#include <math.h>
namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = net::ip::tcp;
static constexpr float kWorldZOffset = 45000.0f;
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
static constexpr float kShipRadius = 15.0f;
static constexpr float kSpawnShipMargin = 25.0f;
static constexpr float kSpawnBoxMargin = 15.0f;
static constexpr float kSpawnZJitter = 60.0f;
std::vector<std::string> split(const std::string& s, char delimiter);
struct DeathInfo {
int targetId = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int killerId = -1;
};
struct ServerBox {
Eigen::Vector3f position;
Eigen::Matrix3f rotation;
float collisionRadius = 2.0f;
bool destroyed = false;
};
struct Projectile {
int shooterId = -1;
uint64_t spawnMs = 0;
Eigen::Vector3f pos;
Eigen::Vector3f vel;
float lifeMs = PROJECTILE_LIFE;
};
struct BoxDestroyedInfo {
int boxIndex = -1;
uint64_t serverTime = 0;
Eigen::Vector3f position = Eigen::Vector3f::Zero();
int destroyedBy = -1;
};
class Server;
class Session : public std::enable_shared_from_this<Session> {
Server& server_;
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
int id_;
bool is_writing_ = false;
std::queue<std::shared_ptr<std::string>> writeQueue_;
std::mutex writeMutex_;
public:
ClientStateInterval timedClientStates;
bool joined_ = false;
std::chrono::system_clock::time_point lastReceivedTime_;
bool hasReservedSpawn_ = false;
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
std::string nickname = "Player";
int shipType = 0;
Session(Server& server, tcp::socket&& socket, int id);
int get_id() const;
bool hasSpawnReserved() const;
const Eigen::Vector3f& reservedSpawn() const;
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const;
void send_message(const std::string& msg);
void run();
bool IsMessageValid(const std::string& fullMessage);
bool is_timed_out(std::chrono::system_clock::time_point now) const;
void force_disconnect();
private:
void sendBoxesToClient();
public:
void init();
ClientState get_latest_state(std::chrono::system_clock::time_point now);
void doWrite();
private:
void do_read();
void process_message(const std::string& msg);
};
class Server
{
public:
tcp::acceptor& acceptor_;
net::io_context& ioc_;
net::steady_timer timer;
std::vector<BoxDestroyedInfo> g_boxDestructions;
std::mutex g_boxDestructions_mutex;
std::vector<ServerBox> g_serverBoxes;
std::mutex g_boxes_mutex;
std::vector<std::shared_ptr<Session>> g_sessions;
std::mutex g_sessions_mutex;
std::vector<Projectile> g_projectiles;
std::mutex g_projectiles_mutex;
std::unordered_set<int> g_dead_players;
std::mutex g_dead_mutex;
int next_id = 1000;
std::vector<ServerBox> generateServerBoxes(int count);
public:
Server(tcp::acceptor& acceptor, net::io_context& ioc);
void broadcastToAll(const std::string& message);
void broadcastToAllExceptId(const std::string& message, int id);
void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
void update_world();
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
void init();
void accept();
};
+1 -1
View File
@@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "render/Renderer.h"
+1 -1
View File
@@ -1,4 +1,4 @@
#ifdef AUDIO
#ifdef AUDIO
#include "AudioPlayerAsync.h"
Loaded 100 of 159 files, more files were not shown because too many files have changed in this diff. Show more