Merge branch 'master' into master

This commit is contained in:
Era 2024-01-06 14:49:54 +03:30 committed by GitHub
commit 0224e2a0da
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
80 changed files with 781 additions and 426 deletions

92
.efrocachemap generated
View File

@ -421,21 +421,21 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
"build/assets/ba_data/data/langdata.json": "fae88cbb2a5b9c24096f2e43452114a2",
"build/assets/ba_data/data/langdata.json": "e71fd5c04d15c544dc15bc15bbe0798a",
"build/assets/ba_data/data/languages/arabic.json": "0db32e21b6d5337ccca478381744aa88",
"build/assets/ba_data/data/languages/belarussian.json": "a112dfca3e188387516788bd8229c5b0",
"build/assets/ba_data/data/languages/chinese.json": "ff9a595726f0aff42a39be576d0ff037",
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
"build/assets/ba_data/data/languages/chinese.json": "93f3ca9f90d86dc7c8d0923f5f11ef46",
"build/assets/ba_data/data/languages/chinesetraditional.json": "319565f8a15667488f48dbce59278e39",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "c9d518a324870066b987b8f412881dd3",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
"build/assets/ba_data/data/languages/english.json": "2434e127b6788e3128d3523fcb1b8994",
"build/assets/ba_data/data/languages/dutch.json": "5cbf1a68a9d93dee00dbc27f834d878a",
"build/assets/ba_data/data/languages/english.json": "1c4037fea1066d39d6eced419f314f35",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "e750fb1a95e4c5611115f9ece9ecab53",
"build/assets/ba_data/data/languages/french.json": "163362f7b33866ef069cae62d0387551",
"build/assets/ba_data/data/languages/filipino.json": "f897776672925499ecda8960b3aae607",
"build/assets/ba_data/data/languages/french.json": "8bc35eb4b20a0b30c3348bcc9a3844a6",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "e24d391c9fd12f9afa92f7ff65a06d23",
"build/assets/ba_data/data/languages/gibberish.json": "b461539243e8efe3137137b886256ba7",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
@ -450,13 +450,13 @@
"build/assets/ba_data/data/languages/russian.json": "9d0b40586301a82e532c4a250d5f6d58",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
"build/assets/ba_data/data/languages/spanish.json": "42f857c40dbd4b637e3866849489f7d1",
"build/assets/ba_data/data/languages/spanish.json": "8d43556cb55cdfa4408fd19a4646b824",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
"build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
"build/assets/ba_data/data/languages/turkish.json": "ab149ebbd57cf4daa3cf8f310d91519a",
"build/assets/ba_data/data/languages/ukrainian.json": "e5c861187c4c6db37d1a033f4ef3dd5a",
"build/assets/ba_data/data/languages/venetian.json": "a559a5608d2e0b4708f7a4dee42ff354",
"build/assets/ba_data/data/languages/turkish.json": "72fac3a6fd13ebb74fb4d68b9323e7d4",
"build/assets/ba_data/data/languages/ukrainian.json": "b54a38e93deebafa5706ba2d1f626892",
"build/assets/ba_data/data/languages/venetian.json": "8e9714d98a85e428ce3543fc49188a46",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
@ -1528,9 +1528,9 @@
"build/assets/ba_data/textures/fontBig.ktx": "94b56c2488d6c9ebabfbbb740eca07dd",
"build/assets/ba_data/textures/fontBig.pvr": "dff3f6c04a8c7b0bb937001640b42c8d",
"build/assets/ba_data/textures/fontBig_preview.png": "f8b15cb04f0deca7774def335a72f053",
"build/assets/ba_data/textures/fontExtras.dds": "0a5a39028853c443cd88bc2492cb6ad9",
"build/assets/ba_data/textures/fontExtras.ktx": "5b14075ce3d1d29c6d5635602e2176d8",
"build/assets/ba_data/textures/fontExtras.pvr": "8cc68ca85ba327c20c45bad73b000d8c",
"build/assets/ba_data/textures/fontExtras.dds": "d2d20fdde7c6114925ba626ade35151f",
"build/assets/ba_data/textures/fontExtras.ktx": "2dde1a343493a9329792d7042116d301",
"build/assets/ba_data/textures/fontExtras.pvr": "80ab1f61fafba22ce0259177944beabf",
"build/assets/ba_data/textures/fontExtras2.dds": "18063a12912dadc9528afd90d1cf2369",
"build/assets/ba_data/textures/fontExtras2.ktx": "36da7f6cfbfb8d32fb14371de0a8f660",
"build/assets/ba_data/textures/fontExtras2.pvr": "7a4e8e64ac05313b1782fb5b958150d0",
@ -1543,7 +1543,7 @@
"build/assets/ba_data/textures/fontExtras4.ktx": "6d872ac15e2e874c1252f63b4584722b",
"build/assets/ba_data/textures/fontExtras4.pvr": "6a0a0a1a8bbbc3ee9d6b8b914e7aa697",
"build/assets/ba_data/textures/fontExtras4_preview.png": "363e2647621917b3821c9068267d2516",
"build/assets/ba_data/textures/fontExtras_preview.png": "b6503267cc15e9e2524f41fabd94e773",
"build/assets/ba_data/textures/fontExtras_preview.png": "182e41ed4042207404a95e17efbdcee7",
"build/assets/ba_data/textures/fontSmall0.dds": "b30bfe5f9e436be7be8b5eae6e8490c3",
"build/assets/ba_data/textures/fontSmall0.ktx": "7e6058f37e6c5a4ea628f35b5f92c227",
"build/assets/ba_data/textures/fontSmall0.pvr": "c66e3d6aa1f7def83aaacd8a6c9185e5",
@ -4060,26 +4060,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5f7e668a6a904ba8c0fe391654a8211b",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "77cd63dbc1760a3d1bb55753794701b0",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "9f674a5b767f798b350ef30d84968d44",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "84546f0452561d3f6519d296a9e9b2ca",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "3122dbce63fb248c9ebe66d545be8480",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "4004c01a3ea1a7b62544f08f14e76f9e",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cd9b5b9ae925cef7417d2b86fd0f0489",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "35c2b307e5d228b105315b8d022d31d2",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "90993c873a2d1ca7b593992e52b1865a",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "46119cb9ff99629ca16471f51032bac7",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "8cac6eff8eb4f9e453a669b6f239b8a5",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "3e20c91bf8948354a0e167faa5c184e9",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "52ff216e1b0394a07bf178b8eea8c269",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "32d85cc58ade8e7a63dfb604ef175553",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "fdf3da5251bc0d3b8f4e5a5b2e0d4b94",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c03afc18b7aa72ab065381be985f48ae",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "67a58d483ac028f9fc59112b59463a99",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "0800d71a41320664bacba49822e8b442",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9019d9b48cefa45a7a16bae6bd696896",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "90eca6cc81b0a2c39fd1d41d0d029f04",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c5e053c4b83c085c21f7445f7d53cb7c",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "2a4ba9065e50bf327b0fc582007ad6ed",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "683b185c89662b0b751a42906464e386",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a3c6c3ac394ba54117930e1c7aea1812",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e3fda2b9dad14b6919ea97a61d21489b",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "e5343c505d4c2f1b3b547e867dab3d41",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "6a723038a6157d810cb6c9d7a921826a",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "39ff22b4a24581bca7c2918b4d10c691",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "abb2c71fa426bc836f38b661afb5e3f7",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "aff057b7be5f10c3e87fb71739bd786b",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c4a32ca21b1621239378d689068015dd",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "41854e470df59e6cace772a620a915ac",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f4acd58fdcb43cc5ad104c6530240f4e",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "a0939128bbd12ebddd19691cbd2a8bdb",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "9a9aadd78d7dc65663e622e6f41ca944",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "5cbd8b0b2a33bfc132b24ebe0798c732",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "339ac97c0591caf6f78f2c9712b1ea19",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "8411b4ae387975f17d1f255e879fbd9c",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "364cbe9a490300dc423e76be2dadd32f",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "a186829073b2a809be8e7505477b7108",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
@ -4096,21 +4096,21 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ca49b32ed573feea11613d62cd89840c",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ff81e6eeea861f59e71db628bc64918b",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "ba89e5949d1cdf2b857089feb901285c",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "5e063f8acc0e0e9f35f82480d7dbf143",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "1f0d14fcc16dd0d4896d91c75e32be25",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "251d3dd0bc9a6418eb1cb5176bb5509c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "96c003edb87b3f506d1b15af461487c3",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "274ebd634f05b23653719ef973119cf5",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "796edace73f874ebf46054b2a1ff0ba1",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "3981a2a3291a86349f99626b35a76a56",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "fdfe72ba7bcb6b2cbcedf144dce4fa44",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "b2178a56d5395f61cc33a70b50d87506",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "7869a7a14c57859529fd564c4a8ad1b1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "6f244022dc28a358eec48524ab04e810",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "94906a8bee71a52820ae294f5267c5bc",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "a61ddb3fc5375e948612d19f282b00c1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "abc867cddab0f02c74e0a39a866d58cc",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/assets/ba_data/python/babase/_mgen/enums.py": "b611c090513a21e2fe90e56582724e9d",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "97efb93f4bfd8e8b09f2db24398e29fc",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
"src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07",
"src/ballistica/core/mgen/pyembed/env.inc": "f015d726b44d2922112fc14d9f146d8b",
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "c25b263f2a31fb5ebe057db07d144879",
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",

View File

