Merge branch 'master' into tournament_entry

This commit is contained in:
TrialTemp 2024-03-05 02:30:16 -06:00 committed by GitHub
commit a1bb24e3e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 268 additions and 134 deletions

80
.efrocachemap generated
View File

@ -421,41 +421,41 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
"build/assets/ba_data/data/langdata.json": "5273cf3bfe2d25d70395690bf3c21825", "build/assets/ba_data/data/langdata.json": "66f05313ffa9880373066332cff4594c",
"build/assets/ba_data/data/languages/arabic.json": "0db32e21b6d5337ccca478381744aa88", "build/assets/ba_data/data/languages/arabic.json": "0db32e21b6d5337ccca478381744aa88",
"build/assets/ba_data/data/languages/belarussian.json": "09954e550d13d3d9cb5a635a1d32a151", "build/assets/ba_data/data/languages/belarussian.json": "09954e550d13d3d9cb5a635a1d32a151",
"build/assets/ba_data/data/languages/chinese.json": "1360ffde06828b63ce4fe956c3c3cd1d", "build/assets/ba_data/data/languages/chinese.json": "bb51b5aa614830c561e8fe2542a9ab8a",
"build/assets/ba_data/data/languages/chinesetraditional.json": "319565f8a15667488f48dbce59278e39", "build/assets/ba_data/data/languages/chinesetraditional.json": "319565f8a15667488f48dbce59278e39",
"build/assets/ba_data/data/languages/croatian.json": "e671b9d0c012be1a30f9c15eb1b81860", "build/assets/ba_data/data/languages/croatian.json": "e671b9d0c012be1a30f9c15eb1b81860",
"build/assets/ba_data/data/languages/czech.json": "7171420af6d662e3a47b64576850a384", "build/assets/ba_data/data/languages/czech.json": "15be4fd59895135bad0265f79b362d5b",
"build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7",
"build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343", "build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343",
"build/assets/ba_data/data/languages/english.json": "28a1c17925aba4f4f908732e5e5cb266", "build/assets/ba_data/data/languages/english.json": "6501b04faba1d81e26725dfa19b15667",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "fe3f1efcb47efaa23524300d21728933", "build/assets/ba_data/data/languages/filipino.json": "fe3f1efcb47efaa23524300d21728933",
"build/assets/ba_data/data/languages/french.json": "cc8ac601f5443dd539893728db983f5c", "build/assets/ba_data/data/languages/french.json": "917e4174d6f0eb7f00c27fd79cfbb924",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad", "build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "ab9571486f703b8d57eab61dbf1d54d8", "build/assets/ba_data/data/languages/gibberish.json": "0c1c4ac59d82a58c4b89328d44510d72",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3", "build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "90f54663e15d85a163f1848a8e9d8d07", "build/assets/ba_data/data/languages/hindi.json": "90f54663e15d85a163f1848a8e9d8d07",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "9103845242b572aa8ba48e24f81ddb68", "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/korean.json": "4e3524327a0174250aff5e1ef4c0c597",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38", "build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "1a4c74ad9089cd746ad6fda4186c2220", "build/assets/ba_data/data/languages/persian.json": "c4144aebf2900fc655860de891d16f83",
"build/assets/ba_data/data/languages/polish.json": "9d22c6643c097c4cb268d0d6b6319cd4", "build/assets/ba_data/data/languages/polish.json": "9d22c6643c097c4cb268d0d6b6319cd4",
"build/assets/ba_data/data/languages/portuguese.json": "b52164747c6308fc9d054eb6c0ff3c54", "build/assets/ba_data/data/languages/portuguese.json": "b52164747c6308fc9d054eb6c0ff3c54",
"build/assets/ba_data/data/languages/romanian.json": "b3e46efd6f869dbd78014570e037c290", "build/assets/ba_data/data/languages/romanian.json": "b3e46efd6f869dbd78014570e037c290",
"build/assets/ba_data/data/languages/russian.json": "30d5f3d2415088e1fb6558fcd6ccfa98", "build/assets/ba_data/data/languages/russian.json": "0590f49889616b5279be569dea926e17",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "c00fb27cf982ffad5a4370ad3b16bd21", "build/assets/ba_data/data/languages/slovak.json": "c00fb27cf982ffad5a4370ad3b16bd21",
"build/assets/ba_data/data/languages/spanish.json": "e3e9ac8f96f52302a480c7e955aed71f", "build/assets/ba_data/data/languages/spanish.json": "b2edb923fdca973a16f0efb1acc26a97",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac", "build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a", "build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a",
"build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945", "build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945",
"build/assets/ba_data/data/languages/turkish.json": "2be25c89ca754341f27750e0d595f31e", "build/assets/ba_data/data/languages/turkish.json": "db71f3776072b7a15ef37b1bb1245795",
"build/assets/ba_data/data/languages/ukrainian.json": "b54a38e93deebafa5706ba2d1f626892", "build/assets/ba_data/data/languages/ukrainian.json": "3d75d21205c82db34fb1a1b014592747",
"build/assets/ba_data/data/languages/venetian.json": "f896fc3df13a42f1bef8813ca80b1a09", "build/assets/ba_data/data/languages/venetian.json": "f896fc3df13a42f1bef8813ca80b1a09",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba", "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/big_g.json": "1dd301d490643088a435ce75df971054",
@ -4060,26 +4060,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a7161a4100172e2bb42b838a9851c353", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5db2ea1c6bacab3fe60eb948aaa4afdb",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "9a63e694db2ed7536374c58a45ce65d3", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "2b476166b869112112d57f26833a8381",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "9668ef38ddc59fadf323cf460c8b692c", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "7b8d2cb654ab2c022584114ed9910c38",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "8f58837d238dba248ae2e23e20bc3f06", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "846150203fe0611a71ff832176579ce5",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f8a6e20f3fffd198494adfba4e884588", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "683c3d3b7ac3b052f3ecc3fef36fc13a",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "255205c95d519a594041fc239e435883", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "e41d6aeb7a2e335ebcc701a35d45df8c",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "36facf256b69a8c0037370e23c82470d", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f0a97f7c34a78bfd829f460a9f4ea81c",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d78aef348baf274f476ce9e344b80122", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d1e697c045e3b4092ec35fb8f3b4bd25",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f1b9732cc7e7728dcedc39a55d9afea2", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "952c02766cecd280af3e9b77c80e91e1",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "8cfc0e04c10a315cce91dae041dfc3ff", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "bceae148212f47bfc9acf60ea52b1003",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "a9fec1930c851f8ed743b08669df2d75", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e9ea0d09ba4af6253025cbe3aa8469cf",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "a6fcaa9d7eb10412787e4416f3536bb9", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "d2cf18fbc6d815268790532bc38d2434",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "a39230df064404a3b1dd18a644f2f6d6", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1134322221c0ccea4057e462d9fa5197",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "04e971f62a000383a13eb021e30afa7b", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "30d10d34fb0e14b8f7ceec1760b521d6",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "a93fe4f0cfb3c2c9061df049068230ac", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d132bc58d9744941144244484bb005a6",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "0f08a84bb09589991faaca9250171e3c", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "5ae4aef6e0291175a3a9e3b77adcc0c0",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "12c079e62d0125b8a24b16e418405ba9", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "abb92db084cdc165d7c1ed500be919ba",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "eb0d76fd3be03082572b0d835df05252", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "57bb6f6b5dadbc8f05fbab3271ef8abb",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "e4268ef0b50e94747081ee83666d80ab", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c4a68563f1237c1679c870def5d91b1a",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f4646fecfed11f5e2b2ee5c892b2940a", "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/debug/libballisticaplus.a": "ee36a39fd0f524989cb68930c89c8868",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "dbed9145e5db116d92aa47cb9e98da39", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "dbed9145e5db116d92aa47cb9e98da39",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "ee36a39fd0f524989cb68930c89c8868", "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_gui/release/libballisticaplus.a": "0896e849885cef50bcf33ce863efa7d2",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "e53c808357cc0a2f0da7b870be147083", "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/mac_x86_64_server/release/libballisticaplus.a": "0896e849885cef50bcf33ce863efa7d2",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "f53601899c23c90c2b7e65836c805d8e", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "72901cf56d898442b6bcf4ecafd5cd65",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f31b348a7612e5fa3a968f3cc81cefcd", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "c185b4f41dfc69c133a75260b95421d1",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "b8339779a2571b169f9d63c11aa7dfa3", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "096880b9e8faac99a72d234a61ddd624",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "511bc23565e830778d5ff183a201579d", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "266f4e6a3d8b39c97ee7b5e766e8b207",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "b02faf2aa2df1de233a0549295e6b0ed", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0927775fb993a977de90e4671a09e996",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2e07aaa6d445caf3b33d79dc40bd2475", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "53fcf97128862b34771ca967f88641c8",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "00f50fb4a3a9bbecd1b1188b78abae4b", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "160a2caaa393f9ddb40ffebc7546e6bb",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "88fb67cb3f3752f0b0db1d583f90490d", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "b611c090513a21e2fe90e56582724e9d", "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.inc": "72bfed2cce8ff19741989dec28302f3f",

