diff --git a/.efrocachemap b/.efrocachemap index 9f55a5f5..b0cbce77 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,41 +421,41 @@ "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": "831b83240126d0a851104f4148712ed1", + "build/assets/ba_data/data/langdata.json": "66f05313ffa9880373066332cff4594c", "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": "1360ffde06828b63ce4fe956c3c3cd1d", + "build/assets/ba_data/data/languages/belarussian.json": "09954e550d13d3d9cb5a635a1d32a151", + "build/assets/ba_data/data/languages/chinese.json": "bb51b5aa614830c561e8fe2542a9ab8a", "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": "7171420af6d662e3a47b64576850a384", - "build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e", + "build/assets/ba_data/data/languages/croatian.json": "e671b9d0c012be1a30f9c15eb1b81860", + "build/assets/ba_data/data/languages/czech.json": "15be4fd59895135bad0265f79b362d5b", + "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", "build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343", - "build/assets/ba_data/data/languages/english.json": "1c4037fea1066d39d6eced419f314f35", + "build/assets/ba_data/data/languages/english.json": "6501b04faba1d81e26725dfa19b15667", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", - "build/assets/ba_data/data/languages/filipino.json": "43e838754fe013b8bac75f75aef78cb3", - "build/assets/ba_data/data/languages/french.json": "cc8ac601f5443dd539893728db983f5c", + "build/assets/ba_data/data/languages/filipino.json": "fe3f1efcb47efaa23524300d21728933", + "build/assets/ba_data/data/languages/french.json": "917e4174d6f0eb7f00c27fd79cfbb924", "build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad", - "build/assets/ba_data/data/languages/gibberish.json": "b461539243e8efe3137137b886256ba7", + "build/assets/ba_data/data/languages/gibberish.json": "0c1c4ac59d82a58c4b89328d44510d72", "build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3", - "build/assets/ba_data/data/languages/hindi.json": "5b6c8e988ffa84a7e26d120b6cd8e1a4", + "build/assets/ba_data/data/languages/hindi.json": "90f54663e15d85a163f1848a8e9d8d07", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/indonesian.json": "9103845242b572aa8ba48e24f81ddb68", - "build/assets/ba_data/data/languages/italian.json": "f550810b6866ea9bcf1985b7228f8cff", + "build/assets/ba_data/data/languages/italian.json": "fc6440be9ba1846172cf5e11df617c05", "build/assets/ba_data/data/languages/korean.json": "4e3524327a0174250aff5e1ef4c0c597", "build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38", - "build/assets/ba_data/data/languages/persian.json": "9728d631cf7d9ad3b209ae1244bb59c0", - "build/assets/ba_data/data/languages/polish.json": "3a90b2d9e2c59305580c96f8098fc839", + "build/assets/ba_data/data/languages/persian.json": "c4144aebf2900fc655860de891d16f83", + "build/assets/ba_data/data/languages/polish.json": "9d22c6643c097c4cb268d0d6b6319cd4", "build/assets/ba_data/data/languages/portuguese.json": "b52164747c6308fc9d054eb6c0ff3c54", - "build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826", - "build/assets/ba_data/data/languages/russian.json": "30d5f3d2415088e1fb6558fcd6ccfa98", + "build/assets/ba_data/data/languages/romanian.json": "b3e46efd6f869dbd78014570e037c290", + "build/assets/ba_data/data/languages/russian.json": "0590f49889616b5279be569dea926e17", "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": "e3e9ac8f96f52302a480c7e955aed71f", + "build/assets/ba_data/data/languages/slovak.json": "c00fb27cf982ffad5a4370ad3b16bd21", + "build/assets/ba_data/data/languages/spanish.json": "b2edb923fdca973a16f0efb1acc26a97", "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": "9c425b420f0488a7f883da98947657ad", - "build/assets/ba_data/data/languages/turkish.json": "2be25c89ca754341f27750e0d595f31e", - "build/assets/ba_data/data/languages/ukrainian.json": "b54a38e93deebafa5706ba2d1f626892", + "build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a", + "build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945", + "build/assets/ba_data/data/languages/turkish.json": "db71f3776072b7a15ef37b1bb1245795", + "build/assets/ba_data/data/languages/ukrainian.json": "3d75d21205c82db34fb1a1b014592747", "build/assets/ba_data/data/languages/venetian.json": "f896fc3df13a42f1bef8813ca80b1a09", "build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba", "build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054", @@ -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": "d5a8312cd9cf65f32ca2a7c4a2063c03", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "aecb00e9044fa677583e1036fa7875d8", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "eca7f9ab892edfa7423a9d4a6f89e571", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "99647f48362f84112d23a9bc89eaa983", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "31e21a64d77fc0834832b633a26d986b", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "7c12b4078c3af6e627a4051b1c1d8370", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f7a66c48321efa4462e8eae6b72db2b2", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "08cdbeb2ca4fa8c996f3369680c4e5cd", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f92679bab5a0d057427962869e19f057", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "d5bcd695f84dab1ab32655989d399c9e", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c766f437ece15dae0ee971e4c2e10a2d", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "cbecc4c11b9aa4621abfdc996fecfd74", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "7af782c9d9bcf1396a15dea6f2493d70", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "2c04f3f68db3e73e4aad4c656d956c00", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "132c83ee8811828739601ac3d0599fe9", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8de942a2e1ff96c147a9500a56ca4f64", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "6bf51ccbd01937bf1b28cfffe029d857", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "c5f0d834a47852f1c240e17a6c933e0a", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "4f74b71dabd207bee732dc91c9a28dc4", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f48ab8e4c4d05f4b2231bebf33c965f1", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5db2ea1c6bacab3fe60eb948aaa4afdb", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "2b476166b869112112d57f26833a8381", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "7b8d2cb654ab2c022584114ed9910c38", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "846150203fe0611a71ff832176579ce5", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "683c3d3b7ac3b052f3ecc3fef36fc13a", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "e41d6aeb7a2e335ebcc701a35d45df8c", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f0a97f7c34a78bfd829f460a9f4ea81c", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d1e697c045e3b4092ec35fb8f3b4bd25", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "952c02766cecd280af3e9b77c80e91e1", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "bceae148212f47bfc9acf60ea52b1003", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e9ea0d09ba4af6253025cbe3aa8469cf", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "d2cf18fbc6d815268790532bc38d2434", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1134322221c0ccea4057e462d9fa5197", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "30d10d34fb0e14b8f7ceec1760b521d6", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d132bc58d9744941144244484bb005a6", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "5ae4aef6e0291175a3a9e3b77adcc0c0", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "abb92db084cdc165d7c1ed500be919ba", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "57bb6f6b5dadbc8f05fbab3271ef8abb", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c4a68563f1237c1679c870def5d91b1a", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e2f338fd1d4d8ff9a079e2e9c492aabb", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "ee36a39fd0f524989cb68930c89c8868", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "dbed9145e5db116d92aa47cb9e98da39", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "ee36a39fd0f524989cb68930c89c8868", @@ -4096,14 +4096,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "0896e849885cef50bcf33ce863efa7d2", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "e53c808357cc0a2f0da7b870be147083", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "0896e849885cef50bcf33ce863efa7d2", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "e34cc55fd284e31d9ed1151c5a51bf34", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "36cb65be158a0103d81c82d8a51dc8b6", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "21f8a61745c2fec88749299f5aeeeaf9", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d61272f101f87b140b84895e482b07f4", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "36c30bcd93d38569b9515ed17896d8de", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "841c7cd3cc96c91ecd11335a91c0c465", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "305aab4423bf510f6bf95fe0c996128f", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "f1066b8591d7859df76c8e976ceee2d5", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "72901cf56d898442b6bcf4ecafd5cd65", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "c185b4f41dfc69c133a75260b95421d1", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "096880b9e8faac99a72d234a61ddd624", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "266f4e6a3d8b39c97ee7b5e766e8b207", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0927775fb993a977de90e4671a09e996", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "53fcf97128862b34771ca967f88641c8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "160a2caaa393f9ddb40ffebc7546e6bb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "90716a0e1310a90247a9aee3a7a97a38", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "b611c090513a21e2fe90e56582724e9d", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f9188b7..ec3ff176 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.33 (build 21766, api 8, 2024-02-01) +### 1.7.33 (build 21772, api 8, 2024-03-02) - 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 @@ -21,9 +21,23 @@ catch problems where a base class changes or removes a method and child classes forget to adapt to the change. - Replays now have rewind/fast-forward buttons!! (Thanks Dliwk, vishal332008!) +- Custom spaz "curse_time" values now work properly. (Thanks Temp!) - Implemented `efro.dataclassio.IOMultiType` which will make my life a lot easier. - +- Punches no longer physically affect powerup boxes which should make it easier + to grab the powerup (Thanks VinniTR!). +- The 'Manual' party tab now supports entering IPv6 addresses (Thanks + brostos!). +- Fixes a bug where Meteor Shower could make the game-end bell sound twice + (Thanks 3alTemp!). +- Leaving the game or dying while touching your team's flag will no longer + recover & return it indefinitely in a teams game of Capture the Flag. (Thanks + 3alTemp!) +- Added a server config setting for max players (not max clients) (Thanks + EraOSBeta!) +- Added a UI for customizing Series Length in Teams and Points-to-Win in FFA + (Thanks EraOSBeta!) + ### 1.7.32 (build 21741, api 8, 2023-12-20) - Fixed a screen message that no one will ever see (Thanks vishal332008?...) - Plugins window now displays 'No Plugins Installed' when no plugins are present (Thanks vishal332008!) @@ -66,7 +80,7 @@ intended. Now, however, such commands get scheduled to a current 'ui-operation' and then run *almost* immediately, which should prevent such situations. Please holler if you run into any UI weirdness at this point. - + ### 1.7.30 (build 21697, api 8, 2023-12-08) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and @@ -343,7 +357,7 @@ - Added a 'glow_type' arg to `bauiv1.textwidget()` to adjust the glow used when the text is selected. The default is 'gradient' but there is now a 'uniform' option which may look better in some circumstances. - + ### 1.7.27 (build 21282, api 8, 2023-08-30) - Fixed a rare crash that could occur if the app shuts down while a background @@ -464,7 +478,7 @@ Visual Studio Code (and potentially other editors), so am seeing if it is worth officially supporting in addition to or as a replacement for Mypy. See `tools/pcommand pyright` - + ### 1.7.24 (build 21199, api 8, 2023-07-27) - Fixed an issue where respawn icons could disappear in epic mode (Thanks for @@ -519,7 +533,7 @@ can be useful for core engine code to directly and clearly point out problems that cannot be recovered from (Exceptions in such cases can tend to be 'handled' which leads to a broken or crashing app). - + ### 1.7.23 (build 21178, api 8, 2023-07-19) - Network security improvements. (Thanks Dliwk!) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8fccc544..e6663602 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -37,12 +37,12 @@ - Added feature ### Vishal332008 -- Bug Fixer +- QoL and Bug Fixer - Modder ### Era0S - Community Suggestions Implementer -- Bug Fixer +- QoL and Bug Fixer - Modder ### VinniTR @@ -50,3 +50,10 @@ ### Rikko - Created the original "reject_recently_left_players" plugin + +### Temp (3alTemp) +- Original idea for customizable series length on GUI builds +- Modder & Bug Fixer + +### brostos +- Added support for joining using ipv6 address diff --git a/src/assets/ba_data/python/babase/modutils.py b/src/assets/ba_data/python/babase/modutils.py index 7780d1f6..1608eb5c 100644 --- a/src/assets/ba_data/python/babase/modutils.py +++ b/src/assets/ba_data/python/babase/modutils.py @@ -136,7 +136,9 @@ def create_user_system_scripts() -> None: path = f'{env.python_directory_user}/sys/{env.version}' pathtmp = path + '_tmp' if os.path.exists(path): - shutil.rmtree(path) + print('Delete Existing User Scripts and try again.') + _babase.screenmessage('Delete Existing User Scripts and try again.') + return if os.path.exists(pathtmp): shutil.rmtree(pathtmp) @@ -159,6 +161,7 @@ def create_user_system_scripts() -> None: f"'\nRestart {_babase.appname()} to use them." f' (use babase.quit() to exit the game)' ) + _babase.screenmessage('Created User System Scripts') if app.classic is not None and app.classic.platform == 'android': print( 'Note: the new files may not be visible via ' @@ -183,6 +186,7 @@ def delete_user_system_scripts() -> None: f'Restart {_babase.appname()} to use internal' f' scripts. (use babase.quit() to exit the game)' ) + _babase.screenmessage('Deleted User System Scripts') else: print(f"User system scripts not found at '{path}'.") diff --git a/src/assets/ba_data/python/baclassic/_servermode.py b/src/assets/ba_data/python/baclassic/_servermode.py index 45ee6eb9..a5479955 100644 --- a/src/assets/ba_data/python/baclassic/_servermode.py +++ b/src/assets/ba_data/python/baclassic/_servermode.py @@ -310,9 +310,7 @@ class ServerController: typename = ( 'teams' if result['playlistType'] == 'Team Tournament' - else 'ffa' - if result['playlistType'] == 'Free-for-All' - else '??' + else 'ffa' if result['playlistType'] == 'Free-for-All' else '??' ) plistname = result['playlistName'] print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}') @@ -390,14 +388,14 @@ class ServerController: if sessiontype is bascenev1.FreeForAllSession: appcfg['Free-for-All Playlist Selection'] = self._playlist_name - appcfg[ - 'Free-for-All Playlist Randomize' - ] = self._config.playlist_shuffle + appcfg['Free-for-All Playlist Randomize'] = ( + self._config.playlist_shuffle + ) elif sessiontype is bascenev1.DualTeamSession: appcfg['Team Tournament Playlist Selection'] = self._playlist_name - appcfg[ - 'Team Tournament Playlist Randomize' - ] = self._config.playlist_shuffle + appcfg['Team Tournament Playlist Randomize'] = ( + self._config.playlist_shuffle + ) elif sessiontype is bascenev1.CoopSession: classic.coop_session_args = { 'campaign': self._config.coop_campaign, @@ -406,6 +404,10 @@ class ServerController: else: raise RuntimeError(f'Unknown session type {sessiontype}') + appcfg['Teams Series Length'] = self._config.teams_series_length + appcfg['FFA Series Length'] = self._config.ffa_series_length + + # deprecated, left here in order to not break mods classic.teams_series_length = self._config.teams_series_length classic.ffa_series_length = self._config.ffa_series_length @@ -427,6 +429,10 @@ class ServerController: self._config.player_rejoin_cooldown ) + bascenev1.set_max_players_override( + self._config.session_max_players_override + ) + # And here.. we.. go. if self._config.stress_test_players is not None: # Special case: run a stress test. diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_subsystem.py index bb9bbadd..51dd7679 100644 --- a/src/assets/ba_data/python/baclassic/_subsystem.py +++ b/src/assets/ba_data/python/baclassic/_subsystem.py @@ -103,8 +103,8 @@ class ClassicSubsystem(babase.AppSubsystem): self.maps: dict[str, type[bascenev1.Map]] = {} # Gameplay. - self.teams_series_length = 7 - self.ffa_series_length = 24 + self.teams_series_length = 7 # deprecated, left for old mods + self.ffa_series_length = 24 # deprecated, left for old mods self.coop_session_args: dict = {} # UI. diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index cdcf043b..1bb9453c 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21766 +TARGET_BALLISTICA_BUILD = 21772 TARGET_BALLISTICA_VERSION = '1.7.33' diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index d0e1850e..ebfb5418 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -232,7 +232,11 @@ from bascenev1._settings import ( IntSetting, Setting, ) -from bascenev1._session import Session, set_player_rejoin_cooldown +from bascenev1._session import ( + Session, + set_player_rejoin_cooldown, + set_max_players_override, +) from bascenev1._stats import PlayerScoredMessage, PlayerRecord, Stats from bascenev1._team import SessionTeam, Team, EmptyTeam from bascenev1._teamgame import TeamGameActivity @@ -428,6 +432,7 @@ __all__ = [ 'set_public_party_queue_enabled', 'set_public_party_stats_url', 'set_player_rejoin_cooldown', + 'set_max_players_override', 'set_replay_speed_exponent', 'set_touchscreen_editing', 'setmusic', diff --git a/src/assets/ba_data/python/bascenev1/_map.py b/src/assets/ba_data/python/bascenev1/_map.py index 832632fc..02f64909 100644 --- a/src/assets/ba_data/python/bascenev1/_map.py +++ b/src/assets/ba_data/python/bascenev1/_map.py @@ -256,9 +256,7 @@ class Map(Actor): return ( None if val is None - else babase.vec3validate(val) - if __debug__ - else val + else babase.vec3validate(val) if __debug__ else val ) def get_def_points(self, name: str) -> list[Sequence[float]]: @@ -334,8 +332,7 @@ class Map(Actor): closest_player_dist = 9999.0 for ppt in player_pts: dist = (ppt - testpt).length() - if dist < closest_player_dist: - closest_player_dist = dist + closest_player_dist = min(dist, closest_player_dist) if closest_player_dist > farthestpt_dist: farthestpt_dist = closest_player_dist farthestpt = testpt diff --git a/src/assets/ba_data/python/bascenev1/_multiteamsession.py b/src/assets/ba_data/python/bascenev1/_multiteamsession.py index 06a5a2e1..77459baa 100644 --- a/src/assets/ba_data/python/bascenev1/_multiteamsession.py +++ b/src/assets/ba_data/python/bascenev1/_multiteamsession.py @@ -67,8 +67,8 @@ class MultiTeamSession(Session): max_players=self.get_max_players(), ) - self._series_length: int = classic.teams_series_length - self._ffa_series_length: int = classic.ffa_series_length + self._series_length: int = int(cfg.get('Teams Series Length', 7)) + self._ffa_series_length: int = int(cfg.get('FFA Series Length', 24)) show_tutorial = cfg.get('Show Tutorial', True) diff --git a/src/assets/ba_data/python/bascenev1/_session.py b/src/assets/ba_data/python/bascenev1/_session.py index 9ee55313..9acad485 100644 --- a/src/assets/ba_data/python/bascenev1/_session.py +++ b/src/assets/ba_data/python/bascenev1/_session.py @@ -23,6 +23,9 @@ if TYPE_CHECKING: # such as skipping respawn waits. _g_player_rejoin_cooldown: float = 0.0 +# overrides the session's decision of max_players +_max_players_override: int | None = None + def set_player_rejoin_cooldown(cooldown: float) -> None: """Set the cooldown for individual players rejoining after leaving.""" @@ -30,6 +33,12 @@ def set_player_rejoin_cooldown(cooldown: float) -> None: _g_player_rejoin_cooldown = max(0.0, cooldown) +def set_max_players_override(max_players: int | None) -> None: + """Set the override for how many players can join a session""" + global _max_players_override # pylint: disable=global-statement + _max_players_override = max_players + + class Session: """Defines a high level series of bascenev1.Activity-es. @@ -161,7 +170,11 @@ class Session: self.sessionteams = [] self.sessionplayers = [] self.min_players = min_players - self.max_players = max_players + self.max_players = ( + max_players + if _max_players_override is None + else _max_players_override + ) self.customdata = {} self._in_set_activity = False @@ -255,7 +268,7 @@ class Session: babase.app.classic is not None and babase.app.classic.stress_test_update_timer is None ): - if len(self.sessionplayers) >= self.max_players: + if len(self.sessionplayers) >= self.max_players >= 0: # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _bascenev1.getsound('error').play() diff --git a/src/assets/ba_data/python/bascenev1lib/actor/spaz.py b/src/assets/ba_data/python/bascenev1lib/actor/spaz.py index ad914b8c..9fc4c321 100644 --- a/src/assets/ba_data/python/bascenev1lib/actor/spaz.py +++ b/src/assets/ba_data/python/bascenev1lib/actor/spaz.py @@ -13,7 +13,7 @@ from typing_extensions import override import bascenev1 as bs from bascenev1lib.actor.bomb import Bomb, Blast -from bascenev1lib.actor.powerupbox import PowerupBoxFactory +from bascenev1lib.actor.powerupbox import PowerupBoxFactory, PowerupBox from bascenev1lib.actor.spazfactory import SpazFactory from bascenev1lib.gameutils import SharedObjects @@ -629,7 +629,8 @@ class Spaz(bs.Actor): 1000.0 * (tval + self.curse_time) ) self._curse_timer = bs.Timer( - 5.0, bs.WeakCall(self.handlemessage, CurseExplodeMessage()) + self.curse_time, + bs.WeakCall(self.handlemessage, CurseExplodeMessage()), ) def equip_boxing_gloves(self) -> None: @@ -1227,6 +1228,10 @@ class Spaz(bs.Actor): return None node = bs.getcollision().opposingnode + # Don't want to physically affect powerups. + if node.getdelegate(PowerupBox): + return None + # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): punch_momentum_angular = ( diff --git a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py index 837c3be1..316483f9 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py +++ b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py @@ -529,6 +529,30 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): if team.flag_return_touches < 0: logging.exception('CTF flag_return_touches < 0') + def _handle_death_flag_capture(self, player: Player) -> None: + """Handles flag values when a player dies or leaves the game.""" + # Don't do anything if the player hasn't touched the flag at all. + if not player.touching_own_flag: + return + + team = player.team + # For each "point" our player has touched theflag (Could be multiple), + # deduct one from both our player and + # the flag's return touches variable. + for _ in range(player.touching_own_flag): + # Deduct + player.touching_own_flag -= 1 + team.flag_return_touches -= 1 + # Update our flag's timer accordingly + # (Prevents immediate resets in case + # there might be more people touching it). + if team.flag_return_touches == 0: + team.touch_return_timer = None + team.touch_return_timer_ticking = None + # Safety check, just to be sure! + if team.flag_return_touches < 0: + logging.exception('CTF flag_return_touches < 0') + def _flash_base(self, team: Team, length: float = 2.0) -> None: light = bs.newnode( 'light', @@ -591,6 +615,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): def handlemessage(self, msg: Any) -> Any: if isinstance(msg, bs.PlayerDiedMessage): super().handlemessage(msg) # Augment standard behavior. + self._handle_death_flag_capture(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player)) elif isinstance(msg, FlagDiedMessage): @@ -617,3 +642,8 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]): else: super().handlemessage(msg) + + @override + def on_player_leave(self, player: Player) -> None: + """Prevents leaving players from capturing their flag.""" + self._handle_death_flag_capture(player) diff --git a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py index a9a70ae0..cff9cf8d 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py +++ b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py @@ -73,6 +73,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): self._last_player_death_time: float | None = None self._meteor_time = 2.0 self._timer: OnScreenTimer | None = None + self._ended: bool = False # Some base class overrides: self.default_music = ( @@ -161,6 +162,10 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): return None def _check_end_game(self) -> None: + # We don't want to end this activity more than once. + if self._ended: + return + living_team_count = 0 for team in self.teams: for player in team.players: @@ -270,4 +275,5 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]): # Submit the score value in milliseconds. results.set_team_score(team, int(1000.0 * longest_life)) + self._ended = True self.end(results=results) diff --git a/src/assets/ba_data/python/bauiv1lib/config.py b/src/assets/ba_data/python/bauiv1lib/config.py index 763a3704..255dd6d3 100644 --- a/src/assets/ba_data/python/bauiv1lib/config.py +++ b/src/assets/ba_data/python/bauiv1lib/config.py @@ -94,6 +94,8 @@ class ConfigNumberEdit: changesound: bool = True, textscale: float = 1.0, as_percent: bool = False, + fallback_value: float = 0.0, + f: int = 1, ): if displayname is None: displayname = configkey @@ -103,8 +105,12 @@ class ConfigNumberEdit: self._maxval = maxval self._increment = increment self._callback = callback - self._value = bui.app.config.resolve(configkey) + try: + self._value = bui.app.config.resolve(configkey) + except ValueError: + self._value = bui.app.config.get(configkey, fallback_value) self._as_percent = as_percent + self._f = f self.nametext = bui.textwidget( parent=parent, @@ -171,5 +177,5 @@ class ConfigNumberEdit: if self._as_percent: val = f'{round(self._value*100.0)}%' else: - val = f'{self._value:.1f}' + val = f'{self._value:.{self._f}f}' bui.textwidget(edit=self.valuetext, text=val) diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index e9f9b4af..540f1804 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -90,9 +90,7 @@ class CoopBrowserWindow(bui.Window): self._height = ( 657 if uiscale is bui.UIScale.SMALL - else 730 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 730 if uiscale is bui.UIScale.MEDIUM else 800 ) app.ui_v1.set_main_menu_location('Coop Select') self._r = 'coopSelectWindow' @@ -104,6 +102,19 @@ class CoopBrowserWindow(bui.Window): 'campaignDifficulty', 'easy' ) + if ( + self._campaign_difficulty == 'hard' + and not app.classic.accounts.have_pro_options() + ): + plus.add_v1_account_transaction( + { + 'type': 'SET_MISC_VAL', + 'name': 'campaignDifficulty', + 'value': 'easy', + } + ) + self._campaign_difficulty = 'easy' + super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height + top_extra), @@ -112,17 +123,13 @@ class CoopBrowserWindow(bui.Window): stack_offset=( (0, -15) if uiscale is bui.UIScale.SMALL - else (0, 0) - if uiscale is bui.UIScale.MEDIUM - else (0, 0) + else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0) ), transition=transition, scale=( 1.2 if uiscale is bui.UIScale.SMALL - else 0.8 - if uiscale is bui.UIScale.MEDIUM - else 0.75 + else 0.8 if uiscale is bui.UIScale.MEDIUM else 0.75 ), ) ) @@ -271,9 +278,11 @@ class CoopBrowserWindow(bui.Window): self._scrollwidget = bui.scrollwidget( parent=self._root_widget, highlight=False, - position=(65 + x_inset, 120) - if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars - else (65 + x_inset, 70), + position=( + (65 + x_inset, 120) + if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars + else (65 + x_inset, 70) + ), size=(self._scroll_width, self._scroll_height), simple_culling_v=10.0, claims_left_right=True, @@ -421,12 +430,14 @@ class CoopBrowserWindow(bui.Window): if tbtn.time_remaining_value_text is not None: bui.textwidget( edit=tbtn.time_remaining_value_text, - text=bui.timestring(tbtn.time_remaining, centi=False) - if ( - tbtn.has_time_remaining - and self._tourney_data_up_to_date - ) - else '-', + text=( + bui.timestring(tbtn.time_remaining, centi=False) + if ( + tbtn.has_time_remaining + and self._tourney_data_up_to_date + ) + else '-' + ), ) # Also adjust the ad icon visibility. @@ -447,9 +458,9 @@ class CoopBrowserWindow(bui.Window): try: bui.imagewidget( edit=self._hard_button_lock_image, - opacity=0.0 - if bui.app.classic.accounts.have_pro_options() - else 1.0, + opacity=( + 0.0 if bui.app.classic.accounts.have_pro_options() else 1.0 + ), ) except Exception: logging.exception('Error updating campaign lock.') @@ -559,12 +570,16 @@ class CoopBrowserWindow(bui.Window): enable_sound=False, on_activate_call=bui.Call(self._set_campaign_difficulty, 'easy'), on_select_call=bui.Call(self.sel_change, 'campaign', 'easyButton'), - color=sel_color - if self._campaign_difficulty == 'easy' - else un_sel_color, - textcolor=sel_textcolor - if self._campaign_difficulty == 'easy' - else un_sel_textcolor, + color=( + sel_color + if self._campaign_difficulty == 'easy' + else un_sel_color + ), + textcolor=( + sel_textcolor + if self._campaign_difficulty == 'easy' + else un_sel_textcolor + ), ) bui.widget(edit=self._easy_button, show_buffer_left=100) if self._selected_campaign_level == 'easyButton': @@ -585,12 +600,16 @@ class CoopBrowserWindow(bui.Window): enable_sound=False, on_activate_call=bui.Call(self._set_campaign_difficulty, 'hard'), on_select_call=bui.Call(self.sel_change, 'campaign', 'hardButton'), - color=sel_color_hard - if self._campaign_difficulty == 'hard' - else un_sel_color, - textcolor=sel_textcolor - if self._campaign_difficulty == 'hard' - else un_sel_textcolor, + color=( + sel_color_hard + if self._campaign_difficulty == 'hard' + else un_sel_color + ), + textcolor=( + sel_textcolor + if self._campaign_difficulty == 'hard' + else un_sel_textcolor + ), ) self._hard_button_lock_image = bui.imagewidget( parent=parent_widget, @@ -960,35 +979,43 @@ class CoopBrowserWindow(bui.Window): for i, tbutton in enumerate(self._tournament_buttons): bui.widget( edit=tbutton.button, - up_widget=self._tournament_info_button - if i == 0 - else self._tournament_buttons[i - 1].button, - down_widget=self._tournament_buttons[(i + 1)].button - if i + 1 < len(self._tournament_buttons) - else custom_h_scroll, + up_widget=( + self._tournament_info_button + if i == 0 + else self._tournament_buttons[i - 1].button + ), + down_widget=( + self._tournament_buttons[(i + 1)].button + if i + 1 < len(self._tournament_buttons) + else custom_h_scroll + ), ) bui.widget( edit=tbutton.more_scores_button, - down_widget=self._tournament_buttons[ - (i + 1) - ].current_leader_name_text - if i + 1 < len(self._tournament_buttons) - else custom_h_scroll, + down_widget=( + self._tournament_buttons[(i + 1)].current_leader_name_text + if i + 1 < len(self._tournament_buttons) + else custom_h_scroll + ), ) bui.widget( edit=tbutton.current_leader_name_text, - up_widget=self._tournament_info_button - if i == 0 - else self._tournament_buttons[i - 1].more_scores_button, + up_widget=( + self._tournament_info_button + if i == 0 + else self._tournament_buttons[i - 1].more_scores_button + ), ) for btn in self._custom_buttons: try: bui.widget( edit=btn.get_button(), - up_widget=tournament_h_scroll - if self._tournament_buttons - else self._tournament_info_button, + up_widget=( + tournament_h_scroll + if self._tournament_buttons + else self._tournament_info_button + ), ) except Exception: logging.exception('Error wiring up custom buttons.') @@ -1042,8 +1069,9 @@ class CoopBrowserWindow(bui.Window): def _switch_to_score( self, - show_tab: StoreBrowserWindow.TabID - | None = StoreBrowserWindow.TabID.EXTRAS, + show_tab: ( + StoreBrowserWindow.TabID | None + ) = StoreBrowserWindow.TabID.EXTRAS, ) -> None: # pylint: disable=cyclic-import from bauiv1lib.account import show_sign_in_prompt diff --git a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py index d7f3205e..88232b73 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py @@ -48,7 +48,10 @@ class _HostLookupThread(Thread): try: import socket - result = socket.gethostbyname(self._name) + result = [ + item[-1][0] + for item in socket.getaddrinfo(self.name, self._port) + ][0] except Exception: result = None bui.pushcall( @@ -212,15 +215,19 @@ class ManualGatherTab(GatherTab): inactive_color = (0.5, 0.4, 0.5) bui.textwidget( edit=self._join_by_address_text, - color=active_color - if value is SubTabType.JOIN_BY_ADDRESS - else inactive_color, + color=( + active_color + if value is SubTabType.JOIN_BY_ADDRESS + else inactive_color + ), ) bui.textwidget( edit=self._favorites_text, - color=active_color - if value is SubTabType.FAVORITES - else inactive_color, + color=( + active_color + if value is SubTabType.FAVORITES + else inactive_color + ), ) # Clear anything existing in the old sub-tab. @@ -354,9 +361,7 @@ class ManualGatherTab(GatherTab): self._height = ( 578 if uiscale is bui.UIScale.SMALL - else 670 - if uiscale is bui.UIScale.MEDIUM - else 800 + else 670 if uiscale is bui.UIScale.MEDIUM else 800 ) self._scroll_width = self._width - 130 + 2 * x_inset @@ -375,16 +380,12 @@ class ManualGatherTab(GatherTab): b_height = ( 107 if uiscale is bui.UIScale.SMALL - else 142 - if uiscale is bui.UIScale.MEDIUM - else 190 + else 142 if uiscale is bui.UIScale.MEDIUM else 190 ) b_space_extra = ( 0 if uiscale is bui.UIScale.SMALL - else -2 - if uiscale is bui.UIScale.MEDIUM - else -5 + else -2 if uiscale is bui.UIScale.MEDIUM else -5 ) btnv = ( @@ -392,9 +393,7 @@ class ManualGatherTab(GatherTab): - ( 48 if uiscale is bui.UIScale.SMALL - else 45 - if uiscale is bui.UIScale.MEDIUM - else 40 + else 45 if uiscale is bui.UIScale.MEDIUM else 40 ) - b_height ) @@ -513,9 +512,7 @@ class ManualGatherTab(GatherTab): scale=( 1.8 if uiscale is bui.UIScale.SMALL - else 1.55 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0 ), size=(c_width, c_height), transition='in_scale', diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py index c5bd8428..9880c4d5 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py @@ -52,9 +52,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window): self._height = ( 380.0 if uiscale is bui.UIScale.SMALL - else 420.0 - if uiscale is bui.UIScale.MEDIUM - else 500.0 + else 420.0 if uiscale is bui.UIScale.MEDIUM else 500.0 ) top_extra = 20.0 if uiscale is bui.UIScale.SMALL else 0.0 @@ -66,13 +64,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): scale=( 2.05 if uiscale is bui.UIScale.SMALL - else 1.5 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -10) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -118,9 +114,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window): scl = ( 1.1 if uiscale is bui.UIScale.SMALL - else 1.27 - if uiscale is bui.UIScale.MEDIUM - else 1.57 + else 1.27 if uiscale is bui.UIScale.MEDIUM else 1.57 ) scl *= 0.63 v -= 65.0 * scl @@ -285,9 +279,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): bui.widget( edit=scrollwidget, left_widget=new_button, - right_widget=bui.get_special_widget('party_button') - if bui.app.ui_v1.use_toolbars - else None, + right_widget=( + bui.get_special_widget('party_button') + if bui.app.ui_v1.use_toolbars + else None + ), ) # make sure config exists @@ -329,9 +325,9 @@ class PlaylistCustomizeBrowserWindow(bui.Window): if self._selected_playlist_name is not None: cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Selection' - ] = self._selected_playlist_name + cfg[self._pvars.config_name + ' Playlist Selection'] = ( + self._selected_playlist_name + ) cfg.commit() bui.containerwidget( @@ -408,9 +404,11 @@ class PlaylistCustomizeBrowserWindow(bui.Window): text=self._get_playlist_display_name(pname), h_align='left', v_align='center', - color=(0.6, 0.6, 0.7, 1.0) - if pname == '__default__' - else (0.85, 0.85, 0.85, 1), + color=( + (0.6, 0.6, 0.7, 1.0) + if pname == '__default__' + else (0.85, 0.85, 0.85, 1) + ), always_highlight=True, on_select_call=bui.Call(self._select, pname, index), on_activate_call=bui.Call(self._edit_button.activate), @@ -458,12 +456,12 @@ class PlaylistCustomizeBrowserWindow(bui.Window): # if we want and also lets us pass it to the game (since we reset # the whole python environment that's not actually easy). cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Selection' - ] = self._selected_playlist_name - cfg[ - self._pvars.config_name + ' Playlist Randomize' - ] = self._do_randomize_val + cfg[self._pvars.config_name + ' Playlist Selection'] = ( + self._selected_playlist_name + ) + cfg[self._pvars.config_name + ' Playlist Randomize'] = ( + self._do_randomize_val + ) cfg.commit() def _new_playlist(self) -> None: @@ -536,12 +534,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window): # (we don't use len()-1 here because the default list adds one) assert self._selected_playlist_index is not None - if self._selected_playlist_index > len( - bui.app.config[self._pvars.config_name + ' Playlists'] - ): - self._selected_playlist_index = len( - bui.app.config[self._pvars.config_name + ' Playlists'] - ) + self._selected_playlist_index = min( + self._selected_playlist_index, + len(bui.app.config[self._pvars.config_name + ' Playlists']), + ) self._refresh() def _import_playlist(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py index b2d42b21..ace486ac 100644 --- a/src/assets/ba_data/python/bauiv1lib/playoptions.py +++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py @@ -33,6 +33,7 @@ class PlayOptionsWindow(PopupWindow): # pylint: disable=too-many-locals from bascenev1 import filter_playlist, get_map_class from bauiv1lib.playlist import PlaylistTypeVars + from bauiv1lib.config import ConfigNumberEdit self._r = 'gameListWindow' self._delegate = delegate @@ -52,7 +53,7 @@ class PlayOptionsWindow(PopupWindow): self._playlist = playlist self._width = 500.0 - self._height = 330.0 - 50.0 + self._height = 370.0 - 50.0 # In teams games, show the custom names/colors button. if self._sessiontype is bs.DualTeamSession: @@ -145,9 +146,7 @@ class PlayOptionsWindow(PopupWindow): scale = ( 1.69 if uiscale is bui.UIScale.SMALL - else 1.1 - if uiscale is bui.UIScale.MEDIUM - else 0.85 + else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.85 ) # Creates our _root_widget. super().__init__( @@ -275,13 +274,39 @@ class PlayOptionsWindow(PopupWindow): texture=bui.gettexture('lock'), ) + y_offs = 50 if show_shuffle_check_box else 0 + + # Series Length + y_offs2 = 40 if self._sessiontype is bs.DualTeamSession else 0 + self._series_length_numedit = ConfigNumberEdit( + parent=self.root_widget, + position=(100, 200 + y_offs + y_offs2), + configkey=( + 'FFA' if self._sessiontype is bs.FreeForAllSession else 'Teams' + ) + + ' Series Length', + displayname=bui.Lstr( + resource=self._r + + ( + '.pointsToWinText' + if self._sessiontype is bs.FreeForAllSession + else '.seriesLengthText' + ) + ), + minval=1.0, + increment=1.0 if self._sessiontype is bs.FreeForAllSession else 2.0, + fallback_value=( + 24 if self._sessiontype is bs.FreeForAllSession else 7 + ), + f=0, + ) + # Team names/colors. self._custom_colors_names_button: bui.Widget | None if self._sessiontype is bs.DualTeamSession: - y_offs = 50 if show_shuffle_check_box else 0 self._custom_colors_names_button = bui.buttonwidget( parent=self.root_widget, - position=(100, 200 + y_offs), + position=(100, 195 + y_offs), size=(290, 35), on_activate_call=bui.WeakCall(self._custom_colors_names_press), autoselect=True, @@ -304,9 +329,9 @@ class PlayOptionsWindow(PopupWindow): def _cb_callback(val: bool) -> None: self._do_randomize_val = val cfg = bui.app.config - cfg[ - self._pvars.config_name + ' Playlist Randomize' - ] = self._do_randomize_val + cfg[self._pvars.config_name + ' Playlist Randomize'] = ( + self._do_randomize_val + ) cfg.commit() if show_shuffle_check_box: diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py index a3b561b4..1899e854 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py @@ -43,9 +43,7 @@ class SoundtrackBrowserWindow(bui.Window): self._height = ( 340 if uiscale is bui.UIScale.SMALL - else 370 - if uiscale is bui.UIScale.MEDIUM - else 440 + else 370 if uiscale is bui.UIScale.MEDIUM else 440 ) spacing = 40.0 v = self._height - 40.0 @@ -60,13 +58,11 @@ class SoundtrackBrowserWindow(bui.Window): scale=( 2.3 if uiscale is bui.UIScale.SMALL - else 1.6 - if uiscale is bui.UIScale.MEDIUM - else 1.0 + else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 + ), + stack_offset=( + (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0) ), - stack_offset=(0, -18) - if uiscale is bui.UIScale.SMALL - else (0, 0), ) ) @@ -110,9 +106,7 @@ class SoundtrackBrowserWindow(bui.Window): scl = ( 1.0 if uiscale is bui.UIScale.SMALL - else 1.13 - if uiscale is bui.UIScale.MEDIUM - else 1.4 + else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4 ) v -= 60.0 * scl self._new_button = btn = bui.buttonwidget( @@ -245,9 +239,11 @@ class SoundtrackBrowserWindow(bui.Window): bui.widget( edit=self._scrollwidget, left_widget=self._new_button, - right_widget=bui.get_special_widget('party_button') - if bui.app.ui_v1.use_toolbars - else self._scrollwidget, + right_widget=( + bui.get_special_widget('party_button') + if bui.app.ui_v1.use_toolbars + else self._scrollwidget + ), ) self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0) @@ -286,8 +282,9 @@ class SoundtrackBrowserWindow(bui.Window): bui.getsound('shieldDown').play() assert self._selected_soundtrack_index is not None assert self._soundtracks is not None - if self._selected_soundtrack_index >= len(self._soundtracks): - self._selected_soundtrack_index = len(self._soundtracks) + self._selected_soundtrack_index = min( + self._selected_soundtrack_index, len(self._soundtracks) + ) self._refresh() def _delete_soundtrack(self) -> None: diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 4e3a9e8d..00fd591e 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -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 = 21766; +const int kEngineBuildNumber = 21772; const char* kEngineVersion = "1.7.33"; const int kEngineApiVersion = 8; diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py index 0fe8e7d8..6cc5cf4b 100644 --- a/tools/bacommon/servermanager.py +++ b/tools/bacommon/servermanager.py @@ -47,10 +47,14 @@ class ServerConfig: # Max devices in the party. Note that this does *NOT* mean max players. # Any device in the party can have more than one player on it if they have # multiple controllers. Also, this number currently includes the server so - # generally make it 1 bigger than you need. Max-players is not currently - # exposed but I'll try to add that soon. + # generally make it 1 bigger than you need. max_party_size: int = 6 + # Max players that can join a session. If present this will override the + # session's preferred max_players. if a value below 0 is given player limit + # will be removed. + session_max_players_override: int | None = None + # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative) # This value is ignored if you supply a playlist_code (see below). session_type: str = 'ffa' diff --git a/tools/efro/cloudshell.py b/tools/efro/cloudshell.py index 3965d072..2d6b63e7 100644 --- a/tools/efro/cloudshell.py +++ b/tools/efro/cloudshell.py @@ -32,6 +32,7 @@ class HostConfig: user: str = 'ubuntu' port: int = 22 mosh_port: int | None = None + mosh_port_2: int | None = None mosh_server_path: str | None = None mosh_shell: str = 'sh' workspaces_root: str = '/home/${USER}/cloudshell_workspaces' diff --git a/tools/efro/util.py b/tools/efro/util.py index 5fc5b3ec..5d1b2f7d 100644 --- a/tools/efro/util.py +++ b/tools/efro/util.py @@ -236,7 +236,7 @@ class DirtyBit: auto_dirty_seconds: float | None = None, min_update_interval: float | None = None, ): - curtime = time.time() + curtime = time.monotonic() self._retry_interval = retry_interval self._auto_dirty_seconds = auto_dirty_seconds self._min_update_interval = min_update_interval @@ -268,11 +268,13 @@ class DirtyBit: # If we're freshly clean, set our next auto-dirty time (if we have # one). if self._dirty and not value and self._auto_dirty_seconds is not None: - self._next_auto_dirty_time = time.time() + self._auto_dirty_seconds + self._next_auto_dirty_time = ( + time.monotonic() + self._auto_dirty_seconds + ) # If we're freshly dirty, schedule an immediate update. if not self._dirty and value: - self._next_update_time = time.time() + self._next_update_time = time.monotonic() # If they want to enforce a minimum update interval, # push out the next update time if it hasn't been long enough. @@ -295,7 +297,7 @@ class DirtyBit: Takes into account the amount of time passed since the target was marked dirty or since should_update last returned True. """ - curtime = time.time() + curtime = time.monotonic() # Auto-dirty ourself if we're into that. if ( @@ -871,3 +873,11 @@ def ago_str( timedelta_str(now - timeval, maxparts=maxparts, decimals=decimals) + ' ago' ) + + +def split_list(input_list: list[T], max_length: int) -> list[list[T]]: + """Split a single list into smaller lists.""" + return [ + input_list[i : i + max_length] + for i in range(0, len(input_list), max_length) + ] diff --git a/tools/efrotools/code.py b/tools/efrotools/code.py index 66f9374b..762c0961 100644 --- a/tools/efrotools/code.py +++ b/tools/efrotools/code.py @@ -103,14 +103,14 @@ def format_project_cpp_files(projroot: Path, full: bool) -> None: dirtyfiles = cache.get_dirty_files() def format_file(filename: str) -> dict[str, Any]: - start_time = time.time() + start_time = time.monotonic() # Note: seems os.system does not unlock the gil; # make sure to use subprocess. result = subprocess.call(['clang-format', '-i', filename]) if result != 0: raise RuntimeError(f'Formatting failed for {filename}') - duration = time.time() - start_time + duration = time.monotonic() - start_time print(f'Formatted {filename} in {duration:.2f} seconds.') sys.stdout.flush() return {'f': filename, 't': duration} @@ -514,7 +514,7 @@ def _run_pylint( from pylint import lint from efro.terminal import Clr - start_time = time.time() + start_time = time.monotonic() args = ['--rcfile', str(pylintrc), '--output-format=colorized'] args += dirtyfiles @@ -540,7 +540,7 @@ def _run_pylint( if run.linter.msg_status != 0: raise CleanError('Pylint failed.') - duration = time.time() - start_time + duration = time.monotonic() - start_time print( f'{Clr.GRN}Pylint passed for {name}' f' in {duration:.1f} seconds.{Clr.RST}' @@ -796,12 +796,12 @@ def mypy(projroot: Path, full: bool) -> None: filenames = get_script_filenames(projroot) desc = '(full)' if full else '(incremental)' print(f'{Clr.BLU}Running Mypy {desc}...{Clr.RST}', flush=True) - starttime = time.time() + starttime = time.monotonic() try: mypy_files(projroot, filenames, full) except Exception as exc: raise CleanError('Mypy failed.') from exc - duration = time.time() - starttime + duration = time.monotonic() - starttime print( f'{Clr.GRN}Mypy passed in {duration:.1f} seconds.{Clr.RST}', flush=True ) @@ -819,7 +819,7 @@ def dmypy(projroot: Path) -> None: return print('Running Mypy (daemon)...', flush=True) - starttime = time.time() + starttime = time.monotonic() try: args = [ 'dmypy', @@ -834,7 +834,7 @@ def dmypy(projroot: Path) -> None: subprocess.run(args, check=True) except Exception as exc: raise CleanError('Mypy daemon: fail.') from exc - duration = time.time() - starttime + duration = time.monotonic() - starttime print( f'{Clr.GRN}Mypy daemon passed in {duration:.1f} seconds.{Clr.RST}', flush=True, @@ -893,7 +893,7 @@ def _run_idea_inspections( from efro.terminal import Clr - start_time = time.time() + start_time = time.monotonic() print( f'{Clr.BLU}{displayname} checking' f' {len(scripts)} file(s)...{Clr.RST}', @@ -944,7 +944,7 @@ def _run_idea_inspections( f'{Clr.SRED}{displayname} inspection' f' found {total_errors} error(s).{Clr.RST}' ) - duration = time.time() - start_time + duration = time.monotonic() - start_time print( f'{Clr.GRN}{displayname} passed for {len(scripts)} files' f' in {duration:.1f} seconds.{Clr.RST}', diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py index 1177ec79..82120d75 100644 --- a/tools/efrotools/pybuild.py +++ b/tools/efrotools/pybuild.py @@ -37,8 +37,8 @@ VERSION_MIN_TVOS = '9.0' # why-is-lldb-generating-exc-bad-instruction-with-user-compiled-library-on-macos # # For now will try to ride out this 3.0 LTS version as long as possible. -OPENSSL_VER_APPLE = '3.0.12' -OPENSSL_VER_ANDROID = '3.0.12' +OPENSSL_VER_APPLE = '3.0.13' +OPENSSL_VER_ANDROID = '3.0.13' LIBFFI_VER_APPLE = '3.4.4' BZIP2_VER_APPLE = '1.0.8'