@ -1,6 +1,18 @@
### 1.7.33 (build 21743, api 8, 2023-12-21)
### 1.7.33 (build 21756, api 8, 2024-01-05)
- Exposed an override for `bascenev1.Session`'s max players on servers (by EraOSBeta)
- Added UI for customizing teams and FFA series length (by EraOSBeta, idea by 3alTemp)
- Stress test input-devices are now a bit smarter; they won't press any buttons
while UIs are up (this could cause lots of chaos if it happened).
- Added a 'Show Demos When Idle' option in advanced settings. If enabled, the
game will show gameplay demos (a slightly modified form of the stress test)
periodically after sitting idle at the main menu for a bit. Like an old arcade
game. I added this for an upcoming conference appearance but though people
might like to enable it in other cases.
- Replays now have a play/pause button alongside the speed adjustment buttons
(Thanks vishal332008!)
- Players now get points for killing bots with their own bombs by catching it
and throwing it back at them. This is actually old logic but was disabled due
to a logic flaw, but should be fixed now. (Thanks VinniTR!)
### 1.7.32 (build 21741, api 8, 2023-12-20)
- Fixed a screen message that no one will ever see (Thanks vishal332008?...)
@ -478,7 +490,7 @@
you run into any problems because of this and we can make an option to use the
old behavior where Ballistica's app and site paths get placed at the end.
- It is now possible to manually run the app loop even on monolithic builds;
just do `PYTHONPATH=ba_data/python ./ballisticacore -c "import baenv;
just do `PYTHONPATH=ba_data/python ./ballisticakit -c "import baenv;
baenv.configure(); import babase; babase.app.run()"`. This is basically the
same thing modular builds are doing except that they use a regular Python
interpreter instead of the ballisticakit binary.
@ -809,7 +821,7 @@
future, the default BallisticaKit app may be expanded with editing
functionality and I feel the name 'Kit' fits better for something used that
way than 'Core' does.
- The `ballisticakit_internal` precompiled library has been renamed to
- The `ballisticacore_internal` precompiled library has been renamed to
`ballistica_plus`. This name better describes what it actually is (basically
precompiled native portion of the `plus` feature set). Also by removing the
'kit' from the end it will no longer be renamed in spinoff projects, meaning
@ -1561,7 +1573,7 @@
`prefab-gui-debug` (more consistent with the existing `prefab-server-debug`
targets).
- Windows builds now go to build/windows instead of
`ballisticakit_windows/build`.
`ballisticacore_windows/build`.
- Lots of project reorganization to allow things such as documentation or the
dummy `_ba.py` module to be rebuilt from the public repo.
- Added network flood attack mitigation.
@ -2217,7 +2229,7 @@
### 1.4.111 (14286)
- BallisticaKit Pro now unlocks 2 additional characters
- BombSquad Pro now unlocks 2 additional characters
- multi-line chat messages are now clamped down to 1 line; should prevent
annoying multi-line fullscreen message spam
@ -2294,7 +2306,7 @@
### 1.4.95 (14236)
- added a port option to the config, so it's now possible to host multiple
parties on one machine (note that ballisticakit 1.4.95+ is required to connect
parties on one machine (note that bombsquad 1.4.95+ is required to connect
ports aside from 43210)
### 1.4.95 (14234)
@ -2304,14 +2316,14 @@
### 1.4.95 (14233)
- ballisticakit (both `bs_headless` and regular) now reads commands from
- bombsquad (both `bs_headless` and regular) now reads commands from
standard input, making it easier to run commands via scripts or the terminal
- server now runs using a 'server' account-type instead of the local 'device'
account. (avoids daily-ticket-reward messages and other stuff that's not
relevant to servers)
- the server script now passes args to the game as a json file instead of
individual args; this should keep things cleaner and more expandable
- the `ballisticakit_server` script also now reads commands from stdin, allowing
- the `bombsquad_server` script also now reads commands from stdin, allowing
reconfiguring server settings on the fly
- added more options such as the ability to set game series lengths and to host
a non-public party

View File

@ -76,8 +76,8 @@ play some games. Or make them. Maybe both.
of what's planned for when. And for the record, the answer to that particular
question is basically '1.8'.
* **Q: When will BombSquad be released on iOS / Steam / Switch / XBox /
Playstation / My Toaster??**
* **Q: When will BombSquad be released on iOS / Steam / Switch / Xbox /
PlayStation / My toaster??**
* A: The 2.0 update will be the big 'relaunch' release and the plan is to
launch on at least iOS and Steam at that time. I'm trying to get there as fast
as I can. As far as consoles, I'd love to and hope to at some point but have

View File

@ -48,6 +48,7 @@ from _babase import (
fatal_error,
get_display_resolution,
get_immediate_return_code,
get_input_idle_time,
get_low_level_config_value,
get_max_graphics_quality,
get_replays_dir,
@ -236,6 +237,7 @@ __all__ = [
'garbage_collect',
'get_display_resolution',
'get_immediate_return_code',
'get_input_idle_time',
'get_ip_address_type',
'get_low_level_config_value',
'get_max_graphics_quality',

View File

@ -4,6 +4,7 @@
from __future__ import annotations
import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
import babase
@ -48,64 +49,74 @@ def run_cpu_benchmark() -> None:
bascenev1.new_host_session(BenchmarkSession, benchmark_type='cpu')
@dataclass
class _StressTestArgs:
playlist_type: str
playlist_name: str
player_count: int
round_duration: int
attract_mode: bool
def run_stress_test(
playlist_type: str = 'Random',
playlist_name: str = '__default__',
player_count: int = 8,
round_duration: int = 30,
attract_mode: bool = False,
) -> None:
"""Run a stress test."""
babase.screenmessage(
"Beginning stress test.. use 'End Test' to stop testing.",
color=(1, 1, 0),
)
with babase.ContextRef.empty():
start_stress_test(
{
'playlist_type': playlist_type,
'playlist_name': playlist_name,
'player_count': player_count,
'round_duration': round_duration,
}
if not attract_mode:
babase.screenmessage(
"Beginning stress test.. use 'End Test' to stop testing.",
color=(1, 1, 0),
)
_start_stress_test(
_StressTestArgs(
playlist_type=playlist_type,
playlist_name=playlist_name,
player_count=player_count,
round_duration=round_duration,
attract_mode=attract_mode,
)
)
def stop_stress_test() -> None:
"""End a running stress test."""
_baclassic.set_stress_testing(False, 0)
assert babase.app.classic is not None
try:
if babase.app.classic.stress_test_reset_timer is not None:
babase.screenmessage('Ending stress test...', color=(1, 1, 0))
except Exception:
pass
babase.app.classic.stress_test_reset_timer = None
_baclassic.set_stress_testing(False, 0, False)
babase.app.classic.stress_test_update_timer = None
babase.app.classic.stress_test_update_timer_2 = None
def start_stress_test(args: dict[str, Any]) -> None:
def _start_stress_test(args: _StressTestArgs) -> None:
"""(internal)"""
from bascenev1 import DualTeamSession, FreeForAllSession
assert babase.app.classic is not None
appconfig = babase.app.config
playlist_type = args['playlist_type']
playlist_type = args.playlist_type
if playlist_type == 'Random':
if random.random() < 0.5:
playlist_type = 'Teams'
else:
playlist_type = 'Free-For-All'
babase.screenmessage(
'Running Stress Test (listType="'
+ playlist_type
+ '", listName="'
+ args['playlist_name']
+ '")...'
)
if not args.attract_mode:
babase.screenmessage(
'Running Stress Test (listType="'
+ playlist_type
+ '", listName="'
+ args.playlist_name
+ '")...'
)
if playlist_type == 'Teams':
appconfig['Team Tournament Playlist Selection'] = args['playlist_name']
appconfig['Team Tournament Playlist Selection'] = args.playlist_name
appconfig['Team Tournament Playlist Randomize'] = 1
babase.apptimer(
1.0,
@ -115,7 +126,7 @@ def start_stress_test(args: dict[str, Any]) -> None:
),
)
else:
appconfig['Free-for-All Playlist Selection'] = args['playlist_name']
appconfig['Free-for-All Playlist Selection'] = args.playlist_name
appconfig['Free-for-All Playlist Randomize'] = 1
babase.apptimer(
1.0,
@ -124,19 +135,38 @@ def start_stress_test(args: dict[str, Any]) -> None:
babase.Call(bascenev1.new_host_session, FreeForAllSession),
),
)
_baclassic.set_stress_testing(True, args['player_count'])
babase.app.classic.stress_test_reset_timer = babase.AppTimer(
args['round_duration'], babase.Call(_reset_stress_test, args)
_baclassic.set_stress_testing(True, args.player_count, args.attract_mode)
babase.app.classic.stress_test_update_timer = babase.AppTimer(
args.round_duration, babase.Call(_reset_stress_test, args)
)
if args.attract_mode:
babase.app.classic.stress_test_update_timer_2 = babase.AppTimer(
0.48, babase.Call(_update_attract_mode_test, args), repeat=True
)
def _reset_stress_test(args: dict[str, Any]) -> None:
_baclassic.set_stress_testing(False, args['player_count'])
babase.screenmessage('Resetting stress test...')
def _update_attract_mode_test(args: _StressTestArgs) -> None:
if babase.get_input_idle_time() < 5.0:
_reset_stress_test(args)
def _reset_stress_test(args: _StressTestArgs) -> None:
_baclassic.set_stress_testing(False, args.player_count, False)
if not args.attract_mode:
babase.screenmessage('Resetting stress test...')
session = bascenev1.get_foreground_host_session()
assert session is not None
session.end()
babase.apptimer(1.0, babase.Call(start_stress_test, args))
assert babase.app.classic is not None
babase.app.classic.stress_test_update_timer = None
babase.app.classic.stress_test_update_timer_2 = None
# For regular stress tests we keep the party going. For attract-mode
# we just end back at the main menu. If things are idle there then
# we'll get sent back to a new stress test.
if not args.attract_mode:
babase.apptimer(1.0, babase.Call(_start_stress_test, args))
def run_gpu_benchmark() -> None:

View File

@ -69,7 +69,8 @@ class ClassicSubsystem(babase.AppSubsystem):
# Misc.
self.tips: list[str] = []
self.stress_test_reset_timer: babase.AppTimer | None = None
self.stress_test_update_timer: babase.AppTimer | None = None
self.stress_test_update_timer_2: babase.AppTimer | None = None
self.value_test_defaults: dict = {}
self.special_offer: dict | None = None
self.ping_thread_count = 0
@ -555,11 +556,18 @@ class ClassicSubsystem(babase.AppSubsystem):
playlist_name: str = '__default__',
player_count: int = 8,
round_duration: int = 30,
attract_mode: bool = False,
) -> None:
"""Run a stress test."""
from baclassic._benchmark import run_stress_test as run
run(playlist_type, playlist_name, player_count, round_duration)
run(
playlist_type=playlist_type,
playlist_name=playlist_name,
player_count=player_count,
round_duration=round_duration,
attract_mode=attract_mode,
)
def get_input_device_mapped_value(
self, device: bascenev1.InputDevice, name: str

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
TARGET_BALLISTICA_BUILD = 21743
TARGET_BALLISTICA_BUILD = 21756
TARGET_BALLISTICA_VERSION = '1.7.33'
@ -350,9 +350,15 @@ def _setup_paths(
# platforms where there is no write access to said built-in
# stuff.
check_dir = Path(user_python_dir, 'sys', TARGET_BALLISTICA_VERSION)
if check_dir.is_dir():
app_python_dir = str(check_dir)
is_user_app_python_dir = True
try:
if check_dir.is_dir():
app_python_dir = str(check_dir)
is_user_app_python_dir = True
except PermissionError:
logging.warning(
"PermissionError checking user-app-python-dir path '%s'.",
check_dir,
)
# Ok, now apply these to sys.path.

View File

@ -103,6 +103,7 @@ from _bascenev1 import (
host_scan_cycle,
InputDevice,
is_in_replay,
is_replay_paused,
ls_input_devices,
ls_objects,
Material,
@ -112,11 +113,13 @@ from _bascenev1 import (
newactivity,
newnode,
Node,
pause_replay,
printnodes,
protocol_version,
release_gamepad_input,
release_keyboard_input,
reset_random_player_names,
resume_replay,
broadcastmessage,
SessionData,
SessionPlayer,
@ -354,6 +357,7 @@ __all__ = [
'IntSetting',
'is_in_replay',
'is_point_in_box',
'is_replay_paused',
'JoinActivity',
'Level',
'Lobby',
@ -376,6 +380,7 @@ __all__ = [
'normalized_color',
'NotFoundError',
'OutOfBoundsMessage',
'pause_replay',
'PickedUpMessage',
'PickUpMessage',
'Player',
@ -396,6 +401,7 @@ __all__ = [
'release_gamepad_input',
'release_keyboard_input',
'reset_random_player_names',
'resume_replay',
'safecolor',
'screenmessage',
'SceneV1AppMode',

View File

@ -441,7 +441,7 @@ class Chooser:
# list might have changed.
input_device = self._sessionplayer.inputdevice
is_remote = input_device.is_remote_client
is_test_input = input_device.name.startswith('TestInput')
is_test_input = input_device.is_test_input
# Pull this player's list of unlocked characters.
if is_remote:

View File

@ -70,6 +70,10 @@ class MultiTeamSession(Session):
show_tutorial = cfg.get('Show Tutorial', True)
# Special case: don't show tutorial while stress testing.
if classic.stress_test_update_timer is not None:
show_tutorial = False
self._tutorial_activity_instance: bascenev1.Activity | None
if show_tutorial:
from bascenev1lib.tutorial import TutorialActivity

View File

@ -266,7 +266,7 @@ class Session:
# Limit player counts *unless* we're in a stress test.
if (
babase.app.classic is not None
and babase.app.classic.stress_test_reset_timer is None
and babase.app.classic.stress_test_update_timer is None
):
if len(self.sessionplayers) >= self.max_players >= 0:
# Print a rejection message *only* to the client trying to

View File

@ -67,7 +67,9 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
# On the first game, show the controls UI momentarily.
# (unless we're being run in co-op mode, in which case we leave
# it up to them)
if not isinstance(self.session, CoopSession):
if not isinstance(self.session, CoopSession) and getattr(
self, 'show_controls_guide', True
):
attrname = '_have_shown_ctrl_help_overlay'
if not getattr(self.session, attrname, False):
delay = 4.0

View File

@ -1145,14 +1145,12 @@ class Bomb(bs.Actor):
self.explode()
elif isinstance(msg, ImpactMessage):
self._handle_impact()
# Ok the logic below looks like it was backwards to me.
# Disabling for now; can bring back if need be.
# elif isinstance(msg, bs.PickedUpMessage):
# # Change our source to whoever just picked us up *only* if it
# # is None. This way we can get points for killing bots with their
# # own bombs. Hmm would there be a downside to this?
# if self._source_player is not None:
# self._source_player = msg.node.source_player
elif isinstance(msg, bs.PickedUpMessage):
# Change our source to whoever just picked us up *only* if it
# is None. This way we can get points for killing bots with their
# own bombs. Hmm would there be a downside to this?
if self._source_player is None:
self._source_player = msg.node.source_player
elif isinstance(msg, SplatMessage):
self._handle_splat()
elif isinstance(msg, bs.DroppedMessage):

View File

@ -42,6 +42,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._language: str | None = None
self._update_timer: bs.Timer | None = None
self._news: NewsDisplay | None = None
self._attract_mode_timer: bs.Timer | None = None
def on_transition_in(self) -> None:
# pylint: disable=too-many-locals
@ -83,7 +84,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
'scale': scale,
'position': (0, 10),
'vr_depth': -10,
'text': '\xa9 2011-2023 Eric Froemling',
'text': '\xa9 2011-2024 Eric Froemling',
},
)
)
@ -295,6 +296,10 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
if not (env.demo or env.arcade) and not app.ui_v1.use_toolbars:
self._news = NewsDisplay(self)
self._attract_mode_timer = bs.Timer(
3.12, self._update_attract_mode, repeat=True
)
# Bring up the last place we were, or start at the main menu otherwise.
with bs.ContextRef.empty():
from bauiv1lib import specialoffer
@ -387,7 +392,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bs.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition=None).get_root_widget(),
from_window=None,
from_window=False, # Disable check here.
)
# attempt to show any pending offers immediately.
@ -403,6 +408,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bui.apptimer(2.0, specialoffer.show_offer)
bui.apptimer(2.0, try_again)
app.classic.main_menu_did_initial_transition = True
def _update(self) -> None:
@ -836,6 +842,26 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bui.apptimer(0.5, _start_menu_music)
def _update_attract_mode(self) -> None:
if bui.app.classic is None:
return
if not bui.app.config.resolve('Show Demos When Idle'):
return
threshold = 20.0
# If we're idle *and* have been in this activity for that long,
# flip over to our cpu demo.
if bui.get_input_idle_time() > threshold and bs.time() > threshold:
bui.app.classic.run_stress_test(
playlist_type='Random',
playlist_name='__default__',
player_count=8,
round_duration=20,
attract_mode=True,
)
class NewsDisplay:
"""Wrangles news display."""

View File

@ -47,6 +47,7 @@ from babase import (
do_once,
fade_screen,
get_display_resolution,
get_input_idle_time,
get_ip_address_type,
get_low_level_config_value,
get_max_graphics_quality,
@ -156,6 +157,7 @@ __all__ = [
'do_once',
'fade_screen',
'get_display_resolution',
'get_input_idle_time',
'get_ip_address_type',
'get_low_level_config_value',
'get_max_graphics_quality',

View File

@ -431,13 +431,15 @@ class MainMenuWindow(bui.Window):
# media players but this works for now).
if bs.is_in_replay():
b_size = 50.0
b_buffer = 10.0
b_buffer_1 = 50.0
b_buffer_2 = 10.0
t_scale = 0.75
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
if uiscale is bui.UIScale.SMALL:
b_size *= 0.6
b_buffer *= 1.0
b_buffer_1 *= 0.8
b_buffer_2 *= 1.0
v_offs = -40
t_scale = 0.5
elif uiscale is bui.UIScale.MEDIUM:
@ -467,8 +469,8 @@ class MainMenuWindow(bui.Window):
btn = bui.buttonwidget(
parent=self._root_widget,
position=(
h - b_size - b_buffer,
v - b_size - b_buffer + v_offs,
h - b_size - b_buffer_1,
v - b_size - b_buffer_2 + v_offs,
),
button_type='square',
size=(b_size, b_size),
@ -481,8 +483,8 @@ class MainMenuWindow(bui.Window):
draw_controller=btn,
text='-',
position=(
h - b_size * 0.5 - b_buffer,
v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs,
h - b_size * 0.5 - b_buffer_1,
v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
),
h_align='center',
v_align='center',
@ -491,7 +493,7 @@ class MainMenuWindow(bui.Window):
)
btn = bui.buttonwidget(
parent=self._root_widget,
position=(h + b_buffer, v - b_size - b_buffer + v_offs),
position=(h + b_buffer_1, v - b_size - b_buffer_2 + v_offs),
button_type='square',
size=(b_size, b_size),
label='',
@ -503,14 +505,27 @@ class MainMenuWindow(bui.Window):
draw_controller=btn,
text='+',
position=(
h + b_size * 0.5 + b_buffer,
v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs,
h + b_size * 0.5 + b_buffer_1,
v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
),
h_align='center',
v_align='center',
size=(0, 0),
scale=3.0 * t_scale,
)
self._pause_resume_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(h - b_size * 0.5, v - b_size - b_buffer_2 + v_offs),
button_type='square',
size=(b_size, b_size),
label=bui.charstr(
bui.SpecialChar.PLAY_BUTTON
if bs.is_replay_paused()
else bui.SpecialChar.PAUSE_BUTTON
),
autoselect=True,
on_activate_call=bui.Call(self._pause_or_resume_replay),
)
def _refresh_not_in_game(
self, positions: list[tuple[float, float, float]]
@ -1034,6 +1049,20 @@ class MainMenuWindow(bui.Window):
),
)
def _pause_or_resume_replay(self) -> None:
if bs.is_replay_paused():
bs.resume_replay()
bui.buttonwidget(
edit=self._pause_resume_button,
label=bui.charstr(bui.SpecialChar.PAUSE_BUTTON),
)
else:
bs.pause_replay()
bui.buttonwidget(
edit=self._pause_resume_button,
label=bui.charstr(bui.SpecialChar.PLAY_BUTTON),
)
def _quit(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.confirm import QuitWindow
@ -1110,7 +1139,7 @@ class MainMenuWindow(bui.Window):
session = bs.get_foreground_host_session()
return getattr(session, 'benchmark_type', None) == 'cpu' or (
bui.app.classic is not None
and bui.app.classic.stress_test_reset_timer is not None
and bui.app.classic.stress_test_update_timer is not None
)
def _confirm_end_game(self) -> None:

View File

@ -94,7 +94,7 @@ class AdvancedSettingsWindow(bui.Window):
self._scroll_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - 115.0
self._sub_width = self._scroll_width * 0.95
self._sub_height = 766.0
self._sub_height = 808.0
if self._show_always_use_internal_keyboard:
self._sub_height += 62
@ -489,6 +489,17 @@ class AdvancedSettingsWindow(bui.Window):
maxwidth=430,
)
v -= 42
self._show_demos_when_idle_check_box = ConfigCheckBox(
parent=self._subcontainer,
position=(50, v),
size=(self._sub_width - 100, 30),
configkey='Show Demos When Idle',
displayname=bui.Lstr(resource=f'{self._r}.showDemosWhenIdleText'),
scale=1.0,
maxwidth=430,
)
v -= 42
self._disable_camera_shake_check_box = ConfigCheckBox(
parent=self._subcontainer,
@ -575,12 +586,18 @@ class AdvancedSettingsWindow(bui.Window):
up_widget=self._always_use_internal_keyboard_check_box.widget,
)
else:
bui.widget(
edit=self._modding_guide_button,
up_widget=self._kick_idle_players_check_box.widget,
# ew.
next_widget_up = (
self._disable_gyro_check_box.widget
if self._disable_gyro_check_box is not None
else self._disable_camera_shake_check_box.widget
)
bui.widget(
edit=self._kick_idle_players_check_box.widget,
edit=self._modding_guide_button,
up_widget=next_widget_up,
)
bui.widget(
edit=next_widget_up,
down_widget=self._modding_guide_button,
)
@ -803,6 +820,8 @@ class AdvancedSettingsWindow(bui.Window):
sel_name = 'Benchmarks'
elif sel == self._kick_idle_players_check_box.widget:
sel_name = 'KickIdlePlayers'
elif sel == self._show_demos_when_idle_check_box.widget:
sel_name = 'ShowDemosWhenIdle'
elif sel == self._show_game_ping_check_box.widget:
sel_name = 'ShowPing'
elif sel == self._disable_camera_shake_check_box.widget:
@ -870,6 +889,8 @@ class AdvancedSettingsWindow(bui.Window):
sel = self._benchmarks_button
elif sel_name == 'KickIdlePlayers':
sel = self._kick_idle_players_check_box.widget
elif sel_name == 'ShowDemosWhenIdle':
sel = self._show_demos_when_idle_check_box.widget
elif sel_name == 'ShowPing':
sel = self._show_game_ping_check_box.widget
elif sel_name == 'DisableCameraShake':

View File

@ -4,12 +4,8 @@
#include "ballistica/base/graphics/renderer/renderer.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/networking/network_reader.h"
#include "ballistica/base/networking/networking.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h"
namespace ballistica::base {

View File

@ -1238,6 +1238,13 @@ void Assets::InitSpecialChars() {
special_char_strings_[SpecialChar::kPlayPauseButton] = "\xee\x80\x8E";
special_char_strings_[SpecialChar::kFastForwardButton] = "\xee\x80\x8F";
special_char_strings_[SpecialChar::kDpadCenterButton] = "\xee\x80\x90";
special_char_strings_[SpecialChar::kPlayStationCrossButton] = "\xee\x80\x91";
special_char_strings_[SpecialChar::kPlayStationCircleButton] = "\xee\x80\x92";
special_char_strings_[SpecialChar::kPlayStationTriangleButton] =
"\xee\x80\x93";
special_char_strings_[SpecialChar::kPlayStationSquareButton] = "\xee\x80\x94";
special_char_strings_[SpecialChar::kPlayButton] = "\xee\x80\x95";
special_char_strings_[SpecialChar::kPauseButton] = "\xee\x80\x96";
special_char_strings_[SpecialChar::kOuyaButtonO] = "\xee\x80\x99";
special_char_strings_[SpecialChar::kOuyaButtonU] = "\xee\x80\x9A";
@ -1610,10 +1617,6 @@ auto Assets::SysTexture(SysTextureID id) -> TextureAsset* {
assert(asset_loads_allowed_ && sys_assets_loaded_);
assert(g_base->InLogicThread());
assert(static_cast<size_t>(id) < system_textures_.size());
// TEMP - tracking down some crashes in the wild.
if (!sys_assets_loaded_) {
FatalError("SysTexture called before sys assets loaded.");
}
return system_textures_[static_cast<int>(id)].Get();
}
@ -1621,21 +1624,17 @@ auto Assets::SysCubeMapTexture(SysCubeMapTextureID id) -> TextureAsset* {
assert(asset_loads_allowed_ && sys_assets_loaded_);
assert(g_base->InLogicThread());
assert(static_cast<size_t>(id) < system_cube_map_textures_.size());
// TEMP - tracking down some crashes in the wild.
if (!sys_assets_loaded_) {
FatalError("SysCubeMapTexture called before sys assets loaded.");
}
return system_cube_map_textures_[static_cast<int>(id)].Get();
}
auto Assets::IsValidSysSound(SysSoundID id) -> bool {
return static_cast<size_t>(id) < system_sounds_.size();
}
auto Assets::SysSound(SysSoundID id) -> SoundAsset* {
assert(asset_loads_allowed_ && sys_assets_loaded_);
assert(g_base->InLogicThread());
assert(static_cast<size_t>(id) < system_sounds_.size());
// TEMP - tracking down some crashes in the wild.
if (!sys_assets_loaded_) {
FatalError("SysSound called before sys assets loaded.");
}
assert(IsValidSysSound(id));
return system_sounds_[static_cast<int>(id)].Get();
}
@ -1643,11 +1642,6 @@ auto Assets::SysMesh(SysMeshID id) -> MeshAsset* {
assert(asset_loads_allowed_ && sys_assets_loaded_);
assert(g_base->InLogicThread());
assert(static_cast<size_t>(id) < system_meshes_.size());
// TEMP - tracking down some crashes in the wild.
if (!sys_assets_loaded_) {
FatalError("SysMesh called before sys assets loaded.");
}
return system_meshes_[static_cast<int>(id)].Get();
}

View File

@ -76,6 +76,7 @@ class Assets {
// available.
auto SysTexture(SysTextureID id) -> TextureAsset*;
auto SysCubeMapTexture(SysCubeMapTextureID id) -> TextureAsset*;
auto IsValidSysSound(SysSoundID id) -> bool;
auto SysSound(SysSoundID id) -> SoundAsset*;
auto SysMesh(SysMeshID id) -> MeshAsset*;

View File

@ -2,6 +2,7 @@
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/audio/audio_server.h"
#include "ballistica/base/audio/audio_source.h"
@ -159,6 +160,32 @@ auto Audio::ShouldPlay(SoundAsset* sound) -> bool {
return (time - sound->last_play_time() > 50);
}
auto Audio::SafePlaySysSound(SysSoundID sound_id) -> std::optional<uint32_t> {
// Save some time on headless.
if (g_core->HeadlessMode()) {
return {};
}
if (!g_base->InLogicThread()) {
Log(LogLevel::kError,
"Audio::SafePlaySysSound called from non-logic thread. id="
+ std::to_string(static_cast<int>(sound_id)));
return {};
}
if (!g_base->assets->sys_assets_loaded()) {
Log(LogLevel::kWarning,
"Audio::SafePlaySysSound called before sys assets loaded. id="
+ std::to_string(static_cast<int>(sound_id)));
return {};
}
if (!g_base->assets->IsValidSysSound(sound_id)) {
Log(LogLevel::kWarning,
"Audio::SafePlaySysSound called with invalid sound_id. id="
+ std::to_string(static_cast<int>(sound_id)));
return {};
}
return PlaySound(g_base->assets->SysSound(sound_id));
}
auto Audio::PlaySound(SoundAsset* sound, float volume)
-> std::optional<uint32_t> {
assert(g_core);

View File

@ -61,6 +61,10 @@ class Audio {
auto PlaySoundAtPosition(SoundAsset* sound, float volume, float x, float y,
float z) -> std::optional<uint32_t>;
/// Load and play a sys sound if possible. Gracefully fail if not
/// (possibly logging warnings or errors).
auto SafePlaySysSound(SysSoundID sound_id) -> std::optional<uint32_t>;
/// Call this if you want to prevent repeated plays of the same sound. It'll
/// tell you if the sound has been played recently. The one-shot sound-play
/// functions use this under the hood. (PlaySound, PlaySoundAtPosition).

View File

@ -16,12 +16,10 @@
#include "ballistica/base/networking/network_reader.h"
#include "ballistica/base/networking/network_writer.h"
#include "ballistica/base/networking/networking.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/python/class/python_class_feature_set_data.h"
#include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/support/app_timer.h"
#include "ballistica/base/support/base_build_switches.h"
#include "ballistica/base/support/huffman.h"
#include "ballistica/base/support/plus_soft.h"

View File

@ -240,7 +240,7 @@ class RenderComponent {
}
protected:
enum class State { kConfiguring, kDrawing, kSubmitted };
enum class State : uint8_t { kConfiguring, kDrawing, kSubmitted };
void EnsureConfiguring() {
if (state_ != State::kConfiguring) {
#if BA_DEBUG_BUILD
@ -331,8 +331,8 @@ class RenderComponent {
// stream.
virtual void WriteConfig() = 0;
RenderCommandBuffer* cmd_buffer_{};
State state_{State::kConfiguring};
RenderCommandBuffer* cmd_buffer_{};
RenderPass* pass_;
public:

View File

@ -19,7 +19,7 @@ class RendererGL::MeshAssetDataGL : public MeshAssetRendererData {
enum BufferType { kVertices, kIndices, kBufferCount };
MeshAssetDataGL(const MeshAsset& model, RendererGL* renderer)
: renderer_(renderer), fake_vao_(nullptr) {
: renderer_(renderer) {
#if BA_DEBUG_BUILD
name_ = model.GetName();
#endif
@ -150,7 +150,7 @@ class RendererGL::MeshAssetDataGL : public MeshAssetRendererData {
GLuint index_type_{};
GLuint vao_{};
GLuint vbos_[kBufferCount]{};
FakeVertexArrayObject* fake_vao_{};
// FakeVertexArrayObject* fake_vao_{};
};
} // namespace ballistica::base

View File

@ -202,7 +202,6 @@ class RendererGL::MeshDataGL : public MeshRendererData {
RendererGL* renderer_{};
uint32_t elem_count_{};
GLuint index_type_{GL_UNSIGNED_SHORT};
FakeVertexArrayObject* fake_vao_{};
};
} // namespace ballistica::base

View File

@ -2339,12 +2339,11 @@ void RendererGL::SetDrawAtEqualDepth(bool enable) {
}
}
// FIXME FIXME FIXME FIXME
// turning off GL_DEPTH_TEST also disables
// depth writing which we may not want.
// It sounds like the proper thing
// to do in that case is leave GL_DEPTH_TEST on
// and set glDepthFunc(GL_ALWAYS)
// FIXME FIXME FIXME FIXME:
//
// Turning off GL_DEPTH_TEST also disables depth writing which we may not
// want. It sounds like the proper thing to do in that case is leave
// GL_DEPTH_TEST on and set glDepthFunc(GL_ALWAYS).
void RendererGL::SetDepthTesting(bool enable) {
if (enable != depth_testing_enabled_) {

View File

@ -39,7 +39,6 @@ namespace ballistica::base {
constexpr int kMaxGLTexUnitsUsed = 5;
class RendererGL : public Renderer {
class FakeVertexArrayObject;
class TextureDataGL;
class MeshAssetDataGL;
class MeshDataGL;

View File

@ -9,7 +9,8 @@
namespace ballistica::base {
// A dynamically defined mesh (unlike a mesh asset which is completely static).
/// A dynamically defined mesh (unlike a mesh asset which is completely
/// static).
class Mesh : public Object {
public:
auto type() const -> MeshDataType { return type_; }
@ -17,7 +18,7 @@ class Mesh : public Object {
return mesh_data_client_handle_;
}
// Return whether it is safe to attempt drawing with present data.
/// Return whether it is safe to attempt drawing with present data.
virtual auto IsValid() const -> bool = 0;
auto last_frame_def_num() const -> int64_t { return last_frame_def_num_; }
void set_last_frame_def_num(int64_t f) { last_frame_def_num_ = f; }
@ -31,14 +32,14 @@ class Mesh : public Object {
}
private:
int64_t last_frame_def_num_{};
MeshDataType type_{};
// Renderer data for this mesh. We keep this as a shared pointer
// so that frame_defs or other things using this mesh can keep it alive
// even if we go away.
Object::Ref<MeshDataClientHandle> mesh_data_client_handle_;
bool valid_{};
int64_t last_frame_def_num_{};
/// Renderer data for this mesh. We keep this as a shared pointer so that
/// frame_defs or other things using this mesh can keep it alive even if
/// we go away.
Object::Ref<MeshDataClientHandle> mesh_data_client_handle_;
};
} // namespace ballistica::base

View File

@ -9,8 +9,8 @@
namespace ballistica::base {
// The portion of a mesh that is owned by the graphics thread.
// This contains the renderer-specific data (GL buffers, etc)
/// The portion of a mesh that is owned by the graphics server. This
/// contains the renderer-specific data (GL buffers, etc).
class MeshData {
public:
MeshData(MeshDataType type, MeshDrawType draw_type)

View File

@ -15,8 +15,8 @@ namespace ballistica::base {
// shadow pass, a window, etc.
class RenderPass {
public:
enum class ReflectionSubPass { kRegular, kMirrored };
enum class Type {
enum class ReflectionSubPass : uint8_t { kRegular, kMirrored };
enum class Type : uint8_t {
// A pass whose results are projected onto the scene for lighting and
// shadow effects. Values lighter than kShadowNeutral will show up as
// light and darker than neutral will show up as shadowing. This version
@ -152,40 +152,43 @@ class RenderPass {
private:
void SetFrustum(float near_val, float far_val);
bool cam_use_fov_tangents_{};
bool floor_reflection_{};
Type type_{};
float cam_near_clip_{};
float cam_far_clip_{};
float cam_fov_x_{};
float cam_fov_y_{};
float physical_width_{};
float physical_height_{};
float virtual_width_{};
float virtual_height_{};
// We can now alternately supply left, right, top, bottom frustum tangents.
float cam_fov_l_tan_{1.0f};
float cam_fov_r_tan_{1.0f};
float cam_fov_t_tan_{1.0f};
float cam_fov_b_tan_{1.0f};
Vector3f cam_pos_{0.0f, 0.0f, 0.0f};
Vector3f cam_target_{0.0f, 0.0f, 0.0f};
Vector3f cam_up_{0.0f, 0.0f, 0.0f};
Matrix44f tex_project_matrix_{kMatrix44fIdentity};
Matrix44f projection_matrix_{kMatrix44fIdentity};
Matrix44f model_view_matrix_{kMatrix44fIdentity};
Matrix44f model_view_projection_matrix_{kMatrix44fIdentity};
FrameDef* frame_def_{};
std::vector<Vector3f> cam_area_of_interest_points_;
// Our pass holds sets of draw-commands bucketed by section and
// component-type.
std::unique_ptr<RenderCommandBuffer>
commands_[static_cast<int>(ShadingType::kCount)];
std::unique_ptr<RenderCommandBuffer> commands_flat_;
std::unique_ptr<RenderCommandBuffer> commands_flat_transparent_;
Vector3f cam_pos_{0.0f, 0.0f, 0.0f};
Vector3f cam_target_{0.0f, 0.0f, 0.0f};
Vector3f cam_up_{0.0f, 0.0f, 0.0f};
float cam_near_clip_{};
float cam_far_clip_{};
float cam_fov_x_{};
float cam_fov_y_{};
// We can now alternately supply left, right, top, bottom frustum tangents.
bool cam_use_fov_tangents_{};
float cam_fov_l_tan_{1.0f};
float cam_fov_r_tan_{1.0f};
float cam_fov_t_tan_{1.0f};
float cam_fov_b_tan_{1.0f};
std::vector<Vector3f> cam_area_of_interest_points_;
Type type_{};
// For lights/shadows.
Matrix44f tex_project_matrix_{kMatrix44fIdentity};
Matrix44f projection_matrix_{kMatrix44fIdentity};
Matrix44f model_view_matrix_{kMatrix44fIdentity};
Matrix44f model_view_projection_matrix_{kMatrix44fIdentity};
bool floor_reflection_{};
FrameDef* frame_def_{};
float physical_width_{};
float physical_height_{};
float virtual_width_{};
float virtual_height_{};
};
} // namespace ballistica::base

View File

@ -39,6 +39,7 @@ auto RenderTarget::GetScissorX(float x) const -> float {
}
}
}
auto RenderTarget::GetScissorY(float y) const -> float {
assert(g_core);
if (g_core->vr_mode()) {
@ -60,6 +61,7 @@ auto RenderTarget::GetScissorY(float y) const -> float {
}
}
}
auto RenderTarget::GetScissorScaleX() const -> float {
assert(g_core);
if (g_core->vr_mode()) {

View File

@ -14,7 +14,7 @@ class RenderTarget : public Object {
auto GetThreadOwnership() const -> ThreadOwnership override {
return ThreadOwnership::kGraphicsContext;
}
enum class Type { kScreen, kFramebuffer };
enum class Type : uint8_t { kScreen, kFramebuffer };
explicit RenderTarget(Type type);
~RenderTarget() override;
@ -36,10 +36,10 @@ class RenderTarget : public Object {
auto GetScissorY(float y) const -> float;
protected:
float physical_width_{};
float physical_height_{};
bool depth_{};
Type type_{};
float physical_width_{};
float physical_height_{};
};
} // namespace ballistica::base

View File

@ -193,6 +193,7 @@ class Renderer {
virtual void RenderFrameDefEnd() = 0;
virtual void CardboardDisableScissor() = 0;
virtual void CardboardEnableScissor() = 0;
#if BA_VR_BUILD
void VRTransformToRightHand();
void VRTransformToLeftHand();
@ -207,30 +208,24 @@ class Renderer {
void DrawWorldToCameraBuffer(FrameDef* frame_def);
void UpdatePixelScaleAndBackingBuffer(FrameDef* frame_def);
void UpdateCameraRenderTargets(FrameDef* frame_def);
// #if BA_OSTYPE_MACOS && BA_SDL_BUILD && !BA_SDL2_BUILD
// void HandleFunkyMacGammaIssue(FrameDef* frame_def);
// #endif
void LoadMedia(FrameDef* frame_def);
void UpdateDOFParams(FrameDef* frame_def);
#if BA_VR_BUILD
void VRPreprocess(FrameDef* frame_def);
void VRUpdateForEyeRender(FrameDef* frame_def);
void VRDrawOverlayFlatPass(FrameDef* frame_def);
#endif // BA_VR_BUILD
#if BA_VR_BUILD
bool vr_use_fov_tangents_{};
// Raw values from vr system.
VRHandsState vr_raw_hands_state_{};
float vr_raw_head_tx_{};
float vr_raw_head_ty_{};
float vr_raw_head_tz_{};
float vr_raw_head_yaw_{};
float vr_raw_head_pitch_{};
float vr_raw_head_roll_{};
// Final game-space transforms.
Matrix44f vr_base_transform_{kMatrix44fIdentity};
Matrix44f vr_transform_right_hand_{kMatrix44fIdentity};
Matrix44f vr_transform_left_hand_{kMatrix44fIdentity};
Matrix44f vr_transform_head_{kMatrix44fIdentity};
// Values for current eye render.
bool vr_use_fov_tangents_{};
int vr_eye_{};
int vr_viewport_x_{};
int vr_viewport_y_{};
float vr_fov_l_tan_{1.0f};
float vr_fov_r_tan_{1.0f};
float vr_fov_b_tan_{1.0f};
@ -240,37 +235,59 @@ class Renderer {
float vr_eye_x_{};
float vr_eye_y_{};
float vr_eye_z_{};
int vr_eye_{};
float vr_eye_yaw_{};
float vr_eye_pitch_{};
float vr_eye_roll_{};
int vr_viewport_x_{};
int vr_viewport_y_{};
float vr_raw_head_tx_{};
float vr_raw_head_ty_{};
float vr_raw_head_tz_{};
float vr_raw_head_yaw_{};
float vr_raw_head_pitch_{};
float vr_raw_head_roll_{};
Matrix44f vr_base_transform_{kMatrix44fIdentity};
Matrix44f vr_transform_right_hand_{kMatrix44fIdentity};
Matrix44f vr_transform_left_hand_{kMatrix44fIdentity};
Matrix44f vr_transform_head_{kMatrix44fIdentity};
#endif // BA_VR_BUILD
// The *actual* current quality (set based on the currently-rendering
// frame_def)
GraphicsQuality last_render_quality_{GraphicsQuality::kLow};
bool debug_draw_mode_{};
bool screen_size_dirty_{};
bool msaa_enabled_dirty_{};
millisecs_t dof_update_time_{};
bool dof_delay_{true};
bool drawing_reflection_{};
bool shadow_ortho_{};
int last_commands_buffer_size_{};
int last_f_vals_buffer_size_{};
int last_i_vals_buffer_size_{};
int last_meshes_buffer_size_{};
int last_textures_buffer_size_{};
int frames_rendered_count_{};
int blur_res_count_{};
int shadow_res_{-1};
float dof_near_smoothed_{};
float dof_far_smoothed_{};
bool drawing_reflection_{};
int blur_res_count_{};
float light_pitch_{};
float light_heading_{};
float light_tz_{-22.0f};
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
float shadow_scale_x_{1.0f};
float shadow_scale_z_{1.0f};
bool shadow_ortho_{};
float screen_gamma_{1.0f};
float pixel_scale_requested_{1.0f};
float pixel_scale_{1.0f};
millisecs_t last_screen_gamma_update_time_{};
millisecs_t dof_update_time_{};
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
Vector3f tint_{1.0f, 1.0f, 1.0f};
Vector3f ambient_color_{1.0f, 1.0f, 1.0f};
Vector3f vignette_outer_{0.0f, 0.0f, 0.0f};
Vector3f vignette_inner_{1.0f, 1.0f, 1.0f};
int shadow_res_{-1};
float screen_gamma_{1.0f};
float pixel_scale_requested_{1.0f};
float pixel_scale_{1.0f};
Object::Ref<RenderTarget> screen_render_target_;
Object::Ref<RenderTarget> backing_render_target_;
Object::Ref<RenderTarget> camera_render_target_;
@ -278,18 +295,6 @@ class Renderer {
Object::Ref<RenderTarget> light_render_target_;
Object::Ref<RenderTarget> light_shadow_render_target_;
Object::Ref<RenderTarget> vr_overlay_flat_render_target_;
millisecs_t last_screen_gamma_update_time_{};
int last_commands_buffer_size_{};
int last_f_vals_buffer_size_{};
int last_i_vals_buffer_size_{};
int last_meshes_buffer_size_{};
int last_textures_buffer_size_{};
bool debug_draw_mode_{};
int frames_rendered_count_{};
// The *actual* current quality (set based on the
// currently-rendering frame_def)
GraphicsQuality last_render_quality_{GraphicsQuality::kLow};
};
} // namespace ballistica::base

View File

@ -52,9 +52,9 @@ InputDevice::~InputDevice() { assert(g_base->InLogicThread()); }
// control something please.
void InputDevice::RequestPlayer() {
assert(g_base->InLogicThread());
// Make note that we're being used in some way.
last_input_time_millisecs_ =
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
UpdateLastActiveTime();
delegate_->RequestPlayer();
}
@ -69,11 +69,19 @@ auto InputDevice::AttachedToPlayer() const -> bool {
void InputDevice::DetachFromPlayer() { delegate_->DetachFromPlayer(); }
void InputDevice::UpdateLastInputTime() {
// Keep our own individual time, and also let the overall input system
// know something happened.
last_input_time_millisecs_ =
void InputDevice::UpdateLastActiveTime() {
// Special case: in attract-mode, prevent our virtual test devices from
// affecting input last-active times otherwise it'll kick us out of
// attract mode.
if (allow_input_in_attract_mode_ && g_base->input->attract_mode()) {
return;
}
// Mark active time on this specific device.
last_active_time_millisecs_ =
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
// Mark input in general as active also.
g_base->input->MarkInputActive();
}
@ -81,7 +89,7 @@ void InputDevice::InputCommand(InputType type, float value) {
assert(g_base->InLogicThread());
// Make note that we're being used in some way.
UpdateLastInputTime();
UpdateLastActiveTime();
delegate_->InputCommand(type, value);
}

View File

@ -93,8 +93,8 @@ class InputDevice : public Object {
/// that button activates default widgets (will cause a start icon to show up
/// on them).
virtual auto start_button_activates_default_widget() -> bool { return false; }
auto last_input_time_millisecs() const -> millisecs_t {
return last_input_time_millisecs_;
auto last_active_time_millisecs() const -> millisecs_t {
return last_active_time_millisecs_;
}
virtual auto ShouldBeHiddenFromUser() -> bool;
@ -117,7 +117,7 @@ class InputDevice : public Object {
/// been added to the input-device list, have a valid ID, name, etc.
virtual void OnAdded() {}
void UpdateLastInputTime();
void UpdateLastActiveTime();
auto delegate() -> InputDeviceDelegate& {
// TEMP - Tracking down a crash in the wild.
@ -136,18 +136,27 @@ class InputDevice : public Object {
auto custom_default_player_name() const -> std::string {
return custom_default_player_name_;
}
void set_custom_default_player_name(const std::string& val) {
custom_default_player_name_ = val;
}
auto allow_input_in_attract_mode() const {
return allow_input_in_attract_mode_;
}
void set_allow_input_in_attract_mode(bool allow) {
allow_input_in_attract_mode_ = allow;
}
private:
Object::Ref<InputDeviceDelegate> delegate_;
// note: this is in base-net-time
millisecs_t last_input_time_millisecs_{};
millisecs_t last_active_time_millisecs_{};
int index_{-1}; // Our overall device index.
int number_{-1}; // Our type-specific number.
bool allow_input_in_attract_mode_{};
std::string custom_default_player_name_;

View File

@ -44,10 +44,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
analog_calibration_val = 0.6f;
}
if (custom_device_name == "TestInput") {
is_test_input_ = true;
}
sdl_joystick_id_ = sdl_joystick_id;
// Non-negative values here mean its an SDL joystick.
@ -799,7 +795,9 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
// Anything that would go to ui also counts to mark us as 'recently-used'.
if (would_go_to_ui) {
UpdateLastInputTime();
if (!(allow_input_in_attract_mode() && g_base->input->attract_mode())) {
UpdateLastActiveTime();
}
}
if (would_go_to_ui && g_base->ui->GetWidgetForInput(this)) {

View File

@ -59,6 +59,8 @@ class JoystickInput : public InputDevice {
auto IsUIOnly() -> bool override { return ui_only_; }
void set_is_test_input(bool val) { is_test_input_ = val; }
auto IsTestInput() -> bool override { return is_test_input_; }
auto IsRemoteApp() -> bool override { return is_remote_app_; }
auto IsMFiController() -> bool override { return is_mfi_controller_; }

View File

@ -3,7 +3,6 @@
#include "ballistica/base/input/device/keyboard_input.h"
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/support/classic_soft.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h"

View File

@ -4,16 +4,23 @@
#include "ballistica/base/input/device/joystick_input.h"
#include "ballistica/base/input/input.h"
#include "ballistica/core/platform/support/min_sdl.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/math/random.h"
namespace ballistica::base {
TestInput::TestInput() {
// In attract-mode (pretty demos) we want this to look more like
// real people connecting to the game, so just say 'Controller'.
const char* device_name =
g_base->input->attract_mode() ? "Controller" : "TestInput";
joystick_ = Object::NewDeferred<JoystickInput>(-1, // not an sdl joystick
"TestInput", // device name
device_name, // device name
false, // allow configuring?
false); // calibrate?;
joystick_->set_allow_input_in_attract_mode(true);
joystick_->set_is_test_input(true);
g_base->input->PushAddInputDeviceCall(joystick_, true);
}
@ -56,6 +63,11 @@ void TestInput::Process(millisecs_t time) {
return;
}
// Do nothing while any UI is up.
if (g_base->ui->MainMenuVisible()) {
return;
}
float r = RandomFloat();
SDL_Event e;

View File

@ -8,7 +8,6 @@
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/macros.h"
namespace ballistica::base {

View File

@ -170,9 +170,6 @@ void Input::AnnounceConnects_() {
} else {
// If there's been several connected, just give a number.
if (newly_connected_controllers_.size() > 1) {
for (auto&& s : newly_connected_controllers_) {
Log(LogLevel::kInfo, "GOT CONTROLLER " + s);
}
std::string s =
g_base->assets->GetResourceString("controllersConnectedText");
Utils::StringReplaceOne(
@ -187,7 +184,7 @@ void Input::AnnounceConnects_() {
ScreenMessage(s);
}
if (g_base->assets->sys_assets_loaded()) {
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kGunCock));
g_base->audio->SafePlaySysSound(SysSoundID::kGunCock);
}
}
newly_connected_controllers_.clear();
@ -210,7 +207,7 @@ void Input::AnnounceDisconnects_() {
ScreenMessage(s);
}
if (g_base->assets->sys_assets_loaded()) {
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kCorkPop));
g_base->audio->SafePlaySysSound(SysSoundID::kCorkPop);
}
newly_disconnected_controllers_.clear();
@ -392,9 +389,9 @@ void Input::UpdateInputDeviceCounts_() {
// just due to those)
if (input_device.Exists()
&& ((*input_device).IsTouchScreen() || (*input_device).IsKeyboard()
|| ((*input_device).last_input_time_millisecs() != 0
|| ((*input_device).last_active_time_millisecs() != 0
&& current_time_millisecs
- (*input_device).last_input_time_millisecs()
- (*input_device).last_active_time_millisecs()
< 60000))) {
total++;
if (!(*input_device).IsTouchScreen()) {
@ -441,9 +438,9 @@ auto Input::GetLocalActiveInputDeviceCount() -> int {
if (input_device.Exists() && !input_device->IsKeyboard()
&& !input_device->IsTouchScreen() && !input_device->IsUIOnly()
&& input_device->IsLocal()
&& (input_device->last_input_time_millisecs() != 0
&& (input_device->last_active_time_millisecs() != 0
&& current_time_millisecs
- input_device->last_input_time_millisecs()
- input_device->last_active_time_millisecs()
< 60000)) {
count++;
}
@ -639,8 +636,9 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) {
permanent ? "permanent unlock: "
: "temp unlock: " + label + " time "
+ std::to_string(g_core->GetAppTimeMillisecs()));
while (recent_input_locks_unlocks_.size() > 10)
while (recent_input_locks_unlocks_.size() > 10) {
recent_input_locks_unlocks_.pop_front();
}
if (permanent) {
input_lock_count_permanent_--;
@ -722,15 +720,15 @@ void Input::PrintLockLabels_() {
void Input::PushTextInputEvent(const std::string& text) {
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall([this, text] {
// Mark as active even if input is locked.
MarkInputActive();
// If the app doesn't want direct text input right now, ignore.
if (!g_base->app_adapter->HasDirectKeyboardInput()) {
if (IsInputLocked()) {
return;
}
// Ignore if input is locked.
if (IsInputLocked()) {
// If the app doesn't want direct text input right now, ignore.
if (!g_base->app_adapter->HasDirectKeyboardInput()) {
return;
}
@ -795,16 +793,14 @@ void Input::HandleJoystickEvent_(const SDL_Event& event,
if (ShouldCompletelyIgnoreInputDevice(input_device)) {
return;
}
if (IsInputLocked()) {
// Mark as active even if input is locked.
input_device->UpdateLastActiveTime();
if (IsInputLocked(input_device)) {
return;
}
// Make note that we're not idle.
MarkInputActive();
// And that this particular device isn't idle either.
input_device->UpdateLastInputTime();
// If someone is capturing these events, give them a crack at it.
if (joystick_input_capture_) {
if (joystick_input_capture_(event, input_device)) {
@ -905,9 +901,9 @@ void Input::HandleKeyReleaseSimple_(int keycode) {
void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
assert(g_base->InLogicThread());
// Mark as active even if input is locked.
MarkInputActive();
// Ignore all key presses if input is locked.
if (IsInputLocked()) {
return;
}
@ -1180,8 +1176,9 @@ void Input::PushMouseScrollEvent(const Vector2f& amount) {
void Input::HandleMouseScroll_(const Vector2f& amount) {
assert(g_base->InLogicThread());
// If input is locked, allow it to mark us active but nothing more.
// Mark as active even if input is locked.
MarkInputActive();
if (IsInputLocked()) {
return;
}
@ -1217,8 +1214,9 @@ void Input::PushSmoothMouseScrollEvent(const Vector2f& velocity,
void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) {
assert(g_base->InLogicThread());
// If input is locked, allow it to mark us active but nothing more.
// Mark as active even if input is locked.
MarkInputActive();
if (IsInputLocked()) {
return;
}
@ -1259,6 +1257,7 @@ void Input::HandleMouseMotion_(const Vector2f& position) {
assert(g_base);
assert(g_base->InLogicThread());
// Mark as active even if input is locked.
MarkInputActive();
if (IsInputLocked()) {
@ -1309,6 +1308,7 @@ void Input::HandleMouseDown_(int button, const Vector2f& position) {
assert(g_base);
assert(g_base->InLogicThread());
// Mark as active even if input is locked.
MarkInputActive();
if (IsInputLocked()) {
@ -1416,12 +1416,13 @@ void Input::HandleTouchEvent_(const TouchEvent& e) {
assert(g_base->InLogicThread());
assert(g_base->graphics);
// Mark as active even if input is locked.
MarkInputActive();
if (IsInputLocked()) {
return;
}
MarkInputActive();
if (g_buildconfig.ostype_ios_tvos()) {
printf("FIXME: update touch handling\n");
}
@ -1564,4 +1565,11 @@ void Input::LsInputDevices() {
Log(LogLevel::kInfo, out);
}
auto Input::ShouldAllowInputInAttractMode_(InputDevice* device) const -> bool {
if (device == nullptr) {
return false;
}
return device->allow_input_in_attract_mode();
}
} // namespace ballistica::base

View File

@ -10,6 +10,7 @@
#include <vector>
#include "ballistica/base/base.h"
#include "ballistica/shared/foundation/macros.h"
#include "ballistica/shared/foundation/object.h"
#include "ballistica/shared/foundation/types.h"
@ -62,7 +63,14 @@ class Input {
void Reset();
void LockAllInput(bool permanent, const std::string& label);
void UnlockAllInput(bool permanent, const std::string& label);
auto IsInputLocked() const -> bool {
auto IsInputLocked(InputDevice* device = nullptr) const -> bool {
// Special case; in attract-mode we ignore all input except our
// dummy controllers.
if (attract_mode_) {
if (!ShouldAllowInputInAttractMode_(device)) {
return true;
}
}
return input_lock_count_temp_ > 0 || input_lock_count_permanent_ > 0;
}
auto cursor_pos_x() const -> float { return cursor_pos_x_; }
@ -92,12 +100,7 @@ class Input {
}
void Draw(FrameDef* frame_def);
// Get the total idle time for the system.
// FIXME - should better coordinate this with InputDevice::getLastUsedTime().
// auto GetIdleTime() const -> millisecs_t;
// Should be called whenever user-input of some form comes through.
// void ResetIdleTime() { last_input_time_ = GetAppTimeMillisecs(); }
/// Should be called whenever user-input of some form comes through.
auto MarkInputActive() { input_active_ = true; }
// returns true if more than one non-keyboard device has been active recently
@ -153,7 +156,11 @@ class Input {
void ReleaseJoystickInput();
void RebuildInputDeviceDelegates();
auto attract_mode() const { return attract_mode_; }
void set_attract_mode(bool val) { attract_mode_ = val; }
private:
auto ShouldAllowInputInAttractMode_(InputDevice* device) const -> bool;
void UpdateInputDeviceCounts_();
auto GetNewNumberedIdentifier_(const std::string& name,
const std::string& identifier) -> int;
@ -183,8 +190,9 @@ class Input {
int max_controller_count_so_far_{};
int local_active_input_device_count_{};
int mouse_move_count_{};
int input_lock_count_temp_{};
int input_lock_count_permanent_{};
int8_t input_lock_count_temp_{};
int8_t input_lock_count_permanent_{};
bool attract_mode_{};
bool input_active_{};
bool have_button_using_inputs_{};
bool have_start_activated_default_button_inputs_{};

View File

@ -166,10 +166,8 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
g_base->graphics->screenmessages->AddScreenMessage(
s, Vector3f(1, 1, 1));
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kCorkPop));
});
g_base->logic->event_loop()->PushCall(
[] { g_base->audio->SafePlaySysSound(SysSoundID::kCorkPop); });
g_base->input->PushRemoveInputDeviceCall(client->joystick_, false);
client->joystick_ = nullptr;
client->in_use = false;
@ -377,8 +375,7 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
});
g_base->logic->event_loop()->PushCall([] {
if (g_base->assets->asset_loads_allowed()) {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
g_base->audio->SafePlaySysSound(SysSoundID::kGunCock);
}
});
}
@ -429,8 +426,7 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
g_base->logic->event_loop()->PushCall([] {
if (g_base->assets->asset_loads_allowed()) {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
g_base->audio->SafePlaySysSound(SysSoundID::kGunCock);
}
});

View File

@ -218,7 +218,8 @@ auto NetworkReader::RunThread_() -> int {
sd = sd6_;
can_read = can_read_6;
} else {
FatalError("Should not get here.");
FatalError("Should not get here; s_index=" + std::to_string(s_index)
+ ".");
sd = -1;
can_read = false;
}

View File

@ -1805,6 +1805,26 @@ static PyMethodDef PyOpenFileExternallyDef = {
"Open the provided file in the default external app.",
};
// --------------------------- get_input_idle_time -----------------------------
static auto PyGetInputIdleTime(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
return PyFloat_FromDouble(0.001 * g_base->input->input_idle_time());
BA_PYTHON_CATCH;
}
static PyMethodDef PyGetInputIdleTimeDef = {
"get_input_idle_time", // name
(PyCFunction)PyGetInputIdleTime, // method
METH_NOARGS, // flags
"get_input_idle_time() -> float\n"
"\n"
"Return seconds since any local input occurred (touch, keypress, etc.).",
};
// -----------------------------------------------------------------------------
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
@ -1873,6 +1893,7 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
PyNativeReviewRequestDef,
PyTempTestingDef,
PyOpenFileExternallyDef,
PyGetInputIdleTimeDef,
};
}

View File

@ -6,7 +6,6 @@
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/python/core_python.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/lambda_runnable.h"
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/python/python.h"
#include "ballistica/shared/python/python_sys.h"

View File

@ -169,7 +169,6 @@ void AppConfig::CompleteMap(const T& entry_map) {
void AppConfig::SetupEntries() {
// Register all our typed entries.
float_entries_[FloatID::kScreenGamma] = FloatEntry("Screen Gamma", 1.0F);
float_entries_[FloatID::kScreenPixelScale] =
FloatEntry("Screen Pixel Scale", 1.0F);
float_entries_[FloatID::kTouchControlsScale] =
@ -236,6 +235,8 @@ void AppConfig::SetupEntries() {
BoolEntry("Disable Camera Shake", false);
bool_entries_[BoolID::kDisableCameraGyro] =
BoolEntry("Disable Camera Gyro", false);
bool_entries_[BoolID::kShowDemosWhenIdle] =
BoolEntry("Show Demos When Idle", false);
// Now add everything to our name map and make sure all is kosher.
CompleteMap(float_entries_);

View File

@ -9,9 +9,10 @@
#include <string>
#include <vector>
// FIXME: this system is old and dumb.
// Should come up with a better one using the meta system
// based on Python dataclassio types or whatnot.
// FIXME: this system is old and dumb. It was built to make C++ stuff
// type-safe but does not handle the Python side at all. We should come up
// with something Python-centric using dataclasses/etc. where a C++
// component gets autogenerated via the meta system/etc.
namespace ballistica::base {
@ -24,7 +25,6 @@ class AppConfig {
// Our official config values:
enum class FloatID {
kScreenGamma,
kScreenPixelScale,
kTouchControlsScale,
kTouchControlsScaleMovement,
@ -74,6 +74,7 @@ class AppConfig {
kEnableRemoteApp,
kDisableCameraShake,
kDisableCameraGyro,
kShowDemosWhenIdle,
kLast // Sentinel.
};

View File

@ -1110,7 +1110,7 @@ void DevConsole::ToggleState() {
state_ = State_::kInactive;
break;
}
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kBlip));
g_base->audio->SafePlaySysSound(SysSoundID::kBlip);
transition_start_ = g_base->logic->display_time();
}
@ -1445,8 +1445,7 @@ auto DevConsole::PasteFromClipboard() -> bool {
if (g_base->ClipboardHasText()) {
auto text = g_base->ClipboardGetText();
if (strstr(text.c_str(), "\n") || strstr(text.c_str(), "\r")) {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kErrorBeep));
g_base->audio->SafePlaySysSound(SysSoundID::kErrorBeep);
ScreenMessage("Can only paste single lines of text.",
Vector3f(1.0f, 0.0f, 0.0f));
} else {

View File

@ -14,8 +14,6 @@
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui_delegate.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/foundation/macros.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
namespace ballistica::base {
@ -427,8 +425,7 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* {
// they're not the chosen one.
if (time - last_widget_input_reject_err_sound_time_ > 5000) {
last_widget_input_reject_err_sound_time_ = time;
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kErrorBeep));
g_base->audio->SafePlaySysSound(SysSoundID::kErrorBeep);
print_menu_owner = true;
}
ret_val = nullptr; // Rejected!

View File

@ -4,6 +4,7 @@
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/support/camera.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/classic/support/stress_test.h"
#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
@ -154,11 +155,13 @@ static auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
int enable;
int player_count;
if (!PyArg_ParseTuple(args, "pi", &enable, &player_count)) {
int attract_mode;
if (!PyArg_ParseTuple(args, "pip", &enable, &player_count, &attract_mode)) {
return nullptr;
}
g_base->logic->event_loop()->PushCall([enable, player_count] {
g_classic->stress_test()->Set(enable, player_count);
g_base->logic->event_loop()->PushCall([enable, player_count, attract_mode] {
g_classic->stress_test()->Set(enable, player_count, attract_mode);
g_base->input->set_attract_mode(enable && attract_mode);
});
Py_RETURN_NONE;
BA_PYTHON_CATCH;
@ -169,7 +172,9 @@ static PyMethodDef PySetStressTestingDef = {
PySetStressTesting, // method
METH_VARARGS, // flags
"set_stress_testing(testing: bool, player_count: int) -> None\n"
"set_stress_testing(testing: bool,\n"
" player_count: int,\n"
" attract_mode: bool) -> None\n"
"\n"
"(internal)",
};

View File

@ -5,22 +5,21 @@
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/graphics/renderer/renderer.h"
#include "ballistica/base/input/device/test_input.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/support/app_timer.h"
#include "ballistica/classic/classic.h"
namespace ballistica::classic {
void StressTest::Set(bool enable, int player_count) {
void StressTest::Set(bool enable, int player_count, bool attract_mode) {
assert(g_base->InLogicThread());
bool was_stress_testing = stress_testing_;
stress_testing_ = enable;
stress_test_player_count_ = player_count;
attract_mode_ = attract_mode;
// If we're turning on, reset our intervals and things.
if (!was_stress_testing && stress_testing_) {
// So our first sample is 1 interval from now.
// last_stress_test_update_time_ = g_core->GetAppTimeMillisecs();
// Reset our frames-rendered tally.
if (g_base && g_base->graphics_server
@ -71,9 +70,11 @@ void StressTest::ProcessInputs(int player_count) {
test_inputs_.push_back(new base::TestInput());
}
// Every so often lets kill the oldest one off.
// Every so often lets kill the oldest one off (less often in attract-mode
// though).
int odds = attract_mode_ ? 10000 : 2000;
if (explicit_bool(true)) {
if (test_inputs_.size() > 0 && (rand() % 2000 < 3)) { // NOLINT
if (test_inputs_.size() > 0 && (rand() % odds < 3)) { // NOLINT
stress_test_last_leave_time_ = time;
// Usually do oldest; sometimes newest.

View File

@ -11,7 +11,7 @@ namespace ballistica::classic {
class StressTest {
public:
void Set(bool enable, int player_count);
void Set(bool enable, int player_count, bool attract_mode);
void Update();
private:
@ -23,8 +23,7 @@ class StressTest {
int stress_test_player_count_{8};
int last_total_frames_rendered_{};
bool stress_testing_{};
// millisecs_t last_stress_test_update_time_{};
// FILE* stress_test_stats_file_{};
bool attract_mode_{};
Object::Ref<base::AppTimer> update_timer_{};
};

View File

@ -8,6 +8,7 @@
#include "ballistica/core/platform/core_platform.h"
#include "ballistica/core/python/core_python.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/foundation/types.h"
namespace ballistica::core {
@ -75,7 +76,8 @@ void CoreFeatureSet::DoImport(const CoreConfig& config) {
// didn't exist before. Can at least add an offset to give an accurate
// time though.
auto seconds_since_actual_start =
static_cast<double>(CorePlatform::GetCurrentMillisecs() - start_millisecs)
static_cast<seconds_t>(CorePlatform::GetCurrentMillisecs()
- start_millisecs)
/ 1000.0;
g_core->LifecycleLog("core import begin", -seconds_since_actual_start);
g_core->LifecycleLog("core import end");
@ -320,9 +322,9 @@ auto CoreFeatureSet::GetAppTimeMicrosecs() -> microsecs_t {
return app_time_microsecs_;
}
auto CoreFeatureSet::GetAppTimeSeconds() -> double {
auto CoreFeatureSet::GetAppTimeSeconds() -> seconds_t {
UpdateAppTime();
return static_cast<double>(app_time_microsecs_) / 1000000;
return static_cast<seconds_t>(app_time_microsecs_) / 1000000;
}
void CoreFeatureSet::UpdateAppTime() {

View File

@ -88,7 +88,7 @@ class CoreFeatureSet {
/// App-time is basically the total time that the engine has been actively
/// running. (The 'App' here is a slight misnomer). It will stop
/// progressing while the app is suspended and will never go backwards.
auto GetAppTimeSeconds() -> double;
auto GetAppTimeSeconds() -> seconds_t;
/// Are we in the 'main' thread? The thread that first inited Core is
/// considered the 'main' thread; on most platforms it is the one where

View File

@ -244,10 +244,12 @@ void CorePython::MonolithicModeBaEnvConfigure() {
auto default_py_dir = std::string("ba_data") + BA_DIRSLASH + "python";
auto data_dir_mono_default =
g_core->platform->GetDataDirectoryMonolithicDefault();
// Keep path clean if data-dir val is ".".
if (data_dir_mono_default != ".") {
default_py_dir = data_dir_mono_default + BA_DIRSLASH + default_py_dir;
}
auto args = PythonRef::Stolen(Py_BuildValue("(s)", default_py_dir.c_str()));
objs().Get(ObjID::kPrependSysPathCall).Call(args);
@ -259,8 +261,9 @@ void CorePython::MonolithicModeBaEnvConfigure() {
g_core->platform->GetDataDirectoryMonolithicDefault();
std::optional<std::string> user_python_dir =
g_core->platform->GetUserPythonDirectoryMonolithicDefault();
// clang-format off
auto kwargs =
// clang-format off
PythonRef::Stolen(Py_BuildValue(
"{"
"sO" // config_dir
@ -277,14 +280,15 @@ void CorePython::MonolithicModeBaEnvConfigure() {
"contains_python_dist",
g_buildconfig.contains_python_dist() ? Py_True : Py_False));
// clang-format on
auto result = objs()
.Get(ObjID::kBaEnvConfigureCall)
.Call(objs().Get(ObjID::kEmptyTuple), kwargs);
if (!result.Exists()) {
FatalError(
"Environment setup failed.\n"
"This usually means you are running the app from the wrong location.\n"
"See log for details.");
FatalError("Environment setup failed (no error info available).");
}
if (result.ValueIsString()) {
FatalError("Environment setup failed:\n" + result.ValueAsString());
}
g_core->LifecycleLog("baenv.configure() end");
}

View File

@ -82,8 +82,9 @@ ConnectionToClient::~ConnectionToClient() {
std::string s = g_base->assets->GetResourceString("playerLeftPartyText");
Utils::StringReplaceOne(&s, "${NAME}", peer_spec().GetDisplayString());
ScreenMessage(s, {1, 0.5f, 0.0f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kCorkPop));
if (g_base->assets->sys_assets_loaded()) {
g_base->audio->SafePlaySysSound(base::SysSoundID::kCorkPop);
}
}
}
@ -234,8 +235,9 @@ void ConnectionToClient::HandleGamePacket(const std::vector<uint8_t>& data) {
Utils::StringReplaceOne(&s, "${NAME}",
peer_spec().GetDisplayString());
ScreenMessage(s, {0.5f, 1, 0.5f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kGunCock));
if (g_base->assets->sys_assets_loaded()) {
g_base->audio->SafePlaySysSound(base::SysSoundID::kGunCock);
}
}
// Also mark the time for flashing the 'someone just joined your

View File

@ -45,8 +45,7 @@ ConnectionToHost::~ConnectionToHost() {
Utils::StringReplaceOne(&s, "${NAME}", peer_spec().GetDisplayString());
}
ScreenMessage(s, {1, 0.5f, 0.0f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kCorkPop));
g_base->audio->SafePlaySysSound(base::SysSoundID::kCorkPop);
} else {
ScreenMessage(g_base->assets->GetResourceString("connectionRejectedText"),
{1, 0, 0});
@ -413,8 +412,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector<uint8_t>& buffer) {
Utils::StringReplaceOne(
&s, "${NAME}", PlayerSpec(str_buffer.data()).GetDisplayString());
ScreenMessage(s, {0.5f, 1.0f, 0.5f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kGunCock));
g_base->audio->SafePlaySysSound(base::SysSoundID::kGunCock);
}
break;
}
@ -430,8 +428,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector<uint8_t>& buffer) {
Utils::StringReplaceOne(
&s, "${NAME}", PlayerSpec(&(str_buffer[0])).GetDisplayString());
ScreenMessage(s, {1, 0.5f, 0.0f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kCorkPop));
g_base->audio->SafePlaySysSound(base::SysSoundID::kCorkPop);
}
break;
}
@ -571,8 +568,7 @@ void ConnectionToHost::HandleMessagePacket(const std::vector<uint8_t>& buffer) {
Utils::StringReplaceOne(&s, "${NAME}", peer_spec().GetDisplayString());
}
ScreenMessage(s, {0.5f, 1, 0.5f});
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kGunCock));
g_base->audio->SafePlaySysSound(base::SysSoundID::kGunCock);
printed_connect_message_ = true;
}

View File

@ -100,7 +100,7 @@ void PropNode::SetIsAreaOfInterest(bool val) {
void PropNode::Draw(base::FrameDef* frame_def) {
#if !BA_HEADLESS_BUILD
// need our texture, mesh, and body to be present to draw..
// We need a texture, mesh, and body to be present to draw.
if ((!mesh_.Exists()) || (!color_texture_.Exists()) || (!body_.Exists())) {
return;
}

View File

@ -73,6 +73,9 @@ void PythonClassInputDevice::SetupType(PyTypeObject* cls) {
" is_remote_client (bool):\n"
" Whether this input-device represents a remotely-connected\n"
" client.\n"
"\n"
" is_test_input (bool):\n"
" Whether this input-device is a dummy device for testing.\n"
"\n";
cls->tp_new = tp_new;
@ -265,6 +268,16 @@ auto PythonClassInputDevice::tp_getattro(PythonClassInputDevice* self,
} else {
Py_RETURN_FALSE;
}
} else if (!strcmp(s, "is_test_input")) {
auto* delegate = self->input_device_delegate_->Get();
if (!delegate) {
throw Exception(PyExcType::kInputDeviceNotFound);
}
if (delegate->input_device().IsTestInput()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
// Fall back to generic behavior.

View File

@ -8,14 +8,12 @@
#include "ballistica/core/python/core_python.h"
#include "ballistica/scene_v1/connection/connection_set.h"
#include "ballistica/scene_v1/connection/connection_to_client.h"
#include "ballistica/scene_v1/connection/connection_to_host.h"
#include "ballistica/scene_v1/connection/connection_to_host_udp.h"
#include "ballistica/scene_v1/python/scene_v1_python.h"
#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
#include "ballistica/shared/math/vector3f.h"
#include "ballistica/shared/networking/sockaddr.h"
#include "ballistica/shared/python/python.h"
#include "ballistica/shared/python/python_ref.h"
#include "ballistica/shared/python/python_sys.h"
namespace ballistica::scene_v1 {

View File

@ -1500,6 +1500,74 @@ static PyMethodDef PySetReplaySpeedExponentDef = {
"Set replay speed. Actual displayed speed is pow(2, speed).",
};
// -------------------------- is_replay_paused ---------------------------------
static auto PyIsReplayPaused(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
auto* appmode = SceneV1AppMode::GetActiveOrThrow();
if (appmode->is_replay_paused()) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
BA_PYTHON_CATCH;
}
static PyMethodDef PyIsReplayPausedDef = {
"is_replay_paused", // name
PyIsReplayPaused, // method
METH_VARARGS, // flags
"is_replay_paused() -> bool\n"
"\n"
"(internal)\n"
"\n"
"Returns if Replay is paused or not.",
};
// ------------------------ pause_replay ---------------------------------------
static auto PyPauseReplay(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
auto* appmode = SceneV1AppMode::GetActiveOrThrow();
appmode->PauseReplay();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyPauseReplayDef = {
"pause_replay", // name
PyPauseReplay, // method
METH_VARARGS, // flags
"pause_replay() -> None\n"
"\n"
"(internal)\n"
"\n"
"Pauses replay.",
};
// ------------------------ resume_replay --------------------------------------
static auto PyResumeReplay(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
auto* appmode = SceneV1AppMode::GetActiveOrThrow();
appmode->ResumeReplay();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyResumeReplayDef = {
"resume_replay", // name
PyResumeReplay, // method
METH_VARARGS, // flags
"resume_replay() -> None\n"
"\n"
"(internal)\n"
"\n"
"Resumes replay.",
};
// ----------------------- reset_random_player_names ---------------------------
static auto PyResetRandomPlayerNames(PyObject* self, PyObject* args,
@ -1777,6 +1845,9 @@ auto PythonMethodsScene::GetMethods() -> std::vector<PyMethodDef> {
PyResetRandomPlayerNamesDef,
PySetReplaySpeedExponentDef,
PyGetReplaySpeedExponentDef,
PyIsReplayPausedDef,
PyPauseReplayDef,
PyResumeReplayDef,
PySetDebugSpeedExponentDef,
PyGetGameRosterDef,
PyGetForegroundHostActivityDef,

View File

@ -17,6 +17,9 @@ namespace ballistica::scene_v1 {
auto ClientSessionReplay::GetActualTimeAdvanceMillisecs(
double base_advance_millisecs) -> double {
auto* appmode = SceneV1AppMode::GetActiveOrFatal();
if (appmode->is_replay_paused()) {
return 0.0;
}
return base_advance_millisecs * pow(2.0f, appmode->replay_speed_exponent());
}
@ -36,7 +39,7 @@ ClientSessionReplay::~ClientSessionReplay() {
// we no longer are responsible for feeding clients to this device..
appmode->connections()->UnregisterClientController(this);
appmode->ResumeReplay();
if (file_) {
fclose(file_);
file_ = nullptr;

View File

@ -2,7 +2,6 @@
#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/audio/audio_source.h"
#include "ballistica/base/graphics/graphics.h"
@ -1160,8 +1159,7 @@ void SceneV1AppMode::LocalDisplayChatMessage(
g_scene_v1->python->HandleLocalChatMessage(final_message);
}
if (!chat_muted_) {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
}
@ -1224,6 +1222,10 @@ void SceneV1AppMode::SetReplaySpeedExponent(int val) {
replay_speed_mult_ = powf(2.0f, static_cast<float>(replay_speed_exponent_));
}
void SceneV1AppMode::PauseReplay() { replay_paused_ = true; }
void SceneV1AppMode::ResumeReplay() { replay_paused_ = false; }
void SceneV1AppMode::SetDebugSpeedExponent(int val) {
debug_speed_exponent_ = val;
debug_speed_mult_ = powf(2.0f, static_cast<float>(debug_speed_exponent_));

View File

@ -96,11 +96,14 @@ class SceneV1AppMode : public base::AppMode {
auto debug_speed_mult() const -> float { return debug_speed_mult_; }
auto replay_speed_exponent() const -> int { return replay_speed_exponent_; }
auto replay_speed_mult() const -> float { return replay_speed_mult_; }
auto is_replay_paused() const -> bool { return replay_paused_; }
void OnScreenSizeChange() override;
auto kick_idle_players() const -> bool { return kick_idle_players_; }
void LanguageChanged() override;
void SetDebugSpeedExponent(int val);
void SetReplaySpeedExponent(int val);
void PauseReplay();
void ResumeReplay();
void set_admin_public_ids(const std::set<std::string>& ids) {
admin_public_ids_ = ids;
}
@ -223,6 +226,7 @@ class SceneV1AppMode : public base::AppMode {
bool game_roster_dirty_{};
bool kick_vote_in_progress_{};
bool kick_voting_enabled_{true};
bool replay_paused_{false};
cJSON* game_roster_{};
millisecs_t last_game_roster_send_time_{};

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21743;
const int kEngineBuildNumber = 21756;
const char* kEngineVersion = "1.7.33";
const int kEngineApiVersion = 8;

View File

@ -11,8 +11,9 @@
namespace ballistica {
// Note: implicitly using core's internal globals here, so our behavior is
// undefined if core has not been imported by *someone*.
// Note: implicitly using core's internal globals here, but we should try to
// behave reasonably if they're not inited since fatal errors can happen any
// time.
using core::g_base_soft;
using core::g_core;
@ -121,8 +122,8 @@ void FatalError::ReportFatalError(const std::string& message,
std::string prefix = "FATAL-ERROR-LOG:";
std::string suffix;
// If we have no core state yet, include this message explicitly
// since it won't be part of the standard log.
// If we have no core state yet, include this message explicitly since it
// won't be part of the standard log.
if (g_core == nullptr) {
suffix = logmsg;
}
@ -147,7 +148,7 @@ void FatalError::ReportFatalError(const std::string& message,
}
void FatalError::DoBlockingFatalErrorDialog(const std::string& message) {
// We should not get here without this intact.
// Should not be possible to get here without this intact.
assert(g_core);
// If we're in the main thread; just fire off the dialog directly.
// Otherwise tell the main thread to do it and wait around until it's

View File

@ -211,6 +211,12 @@ enum class SpecialChar : uint8_t {
kPlayPauseButton,
kFastForwardButton,
kDpadCenterButton,
kPlayStationCrossButton,
kPlayStationCircleButton,
kPlayStationTriangleButton,
kPlayStationSquareButton,
kPlayButton,
kPauseButton,
kOuyaButtonO,
kOuyaButtonU,
kOuyaButtonY,

View File

@ -157,6 +157,12 @@ auto PythonRef::ValueIsNone() const -> bool {
return obj_ == Py_None;
}
auto PythonRef::ValueIsString() const -> bool {
assert(Python::HaveGIL());
ThrowIfUnset();
return Python::IsPyString(obj_);
}
auto PythonRef::ValueAsLString() const -> std::string {
assert(Python::HaveGIL());
ThrowIfUnset();

View File

@ -171,6 +171,7 @@ class PythonRef {
/// Throws an exception for other types.
auto ValueAsLString() const -> std::string;
auto ValueIsString() const -> bool;
auto ValueAsString() const -> std::string;
auto ValueAsStringSequence() const -> std::list<std::string>;
auto ValueAsOptionalString() const -> std::optional<std::string>;

View File

@ -5,10 +5,8 @@
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/app_mode/app_mode.h"
#include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/plus_soft.h"
#include "ballistica/ui_v1/python/class/python_class_ui_mesh.h"
#include "ballistica/ui_v1/python/class/python_class_ui_sound.h"
#include "ballistica/ui_v1/python/class/python_class_ui_texture.h"

View File

@ -17,7 +17,6 @@
#include "ballistica/ui_v1/python/class/python_class_ui_texture.h"
#include "ballistica/ui_v1/python/class/python_class_widget.h"
#include "ballistica/ui_v1/python/methods/python_methods_ui_v1.h"
#include "ballistica/ui_v1/widget/text_widget.h"
namespace ballistica::ui_v1 {
@ -107,7 +106,7 @@ void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) {
BA_PRECONDITION(string_edit_adapter_instance);
base::ScopedSetContext ssc(nullptr);
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish);
PythonRef args(Py_BuildValue("(O)", string_edit_adapter_instance),
PythonRef::kSteal);
@ -139,7 +138,7 @@ void UIV1Python::InvokeQuitWindow(QuitType quit_type) {
}
}
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish);
auto py_enum = g_base->python->PyQuitType(quit_type);
auto args = PythonRef::Stolen(Py_BuildValue("(O)", py_enum.Get()));
objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call(args);

View File

@ -6,7 +6,6 @@
#include "ballistica/base/graphics/component/empty_component.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/ui_v1/python/ui_v1_python.h"
#include "ballistica/ui_v1/support/root_ui.h"
#include "ballistica/ui_v1/widget/root_widget.h"

View File

@ -557,14 +557,11 @@ void ButtonWidget::DoActivate(bool is_repeat) {
if (sound_enabled_) {
int r = rand() % 3; // NOLINT
if (r == 0) {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kSwish));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish);
} else if (r == 1) {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kSwish2));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish2);
} else {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kSwish3));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish3);
}
}
if (auto* call = on_activate_call_.Get()) {

View File

@ -237,7 +237,7 @@ void CheckBoxWidget::SetValue(bool value) {
}
void CheckBoxWidget::Activate() {
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish3));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish3);
checked_ = !checked_;
check_dirty_ = true;
last_change_time_ = g_core->GetAppTimeMillisecs();

View File

@ -607,8 +607,7 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// First click just selects.
if (click_count == 1) {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
} else {
// Special case: If we've got a child text widget that's
@ -1637,8 +1636,7 @@ void ContainerWidget::SelectDownWidget() {
// Avoid tap sounds and whatnot if we're just re-selecting ourself.
if (w != selected_widget_) {
w->GlobalSelect();
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
} else {
@ -1702,8 +1700,7 @@ void ContainerWidget::SelectUpWidget() {
// Avoid tap sounds and whatnot if we're just re-selecting ourself.
if (w != selected_widget_) {
w->GlobalSelect();
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
} else {
@ -1755,8 +1752,7 @@ void ContainerWidget::SelectLeftWidget() {
// Avoid tap sounds and whatnot if we're just re-selecting ourself.
if (w != selected_widget_) {
w->GlobalSelect();
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
} else {
@ -1808,8 +1804,7 @@ void ContainerWidget::SelectRightWidget() {
// Avoid tap sounds and whatnot if we're just re-selecting ourself.
if (w != selected_widget_) {
w->GlobalSelect();
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
} else {
@ -1891,8 +1886,7 @@ void ContainerWidget::SelectNextWidget() {
}
if ((**i).IsSelectable() && (**i).IsSelectableViaKeys()) {
SelectWidget(&(**i), SelectionCause::NEXT_SELECTED);
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
return;
}
i++;
@ -1907,8 +1901,7 @@ void ContainerWidget::PrintExitListInstructions(
if ((t - old_last_prev_next_time > 250)
&& (t - last_list_exit_instructions_print_time_ > 5000)) {
last_list_exit_instructions_print_time_ = t;
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kErrorBeep));
g_base->audio->SafePlaySysSound(base::SysSoundID::kErrorBeep);
std::string s = g_base->assets->GetResourceString("arrowsToExitListText");
{
// Left arrow.
@ -1981,8 +1974,7 @@ void ContainerWidget::SelectPrevWidget() {
if ((**i).IsSelectable() && (**i).IsSelectableViaKeys()) {
SelectWidget(&(**i), SelectionCause::PREV_SELECTED);
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
return;
}
i++;

View File

@ -2,7 +2,6 @@
#include "ballistica/ui_v1/widget/text_widget.h"
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/graphics/component/empty_component.h"
#include "ballistica/base/graphics/component/simple_component.h"
@ -711,8 +710,7 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
case SDLK_KP_ENTER:
if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
// On mobile, return currently just deselects us.
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kSwish));
g_base->audio->SafePlaySysSound(base::SysSoundID::kSwish);
parent_widget()->SelectWidget(nullptr);
return true;
} else {
@ -847,8 +845,7 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
pressed_activate_ =
(click_count == 2 || click_activate_) && !editable_;
if (click_count == 1) {
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
}
}
return true;
@ -871,8 +868,7 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
carat_position_ = 0;
text_group_dirty_ = true;
clear_pressed_ = false;
g_base->audio->PlaySound(
g_base->assets->SysSound(base::SysSoundID::kTap));
g_base->audio->SafePlaySysSound(base::SysSoundID::kTap);
return true;
}
clear_pressed_ = false;

View File

@ -23,16 +23,27 @@ def import_baenv_and_run_configure(
data_dir: str | None,
user_python_dir: str | None,
contains_python_dist: bool,
) -> None:
"""Import baenv and run its configure method."""
import baenv
) -> None | str:
"""Import baenv and run its configure method.
baenv.configure(
config_dir=config_dir,
data_dir=data_dir,
user_python_dir=user_python_dir,
contains_python_dist=contains_python_dist,
)
On success, returns None. On Failure, attempts to return an error
traceback as a string (logging may not yet be functional at this point
so we need to be direct).
"""
try:
import baenv
baenv.configure(
config_dir=config_dir,
data_dir=data_dir,
user_python_dir=user_python_dir,
contains_python_dist=contains_python_dist,
)
return None
except Exception:
import traceback
return traceback.format_exc()
def get_env_config() -> baenv.EnvConfig:

View File

@ -19,6 +19,11 @@ if TYPE_CHECKING:
def _get_legal_notice_private() -> str:
"""Return the one line legal notice we expect private files to have."""
return 'Copyright (c) 2011-2024 Eric Froemling'
def _get_legal_notice_private_prev() -> str:
"""Allows us to auto-update."""
return 'Copyright (c) 2011-2023 Eric Froemling'
@ -210,6 +215,7 @@ def _check_c_license(
# Look for public license line (public or private repo) or private
# license line (private repo only)
line_private = '// ' + _get_legal_notice_private()
line_private_prev = '// ' + _get_legal_notice_private_prev()
line_public = get_public_license('c++')
lnum = 0
@ -229,7 +235,7 @@ def _check_c_license(
fname,
line_number=lnum,
expected=line_private,
can_auto_update=False,
can_auto_update=(lines[lnum] == line_private_prev),
)
@ -463,6 +469,7 @@ def _check_python_file_license(
if self.license_line_checks:
public_license = get_public_license('python')
private_license = '# ' + _get_legal_notice_private()
private_license_prev = '# ' + _get_legal_notice_private_prev()
lnum = copyrightline
if len(lines) < lnum + 1:
raise RuntimeError('Not enough lines in file:', fname)
@ -486,17 +493,27 @@ def _check_python_file_license(
f'{disable_note}'
)
else:
if lines[lnum] != public_license and lines[lnum] != private_license:
raise CleanError(
f'License text not found'
f" at '{fname}' line {lnum+1};"
f' please correct.\n'
f'Expected text (for public files):'
f' {public_license}\n'
f'Expected text (for private files):'
f' {private_license}\n'
f'{disable_note}'
)
if lines[lnum] not in [public_license, private_license]:
# Special case: if we find last year's private license
# we can update to this year's.
if lines[lnum] == private_license_prev:
self.add_line_correction(
fname,
line_number=lnum,
expected=private_license,
can_auto_update=(lines[lnum] == private_license_prev),
)
else:
raise CleanError(
f'License text not found'
f" at '{fname}' line {lnum+1};"
f' please correct.\n'
f'Expected text (for public files):'
f' {public_license}\n'
f'Expected text (for private files):'
f' {private_license}\n'
f'{disable_note}'
)
def _calc_python_file_copyright_line(