View File

@ -1,4 +1,4 @@
### 1.7.33 (build 21770, api 8, 2024-03-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 - 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). 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 - Added a 'Show Demos When Idle' option in advanced settings. If enabled, the
@ -20,11 +20,22 @@
languages; I feel it helps keep logic more understandable and should help us languages; I feel it helps keep logic more understandable and should help us
catch problems where a base class changes or removes a method and child catch problems where a base class changes or removes a method and child
classes forget to adapt to the change. classes forget to adapt to the change.
- Custom spaz "curse_time" values now work properly. (Thanks Temp!)
- Implemented `efro.dataclassio.IOMultiType` which will make my life a lot - Implemented `efro.dataclassio.IOMultiType` which will make my life a lot
easier. easier.
- Punches no longer physically affect powerup boxes which should make it easier - Punches no longer physically affect powerup boxes which should make it easier
to grab the powerup (Thanks VinniTR!). to grab the powerup (Thanks VinniTR!).
- The 'Manual' party tab now supports entering IPv6 addresses (Thanks brostosjoined!). - 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!)
- Added a 'Practice' button in tournaments, letting you play them free of - Added a 'Practice' button in tournaments, letting you play them free of
charge while not submitting scores. (Thanks Temp!) charge while not submitting scores. (Thanks Temp!)

