mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-29 10:43:21 +08:00
opening more internal sources
This commit is contained in:
parent
dcf8671f4a
commit
20745818b2
@ -3995,49 +3995,49 @@
|
||||
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
||||
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
|
||||
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/02/e0/0336db3c3989a1768271ee7a12ba",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/3d/8c/9689aa6ff3fa826fc3f524b3115f",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/00/04/1ea1f6833f131458cbc8e7039591",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/d9/f7/c87406c9229cbe5aa9439e3562c5",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/66/1e/53122c4f96bca326aaa42d5cb98b",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/6b/28/ee8a01e3155e05c5c970ace23107",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/89/5e/fbf702ecbd5376efe63720632883",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/c2/c4/958ba681823bcad105bc51d6b3ac",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/95/55/3188c8463c50ce1cb7182788d0b8",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/70/10/b99df359d806c3f4fc3c91685749",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3e/40/9778e11391ca3832caf982a03832",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7a/0c/756391725d7bbbab568b8d946753",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b9/fe/e0f9747f9f03647d1becb31ac08d",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ee/d1/b0e387dfdbe8ac032456d4124bdf",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/10/db/85cb3b50f58df978a42f5b199c0d",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/3f/77/d66d99ba56643c20493a56cb3dba",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/2b/23/eb989bada6166e03bf5756717194",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/73/26/4670e939bfef0ad417f56d532fc0",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/04/73/6951027e00d62310e59eca1737c3",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/1d/31/7698b0ee7abe6090d62295df170e",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/02/ca/38de2750e629dd5f193a7c00a7d9",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7a/b3/e30adb7116efb17abe63ce70def8",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e9/c3/191d440460fbb95eb8c131081c4a",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/06/e6d9a361c5df654e5db27bec0999",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a8/a5/70d83d086f2658e42c5d1e11aa53",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b2/09/4105d848598a9cb1695f8be31696",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/57/1ec9697d93ade3ef0a29d8ad2008",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e6/54/c828ae0046a1023f28a02edffc4f",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/92/ef/bbe7906558281bb1a57a9ebe4ad1",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/62/ea/d5de2257b62b625308df5760ce32",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e5/a5/6304f685116de2c62c9d4b445dab",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a0/86/1a7de5eb337f446ef952a0572214",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cb/f1/f26f8c525637c9a29b511a6ffc2f",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/ca/11a87c0fecc6220b9756caa2a86e",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/59/65/0b8fb1aeb2ae528f31d1455bb180",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9f/88/c4ec00dff29865d4899395067b03",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/6c/05/f66f58d20fab5d0e658961ac65b5",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e1/8c/2ee1f6a353878bf6cef93870f424",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/eb/a9/fa25af1dbae41645d1c670e36404",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a9/42/4f64a2f1ea9421f883e34f29793f",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/41/ad/bebe0575f1e1c4f2e09ef522b326",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/05/f8/4dcd0854a6ac62afe9f22736c1c7",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e3/7a/f510a5e6c5f69feb1c9ed139a29b",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ea/cf/ceabc09c70adab68291bb9fa816f",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/87/f7/3b5ec7037e46b6b58f650048e39c",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/6a/46/fa941d72c4f0b8c998f9403d5608",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f2/f3/ae030d3f548d6ab7586a867f1c7a",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/da/c0/9af017be0becf14ab1fc1ee96254",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b6/63/f870c817beebd8ee046d2dfc0364",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/2c/3aa53aeab23a9816877787e3ab88",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/64/b5/a615b79eee5bea7bb8138195fb00",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0b/6a/d385633d0f26444362fbd79b262d",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/42/14/7505a806e090eca242dfb3ebb136",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/54/be/f241c6b573f203851221eaa3d58b",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a4/d3/44dd3f15ba30b945c0064fcbd0f5",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/62/ed/67a279057579f659bed20b32f44e",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/b1/cf/a68c3ced4167252df2cba97f3233",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d3/e8/063c0165376117c575262aa34e8e",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4a/ac/490695a5b3def64b27e5293c3abb",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/76/6e/318f1a492c6d861f0f2eba2cf174",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9f/f8/2ab60dff814ac7652328150fa70d",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/28/12f42bffe9da914a7bed5fca11e5",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/24/c2/124f383e647b185c644e2d8a6c01",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/94/99/b39898c43f81f5f85e13aebed284",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/90/04/1035d3fcc228298846a6293f3d8b",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e1/0a/92574d0a13fe1cff98ea380b80f5",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/47/f0285e4ef62926f50f7bc737ff7d",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/49/e7/4a8abfd1d32071165cfd3693d085",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ff/97/87a7d0fd328e28c42e069ad6f9cf",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/71/4e/59cb0a44813ce3a291b7665f3981",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/77/38da1bc0cf1f1a63fd0b7c408d15",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/72/fc/96d3489a0237c5727fbe2288b7c9",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/64/7a/c1889e8bc60641cdb465f142caac",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/b0/6e/7f5f4a266768a9800ae9e95f1200",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/e9/54/b71bde6d97129447853b43879d5b",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/66/90/610ebef166a7729d764f8ef34d5d",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/d1/68/701d8bc9740ecbf54b21c5cc2255",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/09/a7/875d4894eac1ec9bdbc2634b4815",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/96/bc/6d79640b1553959e85df1c12fb8d",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/89/46/802c29697180eaf95d4afb9a258e",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/a0/64/5b30f7f3139e59e54ad5244e9101",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/35/f8/8e2e0eb328b241af4e73b81d59a9",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/4d/1f/6bda20a013ce0be6fbd603fcf42c",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/b2/5c/3008c02fffd966044e7ba803f52d",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7",
|
||||
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/d3/db/e73d4dcf1280d5f677c3cf8b47c3"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
### 1.7.6 (build 20681, api 7, 2022-08-10)
|
||||
### 1.7.6 (build 20683, api 7, 2022-08-10)
|
||||
- Cleaned up da MetaSubsystem code.
|
||||
- It is now possible to tell the meta system about arbitrary classes (ba_meta export foo.bar.Class) instead of just the preset types 'plugin', 'game', etc.
|
||||
- Newly discovered plugins are now activated immediately instead of requiring a restart.
|
||||
|
||||
@ -300,6 +300,7 @@ add_executable(ballisticacore
|
||||
${BA_SRC_ROOT}/ballistica/dynamics/part.h
|
||||
${BA_SRC_ROOT}/ballistica/dynamics/rigid_body.cc
|
||||
${BA_SRC_ROOT}/ballistica/dynamics/rigid_body.h
|
||||
${BA_SRC_ROOT}/ballistica/game/account.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/account.h
|
||||
${BA_SRC_ROOT}/ballistica/game/client_controller_interface.h
|
||||
${BA_SRC_ROOT}/ballistica/game/connection/connection.cc
|
||||
@ -317,6 +318,7 @@ add_executable(ballisticacore
|
||||
${BA_SRC_ROOT}/ballistica/game/friend_score_set.h
|
||||
${BA_SRC_ROOT}/ballistica/game/game.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/game.h
|
||||
${BA_SRC_ROOT}/ballistica/game/game_stream.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/game_stream.h
|
||||
${BA_SRC_ROOT}/ballistica/game/host_activity.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/host_activity.h
|
||||
@ -325,10 +327,15 @@ add_executable(ballisticacore
|
||||
${BA_SRC_ROOT}/ballistica/game/player_spec.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/player_spec.h
|
||||
${BA_SRC_ROOT}/ballistica/game/score_to_beat.h
|
||||
${BA_SRC_ROOT}/ballistica/game/session/client_session.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/session/client_session.h
|
||||
${BA_SRC_ROOT}/ballistica/game/session/host_session.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/session/host_session.h
|
||||
${BA_SRC_ROOT}/ballistica/game/session/net_client_session.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/session/net_client_session.h
|
||||
${BA_SRC_ROOT}/ballistica/game/session/replay_client_session.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/session/replay_client_session.h
|
||||
${BA_SRC_ROOT}/ballistica/game/session/session.cc
|
||||
${BA_SRC_ROOT}/ballistica/game/session/session.h
|
||||
${BA_SRC_ROOT}/ballistica/generic/base64.cc
|
||||
${BA_SRC_ROOT}/ballistica/generic/base64.h
|
||||
|
||||
@ -291,6 +291,7 @@
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\part.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\dynamics\rigid_body.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\rigid_body.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\account.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\account.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\client_controller_interface.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\connection\connection.cc" />
|
||||
@ -308,6 +309,7 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\friend_score_set.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\game.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\game.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\game_stream.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\game_stream.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\host_activity.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\host_activity.h" />
|
||||
@ -316,10 +318,15 @@
|
||||
<ClCompile Include="..\..\src\ballistica\game\player_spec.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\player_spec.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\score_to_beat.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\host_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\host_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\net_client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\net_client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\replay_client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\replay_client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\generic\base64.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\generic\base64.h" />
|
||||
|
||||
@ -307,6 +307,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\rigid_body.h">
|
||||
<Filter>ballistica\dynamics</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\account.cc">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\account.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
@ -358,6 +361,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\game.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\game_stream.cc">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\game_stream.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
@ -382,18 +388,33 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\score_to_beat.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\host_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\host_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\net_client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\net_client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\replay_client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\replay_client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@ -286,6 +286,7 @@
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\part.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\dynamics\rigid_body.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\rigid_body.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\account.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\account.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\client_controller_interface.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\connection\connection.cc" />
|
||||
@ -303,6 +304,7 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\friend_score_set.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\game.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\game.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\game_stream.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\game_stream.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\host_activity.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\host_activity.h" />
|
||||
@ -311,10 +313,15 @@
|
||||
<ClCompile Include="..\..\src\ballistica\game\player_spec.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\player_spec.h" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\score_to_beat.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\host_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\host_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\net_client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\net_client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\replay_client_session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\replay_client_session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\session.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\session.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\generic\base64.cc" />
|
||||
<ClInclude Include="..\..\src\ballistica\generic\base64.h" />
|
||||
|
||||
@ -307,6 +307,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\dynamics\rigid_body.h">
|
||||
<Filter>ballistica\dynamics</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\account.cc">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\account.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
@ -358,6 +361,9 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\game.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\game_stream.cc">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\game_stream.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
@ -382,18 +388,33 @@
|
||||
<ClInclude Include="..\..\src\ballistica\game\score_to_beat.h">
|
||||
<Filter>ballistica\game</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\host_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\host_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\net_client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\net_client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\replay_client_session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\replay_client_session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ballistica\game\session\session.cc">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ballistica\game\session\session.h">
|
||||
<Filter>ballistica\game\session</Filter>
|
||||
</ClInclude>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kAppBuildNumber = 20681;
|
||||
const int kAppBuildNumber = 20683;
|
||||
const char* kAppVersion = "1.7.6";
|
||||
|
||||
// Our standalone globals.
|
||||
|
||||
207
src/ballistica/game/account.cc
Normal file
207
src/ballistica/game/account.cc
Normal file
@ -0,0 +1,207 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/game/account.h"
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/app/app_internal.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto Account::AccountTypeFromString(const std::string& val) -> V1AccountType {
|
||||
if (val == "Game Center") {
|
||||
return V1AccountType::kGameCenter;
|
||||
} else if (val == "Game Circle") {
|
||||
return V1AccountType::kGameCircle;
|
||||
} else if (val == "Google Play") {
|
||||
return V1AccountType::kGooglePlay;
|
||||
} else if (val == "Steam") {
|
||||
return V1AccountType::kSteam;
|
||||
} else if (val == "Oculus") {
|
||||
return V1AccountType::kOculus;
|
||||
} else if (val == "NVIDIA China") {
|
||||
return V1AccountType::kNvidiaChina;
|
||||
} else if (val == "Test") {
|
||||
return V1AccountType::kTest;
|
||||
} else if (val == "Local") {
|
||||
return V1AccountType::kDevice;
|
||||
} else if (val == "Server") {
|
||||
return V1AccountType::kServer;
|
||||
} else if (val == "V2") {
|
||||
return V1AccountType::kV2;
|
||||
} else {
|
||||
return V1AccountType::kInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::AccountTypeToString(V1AccountType type) -> std::string {
|
||||
switch (type) {
|
||||
case V1AccountType::kGameCenter:
|
||||
return "Game Center";
|
||||
case V1AccountType::kGameCircle:
|
||||
return "Game Circle";
|
||||
case V1AccountType::kGooglePlay:
|
||||
return "Google Play";
|
||||
case V1AccountType::kSteam:
|
||||
return "Steam";
|
||||
case V1AccountType::kOculus:
|
||||
return "Oculus";
|
||||
case V1AccountType::kTest:
|
||||
return "Test";
|
||||
case V1AccountType::kDevice:
|
||||
return "Local";
|
||||
case V1AccountType::kServer:
|
||||
return "Server";
|
||||
case V1AccountType::kNvidiaChina:
|
||||
return "NVIDIA China";
|
||||
case V1AccountType::kV2:
|
||||
return "V2";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::AccountTypeToIconString(V1AccountType type) -> std::string {
|
||||
switch (type) {
|
||||
case V1AccountType::kTest:
|
||||
return g_game->CharStr(SpecialChar::kTestAccount);
|
||||
case V1AccountType::kNvidiaChina:
|
||||
return g_game->CharStr(SpecialChar::kNvidiaLogo);
|
||||
case V1AccountType::kGooglePlay:
|
||||
return g_game->CharStr(SpecialChar::kGooglePlayGamesLogo);
|
||||
case V1AccountType::kSteam:
|
||||
return g_game->CharStr(SpecialChar::kSteamLogo);
|
||||
case V1AccountType::kOculus:
|
||||
return g_game->CharStr(SpecialChar::kOculusLogo);
|
||||
case V1AccountType::kGameCenter:
|
||||
return g_game->CharStr(SpecialChar::kGameCenterLogo);
|
||||
case V1AccountType::kGameCircle:
|
||||
return g_game->CharStr(SpecialChar::kGameCircleLogo);
|
||||
case V1AccountType::kDevice:
|
||||
case V1AccountType::kServer:
|
||||
return g_game->CharStr(SpecialChar::kLocalAccount);
|
||||
case V1AccountType::kV2:
|
||||
return g_game->CharStr(SpecialChar::kV2Logo);
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
Account::Account() = default;
|
||||
|
||||
auto Account::GetLoginName() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return login_name_;
|
||||
}
|
||||
|
||||
auto Account::GetLoginID() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return login_id_;
|
||||
}
|
||||
|
||||
auto Account::GetToken() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return token_;
|
||||
}
|
||||
|
||||
auto Account::GetExtra() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return extra_;
|
||||
}
|
||||
|
||||
auto Account::GetExtra2() -> std::string {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return extra_2_;
|
||||
}
|
||||
|
||||
auto Account::GetLoginState(int* state_num) -> V1LoginState {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (state_num) {
|
||||
*state_num = login_state_num_;
|
||||
}
|
||||
return login_state_;
|
||||
}
|
||||
|
||||
void Account::SetExtra(const std::string& extra) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
extra_ = extra;
|
||||
}
|
||||
|
||||
void Account::SetExtra2(const std::string& extra) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
extra_2_ = extra;
|
||||
}
|
||||
|
||||
void Account::SetToken(const std::string& account_id,
|
||||
const std::string& token) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
// Hmm, does this compare logic belong in here?
|
||||
if (login_id_ == account_id) {
|
||||
token_ = token;
|
||||
}
|
||||
}
|
||||
|
||||
void Account::SetLogin(V1AccountType account_type, V1LoginState login_state,
|
||||
const std::string& login_name,
|
||||
const std::string& login_id) {
|
||||
bool call_login_did_change = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
|
||||
// We call out to Python so need to be in game thread.
|
||||
assert(InGameThread());
|
||||
if (login_state_ != login_state
|
||||
|| g_app_globals->account_type != account_type || login_id_ != login_id
|
||||
|| login_name_ != login_name) {
|
||||
// Special case: if they sent a sign-out for an account type that is
|
||||
// currently not signed in, ignore it.
|
||||
if (login_state == V1LoginState::kSignedOut
|
||||
&& (account_type != g_app_globals->account_type)) {
|
||||
// No-op.
|
||||
} else {
|
||||
login_state_ = login_state;
|
||||
g_app_globals->account_type = account_type;
|
||||
login_id_ = login_id;
|
||||
login_name_ = Utils::GetValidUTF8(login_name.c_str(), "gthm");
|
||||
|
||||
// If they signed out of an account, account type switches to invalid.
|
||||
if (login_state == V1LoginState::kSignedOut) {
|
||||
g_app_globals->account_type = V1AccountType::kInvalid;
|
||||
}
|
||||
login_state_num_ += 1;
|
||||
call_login_did_change = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call_login_did_change) {
|
||||
// Inform a few subsystems of the change.
|
||||
g_app_internal->V1LoginDidChange();
|
||||
g_platform->V1LoginDidChange();
|
||||
}
|
||||
}
|
||||
|
||||
void Account::SetProductsPurchased(const std::vector<std::string>& products) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::unordered_map<std::string, bool> purchases_old = product_purchases_;
|
||||
product_purchases_.clear();
|
||||
for (auto&& i : products) {
|
||||
product_purchases_[i] = true;
|
||||
}
|
||||
if (product_purchases_ != purchases_old) {
|
||||
product_purchases_state_++;
|
||||
}
|
||||
}
|
||||
|
||||
auto Account::GetProductPurchased(const std::string& product) -> bool {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto i = product_purchases_.find(product);
|
||||
if (i == product_purchases_.end()) {
|
||||
return false;
|
||||
} else {
|
||||
return i->second;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
1249
src/ballistica/game/game_stream.cc
Normal file
1249
src/ballistica/game/game_stream.cc
Normal file
File diff suppressed because it is too large
Load Diff
1122
src/ballistica/game/session/client_session.cc
Normal file
1122
src/ballistica/game/session/client_session.cc
Normal file
File diff suppressed because it is too large
Load Diff
762
src/ballistica/game/session/host_session.cc
Normal file
762
src/ballistica/game/session/host_session.cc
Normal file
@ -0,0 +1,762 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/game/session/host_session.h"
|
||||
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/game/host_activity.h"
|
||||
#include "ballistica/game/player.h"
|
||||
#include "ballistica/generic/timer.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/input/device/input_device.h"
|
||||
#include "ballistica/media/component/data.h"
|
||||
#include "ballistica/media/component/model.h"
|
||||
#include "ballistica/media/component/sound.h"
|
||||
#include "ballistica/media/component/texture.h"
|
||||
#include "ballistica/python/python.h"
|
||||
#include "ballistica/python/python_command.h"
|
||||
#include "ballistica/python/python_context_call.h"
|
||||
#include "ballistica/python/python_sys.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
HostSession::HostSession(PyObject* session_type_obj)
|
||||
: last_kick_idle_players_decrement_time_(GetRealTime()) {
|
||||
assert(g_game);
|
||||
assert(InGameThread());
|
||||
assert(session_type_obj != nullptr);
|
||||
|
||||
ScopedSetContext cp(this);
|
||||
|
||||
// FIXME: Should be an attr of the session class, not hard-coded.
|
||||
is_main_menu_ =
|
||||
static_cast<bool>(strstr(Python::ObjToString(session_type_obj).c_str(),
|
||||
"bastd.mainmenu.MainMenuSession"));
|
||||
// Log("MAIN MENU? " + std::to_string(is_main_menu()));
|
||||
|
||||
kick_idle_players_ = g_game->kick_idle_players();
|
||||
|
||||
// Create a timer to step our session scene.
|
||||
step_scene_timer_ =
|
||||
base_timers_.NewTimer(base_time_, kGameStepMilliseconds, 0, -1,
|
||||
NewLambdaRunnable([this] { StepScene(); }));
|
||||
|
||||
// Set up our output-stream, which will go to a replay and/or the network.
|
||||
// We don't dump to a replay if we're doing the main menu; that replay
|
||||
// would be boring.
|
||||
bool do_replay = !is_main_menu_;
|
||||
|
||||
// At the moment headless-server don't write replays.
|
||||
if (HeadlessMode()) {
|
||||
do_replay = false;
|
||||
}
|
||||
|
||||
output_stream_ = Object::New<GameStream>(this, do_replay);
|
||||
|
||||
// Make a scene for our session-level nodes, etc.
|
||||
scene_ = Object::New<Scene>(0);
|
||||
if (output_stream_.exists()) {
|
||||
output_stream_->AddScene(scene_.get());
|
||||
}
|
||||
|
||||
// Fade in from our current blackness.
|
||||
g_graphics->FadeScreen(true, 250, nullptr);
|
||||
|
||||
// Start by showing the progress bar instead of hitching.
|
||||
g_graphics->EnableProgressBar(true);
|
||||
|
||||
// Now's a good time to run garbage collection; there should be pretty much
|
||||
// no game stuff to speak of in existence (provided the last session went
|
||||
// down peacefully).
|
||||
g_python->obj(Python::ObjID::kGarbageCollectSessionEndCall).Call();
|
||||
|
||||
// Instantiate our Python Session instance.
|
||||
PythonRef obj;
|
||||
PythonRef session_type(session_type_obj, PythonRef::kAcquire);
|
||||
{
|
||||
Python::ScopedCallLabel label("Session instantiation");
|
||||
obj = session_type.Call();
|
||||
}
|
||||
if (!obj.exists()) {
|
||||
throw Exception("Error creating game session: '" + session_type.Str()
|
||||
+ "'");
|
||||
}
|
||||
|
||||
// The session python object should have called
|
||||
// _ba.register_session() in its constructor to set session_py_obj_.
|
||||
if (session_py_obj_ != obj) {
|
||||
throw Exception("session not set up correctly");
|
||||
}
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case
|
||||
// it is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
}
|
||||
|
||||
auto HostSession::GetHostSession() -> HostSession* { return this; }
|
||||
|
||||
void HostSession::DestroyHostActivity(HostActivity* a) {
|
||||
BA_PRECONDITION(a);
|
||||
BA_PRECONDITION(a->GetHostSession() == this);
|
||||
if (a == foreground_host_activity_.get()) {
|
||||
foreground_host_activity_.Clear();
|
||||
}
|
||||
|
||||
// Clear it from our activities list if its still on there.
|
||||
for (auto i = host_activities_.begin(); i < host_activities_.end(); i++) {
|
||||
if (i->get() == a) {
|
||||
host_activities_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The only reason it wouldn't be there should be because the activity is
|
||||
// dying due our clearing of the list in our destructor; make sure that's
|
||||
// the case.
|
||||
assert(shutting_down_);
|
||||
}
|
||||
|
||||
auto HostSession::GetMutableScene() -> Scene* {
|
||||
assert(scene_.exists());
|
||||
return scene_.get();
|
||||
}
|
||||
|
||||
void HostSession::DebugSpeedMultChanged() {
|
||||
// FIXME - should we progress our own scene faster/slower depending on
|
||||
// this too? Is there really a need to?
|
||||
|
||||
// Let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->DebugSpeedMultChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::ScreenSizeChanged() {
|
||||
// Let our internal scene know.
|
||||
scene()->ScreenSizeChanged();
|
||||
|
||||
// Also let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->ScreenSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::LanguageChanged() {
|
||||
// Let our internal scene know.
|
||||
scene()->LanguageChanged();
|
||||
|
||||
// Also let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->LanguageChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::GraphicsQualityChanged(GraphicsQuality q) {
|
||||
// Let our internal scene know.
|
||||
scene()->GraphicsQualityChanged(q);
|
||||
|
||||
// Let all our activities know.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->GraphicsQualityChanged(q);
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::DoesFillScreen() const -> bool {
|
||||
// FIXME not necessarily the case.
|
||||
return true;
|
||||
}
|
||||
|
||||
void HostSession::Draw(FrameDef* f) {
|
||||
// First draw our session scene.
|
||||
scene()->Draw(f);
|
||||
|
||||
// Let all our activities draw their own scenes/etc.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->Draw(f);
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::NewTimer(TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
if (shutting_down_) {
|
||||
BA_LOG_PYTHON_TRACE_ONCE(
|
||||
"WARNING: Creating game timer during host-session shutdown");
|
||||
return 123; // dummy...
|
||||
}
|
||||
if (length == 0 && repeat) {
|
||||
throw Exception("Can't add game-timer with length 0 and repeat on");
|
||||
}
|
||||
if (length < 0) {
|
||||
throw Exception("Timer length cannot be < 0 (got " + std::to_string(length)
|
||||
+ ")");
|
||||
}
|
||||
int offset = 0;
|
||||
Timer* t = sim_timers_.NewTimer(scene()->time(), length, offset,
|
||||
repeat ? -1 : 0, runnable);
|
||||
return t->id();
|
||||
}
|
||||
|
||||
void HostSession::DeleteTimer(int timer_id) {
|
||||
assert(InGameThread());
|
||||
if (shutting_down_) return;
|
||||
sim_timers_.DeleteTimer(timer_id);
|
||||
}
|
||||
|
||||
auto HostSession::GetSound(const std::string& name) -> Object::Ref<Sound> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&sounds_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetData(const std::string& name) -> Object::Ref<Data> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&datas_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetTexture(const std::string& name) -> Object::Ref<Texture> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load assets during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&textures_, name, scene());
|
||||
}
|
||||
auto HostSession::GetModel(const std::string& name) -> Object::Ref<Model> {
|
||||
if (shutting_down_) {
|
||||
throw Exception("can't load media during session shutdown");
|
||||
}
|
||||
return Media::GetMedia(&models_, name, scene());
|
||||
}
|
||||
|
||||
auto HostSession::GetForegroundContext() -> Context {
|
||||
HostActivity* a = foreground_host_activity_.get();
|
||||
if (a) {
|
||||
return Context(a);
|
||||
}
|
||||
return Context(this);
|
||||
}
|
||||
|
||||
void HostSession::RequestPlayer(InputDevice* device) {
|
||||
assert(InGameThread());
|
||||
|
||||
// Ignore if we have no Python session obj.
|
||||
if (!GetSessionPyObj()) {
|
||||
Log("Error: HostSession::RequestPlayer() called w/no session_py_obj_.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to at least temporarily create and attach to a player for passing to
|
||||
// the callback.
|
||||
int player_id = next_player_id_++;
|
||||
auto player(Object::New<Player>(player_id, this));
|
||||
players_.push_back(player);
|
||||
device->AttachToLocalPlayer(player.get());
|
||||
|
||||
// Ask the python layer to accept/deny this guy.
|
||||
bool accept;
|
||||
{
|
||||
// Set the session as context.
|
||||
ScopedSetContext cp(this);
|
||||
accept = static_cast<bool>(
|
||||
session_py_obj_.GetAttr("_request_player")
|
||||
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
|
||||
PythonRef::kSteal))
|
||||
.ValueAsInt());
|
||||
if (accept) {
|
||||
player->set_accepted(true);
|
||||
} else {
|
||||
RemovePlayer(player.get());
|
||||
}
|
||||
}
|
||||
|
||||
// If he was accepted, update our game roster with the new info.
|
||||
if (accept) {
|
||||
g_game->UpdateGameRoster();
|
||||
}
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case it
|
||||
// is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
}
|
||||
|
||||
void HostSession::RemovePlayer(Player* player) {
|
||||
assert(player);
|
||||
|
||||
for (auto i = players_.begin(); i != players_.end(); ++i) {
|
||||
if (i->get() == player) {
|
||||
// Grab a ref to keep the player alive, pull him off the list, then call
|
||||
// his leaving callback.
|
||||
Object::Ref<Player> player2 = *i;
|
||||
players_.erase(i);
|
||||
|
||||
// Only make the callback for this player if they were accepted.
|
||||
if (player2->accepted()) {
|
||||
IssuePlayerLeft(player2.get());
|
||||
}
|
||||
|
||||
// Update our game roster with the departure.
|
||||
g_game->UpdateGameRoster();
|
||||
|
||||
// Lastly, keep the python layer fed with our latest player count in case
|
||||
// it is updating the master-server with our current/max player counts.
|
||||
g_game->SetPublicPartyPlayerCount(static_cast<int>(players_.size()));
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
BA_LOG_ERROR_TRACE("Player not found in HostSession::RemovePlayer()");
|
||||
}
|
||||
|
||||
void HostSession::IssuePlayerLeft(Player* player) {
|
||||
assert(player);
|
||||
assert(InGameThread());
|
||||
|
||||
try {
|
||||
if (GetSessionPyObj()) {
|
||||
if (player) {
|
||||
// Make sure we're the context for session callbacks.
|
||||
ScopedSetContext cp(this);
|
||||
Python::ScopedCallLabel label("Session on_player_leave");
|
||||
session_py_obj_.GetAttr("on_player_leave")
|
||||
.Call(PythonRef(Py_BuildValue("(O)", player->BorrowPyRef()),
|
||||
PythonRef::kSteal));
|
||||
} else {
|
||||
BA_LOG_PYTHON_TRACE_ONCE("missing player on IssuePlayerLeft");
|
||||
}
|
||||
} else {
|
||||
Log("WARNING: HostSession: IssuePlayerLeft caled with no "
|
||||
"session_py_obj_");
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Log(std::string("Error calling on_player_leave(): ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::SetKickIdlePlayers(bool enable) {
|
||||
// If this has changed, reset our disconnect-time reporting.
|
||||
assert(InGameThread());
|
||||
if (enable != kick_idle_players_) {
|
||||
last_kick_idle_players_decrement_time_ = GetRealTime();
|
||||
}
|
||||
kick_idle_players_ = enable;
|
||||
}
|
||||
|
||||
void HostSession::SetForegroundHostActivity(HostActivity* a) {
|
||||
assert(a);
|
||||
assert(InGameThread());
|
||||
|
||||
if (shutting_down_) {
|
||||
Log("WARNING: SetForegroundHostActivity called during session shutdown; "
|
||||
"ignoring.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Sanity check: make sure the one provided is part of this session.
|
||||
bool found = false;
|
||||
for (auto&& i : host_activities_) {
|
||||
if (i == a) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((a->GetHostSession() != this) || !found) {
|
||||
throw Exception("HostActivity is not part of this HostSession");
|
||||
}
|
||||
|
||||
foreground_host_activity_ = a;
|
||||
|
||||
// Now go through telling each host-activity whether it's foregrounded or not.
|
||||
// FIXME: Dying sessions never get told they're un-foregrounded.. could that
|
||||
// ever be a problem?
|
||||
bool session_is_foreground = (g_game->GetForegroundSession() != nullptr);
|
||||
for (auto&& i : host_activities_) {
|
||||
i->SetIsForeground(session_is_foreground && (i == a));
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::AddHostActivity(HostActivity* a) {
|
||||
host_activities_.emplace_back(a);
|
||||
}
|
||||
|
||||
// Called by the constructor of the session python object.
|
||||
void HostSession::RegisterPySession(PyObject* obj) {
|
||||
session_py_obj_.Acquire(obj);
|
||||
}
|
||||
|
||||
// Given an activity python type, instantiates and returns a new activity.
|
||||
auto HostSession::NewHostActivity(PyObject* activity_type_obj,
|
||||
PyObject* settings_obj) -> PyObject* {
|
||||
PythonRef activity_type(activity_type_obj, PythonRef::kAcquire);
|
||||
if (!activity_type.CallableCheck()) {
|
||||
throw Exception("Invalid HostActivity type passed; not callable");
|
||||
}
|
||||
|
||||
// First generate our C++ activity instance and point the context at it.
|
||||
auto activity(Object::New<HostActivity>(this));
|
||||
AddHostActivity(activity.get());
|
||||
|
||||
ScopedSetContext cp(activity.get());
|
||||
|
||||
// Now instantiate the Python instance.. pass args if some were provided, or
|
||||
// an empty dict otherwise.
|
||||
PythonRef args;
|
||||
if (settings_obj == Py_None) {
|
||||
args.Steal(Py_BuildValue("({})"));
|
||||
} else {
|
||||
args.Steal(Py_BuildValue("(O)", settings_obj));
|
||||
}
|
||||
|
||||
PythonRef result = activity_type.Call(args);
|
||||
if (!result.exists()) {
|
||||
throw Exception("HostActivity creation failed");
|
||||
}
|
||||
|
||||
// If all went well, the python activity constructor should have called
|
||||
// _ba.register_activity(), so we should be able to get at the same python
|
||||
// activity we just instantiated through the c++ class.
|
||||
if (activity->GetPyActivity() != result.get()) {
|
||||
throw Exception("Error on HostActivity construction");
|
||||
}
|
||||
|
||||
PyObject* obj = result.get();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
|
||||
auto HostSession::RegisterPyActivity(PyObject* activity_obj) -> HostActivity* {
|
||||
// The context should be pointing to an unregistered HostActivity;
|
||||
// register and return it.
|
||||
HostActivity* activity = Context::current().GetHostActivity();
|
||||
if (!activity)
|
||||
throw Exception(
|
||||
"No current activity in RegisterPyActivity; did you remember to call "
|
||||
"ba.newHostActivity() to instantiate your activity?");
|
||||
activity->RegisterPyActivity(activity_obj);
|
||||
return activity;
|
||||
}
|
||||
|
||||
void HostSession::DecrementPlayerTimeOuts(millisecs_t millisecs) {
|
||||
for (auto&& i : players_) {
|
||||
Player* player = i.get();
|
||||
assert(player);
|
||||
if (player->time_out() < millisecs) {
|
||||
std::string kick_str =
|
||||
g_game->GetResourceString("kickIdlePlayersKickedText");
|
||||
Utils::StringReplaceOne(&kick_str, "${NAME}", player->GetName());
|
||||
ScreenMessage(kick_str);
|
||||
RemovePlayer(player);
|
||||
return; // Bail for this round since we prolly mucked with the list.
|
||||
} else if (player->time_out() > BA_PLAYER_TIME_OUT_WARN
|
||||
&& (player->time_out() - millisecs <= BA_PLAYER_TIME_OUT_WARN)) {
|
||||
std::string kick_str_1 =
|
||||
g_game->GetResourceString("kickIdlePlayersWarning1Text");
|
||||
Utils::StringReplaceOne(&kick_str_1, "${NAME}", player->GetName());
|
||||
Utils::StringReplaceOne(&kick_str_1, "${COUNT}",
|
||||
std::to_string(BA_PLAYER_TIME_OUT_WARN / 1000));
|
||||
ScreenMessage(kick_str_1);
|
||||
ScreenMessage(g_game->GetResourceString("kickIdlePlayersWarning2Text"));
|
||||
}
|
||||
player->set_time_out(player->time_out() - millisecs);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::ProcessPlayerTimeOuts() {
|
||||
millisecs_t real_time = GetRealTime();
|
||||
|
||||
if (foreground_host_activity_.exists()
|
||||
&& foreground_host_activity_->game_speed() > 0.0
|
||||
&& !foreground_host_activity_->paused()
|
||||
&& foreground_host_activity_->getAllowKickIdlePlayers()
|
||||
&& kick_idle_players_) {
|
||||
// Let's only do this every now and then.
|
||||
if (real_time - last_kick_idle_players_decrement_time_ > 1000) {
|
||||
DecrementPlayerTimeOuts(real_time
|
||||
- last_kick_idle_players_decrement_time_);
|
||||
last_kick_idle_players_decrement_time_ = real_time;
|
||||
}
|
||||
} else {
|
||||
// If we're not kicking, we still store the latest time (so it doesnt
|
||||
// accumulate for when we start again).
|
||||
last_kick_idle_players_decrement_time_ = real_time;
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::StepScene() {
|
||||
// Run up our game-time timers.
|
||||
sim_timers_.Run(scene()->time());
|
||||
|
||||
// And step.
|
||||
scene()->Step();
|
||||
}
|
||||
|
||||
void HostSession::Update(int time_advance) {
|
||||
assert(InGameThread());
|
||||
|
||||
// We can be killed at any time, so let's keep an eye out for that.
|
||||
WeakRef<HostSession> test_ref(this);
|
||||
assert(test_ref.exists());
|
||||
|
||||
ProcessPlayerTimeOuts();
|
||||
|
||||
GameStream* output_stream = GetGameStream();
|
||||
|
||||
// Advance base time by the specified amount,
|
||||
// firing all timers along the way.
|
||||
millisecs_t target_base_time = base_time_ + time_advance;
|
||||
while (!base_timers_.empty()
|
||||
&& (base_time_ + base_timers_.GetTimeToNextExpire(base_time_)
|
||||
<= target_base_time)) {
|
||||
base_time_ += base_timers_.GetTimeToNextExpire(base_time_);
|
||||
if (output_stream) {
|
||||
output_stream->SetTime(base_time_);
|
||||
}
|
||||
base_timers_.Run(base_time_);
|
||||
}
|
||||
base_time_ = target_base_time;
|
||||
if (output_stream) {
|
||||
output_stream->SetTime(base_time_);
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
|
||||
// Update our activities (iterate via weak-refs as this list may change under
|
||||
// us at any time).
|
||||
std::vector<Object::WeakRef<HostActivity> > activities =
|
||||
PointersToWeakRefs(RefsToPointers(host_activities_));
|
||||
for (auto&& i : activities) {
|
||||
if (i.exists()) {
|
||||
i->Update(time_advance);
|
||||
assert(test_ref.exists());
|
||||
}
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
|
||||
// Periodically prune various dead refs.
|
||||
if (base_time_ > next_prune_time_) {
|
||||
PruneDeadMapRefs(&textures_);
|
||||
PruneDeadMapRefs(&sounds_);
|
||||
PruneDeadMapRefs(&models_);
|
||||
PruneDeadRefs(&python_calls_);
|
||||
next_prune_time_ = base_time_ + 5000;
|
||||
}
|
||||
assert(test_ref.exists());
|
||||
}
|
||||
|
||||
HostSession::~HostSession() {
|
||||
try {
|
||||
shutting_down_ = true;
|
||||
|
||||
// Put the scene in shut-down mode before we start killing stuff
|
||||
// (this generates warnings, suppresses messages, etc).
|
||||
scene_->set_shutting_down(true);
|
||||
|
||||
// Clear out all python calls registered in our context
|
||||
// (should wipe out refs to our session and prevent them from running
|
||||
// without a valid session context).
|
||||
for (auto&& i : python_calls_) {
|
||||
if (i.exists()) {
|
||||
i->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all our media dead to clear it out of our output-stream cleanly.
|
||||
for (auto&& i : textures_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (i.second.exists()) {
|
||||
i.second->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
// Clear our timers and scene; this should wipe out any remaining refs
|
||||
// to our session scene.
|
||||
base_timers_.Clear();
|
||||
sim_timers_.Clear();
|
||||
scene_.Clear();
|
||||
|
||||
// Kill our python session object.
|
||||
{
|
||||
ScopedSetContext cp(this);
|
||||
session_py_obj_.Release();
|
||||
}
|
||||
|
||||
// Kill any remaining activity data. Generally all activities should die
|
||||
// when the session python object goes down, but lets clean up in case any
|
||||
// didn't.
|
||||
for (auto&& i : host_activities_) {
|
||||
ScopedSetContext cp{Object::Ref<ContextTarget>(i)};
|
||||
i.Clear();
|
||||
}
|
||||
|
||||
// Report outstanding calls. There shouldn't be any at this point. Actually
|
||||
// it turns out there's generally 1; whichever call was responsible for
|
||||
// killing this activity will still be in progress.. so let's report on 2 or
|
||||
// more I guess.
|
||||
if (g_buildconfig.debug_build()) {
|
||||
PruneDeadRefs(&python_calls_);
|
||||
if (python_calls_.size() > 1) {
|
||||
std::string s = "WARNING: " + std::to_string(python_calls_.size())
|
||||
+ " live PythonContextCalls at shutdown for "
|
||||
+ "HostSession" + " (1 call is expected):";
|
||||
int count = 1;
|
||||
for (auto&& i : python_calls_) {
|
||||
s += ("\n " + std::to_string(count++) + ": "
|
||||
+ i->GetObjectDescription());
|
||||
}
|
||||
Log(s);
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Log("Exception in HostSession destructor: " + std::string(e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::RegisterCall(PythonContextCall* call) {
|
||||
assert(call);
|
||||
python_calls_.emplace_back(call);
|
||||
|
||||
// If we're shutting down, just kill the call immediately.
|
||||
// (we turn all of our calls to no-ops as we shut down).
|
||||
if (shutting_down_) {
|
||||
Log("WARNING: adding call to expired session; call will not function: "
|
||||
+ call->GetObjectDescription());
|
||||
call->MarkDead();
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::GetUnusedPlayerName(Player* p, const std::string& base_name)
|
||||
-> std::string {
|
||||
// Now find the first non-taken variation.
|
||||
int index = 1;
|
||||
std::string name_test;
|
||||
while (true) {
|
||||
if (index > 1) {
|
||||
name_test = base_name + " " + std::to_string(index);
|
||||
} else {
|
||||
name_test = base_name;
|
||||
}
|
||||
bool name_found = false;
|
||||
for (auto&& j : players_) {
|
||||
if ((j->GetName() == name_test) && (j.get() != p)) {
|
||||
name_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!name_found) break;
|
||||
index += 1;
|
||||
}
|
||||
return name_test;
|
||||
}
|
||||
|
||||
void HostSession::DumpFullState(GameStream* out) {
|
||||
// Add session-scene.
|
||||
if (scene_.exists()) {
|
||||
scene_->Dump(out);
|
||||
}
|
||||
|
||||
// Dump media associated with session-scene.
|
||||
for (auto&& i : textures_) {
|
||||
if (Texture* t = i.second.get()) {
|
||||
out->AddTexture(t);
|
||||
}
|
||||
}
|
||||
for (auto&& i : sounds_) {
|
||||
if (Sound* s = i.second.get()) {
|
||||
out->AddSound(s);
|
||||
}
|
||||
}
|
||||
for (auto&& i : models_) {
|
||||
if (Model* s = i.second.get()) {
|
||||
out->AddModel(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Dump session-scene's nodes.
|
||||
if (scene_.exists()) {
|
||||
scene_->DumpNodes(out);
|
||||
}
|
||||
|
||||
// Now let our activities dump themselves.
|
||||
for (auto&& i : host_activities_) {
|
||||
i->DumpFullState(out);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::GetCorrectionMessages(
|
||||
bool blend, std::vector<std::vector<uint8_t> >* messages) {
|
||||
std::vector<uint8_t> message;
|
||||
|
||||
// Grab correction for session scene (though there shouldn't be one).
|
||||
if (scene_.exists()) {
|
||||
message = scene_->GetCorrectionMessage(blend);
|
||||
if (message.size() > 4) {
|
||||
// A correction packet of size 4 is empty; ignore it.
|
||||
messages->push_back(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Now do same for activity scenes.
|
||||
for (auto&& i : host_activities_) {
|
||||
if (HostActivity* ha = i.get()) {
|
||||
if (Scene* sg = ha->scene()) {
|
||||
message = sg->GetCorrectionMessage(blend);
|
||||
if (message.size() > 4) {
|
||||
// A correction packet of size 4 is empty; ignore it.
|
||||
messages->push_back(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::NewTimer(TimeType timetype, TimerMedium length, bool repeat,
|
||||
const Object::Ref<Runnable>& runnable) -> int {
|
||||
// Make sure the runnable passed in is reference-managed already
|
||||
// (we may not add an initial reference ourself).
|
||||
assert(runnable->is_valid_refcounted_object());
|
||||
|
||||
// We currently support game and base timers.
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
// Game and base timers are the same thing for us.
|
||||
return NewTimer(length, repeat, runnable);
|
||||
default:
|
||||
// Gall back to default for descriptive error otherwise.
|
||||
return ContextTarget::NewTimer(timetype, length, repeat, runnable);
|
||||
}
|
||||
}
|
||||
|
||||
void HostSession::DeleteTimer(TimeType timetype, int timer_id) {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
// Game and base timers are the same thing for us.
|
||||
DeleteTimer(timer_id);
|
||||
break;
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
ContextTarget::DeleteTimer(timetype, timer_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto HostSession::GetTime(TimeType timetype) -> millisecs_t {
|
||||
switch (timetype) {
|
||||
case TimeType::kSim:
|
||||
case TimeType::kBase:
|
||||
return scene_->time();
|
||||
default:
|
||||
// Fall back to default for descriptive error otherwise.
|
||||
return ContextTarget::GetTime(timetype);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
201
src/ballistica/game/session/net_client_session.cc
Normal file
201
src/ballistica/game/session/net_client_session.cc
Normal file
@ -0,0 +1,201 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/game/session/net_client_session.h"
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/connection/connection_to_host.h"
|
||||
#include "ballistica/graphics/graphics.h"
|
||||
#include "ballistica/graphics/net_graph.h"
|
||||
#include "ballistica/media/media_server.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
NetClientSession::NetClientSession() {
|
||||
// Sanity check: we should only ever be writing one replay at once.
|
||||
if (g_app_globals->replay_open) {
|
||||
Log("ERROR: g_replay_open true at netclient start; shouldn't happen.");
|
||||
}
|
||||
assert(g_media_server);
|
||||
g_media_server->PushBeginWriteReplayCall();
|
||||
writing_replay_ = true;
|
||||
g_app_globals->replay_open = true;
|
||||
}
|
||||
|
||||
NetClientSession::~NetClientSession() {
|
||||
if (writing_replay_) {
|
||||
// Sanity check: we should only ever be writing one replay at once.
|
||||
if (!g_app_globals->replay_open) {
|
||||
Log("ERROR: g_replay_open false at net-client close; shouldn't happen.");
|
||||
}
|
||||
g_app_globals->replay_open = false;
|
||||
assert(g_media_server);
|
||||
g_media_server->PushEndWriteReplayCall();
|
||||
writing_replay_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void NetClientSession::SetConnectionToHost(ConnectionToHost* c) {
|
||||
connection_to_host_ = c;
|
||||
}
|
||||
|
||||
void NetClientSession::OnCommandBufferUnderrun() {
|
||||
// We currently don't do anything here; we want to just power
|
||||
// through hitches and keep aiming for our target time.
|
||||
// (though perhaps we could take note here for analytics purposes).
|
||||
// printf("Underrun at %d\n", GetRealTime());
|
||||
// fflush(stdout);
|
||||
}
|
||||
|
||||
void NetClientSession::Update(int time_advance) {
|
||||
if (shutting_down()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Now do standard step.
|
||||
ClientSession::Update(time_advance);
|
||||
|
||||
// And update our timing to try and ensure we don't run out of buffer.
|
||||
UpdateBuffering();
|
||||
}
|
||||
|
||||
auto NetClientSession::GetBucketNum() -> int {
|
||||
return (delay_sample_counter_ / g_app_globals->delay_bucket_samples)
|
||||
% static_cast<int>(buckets_.size());
|
||||
}
|
||||
|
||||
auto NetClientSession::UpdateBuffering() -> void {
|
||||
// Keep record of the most and least amount of time we've had buffered
|
||||
// recently, and slow down/speed up a bit based on that.
|
||||
{
|
||||
// Change bucket every `g_delay_samples` samples.
|
||||
int bucketnum{GetBucketNum()};
|
||||
int bucket_iteration =
|
||||
delay_sample_counter_ % g_app_globals->delay_bucket_samples;
|
||||
delay_sample_counter_++;
|
||||
SampleBucket& bucket{buckets_[bucketnum]};
|
||||
if (bucket_iteration == 0) {
|
||||
bucket.max_delay_from_projection = 0;
|
||||
}
|
||||
|
||||
// After the last sample in each bucket, update our smoothed values with
|
||||
// the full sample set in the bucket.
|
||||
if (bucket_iteration == g_app_globals->delay_bucket_samples - 1) {
|
||||
float smoothing = 0.7f;
|
||||
last_bucket_max_delay_ =
|
||||
static_cast<float>(bucket.max_delay_from_projection);
|
||||
max_delay_smoothed_ =
|
||||
smoothing * max_delay_smoothed_
|
||||
+ (1.0f - smoothing)
|
||||
* static_cast<float>(bucket.max_delay_from_projection);
|
||||
}
|
||||
auto now = GetRealTime();
|
||||
|
||||
// We want target-base-time to wind up at our projected time minus some
|
||||
// safety offset to account for buffering fluctuations.
|
||||
|
||||
// We might want to consider exposing this value or calculate it in a smart
|
||||
// way based on conditions. 0.0 gives us lowest latency possible but makes
|
||||
// lag spikes very noticeable. 1.0 should avoid most lag spikes. Higher
|
||||
// values even moreso at the price of latency;
|
||||
float safety_amt{1.0};
|
||||
|
||||
float to_ideal_offset =
|
||||
static_cast<float>(ProjectedBaseTime(now) - target_base_time())
|
||||
- safety_amt * max_delay_smoothed_;
|
||||
|
||||
// How aggressively we throttle the game speed up or down to accommodate lag
|
||||
// spikes.
|
||||
float speed_change_aggression{0.004f};
|
||||
float new_consume_rate = std::min(
|
||||
10.0f,
|
||||
std::max(0.5f, 1.0f + speed_change_aggression * to_ideal_offset));
|
||||
set_consume_rate(new_consume_rate);
|
||||
|
||||
if (g_graphics->network_debug_info_display_enabled()) {
|
||||
if (NetGraph* graph =
|
||||
g_graphics->GetDebugGraph("1: packet delay", false)) {
|
||||
graph->AddSample(now, current_delay_);
|
||||
}
|
||||
if (NetGraph* graph =
|
||||
g_graphics->GetDebugGraph("2: max delay bucketed", false)) {
|
||||
graph->AddSample(now, last_bucket_max_delay_);
|
||||
}
|
||||
if (NetGraph* graph =
|
||||
g_graphics->GetDebugGraph("3: filtered delay", false)) {
|
||||
graph->AddSample(now, max_delay_smoothed_);
|
||||
}
|
||||
if (NetGraph* graph = g_graphics->GetDebugGraph("4: run rate", false)) {
|
||||
graph->AddSample(now, new_consume_rate);
|
||||
}
|
||||
if (NetGraph* graph =
|
||||
g_graphics->GetDebugGraph("5: time buffered", true)) {
|
||||
graph->AddSample(now, base_time_buffered());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto NetClientSession::OnReset(bool rewind) -> void {
|
||||
// Resets should never happen for us after we start, right?...
|
||||
base_time_received_ = 0;
|
||||
last_base_time_receive_time_ = 0;
|
||||
leading_base_time_received_ = 0;
|
||||
leading_base_time_receive_time_ = 0;
|
||||
ClientSession::OnReset(rewind);
|
||||
}
|
||||
|
||||
auto NetClientSession::OnBaseTimeStepAdded(int step) -> void {
|
||||
auto now = GetRealTime();
|
||||
|
||||
millisecs_t new_base_time_received = base_time_received_ + step;
|
||||
|
||||
// We want to be able to project as close as possible to what the
|
||||
// current base time is based on when we receive steps (regardless of lag
|
||||
// spikes). To do this, we only factor in steps we receive if their times are
|
||||
// newer than what we get projecting forward from the last one.
|
||||
bool use;
|
||||
if (leading_base_time_receive_time_ == 0) {
|
||||
use = true;
|
||||
} else {
|
||||
millisecs_t projected = ProjectedBaseTime(now);
|
||||
|
||||
// Hopefully we'll keep refreshing our leading value consistently
|
||||
// but force the issue if it becomes too old.
|
||||
use = (new_base_time_received >= projected
|
||||
|| (now - leading_base_time_receive_time_ > 250));
|
||||
|
||||
// Keep track of the biggest recent delays we get compared to the projected
|
||||
// time. (we can use this when calcing how much to buffer to avoid stutter).
|
||||
if (new_base_time_received < projected) {
|
||||
auto& bucket{buckets_[GetBucketNum()]};
|
||||
current_delay_ = bucket.max_delay_from_projection =
|
||||
std::max(bucket.max_delay_from_projection,
|
||||
static_cast<int>(projected - new_base_time_received));
|
||||
|
||||
} else {
|
||||
current_delay_ = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
base_time_received_ = new_base_time_received;
|
||||
last_base_time_receive_time_ = now;
|
||||
|
||||
if (use) {
|
||||
leading_base_time_received_ = new_base_time_received;
|
||||
leading_base_time_receive_time_ = now;
|
||||
}
|
||||
}
|
||||
|
||||
void NetClientSession::HandleSessionMessage(
|
||||
const std::vector<uint8_t>& message) {
|
||||
// Do the standard thing, but also write this message straight to our replay
|
||||
// stream if we have one.
|
||||
ClientSession::HandleSessionMessage(message);
|
||||
|
||||
if (writing_replay_) {
|
||||
assert(g_media_server);
|
||||
g_media_server->PushAddMessageToReplayCall(message);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
257
src/ballistica/game/session/replay_client_session.cc
Normal file
257
src/ballistica/game/session/replay_client_session.cc
Normal file
@ -0,0 +1,257 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/game/session/replay_client_session.h"
|
||||
|
||||
#include "ballistica/dynamics/material/material.h"
|
||||
#include "ballistica/game/connection/connection_set.h"
|
||||
#include "ballistica/game/connection/connection_to_client.h"
|
||||
#include "ballistica/game/game_stream.h"
|
||||
#include "ballistica/generic/huffman.h"
|
||||
#include "ballistica/generic/utils.h"
|
||||
#include "ballistica/math/vector3f.h"
|
||||
#include "ballistica/networking/networking.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/scene/scene.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
auto ReplayClientSession::GetActualTimeAdvance(int advance_in) -> int {
|
||||
return static_cast<int>(
|
||||
round(advance_in * pow(2.0f, g_game->replay_speed_exponent())));
|
||||
}
|
||||
|
||||
ReplayClientSession::ReplayClientSession(std::string filename)
|
||||
: file_name_(std::move(filename)) {
|
||||
// take responsibility for feeding all clients to this device..
|
||||
g_game->connections()->RegisterClientController(this);
|
||||
|
||||
// go ahead and just do a reset here, which will get things going..
|
||||
Reset(true);
|
||||
}
|
||||
|
||||
ReplayClientSession::~ReplayClientSession() {
|
||||
// we no longer are responsible for feeding clients to this device..
|
||||
g_game->connections()->UnregisterClientController(this);
|
||||
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnCommandBufferUnderrun() { ResetTargetBaseTime(); }
|
||||
|
||||
void ReplayClientSession::OnClientConnected(ConnectionToClient* c) {
|
||||
// sanity check - abort if its on either of our lists already
|
||||
for (ConnectionToClient* i : connections_to_clients_) {
|
||||
if (i == c) {
|
||||
Log("Error: ReplayClientSession::OnClientConnected()"
|
||||
" got duplicate connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (ConnectionToClient* i : connections_to_clients_ignored_) {
|
||||
if (i == c) {
|
||||
Log("Error: ReplayClientSession::OnClientConnected()"
|
||||
" got duplicate connection");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we've sent *any* commands out to clients so far, we currently have to
|
||||
// ignore new connections (need to rebuild state to match current session
|
||||
// state)
|
||||
{
|
||||
connections_to_clients_.push_back(c);
|
||||
|
||||
// we create a temporary output stream just for the purpose of building
|
||||
// a giant session-commands message that we can send to the client
|
||||
// to build its state up to where we are currently.
|
||||
GameStream out(nullptr, false);
|
||||
|
||||
// go ahead and dump our full state..
|
||||
DumpFullState(&out);
|
||||
|
||||
// grab the message that's been built up..
|
||||
// if its not empty, send it to the client.
|
||||
std::vector<uint8_t> out_message = out.GetOutMessage();
|
||||
if (!out_message.empty()) {
|
||||
c->SendReliableMessage(out_message);
|
||||
}
|
||||
|
||||
// also send a correction packet to sync up all our dynamics
|
||||
// (technically could do this *just* for the new client)
|
||||
{
|
||||
std::vector<std::vector<uint8_t> > messages;
|
||||
bool blend = false;
|
||||
GetCorrectionMessages(blend, &messages);
|
||||
|
||||
// FIXME - have to send reliably at the moment since these will most
|
||||
// likely be bigger than our unreliable packet limit.. :-(
|
||||
for (auto&& i : messages) {
|
||||
for (auto&& j : connections_to_clients_) {
|
||||
j->SendReliableMessage(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnClientDisconnected(ConnectionToClient* c) {
|
||||
// Search for it on either our ignored or regular lists.
|
||||
for (auto i = connections_to_clients_.begin();
|
||||
i != connections_to_clients_.end(); i++) {
|
||||
if (*i == c) {
|
||||
connections_to_clients_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
for (auto i = connections_to_clients_ignored_.begin();
|
||||
i != connections_to_clients_ignored_.end(); i++) {
|
||||
if (*i == c) {
|
||||
connections_to_clients_ignored_.erase(i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Log("Error: ReplayClientSession::OnClientDisconnected()"
|
||||
" called for connection not on lists");
|
||||
}
|
||||
|
||||
void ReplayClientSession::FetchMessages() {
|
||||
if (!file_ || shutting_down()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have no messages left, read from the file until we get some.
|
||||
while (commands().empty()) {
|
||||
std::vector<uint8_t> buffer;
|
||||
uint8_t len8;
|
||||
uint32_t len32;
|
||||
|
||||
// Read the size of the message.
|
||||
// the first byte represents the actual size if the value is < 254
|
||||
// if it is 254, the 2 bytes after it represent size
|
||||
// if it is 255, the 4 bytes after it represent size
|
||||
if (fread(&len8, 1, 1, file_) != 1) {
|
||||
// So they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
add_end_of_file_command();
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
if (len8 < 254) {
|
||||
len32 = len8;
|
||||
} else {
|
||||
// Pull 16 bit len.
|
||||
if (len8 == 254) {
|
||||
uint16_t len16;
|
||||
if (fread(&len16, 2, 1, file_) != 1) {
|
||||
// so they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
add_end_of_file_command();
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
assert(len16 >= 254);
|
||||
len32 = len16;
|
||||
} else {
|
||||
// Pull 32 bit len.
|
||||
if (fread(&len32, 4, 1, file_) != 1) {
|
||||
// so they know to be done when they reach the end of the command list
|
||||
// (instead of just waiting for more commands)
|
||||
add_end_of_file_command();
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
assert(len32 > 65535);
|
||||
}
|
||||
}
|
||||
|
||||
// Read and decompress the actual message.
|
||||
BA_PRECONDITION(len32 > 0);
|
||||
buffer.resize(len32);
|
||||
if (fread(&(buffer[0]), len32, 1, file_) != 1) {
|
||||
add_end_of_file_command();
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
return;
|
||||
}
|
||||
std::vector<uint8_t> data_decompressed =
|
||||
g_utils->huffman()->decompress(buffer);
|
||||
HandleSessionMessage(data_decompressed);
|
||||
|
||||
// Also send it to all client-connections we're attached to.
|
||||
// NOTE: We currently are sending everything as reliable; we can maybe do
|
||||
// unreliable for certain type of messages. Though perhaps when passing
|
||||
// around replays maybe its best to keep everything intact.
|
||||
have_sent_client_message_ = true;
|
||||
for (auto&& i : connections_to_clients_) {
|
||||
i->SendReliableMessage(data_decompressed);
|
||||
}
|
||||
message_fetch_num_++;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayClientSession::Error(const std::string& description) {
|
||||
// Close the replay, announce something went wrong with it, and then do
|
||||
// standard error response..
|
||||
ScreenMessage(g_game->GetResourceString("replayReadErrorText"), {1, 0, 0});
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
ClientSession::Error(description);
|
||||
}
|
||||
|
||||
void ReplayClientSession::OnReset(bool rewind) {
|
||||
// Handles base resetting.
|
||||
ClientSession::OnReset(rewind);
|
||||
|
||||
// If we've got any clients attached to us, tell them to reset as well.
|
||||
for (auto&& i : connections_to_clients_) {
|
||||
i->SendReliableMessage(std::vector<uint8_t>(1, BA_MESSAGE_SESSION_RESET));
|
||||
}
|
||||
|
||||
// If rewinding, pop back to the start of our file.
|
||||
if (rewind) {
|
||||
if (file_) {
|
||||
fclose(file_);
|
||||
file_ = nullptr;
|
||||
}
|
||||
|
||||
file_ = g_platform->FOpen(file_name_.c_str(), "rb");
|
||||
if (!file_) {
|
||||
Error("can't open file for reading");
|
||||
return;
|
||||
}
|
||||
|
||||
// Read file ID and version to make sure we support this file.
|
||||
uint32_t file_id;
|
||||
if ((fread(&file_id, sizeof(file_id), 1, file_) != 1)) {
|
||||
Error("error reading file_id");
|
||||
return;
|
||||
}
|
||||
if (file_id != kBrpFileID) {
|
||||
Error("incorrect file_id");
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure its a compatible protocol version.
|
||||
uint16_t version;
|
||||
if (fread(&version, sizeof(version), 1, file_) != 1) {
|
||||
Error("error reading version");
|
||||
return;
|
||||
}
|
||||
if (version > kProtocolVersion || version < kProtocolVersionMin) {
|
||||
ScreenMessage(g_game->GetResourceString("replayVersionErrorText"),
|
||||
{1, 0, 0});
|
||||
End();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
37
src/ballistica/game/session/session.cc
Normal file
37
src/ballistica/game/session/session.cc
Normal file
@ -0,0 +1,37 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#include "ballistica/game/session/session.h"
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
#include "ballistica/game/game.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
Session::Session() {
|
||||
g_app_globals->session_count++;
|
||||
|
||||
// New sessions immediately become foreground.
|
||||
g_game->SetForegroundSession(this);
|
||||
}
|
||||
|
||||
Session::~Session() { g_app_globals->session_count--; }
|
||||
|
||||
void Session::Update(int time_advance) {}
|
||||
|
||||
auto Session::GetForegroundContext() -> Context { return Context(); }
|
||||
|
||||
void Session::Draw(FrameDef*) {}
|
||||
|
||||
void Session::ScreenSizeChanged() {}
|
||||
|
||||
void Session::LanguageChanged() {}
|
||||
|
||||
void Session::GraphicsQualityChanged(GraphicsQuality q) {}
|
||||
|
||||
void Session::DebugSpeedMultChanged() {}
|
||||
|
||||
void Session::DumpFullState(GameStream* out) {
|
||||
Log("Session::DumpFullState() being called; shouldn't happen.");
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
Loading…
x
Reference in New Issue
Block a user