Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b5bea540 | ||
|
|
9da3cc4401 | ||
|
|
4a542fd6c8 | ||
|
|
a06cb77a66 | ||
|
|
3c2c06ca40 | ||
|
|
928600acd4 | ||
|
|
7ccda081b0 | ||
|
|
41f370f2ee | ||
|
|
4af20c65e6 | ||
|
|
ac551122d9 | ||
|
|
8528b4dc90 | ||
|
|
c35e093cc5 | ||
|
|
a8c76dee3c | ||
|
|
e84a094ad9 | ||
|
|
b0b7391939 | ||
|
|
3e2632c5b5 | ||
|
|
70ef730e86 | ||
|
|
92b288d978 | ||
|
|
cca1d0ece2 | ||
|
|
8effdd5dfa | ||
|
|
e78429b600 | ||
|
|
05a13a40e7 | ||
|
|
ef7617701e | ||
|
|
2f50dc1522 | ||
|
|
9eafcd27fb | ||
|
|
3879eb1d75 | ||
|
|
78046e5e2d | ||
|
|
26233f934f | ||
|
|
167c04b107 | ||
|
|
5fcb1d1234 | ||
|
|
70a617b688 | ||
|
|
bb0f584bf6 | ||
|
|
6b4b549b3c | ||
|
|
ffbecbbcde | ||
|
|
2728ca9646 | ||
|
|
7e5aa22fee | ||
|
|
c20e82634a | ||
|
|
9f82e7a2e6 | ||
|
|
7418bbbe27 | ||
|
|
be2aa76f8b | ||
|
|
e8f1786a2a | ||
|
|
e30f8a4c70 | ||
|
|
f8642efa23 | ||
|
|
5da5a754fe | ||
|
|
fb1b0a1c2e | ||
|
|
74c2f786a1 | ||
|
|
5216965496 | ||
|
|
ad7294ceea | ||
|
|
3893038d9a | ||
|
|
cb2e8318e7 | ||
|
|
5cca2413fe | ||
|
|
db4254e205 | ||
|
|
fa3a4c66c9 | ||
|
|
84006380db | ||
|
|
f6cc30a30c | ||
|
|
d883b58260 | ||
|
|
b59a10b7e6 | ||
|
|
5b57696acf | ||
|
|
1265d87bc5 | ||
|
|
46d7a2a25d | ||
|
|
0babfff28b | ||
|
|
ed6e1bacc7 | ||
|
|
9c39782729 | ||
|
|
efad2dde3e | ||
|
|
84d8ee7eee | ||
|
|
7693237aa5 | ||
|
|
528c94e921 | ||
|
|
a8700af1d0 | ||
|
|
9793c5bf06 | ||
|
|
b56fafa0e0 | ||
|
|
22ee99418d | ||
|
|
4eda57b4e4 | ||
|
|
2ffd8124f2 | ||
|
|
210c191d41 | ||
|
|
251a59ddbe | ||
|
|
5df2216da6 | ||
|
|
2c1c077611 | ||
|
|
629c9ba7b1 | ||
|
|
a59bcc0c4b | ||
|
|
cf1ec1e76f | ||
|
|
8a374a4231 | ||
|
|
da2b6e5577 | ||
|
|
aa9590e2f4 | ||
|
|
a1d54b01b0 | ||
|
|
65a2066727 | ||
|
|
c49c4626d0 | ||
|
|
ec156bd679 | ||
|
|
7b1d9363a0 | ||
|
|
82ca994fac | ||
|
|
0d2f705d42 | ||
|
|
e799b4ec79 | ||
|
|
c8076b6018 | ||
|
|
88f68b20ea | ||
|
|
d249f17d22 | ||
|
|
bee3545196 | ||
|
|
e050d7d0ec | ||
|
|
e18484ea62 | ||
|
|
204b79bf06 | ||
|
|
50232c0816 | ||
|
|
e8fb14b809 | ||
|
|
d425167dce | ||
|
|
f41228acf8 | ||
|
|
cacc18dc7e |
No files matched your search
@@ -397,9 +397,11 @@ endif()
|
||||
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
|
||||
"$<IF:$<CONFIG:Debug>,${FREETYPE_BASE_DIR}-Debug/include,${FREETYPE_BASE_DIR}-Release/include>"
|
||||
IMPORTED_LOCATION_RELEASE "${_ft_release_lib}"
|
||||
)
|
||||
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
|
||||
|
||||
+62
-22
@@ -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/planet/PlanetObject.cpp
|
||||
../src/planet/StoneObject.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
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -1,100 +1,52 @@
|
||||
<!doctypehtml>
|
||||
<html lang=en-us>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-us">
|
||||
<head>
|
||||
<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, html {
|
||||
margin: 0; padding: 0; width: 100%; height: 100%;
|
||||
overflow: hidden; background-color: #000;
|
||||
position: fixed; /* Предотвращает pull-to-refresh на Android */
|
||||
}
|
||||
#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 id="status">Downloading...</div>
|
||||
<canvas id="canvas" oncontextmenu="event.preventDefault()" tabindex="-1"></canvas>
|
||||
|
||||
<head>
|
||||
<meta charset=utf-8>
|
||||
<meta content="text/html; charset=utf-8" http-equiv=Content-Type>
|
||||
<title>Space Game</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: arial;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
min-height: 100vh;
|
||||
background-color: #000;
|
||||
<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';
|
||||
}
|
||||
};
|
||||
|
||||
.emscripten {
|
||||
padding-right: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block
|
||||
}
|
||||
// Обработка ориентации
|
||||
window.addEventListener("orientationchange", function() {
|
||||
// Chrome на Android обновляет innerWidth/Height не мгновенно.
|
||||
// Ждем завершения анимации поворота.
|
||||
setTimeout(() => {
|
||||
// В Emscripten это вызовет ваш onWindowResized в C++
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}, 200);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
</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>
|
||||
|
||||
</html>
|
||||
</script>
|
||||
<script async src="space-game001.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -57,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
|
||||
@@ -78,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.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large.
Load diff
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
]
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -1,55 +1,85 @@
|
||||
{
|
||||
"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": 350,
|
||||
"y": 400,
|
||||
"width": 600,
|
||||
"height": 150,
|
||||
"textures": {
|
||||
"normal": "resources/gameover.png",
|
||||
"hover": "resources/gameover.png",
|
||||
"pressed": "resources/gameover.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "restartButton",
|
||||
"x": 350,
|
||||
"y": 300,
|
||||
"width": 300,
|
||||
"height": 80,
|
||||
"textures": {
|
||||
"normal": "resources/shoot_normal.png",
|
||||
"hover": "resources/shoot_normal.png",
|
||||
"pressed": "resources/shoot_normal.png"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Button",
|
||||
"name": "gameOverExitButton",
|
||||
"x": 650,
|
||||
"y": 300,
|
||||
"width": 300,
|
||||
"height": 80,
|
||||
"textures": {
|
||||
"normal": "resources/sand2.png",
|
||||
"hover": "resources/sand2.png",
|
||||
"pressed": "resources/sand2.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
"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": "Button",
|
||||
"name": "gameOverText",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
"width": 382,
|
||||
"height": 56,
|
||||
"textures": {
|
||||
"normal": "resources/game_over/Secondarybutton.png",
|
||||
"hover": "resources/game_over/Secondarybutton.png",
|
||||
"pressed": "resources/game_over/Secondarybutton.png"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
+83
-163
@@ -1,169 +1,89 @@
|
||||
{
|
||||
"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": "FrameLayout",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": "match_parent",
|
||||
"height": "match_parent",
|
||||
"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": "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",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"width": 150,
|
||||
"height": 150,
|
||||
"horizontal_gravity": "right",
|
||||
"vertical_gravity": "bottom",
|
||||
"textures": {
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
},
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
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
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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
BIN
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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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); // Синий туман
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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})
|
||||
|
||||
+805
-153
@@ -1,182 +1,834 @@
|
||||
#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;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
std::vector<std::string> tokens;
|
||||
std::string token;
|
||||
std::istringstream tokenStream(s);
|
||||
while (std::getline(tokenStream, token, delimiter)) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
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_;
|
||||
|
||||
ClientStateInterval timedClientStates;
|
||||
int Session::get_id() const { return id_; }
|
||||
|
||||
void process_message(const std::string& msg) {
|
||||
auto now_server = std::chrono::system_clock::now();
|
||||
bool Session::hasSpawnReserved() const { return hasReservedSpawn_; }
|
||||
const Eigen::Vector3f& Session::reservedSpawn() const { return reservedSpawn_; }
|
||||
|
||||
auto parts = split(msg, ':');
|
||||
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;
|
||||
}
|
||||
|
||||
if (parts.size() < 16)
|
||||
{
|
||||
throw std::runtime_error("Unknown message type received, too small");
|
||||
}
|
||||
void Session::send_message(const std::string& msg) {
|
||||
auto ss = std::make_shared<std::string>(msg);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(writeMutex_);
|
||||
writeQueue_.push(ss);
|
||||
}
|
||||
doWrite();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
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) {});
|
||||
}
|
||||
|
||||
int get_id() const {
|
||||
return id_;
|
||||
void Session::run() {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
|
||||
server_.g_sessions.push_back(shared_from_this());
|
||||
}
|
||||
|
||||
private:
|
||||
void do_read() {
|
||||
ws_.async_read(buffer_, [self = shared_from_this()](beast::error_code ec, std::size_t) {
|
||||
if (ec)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_sessions_mutex);
|
||||
g_sessions.erase(std::remove_if(g_sessions.begin(), g_sessions.end(),
|
||||
[self](const std::shared_ptr<Session>& session) {
|
||||
return session.get() == self.get();
|
||||
}), g_sessions.end());
|
||||
return;
|
||||
}
|
||||
ws_.async_accept([self = shared_from_this()](beast::error_code ec) {
|
||||
if (ec) return;
|
||||
std::cout << "Client " << self->id_ << " connected\n";
|
||||
self->init();
|
||||
});
|
||||
}
|
||||
|
||||
std::string msg = beast::buffers_to_string(self->buffer_.data());
|
||||
self->process_message(msg);
|
||||
bool Session::IsMessageValid(const std::string& fullMessage) {
|
||||
#ifdef ENABLE_NETWORK_CHECKSUM
|
||||
size_t hashPos = fullMessage.find("#hash:");
|
||||
if (hashPos == std::string::npos) {
|
||||
return false; // Хеша нет, хотя он ожидался
|
||||
}
|
||||
|
||||
self->buffer_.consume(self->buffer_.size());
|
||||
self->do_read();
|
||||
});
|
||||
}
|
||||
};
|
||||
std::string originalContent = fullMessage.substr(0, hashPos);
|
||||
std::string receivedHashStr = fullMessage.substr(hashPos + 6); // 6 — длина "#hash:"
|
||||
|
||||
void update_world(net::steady_timer& timer, net::io_context& ioc) {
|
||||
// Вычисляем ожидаемый хеш от контента
|
||||
size_t expectedHash = fnv1a_hash(originalContent + NET_SECRET);
|
||||
|
||||
// TODO: Renew game state
|
||||
std::stringstream ss;
|
||||
ss << std::hex << expectedHash;
|
||||
|
||||
timer.expires_after(std::chrono::milliseconds(50));
|
||||
timer.async_wait([&](const boost::system::error_code& ec) {
|
||||
if (!ec) update_world(timer, ioc);
|
||||
});
|
||||
return ss.str() == receivedHashStr;
|
||||
#else
|
||||
return true; // В режиме отладки пропускаем всё
|
||||
#endif
|
||||
}
|
||||
|
||||
void Session::sendBoxesToClient() {
|
||||
std::lock_guard<std::mutex> lock(server_.g_boxes_mutex);
|
||||
|
||||
std::string boxMsg = "BOXES:";
|
||||
bool first = true;
|
||||
|
||||
for (size_t i = 0; i < server_.g_serverBoxes.size(); ++i) {
|
||||
const auto& box = server_.g_serverBoxes[i];
|
||||
|
||||
if (box.destroyed) continue;
|
||||
|
||||
Eigen::Quaternionf q(box.rotation);
|
||||
|
||||
if (!first) boxMsg += "|";
|
||||
first = false;
|
||||
|
||||
boxMsg += std::to_string(i) + ":" +
|
||||
std::to_string(box.position.x()) + ":" +
|
||||
std::to_string(box.position.y()) + ":" +
|
||||
std::to_string(box.position.z()) + ":" +
|
||||
std::to_string(q.w()) + ":" +
|
||||
std::to_string(q.x()) + ":" +
|
||||
std::to_string(q.y()) + ":" +
|
||||
std::to_string(q.z()) + ":" +
|
||||
"0";
|
||||
}
|
||||
|
||||
send_message(boxMsg);
|
||||
}
|
||||
|
||||
|
||||
void Session::init()
|
||||
{
|
||||
sendBoxesToClient();
|
||||
|
||||
auto timer = std::make_shared<net::steady_timer>(ws_.get_executor());
|
||||
timer->expires_after(std::chrono::milliseconds(100));
|
||||
timer->async_wait([self = shared_from_this(), timer](const boost::system::error_code& ec) {
|
||||
if (!ec) {
|
||||
auto now_tp = std::chrono::system_clock::now();
|
||||
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();
|
||||
}), self->server_.g_sessions.end());
|
||||
std::cout << "Client " << self->id_ << " removed from session list\n";
|
||||
return;
|
||||
}
|
||||
|
||||
std::string msg = beast::buffers_to_string(self->buffer_.data());
|
||||
self->process_message(msg);
|
||||
|
||||
self->buffer_.consume(self->buffer_.size());
|
||||
self->do_read();
|
||||
});
|
||||
}
|
||||
|
||||
void Session::process_message(const std::string& msg) {
|
||||
if (!IsMessageValid(msg)) {
|
||||
std::cout << "[Security] Invalid packet hash. Dropping message: " << msg << std::endl;
|
||||
return;
|
||||
}
|
||||
lastReceivedTime_ = std::chrono::system_clock::now();
|
||||
std::string cleanMessage = msg.substr(0, msg.find("#hash:"));
|
||||
|
||||
std::cout << "Received from player " << id_ << ": " << cleanMessage << std::endl;
|
||||
auto parts = split(cleanMessage, ':');
|
||||
|
||||
if (parts.empty()) return;
|
||||
|
||||
std::string type = parts[0];
|
||||
|
||||
if (type == "JOIN") {
|
||||
std::string nick = "Player";
|
||||
int sType = 0;
|
||||
if (parts.size() >= 2) nick = parts[1];
|
||||
if (parts.size() >= 3) {
|
||||
try { sType = std::stoi(parts[2]); }
|
||||
catch (...) { sType = 0; }
|
||||
}
|
||||
|
||||
this->nickname = nick;
|
||||
this->shipType = sType;
|
||||
this->joined_ = true;
|
||||
|
||||
auto now_tp = std::chrono::system_clock::now();
|
||||
uint64_t now_ms = static_cast<uint64_t>(
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
|
||||
|
||||
Eigen::Vector3f spawnPos = server_.PickSafeSpawnPos(id_);
|
||||
this->hasReservedSpawn_ = true;
|
||||
this->reservedSpawn_ = spawnPos;
|
||||
|
||||
ClientState st;
|
||||
st.id = id_;
|
||||
st.position = spawnPos;
|
||||
st.rotation = Eigen::Matrix3f::Identity();
|
||||
st.currentAngularVelocity = Eigen::Vector3f::Zero();
|
||||
st.velocity = 0.0f;
|
||||
st.selectedVelocity = 0;
|
||||
st.discreteMag = 0.0f;
|
||||
st.discreteAngle = -1;
|
||||
st.lastUpdateServerTime = now_tp;
|
||||
st.nickname = this->nickname;
|
||||
st.shipType = this->shipType;
|
||||
|
||||
timedClientStates.add_state(st);
|
||||
|
||||
this->send_message(
|
||||
"SPAWN:" + std::to_string(id_) + ":" + std::to_string(now_ms) + ":" + st.formPingMessageContent()
|
||||
);
|
||||
|
||||
std::string eventMsg =
|
||||
"EVENT:" + std::to_string(id_) + ":UPD:" + std::to_string(now_ms) + ":" + st.formPingMessageContent();
|
||||
|
||||
server_.broadcastToAllExceptId(eventMsg, id_);
|
||||
|
||||
std::cout << "Server: Player " << id_ << " joined as [" << nick << "] shipType=" << sType << std::endl;
|
||||
|
||||
std::string info = "PLAYERINFO:" + std::to_string(id_) + ":" + nick + ":" + std::to_string(sType);
|
||||
server_.broadcastToAllExceptId(info, id_);
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(server_.g_sessions_mutex);
|
||||
for (auto& session : server_.g_sessions) {
|
||||
if (session->get_id() == this->id_) continue;
|
||||
std::string otherInfo = "PLAYERINFO:" + std::to_string(session->get_id()) + ":" + session->nickname + ":" + std::to_string(session->shipType);
|
||||
|
||||
this->send_message(otherInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == "UPD") {
|
||||
if (!joined_) {
|
||||
std::cout << "Server: Ignoring UPD before JOIN from " << id_ << std::endl;
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
|
||||
if (server_.g_dead_players.find(id_) != server_.g_dead_players.end()) {
|
||||
std::cout << "Server: Ignoring UPD from dead player " << id_ << std::endl;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.size() < 16) return;
|
||||
uint64_t clientTimestamp = std::stoull(parts[1]);
|
||||
ClientState receivedState;
|
||||
receivedState.id = id_;
|
||||
std::chrono::system_clock::time_point uptime_timepoint{
|
||||
std::chrono::milliseconds(clientTimestamp)
|
||||
};
|
||||
receivedState.lastUpdateServerTime = uptime_timepoint;
|
||||
receivedState.handle_full_sync(parts, 2);
|
||||
receivedState.nickname = this->nickname;
|
||||
receivedState.shipType = this->shipType;
|
||||
timedClientStates.add_state(receivedState);
|
||||
|
||||
}
|
||||
else if (type == "RESPAWN") {
|
||||
{
|
||||
std::lock_guard<std::mutex> gd(server_.g_dead_mutex);
|
||||
server_.g_dead_players.erase(id_);
|
||||
}
|
||||
|
||||
{
|
||||
auto now_tp = std::chrono::system_clock::now();
|
||||
uint64_t now_ms = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(now_tp.time_since_epoch()).count());
|
||||
|
||||
ClientState st;
|
||||
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;
|
||||
};
|
||||
|
||||
const float radii[] = { 150.f, 250.f, 400.f, 650.f, 1000.f, 1600.f };
|
||||
|
||||
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([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;
|
||||
try {
|
||||
net::io_context ioc;
|
||||
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();
|
||||
|
||||
|
||||
net::steady_timer timer(ioc);
|
||||
update_world(timer, ioc);
|
||||
std::cout << "Server started on port 8081...\n";
|
||||
|
||||
do_accept(do_accept);
|
||||
ioc.run();
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
return 0;
|
||||
server.update_world();
|
||||
ioc.run();
|
||||
}
|
||||
catch (std::exception const& e) {
|
||||
std::cerr << "Error: " << e.what() << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
#pragma once
|
||||
#include <boost/beast/core.hpp>
|
||||
#include <boost/beast/websocket.hpp>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <unordered_set>
|
||||
#include <Eigen/Dense>
|
||||
#include "../src/network/ClientState.h"
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <math.h>
|
||||
|
||||
namespace beast = boost::beast;
|
||||
namespace http = beast::http;
|
||||
namespace websocket = beast::websocket;
|
||||
namespace net = boost::asio;
|
||||
using tcp = net::ip::tcp;
|
||||
static constexpr float kWorldZOffset = 45000.0f;
|
||||
static const Eigen::Vector3f kWorldOffset(0.0f, 0.0f, kWorldZOffset);
|
||||
|
||||
static constexpr float kShipRadius = 15.0f;
|
||||
static constexpr float kSpawnShipMargin = 25.0f;
|
||||
static constexpr float kSpawnBoxMargin = 15.0f;
|
||||
static constexpr float kSpawnZJitter = 60.0f;
|
||||
|
||||
std::vector<std::string> split(const std::string& s, char delimiter);
|
||||
|
||||
struct DeathInfo {
|
||||
int targetId = -1;
|
||||
uint64_t serverTime = 0;
|
||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||
int killerId = -1;
|
||||
};
|
||||
|
||||
|
||||
struct ServerBox {
|
||||
Eigen::Vector3f position;
|
||||
Eigen::Matrix3f rotation;
|
||||
float collisionRadius = 2.0f;
|
||||
bool destroyed = false;
|
||||
};
|
||||
|
||||
struct Projectile {
|
||||
int shooterId = -1;
|
||||
uint64_t spawnMs = 0;
|
||||
Eigen::Vector3f pos;
|
||||
Eigen::Vector3f vel;
|
||||
float lifeMs = PROJECTILE_LIFE;
|
||||
};
|
||||
|
||||
struct BoxDestroyedInfo {
|
||||
int boxIndex = -1;
|
||||
uint64_t serverTime = 0;
|
||||
Eigen::Vector3f position = Eigen::Vector3f::Zero();
|
||||
int destroyedBy = -1;
|
||||
};
|
||||
|
||||
class Server;
|
||||
|
||||
class Session : public std::enable_shared_from_this<Session> {
|
||||
Server& server_;
|
||||
websocket::stream<beast::tcp_stream> ws_;
|
||||
beast::flat_buffer buffer_;
|
||||
int id_;
|
||||
bool is_writing_ = false;
|
||||
std::queue<std::shared_ptr<std::string>> writeQueue_;
|
||||
std::mutex writeMutex_;
|
||||
|
||||
|
||||
public:
|
||||
ClientStateInterval timedClientStates;
|
||||
bool joined_ = false;
|
||||
std::chrono::system_clock::time_point lastReceivedTime_;
|
||||
|
||||
bool hasReservedSpawn_ = false;
|
||||
Eigen::Vector3f reservedSpawn_ = Eigen::Vector3f(0.0f, 0.0f, kWorldZOffset);
|
||||
|
||||
std::string nickname = "Player";
|
||||
int shipType = 0;
|
||||
|
||||
Session(Server& server, tcp::socket&& socket, int id);
|
||||
int get_id() const;
|
||||
bool hasSpawnReserved() const;
|
||||
const Eigen::Vector3f& reservedSpawn() const;
|
||||
bool fetchStateAtTime(std::chrono::system_clock::time_point targetTime, ClientState& outState) const;
|
||||
void send_message(const std::string& msg);
|
||||
void run();
|
||||
bool IsMessageValid(const std::string& fullMessage);
|
||||
bool is_timed_out(std::chrono::system_clock::time_point now) const;
|
||||
void force_disconnect();
|
||||
|
||||
private:
|
||||
void sendBoxesToClient();
|
||||
public:
|
||||
void init();
|
||||
ClientState get_latest_state(std::chrono::system_clock::time_point now);
|
||||
void doWrite();
|
||||
private:
|
||||
|
||||
void do_read();
|
||||
void process_message(const std::string& msg);
|
||||
};
|
||||
|
||||
class Server
|
||||
{
|
||||
public:
|
||||
tcp::acceptor& acceptor_;
|
||||
net::io_context& ioc_;
|
||||
net::steady_timer timer;
|
||||
std::vector<BoxDestroyedInfo> g_boxDestructions;
|
||||
std::mutex g_boxDestructions_mutex;
|
||||
|
||||
std::vector<ServerBox> g_serverBoxes;
|
||||
std::mutex g_boxes_mutex;
|
||||
|
||||
std::vector<std::shared_ptr<Session>> g_sessions;
|
||||
std::mutex g_sessions_mutex;
|
||||
|
||||
std::vector<Projectile> g_projectiles;
|
||||
std::mutex g_projectiles_mutex;
|
||||
|
||||
std::unordered_set<int> g_dead_players;
|
||||
std::mutex g_dead_mutex;
|
||||
|
||||
int next_id = 1000;
|
||||
|
||||
std::vector<ServerBox> generateServerBoxes(int count);
|
||||
public:
|
||||
Server(tcp::acceptor& acceptor, net::io_context& ioc);
|
||||
|
||||
void broadcastToAll(const std::string& message);
|
||||
void broadcastToAllExceptId(const std::string& message, int id);
|
||||
void createProjectile(int id, Eigen::Vector3f pos, Eigen::Quaternionf dir, float velocity);
|
||||
void update_world();
|
||||
Eigen::Vector3f PickSafeSpawnPos(int forPlayerId);
|
||||
void init();
|
||||
void accept();
|
||||
};
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
|
||||
#include "render/Renderer.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef AUDIO
|
||||
#ifdef AUDIO
|
||||
|
||||
#include "AudioPlayerAsync.h"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#ifdef AUDIO
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "BoneAnimatedModel.h"
|
||||
#include "BoneAnimatedModel.h"
|
||||
#include <regex>
|
||||
#include <string>
|
||||
#include <fstream>
|
||||
|
||||
Loaded 100 of 153 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user