View File

@ -37,12 +37,12 @@
- Added feature - Added feature
### Vishal332008 ### Vishal332008
- Bug Fixer - QoL and Bug Fixer
- Modder - Modder
### Era0S ### Era0S
- Community Suggestions Implementer - Community Suggestions Implementer
- Bug Fixer - QoL and Bug Fixer
- Modder - Modder
### VinniTR ### VinniTR
@ -52,6 +52,7 @@
- Created the original "reject_recently_left_players" plugin - Created the original "reject_recently_left_players" plugin
### Temp (3alTemp) ### Temp (3alTemp)
- Original idea for customizable series length on GUI builds
- Modder & Bug Fixer - Modder & Bug Fixer
### brostos ### brostos

View File

@ -136,7 +136,9 @@ def create_user_system_scripts() -> None:
path = f'{env.python_directory_user}/sys/{env.version}' path = f'{env.python_directory_user}/sys/{env.version}'
pathtmp = path + '_tmp' pathtmp = path + '_tmp'
if os.path.exists(path): 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): if os.path.exists(pathtmp):
shutil.rmtree(pathtmp) shutil.rmtree(pathtmp)
@ -159,6 +161,7 @@ def create_user_system_scripts() -> None:
f"'\nRestart {_babase.appname()} to use them." f"'\nRestart {_babase.appname()} to use them."
f' (use babase.quit() to exit the game)' 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': if app.classic is not None and app.classic.platform == 'android':
print( print(
'Note: the new files may not be visible via ' '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'Restart {_babase.appname()} to use internal'
f' scripts. (use babase.quit() to exit the game)' f' scripts. (use babase.quit() to exit the game)'
) )
_babase.screenmessage('Deleted User System Scripts')
else: else:
print(f"User system scripts not found at '{path}'.") print(f"User system scripts not found at '{path}'.")

View File

@ -310,9 +310,7 @@ class ServerController:
typename = ( typename = (
'teams' 'teams'
if result['playlistType'] == 'Team Tournament' if result['playlistType'] == 'Team Tournament'
else 'ffa' else 'ffa' if result['playlistType'] == 'Free-for-All' else '??'
if result['playlistType'] == 'Free-for-All'
else '??'
) )
plistname = result['playlistName'] plistname = result['playlistName']
print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}') print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}')
@ -390,14 +388,14 @@ class ServerController:
if sessiontype is bascenev1.FreeForAllSession: if sessiontype is bascenev1.FreeForAllSession:
appcfg['Free-for-All Playlist Selection'] = self._playlist_name appcfg['Free-for-All Playlist Selection'] = self._playlist_name
appcfg[ appcfg['Free-for-All Playlist Randomize'] = (
'Free-for-All Playlist Randomize' self._config.playlist_shuffle
] = self._config.playlist_shuffle )
elif sessiontype is bascenev1.DualTeamSession: elif sessiontype is bascenev1.DualTeamSession:
appcfg['Team Tournament Playlist Selection'] = self._playlist_name appcfg['Team Tournament Playlist Selection'] = self._playlist_name
appcfg[ appcfg['Team Tournament Playlist Randomize'] = (
'Team Tournament Playlist Randomize' self._config.playlist_shuffle
] = self._config.playlist_shuffle )
elif sessiontype is bascenev1.CoopSession: elif sessiontype is bascenev1.CoopSession:
classic.coop_session_args = { classic.coop_session_args = {
'campaign': self._config.coop_campaign, 'campaign': self._config.coop_campaign,
@ -406,6 +404,10 @@ class ServerController:
else: else:
raise RuntimeError(f'Unknown session type {sessiontype}') 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.teams_series_length = self._config.teams_series_length
classic.ffa_series_length = self._config.ffa_series_length classic.ffa_series_length = self._config.ffa_series_length
@ -427,6 +429,10 @@ class ServerController:
self._config.player_rejoin_cooldown self._config.player_rejoin_cooldown
) )
bascenev1.set_max_players_override(
self._config.session_max_players_override
)
# And here.. we.. go. # And here.. we.. go.
if self._config.stress_test_players is not None: if self._config.stress_test_players is not None:
# Special case: run a stress test. # Special case: run a stress test.

View File

@ -103,8 +103,8 @@ class ClassicSubsystem(babase.AppSubsystem):
self.maps: dict[str, type[bascenev1.Map]] = {} self.maps: dict[str, type[bascenev1.Map]] = {}
# Gameplay. # Gameplay.
self.teams_series_length = 7 self.teams_series_length = 7 # deprecated, left for old mods
self.ffa_series_length = 24 self.ffa_series_length = 24 # deprecated, left for old mods
self.coop_session_args: dict = {} self.coop_session_args: dict = {}
# UI. # UI.

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21770 TARGET_BALLISTICA_BUILD = 21772
TARGET_BALLISTICA_VERSION = '1.7.33' TARGET_BALLISTICA_VERSION = '1.7.33'

View File

@ -231,7 +231,11 @@ from bascenev1._settings import (
IntSetting, IntSetting,
Setting, 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._stats import PlayerScoredMessage, PlayerRecord, Stats
from bascenev1._team import SessionTeam, Team, EmptyTeam from bascenev1._team import SessionTeam, Team, EmptyTeam
from bascenev1._teamgame import TeamGameActivity from bascenev1._teamgame import TeamGameActivity
@ -426,6 +430,7 @@ __all__ = [
'set_public_party_queue_enabled', 'set_public_party_queue_enabled',
'set_public_party_stats_url', 'set_public_party_stats_url',
'set_player_rejoin_cooldown', 'set_player_rejoin_cooldown',
'set_max_players_override',
'set_replay_speed_exponent', 'set_replay_speed_exponent',
'set_touchscreen_editing', 'set_touchscreen_editing',
'setmusic', 'setmusic',

View File

@ -67,8 +67,8 @@ class MultiTeamSession(Session):
max_players=self.get_max_players(), max_players=self.get_max_players(),
) )
self._series_length: int = classic.teams_series_length self._series_length: int = int(cfg.get('Teams Series Length', 7))
self._ffa_series_length: int = classic.ffa_series_length self._ffa_series_length: int = int(cfg.get('FFA Series Length', 24))
show_tutorial = cfg.get('Show Tutorial', True) show_tutorial = cfg.get('Show Tutorial', True)

View File

@ -23,6 +23,9 @@ if TYPE_CHECKING:
# such as skipping respawn waits. # such as skipping respawn waits.
_g_player_rejoin_cooldown: float = 0.0 _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: def set_player_rejoin_cooldown(cooldown: float) -> None:
"""Set the cooldown for individual players rejoining after leaving.""" """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) _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: class Session:
"""Defines a high level series of bascenev1.Activity-es. """Defines a high level series of bascenev1.Activity-es.
@ -162,7 +171,11 @@ class Session:
self.sessionteams = [] self.sessionteams = []
self.sessionplayers = [] self.sessionplayers = []
self.min_players = min_players 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._submit_score = submit_score self._submit_score = submit_score
self.customdata = {} self.customdata = {}
@ -257,7 +270,7 @@ class Session:
babase.app.classic is not None babase.app.classic is not None
and babase.app.classic.stress_test_update_timer is 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 # Print a rejection message *only* to the client trying to
# join (prevents spamming everyone else in the game). # join (prevents spamming everyone else in the game).
_bascenev1.getsound('error').play() _bascenev1.getsound('error').play()

View File

@ -629,7 +629,8 @@ class Spaz(bs.Actor):
1000.0 * (tval + self.curse_time) 1000.0 * (tval + self.curse_time)
) )
self._curse_timer = bs.Timer( 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: def equip_boxing_gloves(self) -> None:

View File

@ -529,6 +529,30 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
if team.flag_return_touches < 0: if team.flag_return_touches < 0:
logging.exception('CTF 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: def _flash_base(self, team: Team, length: float = 2.0) -> None:
light = bs.newnode( light = bs.newnode(
'light', 'light',
@ -591,6 +615,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage): if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment standard behavior. super().handlemessage(msg) # Augment standard behavior.
self._handle_death_flag_capture(msg.getplayer(Player))
self.respawn_player(msg.getplayer(Player)) self.respawn_player(msg.getplayer(Player))
elif isinstance(msg, FlagDiedMessage): elif isinstance(msg, FlagDiedMessage):
@ -617,3 +642,8 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
else: else:
super().handlemessage(msg) 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)

View File

@ -94,6 +94,8 @@ class ConfigNumberEdit:
changesound: bool = True, changesound: bool = True,
textscale: float = 1.0, textscale: float = 1.0,
as_percent: bool = False, as_percent: bool = False,
fallback_value: float = 0.0,
f: int = 1,
): ):
if displayname is None: if displayname is None:
displayname = configkey displayname = configkey
@ -103,8 +105,12 @@ class ConfigNumberEdit:
self._maxval = maxval self._maxval = maxval
self._increment = increment self._increment = increment
self._callback = callback self._callback = callback
try:
self._value = bui.app.config.resolve(configkey) self._value = bui.app.config.resolve(configkey)
except ValueError:
self._value = bui.app.config.get(configkey, fallback_value)
self._as_percent = as_percent self._as_percent = as_percent
self._f = f
self.nametext = bui.textwidget( self.nametext = bui.textwidget(
parent=parent, parent=parent,
@ -171,5 +177,5 @@ class ConfigNumberEdit:
if self._as_percent: if self._as_percent:
val = f'{round(self._value*100.0)}%' val = f'{round(self._value*100.0)}%'
else: else:
val = f'{self._value:.1f}' val = f'{self._value:.{self._f}f}'
bui.textwidget(edit=self.valuetext, text=val) bui.textwidget(edit=self.valuetext, text=val)

View File

@ -90,9 +90,7 @@ class CoopBrowserWindow(bui.Window):
self._height = ( self._height = (
657 657
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else 730 else 730 if uiscale is bui.UIScale.MEDIUM else 800
if uiscale is bui.UIScale.MEDIUM
else 800
) )
app.ui_v1.set_main_menu_location('Coop Select') app.ui_v1.set_main_menu_location('Coop Select')
self._r = 'coopSelectWindow' self._r = 'coopSelectWindow'
@ -104,6 +102,19 @@ class CoopBrowserWindow(bui.Window):
'campaignDifficulty', 'easy' '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__( super().__init__(
root_widget=bui.containerwidget( root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra), size=(self._width, self._height + top_extra),
@ -112,17 +123,13 @@ class CoopBrowserWindow(bui.Window):
stack_offset=( stack_offset=(
(0, -15) (0, -15)
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else (0, 0) else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0)
if uiscale is bui.UIScale.MEDIUM
else (0, 0)
), ),
transition=transition, transition=transition,
scale=( scale=(
1.2 1.2
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else 0.8 else 0.8 if uiscale is bui.UIScale.MEDIUM else 0.75
if uiscale is bui.UIScale.MEDIUM
else 0.75
), ),
) )
) )
@ -271,9 +278,11 @@ class CoopBrowserWindow(bui.Window):
self._scrollwidget = bui.scrollwidget( self._scrollwidget = bui.scrollwidget(
parent=self._root_widget, parent=self._root_widget,
highlight=False, highlight=False,
position=(65 + x_inset, 120) position=(
(65 + x_inset, 120)
if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars
else (65 + x_inset, 70), else (65 + x_inset, 70)
),
size=(self._scroll_width, self._scroll_height), size=(self._scroll_width, self._scroll_height),
simple_culling_v=10.0, simple_culling_v=10.0,
claims_left_right=True, claims_left_right=True,
@ -421,12 +430,14 @@ class CoopBrowserWindow(bui.Window):
if tbtn.time_remaining_value_text is not None: if tbtn.time_remaining_value_text is not None:
bui.textwidget( bui.textwidget(
edit=tbtn.time_remaining_value_text, edit=tbtn.time_remaining_value_text,
text=bui.timestring(tbtn.time_remaining, centi=False) text=(
bui.timestring(tbtn.time_remaining, centi=False)
if ( if (
tbtn.has_time_remaining tbtn.has_time_remaining
and self._tourney_data_up_to_date and self._tourney_data_up_to_date
) )
else '-', else '-'
),
) )
# Also adjust the ad icon visibility. # Also adjust the ad icon visibility.
@ -447,9 +458,9 @@ class CoopBrowserWindow(bui.Window):
try: try:
bui.imagewidget( bui.imagewidget(
edit=self._hard_button_lock_image, edit=self._hard_button_lock_image,
opacity=0.0 opacity=(
if bui.app.classic.accounts.have_pro_options() 0.0 if bui.app.classic.accounts.have_pro_options() else 1.0
else 1.0, ),
) )
except Exception: except Exception:
logging.exception('Error updating campaign lock.') logging.exception('Error updating campaign lock.')
@ -559,12 +570,16 @@ class CoopBrowserWindow(bui.Window):
enable_sound=False, enable_sound=False,
on_activate_call=bui.Call(self._set_campaign_difficulty, 'easy'), on_activate_call=bui.Call(self._set_campaign_difficulty, 'easy'),
on_select_call=bui.Call(self.sel_change, 'campaign', 'easyButton'), on_select_call=bui.Call(self.sel_change, 'campaign', 'easyButton'),
color=sel_color color=(
sel_color
if self._campaign_difficulty == 'easy' if self._campaign_difficulty == 'easy'
else un_sel_color, else un_sel_color
textcolor=sel_textcolor ),
textcolor=(
sel_textcolor
if self._campaign_difficulty == 'easy' if self._campaign_difficulty == 'easy'
else un_sel_textcolor, else un_sel_textcolor
),
) )
bui.widget(edit=self._easy_button, show_buffer_left=100) bui.widget(edit=self._easy_button, show_buffer_left=100)
if self._selected_campaign_level == 'easyButton': if self._selected_campaign_level == 'easyButton':
@ -585,12 +600,16 @@ class CoopBrowserWindow(bui.Window):
enable_sound=False, enable_sound=False,
on_activate_call=bui.Call(self._set_campaign_difficulty, 'hard'), on_activate_call=bui.Call(self._set_campaign_difficulty, 'hard'),
on_select_call=bui.Call(self.sel_change, 'campaign', 'hardButton'), on_select_call=bui.Call(self.sel_change, 'campaign', 'hardButton'),
color=sel_color_hard color=(
sel_color_hard
if self._campaign_difficulty == 'hard' if self._campaign_difficulty == 'hard'
else un_sel_color, else un_sel_color
textcolor=sel_textcolor ),
textcolor=(
sel_textcolor
if self._campaign_difficulty == 'hard' if self._campaign_difficulty == 'hard'
else un_sel_textcolor, else un_sel_textcolor
),
) )
self._hard_button_lock_image = bui.imagewidget( self._hard_button_lock_image = bui.imagewidget(
parent=parent_widget, parent=parent_widget,
@ -960,35 +979,43 @@ class CoopBrowserWindow(bui.Window):
for i, tbutton in enumerate(self._tournament_buttons): for i, tbutton in enumerate(self._tournament_buttons):
bui.widget( bui.widget(
edit=tbutton.button, edit=tbutton.button,
up_widget=self._tournament_info_button up_widget=(
self._tournament_info_button
if i == 0 if i == 0
else self._tournament_buttons[i - 1].button, else self._tournament_buttons[i - 1].button
down_widget=self._tournament_buttons[(i + 1)].button ),
down_widget=(
self._tournament_buttons[(i + 1)].button
if i + 1 < len(self._tournament_buttons) if i + 1 < len(self._tournament_buttons)
else custom_h_scroll, else custom_h_scroll
),
) )
bui.widget( bui.widget(
edit=tbutton.more_scores_button, edit=tbutton.more_scores_button,
down_widget=self._tournament_buttons[ down_widget=(
(i + 1) self._tournament_buttons[(i + 1)].current_leader_name_text
].current_leader_name_text
if i + 1 < len(self._tournament_buttons) if i + 1 < len(self._tournament_buttons)
else custom_h_scroll, else custom_h_scroll
),
) )
bui.widget( bui.widget(
edit=tbutton.current_leader_name_text, edit=tbutton.current_leader_name_text,
up_widget=self._tournament_info_button up_widget=(
self._tournament_info_button
if i == 0 if i == 0
else self._tournament_buttons[i - 1].more_scores_button, else self._tournament_buttons[i - 1].more_scores_button
),
) )
for btn in self._custom_buttons: for btn in self._custom_buttons:
try: try:
bui.widget( bui.widget(
edit=btn.get_button(), edit=btn.get_button(),
up_widget=tournament_h_scroll up_widget=(
tournament_h_scroll
if self._tournament_buttons if self._tournament_buttons
else self._tournament_info_button, else self._tournament_info_button
),
) )
except Exception: except Exception:
logging.exception('Error wiring up custom buttons.') logging.exception('Error wiring up custom buttons.')
@ -1042,8 +1069,9 @@ class CoopBrowserWindow(bui.Window):
def _switch_to_score( def _switch_to_score(
self, self,
show_tab: StoreBrowserWindow.TabID show_tab: (
| None = StoreBrowserWindow.TabID.EXTRAS, StoreBrowserWindow.TabID | None
) = StoreBrowserWindow.TabID.EXTRAS,
) -> None: ) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt

View File

@ -33,6 +33,7 @@ class PlayOptionsWindow(PopupWindow):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
from bascenev1 import filter_playlist, get_map_class from bascenev1 import filter_playlist, get_map_class
from bauiv1lib.playlist import PlaylistTypeVars from bauiv1lib.playlist import PlaylistTypeVars
from bauiv1lib.config import ConfigNumberEdit
self._r = 'gameListWindow' self._r = 'gameListWindow'
self._delegate = delegate self._delegate = delegate
@ -52,7 +53,7 @@ class PlayOptionsWindow(PopupWindow):
self._playlist = playlist self._playlist = playlist
self._width = 500.0 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. # In teams games, show the custom names/colors button.
if self._sessiontype is bs.DualTeamSession: if self._sessiontype is bs.DualTeamSession:
@ -145,9 +146,7 @@ class PlayOptionsWindow(PopupWindow):
scale = ( scale = (
1.69 1.69
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else 1.1 else 1.1 if uiscale is bui.UIScale.MEDIUM else 0.85
if uiscale is bui.UIScale.MEDIUM
else 0.85
) )
# Creates our _root_widget. # Creates our _root_widget.
super().__init__( super().__init__(
@ -275,13 +274,39 @@ class PlayOptionsWindow(PopupWindow):
texture=bui.gettexture('lock'), 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. # Team names/colors.
self._custom_colors_names_button: bui.Widget | None self._custom_colors_names_button: bui.Widget | None
if self._sessiontype is bs.DualTeamSession: if self._sessiontype is bs.DualTeamSession:
y_offs = 50 if show_shuffle_check_box else 0
self._custom_colors_names_button = bui.buttonwidget( self._custom_colors_names_button = bui.buttonwidget(
parent=self.root_widget, parent=self.root_widget,
position=(100, 200 + y_offs), position=(100, 195 + y_offs),
size=(290, 35), size=(290, 35),
on_activate_call=bui.WeakCall(self._custom_colors_names_press), on_activate_call=bui.WeakCall(self._custom_colors_names_press),
autoselect=True, autoselect=True,
@ -304,9 +329,9 @@ class PlayOptionsWindow(PopupWindow):
def _cb_callback(val: bool) -> None: def _cb_callback(val: bool) -> None:
self._do_randomize_val = val self._do_randomize_val = val
cfg = bui.app.config cfg = bui.app.config
cfg[ cfg[self._pvars.config_name + ' Playlist Randomize'] = (
self._pvars.config_name + ' Playlist Randomize' self._do_randomize_val
] = self._do_randomize_val )
cfg.commit() cfg.commit()
if show_shuffle_check_box: if show_shuffle_check_box:

View File

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

View File

@ -47,10 +47,14 @@ class ServerConfig:
# Max devices in the party. Note that this does *NOT* mean max players. # 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 # 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 # multiple controllers. Also, this number currently includes the server so
# generally make it 1 bigger than you need. Max-players is not currently # generally make it 1 bigger than you need.
# exposed but I'll try to add that soon.
max_party_size: int = 6 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) # Options here are 'ffa' (free-for-all), 'teams' and 'coop' (cooperative)
# This value is ignored if you supply a playlist_code (see below). # This value is ignored if you supply a playlist_code (see below).
session_type: str = 'ffa' session_type: str = 'ffa'