mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 21:37:57 +08:00
switching from yapf to black for python formatting
This commit is contained in:
parent
eea5c2130e
commit
86f6933e54
@ -3997,56 +3997,56 @@
|
||||
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/f5/8b/14895df9caf46f326a3c939b34a4",
|
||||
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
|
||||
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
|
||||
"assets/build/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/27/16/71d2713a32c66caf37806f645a71",
|
||||
"assets/build/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/c6/4c/47fe21bbcd938711d1ec2a19589d",
|
||||
"assets/build/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/b1/38/beac9de90bee75363d1de76706b4",
|
||||
"assets/build/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/f1/8d/934610811db8bdd58ea84a8f4408",
|
||||
"assets/build/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/08/ed/d671c39a3ece6362a6d985112c8e",
|
||||
"assets/build/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/4d/71/1292911f5369bdb83ef6a34921c0",
|
||||
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
|
||||
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
|
||||
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/fe/97/44d57cdd1e67e3fd8f55c0c31cad",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/64/91/fd33398c70b862e46adb6c39e9e1",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e9/15/70169fee1aac6a96861e4862b889",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/57/68/9d3fdec5450f357b6c57d35ab72c",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9f/1f/141dd5847b0efca0817dcbba7500",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/49/64/d20ca535494f0af62272b0851268",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8f/df/c356b826190808fbc45625cf8ae1",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6f/33/424b11de9d4825beb58aa8394906",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/05/c4/bfd09f076c82e5a3c7c4414f4d70",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/51/92/6082c1fa0758c80cef9ebb984a83",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/51/33/3f6541f11a29786e91234e0288b8",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/6f/49/3565fc9e6e1d69f06e486f963fd0",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/78/eb/543d44646dcfbd8476a1516cd77a",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/2b/1e/b7ce51aa9f579e4a5a00ce4dd8bc",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/04/d5/f27969fe1b0d587197c8fdc58c2e",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/26/6c/da8ae887cb6ac020fad3c93e7cc9",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/e6/64/460d773403bea1b2a1fb6a846b42",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/84/51/54f2af640302aa0c116a0722f8f4",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/45/06/55dd8fffa7c2ea80256e339d4c36",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/25/78/a7abd69f73bc65602fa2e8662c88",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/a6/c963859c531bb19f507f405cf589",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/df/dc/79349a169d3b00964d9f35853f84",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a7/63/d057e19cb7806302b9570b91c573",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/c0/3da5a81581aa9275b6c32bb03fc8",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3f/84/d66202e8a15d5518f876dccdd6d6",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/ba/3a6f95b9e4a9c310ac63d0bc0a8c",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2f/58/9462e34601e2f442eb429185ee35",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/50/09/6e8feb718cb60ea80ecaab4ba9a0",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7d/a3/f8fd8ad1037e5f5b47b72a6d1edf",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9d/c6/3902a717b71f9f8781d724c8ca23",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/5a/3b25cbbca51ef2a6036d8d1805aa",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fd/11/f0fb88f01753350f88f068b6c6a0",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e4/22/d3700b99ec02b9bd99b8801ad69b",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8b/d8/4b2e840ace5be8dd8fc9d6841cdd",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/46/07/545eec0e6bde25bba8b3857d7e9b",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b4/ae/d9a2b38dc9824ac6acc79d520404",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/b1/0f/10eeef2cec516e68c5821f55fe1c",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/46/19/b3826f960327eb7585f546c3c878",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/50/d8/1ca969ac3469fa1d1f75ea9f62b9",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/51/37/888771c771d08b32be09d9203c19",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/2f/04/0ac355606ea7f1609adf1bba693b",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/5e/87/4774a28073c2cc9e1d918977252c",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/9b/fe/f8a6cbf3ec67157fd09b66b344e7",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/54/73/160cb6f003d538aca54a4e98c531",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/bf/79/93a020d1ffda39d0d01ea8e0479a",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7a/15/d3208b2f2a3eef6fd99e1f8ff33a",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/68/c7/d03c7b9e2b27080abf8dab81c353",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/85/e8/5ac6499c33d01935df05b28d6e35",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/eb/26/456b39bca29b1f9409467be6cbc9",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a5/bd/e6e7e45ccb7a4d4569e1a574ccb5",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f7/56/994e3e2dd054c838db5fa60089b1",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/62/ca/fb78c9774ea0d670854fd7c3b68f",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/79/b9/0886caeb0bcceaeefde3009b3260",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/04/60/a49352dea8100f7f979c42428566",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/49/b3/ed8416d34b63ad225d0199c70c40",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f9/69/1f530c56a627c8d36a1c14bfc023",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ee/a0/8f3610554e441a9f55e9a0b5a20a",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7e/5d/28fc24d104d3f0a8f98e6f830062",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/fd/4b/348eebb51a0e01a740fe1cac1b0c",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/44/03/fc0726f576a224b811eb4767c08d",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/0b/b3/57b75dfabbb22802aaca7eec164e",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/1f/63/a05a78b98665377e24e9f8db8bf1",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/b2/55/ba110ac1a33d84a1458315d4b4fe",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/c1/32/730d6c42e157f214083d5dcdbea2",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a0/2a/df32b0c34525af7de212e9a2cf30",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8f/28/f103463730f785ba4fe6a91f8943",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/29/4a/0df587009671edf52d6939c8f966",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c8/df/12ea6b0a703568e52a84001ca2db",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fb/29/5a74e41f2aa46aaa4ebb62b2612f",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d0/89/1995ef2194458c78c546698de761",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/7e/01b2fbad142edaac0c0af7853874",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9c/2f/3d0f4b2439b7da9def68597c14c8",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/26/d8/2a352f23af0375e97752da915d38",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/0f/72032ea268956736a6eabce738c3",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/92/5d/a8e2b0137a4e67821fc0c233704f",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/01/7c/c21ceaa90f0a0794979056c0a5dc",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/60/4a/238ad7b1d8b5cac2376e58cdd6de",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/27/b6/676cee2d1996ab592e1006ba4721",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/b1/fe/bf79be22fc78040b28b60aa2a8e3",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/37/3e/689be0ffce83ac46775c5dec6446",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/76/0a/1fbf3abebda99e8bddc30c60a776",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/cb/ce/496131ca3fec3ebd3cf7e8356465",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/2f/02/99c9b9887c50a45007a3825c923a",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/11/10/4e32246cf49a018b34012d167a31",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/02/62/edbc7740c386330cd72be2f7b50a",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/1e/d8/bb3d3b9c170b1696709ba53ef356",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/61/12/5ff38e6e4b2d3dcc1db55db9cf06",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/eb/92/0c028cdd75d65b2f8c37f6fbc1da",
|
||||
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/c0/32/b7907e3859a5c5013a3d97b6b523",
|
||||
"src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
|
||||
"src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"
|
||||
|
||||
5
.idea/dictionaries/ericf.xml
generated
5
.idea/dictionaries/ericf.xml
generated
@ -57,6 +57,7 @@
|
||||
<w>addr</w>
|
||||
<w>addrstr</w>
|
||||
<w>adisp</w>
|
||||
<w>advb</w>
|
||||
<w>advertizing</w>
|
||||
<w>aidl</w>
|
||||
<w>aint</w>
|
||||
@ -1935,6 +1936,7 @@
|
||||
<w>ppre</w>
|
||||
<w>pproxy</w>
|
||||
<w>pptabcom</w>
|
||||
<w>prab</w>
|
||||
<w>pragmas</w>
|
||||
<w>prch</w>
|
||||
<w>prec</w>
|
||||
@ -1994,6 +1996,7 @@
|
||||
<w>projs</w>
|
||||
<w>promocode</w>
|
||||
<w>proxykey</w>
|
||||
<w>prtb</w>
|
||||
<w>prunedir</w>
|
||||
<w>prval</w>
|
||||
<w>pstats</w>
|
||||
@ -2041,6 +2044,7 @@
|
||||
<w>pvrtcbest</w>
|
||||
<w>pvrtcfast</w>
|
||||
<w>pvval</w>
|
||||
<w>pwin</w>
|
||||
<w>pybee</w>
|
||||
<w>pybuild</w>
|
||||
<w>pybuildapple</w>
|
||||
@ -2932,6 +2936,7 @@
|
||||
<w>yscl</w>
|
||||
<w>ytweak</w>
|
||||
<w>yval</w>
|
||||
<w>zabcdefghijklmnopqrstuvwxyz</w>
|
||||
<w>zaggy</w>
|
||||
<w>zimbot</w>
|
||||
<w>zipapp</w>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
### 1.7.11 (build 20897, api 7, 2022-10-09)
|
||||
- Switched our Python autoformatting from yapf to black. The yapf project seems to be mostly dead whereas black seems to be thriving. The final straw was yapf not supporting the `match` statement in Python 3.10.
|
||||
|
||||
### 1.7.10 (build 20895, api 7, 2022-10-09)
|
||||
- Added eval support for cloud-console. This means you can type something like '1+1' in the console and see '2' printed. This is how Python behaves in the stdin console or in-game console or the standard Python interpreter.
|
||||
|
||||
@ -1 +1 @@
|
||||
194057364831757023796080999188881665880
|
||||
180157672676216986210895241045962795292
|
||||
File diff suppressed because it is too large
Load Diff
@ -35,6 +35,7 @@ from typing import TYPE_CHECKING, TypeVar
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
_T = TypeVar('_T')
|
||||
|
||||
|
||||
@ -44,13 +45,16 @@ def _uninferrable() -> Any:
|
||||
return _not_a_real_variable # type: ignore
|
||||
|
||||
|
||||
def add_transaction(transaction: dict,
|
||||
callback: Callable | None = None) -> None:
|
||||
def add_transaction(
|
||||
transaction: dict, callback: Callable | None = None
|
||||
) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Given a game and config string, returns whether there is a leaderboard
|
||||
@ -60,6 +64,7 @@ def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||
|
||||
|
||||
def get_master_server_address(source: int = -1, version: int = 1) -> str:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Return the address of the master server.
|
||||
@ -68,66 +73,79 @@ def get_master_server_address(source: int = -1, version: int = 1) -> str:
|
||||
|
||||
|
||||
def get_news_show() -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_price(item: str) -> str | None:
|
||||
|
||||
"""(internal)"""
|
||||
return ''
|
||||
|
||||
|
||||
def get_public_login_id() -> str | None:
|
||||
|
||||
"""(internal)"""
|
||||
return ''
|
||||
|
||||
|
||||
def get_purchased(item: str) -> bool:
|
||||
|
||||
"""(internal)"""
|
||||
return bool()
|
||||
|
||||
|
||||
def get_purchases_state() -> int:
|
||||
|
||||
"""(internal)"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_v1_account_display_string(full: bool = True) -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
|
||||
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
||||
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
|
||||
|
||||
"""(internal)"""
|
||||
return _uninferrable()
|
||||
|
||||
|
||||
def get_v1_account_name() -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_state() -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v1_account_state_num() -> int:
|
||||
|
||||
"""(internal)"""
|
||||
return int()
|
||||
|
||||
|
||||
def get_v1_account_ticket_count() -> int:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Returns the number of tickets for the current account.
|
||||
@ -136,31 +154,37 @@ def get_v1_account_ticket_count() -> int:
|
||||
|
||||
|
||||
def get_v1_account_type() -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def get_v2_fleet() -> str:
|
||||
|
||||
"""(internal)"""
|
||||
return str()
|
||||
|
||||
|
||||
def have_outstanding_transactions() -> bool:
|
||||
|
||||
"""(internal)"""
|
||||
return bool()
|
||||
|
||||
|
||||
def in_game_purchase(item: str, price: int) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def is_blessed() -> bool:
|
||||
|
||||
"""(internal)"""
|
||||
return bool()
|
||||
|
||||
|
||||
def mark_config_dirty() -> None:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Category: General Utility Functions
|
||||
@ -169,36 +193,43 @@ def mark_config_dirty() -> None:
|
||||
|
||||
|
||||
def power_ranking_query(callback: Callable, season: Any = None) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def purchase(item: str) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def report_achievement(achievement: str, pass_to_account: bool = True) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def reset_achievements() -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def restore_purchases() -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def run_transactions() -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
|
||||
def sign_in_v1(account_type: str) -> None:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Category: General Utility Functions
|
||||
@ -207,6 +238,7 @@ def sign_in_v1(account_type: str) -> None:
|
||||
|
||||
|
||||
def sign_out_v1(v2_embedded: bool = False) -> None:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Category: General Utility Functions
|
||||
@ -214,17 +246,20 @@ def sign_out_v1(v2_embedded: bool = False) -> None:
|
||||
return None
|
||||
|
||||
|
||||
def submit_score(game: str,
|
||||
config: str,
|
||||
name: Any,
|
||||
score: int | None,
|
||||
callback: Callable,
|
||||
friend_callback: Callable | None,
|
||||
order: str = 'increasing',
|
||||
tournament_id: str | None = None,
|
||||
score_type: str = 'points',
|
||||
campaign: str | None = None,
|
||||
level: str | None = None) -> None:
|
||||
def submit_score(
|
||||
game: str,
|
||||
config: str,
|
||||
name: Any,
|
||||
score: int | None,
|
||||
callback: Callable,
|
||||
friend_callback: Callable | None,
|
||||
order: str = 'increasing',
|
||||
tournament_id: str | None = None,
|
||||
score_type: str = 'points',
|
||||
campaign: str | None = None,
|
||||
level: str | None = None,
|
||||
) -> None:
|
||||
|
||||
"""(internal)
|
||||
|
||||
Submit a score to the server; callback will be called with the results.
|
||||
@ -235,7 +270,9 @@ def submit_score(game: str,
|
||||
return None
|
||||
|
||||
|
||||
def tournament_query(callback: Callable[[dict | None], None],
|
||||
args: dict) -> None:
|
||||
def tournament_query(
|
||||
callback: Callable[[dict | None], None], args: dict
|
||||
) -> None:
|
||||
|
||||
"""(internal)"""
|
||||
return None
|
||||
|
||||
@ -9,15 +9,61 @@ In some specific cases you may need to pull in individual submodules instead.
|
||||
# pylint: disable=redefined-builtin
|
||||
|
||||
from _ba import (
|
||||
CollideModel, Context, ContextCall, Data, InputDevice, Material, Model,
|
||||
Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget,
|
||||
camerashake, checkboxwidget, columnwidget, containerwidget, do_once,
|
||||
emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession,
|
||||
getsound, gettexture, hscrollwidget, imagewidget, newactivity, newnode,
|
||||
playsound, printnodes, printobjects, pushcall, quit, rowwidget, safecolor,
|
||||
screenmessage, scrollwidget, set_analytics_screen, charstr, textwidget,
|
||||
time, timer, open_url, widget, clipboard_is_supported, clipboard_has_text,
|
||||
clipboard_get_text, clipboard_set_text, getdata, in_logic_thread)
|
||||
CollideModel,
|
||||
Context,
|
||||
ContextCall,
|
||||
Data,
|
||||
InputDevice,
|
||||
Material,
|
||||
Model,
|
||||
Node,
|
||||
SessionPlayer,
|
||||
Sound,
|
||||
Texture,
|
||||
Timer,
|
||||
Vec3,
|
||||
Widget,
|
||||
buttonwidget,
|
||||
camerashake,
|
||||
checkboxwidget,
|
||||
columnwidget,
|
||||
containerwidget,
|
||||
do_once,
|
||||
emitfx,
|
||||
getactivity,
|
||||
getcollidemodel,
|
||||
getmodel,
|
||||
getnodes,
|
||||
getsession,
|
||||
getsound,
|
||||
gettexture,
|
||||
hscrollwidget,
|
||||
imagewidget,
|
||||
newactivity,
|
||||
newnode,
|
||||
playsound,
|
||||
printnodes,
|
||||
printobjects,
|
||||
pushcall,
|
||||
quit,
|
||||
rowwidget,
|
||||
safecolor,
|
||||
screenmessage,
|
||||
scrollwidget,
|
||||
set_analytics_screen,
|
||||
charstr,
|
||||
textwidget,
|
||||
time,
|
||||
timer,
|
||||
open_url,
|
||||
widget,
|
||||
clipboard_is_supported,
|
||||
clipboard_has_text,
|
||||
clipboard_get_text,
|
||||
clipboard_set_text,
|
||||
getdata,
|
||||
in_logic_thread,
|
||||
)
|
||||
from ba._activity import Activity
|
||||
from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem
|
||||
from ba._actor import Actor
|
||||
@ -27,21 +73,50 @@ from ba._app import App
|
||||
from ba._cloud import CloudSubsystem
|
||||
from ba._coopgame import CoopGameActivity
|
||||
from ba._coopsession import CoopSession
|
||||
from ba._dependency import (Dependency, DependencyComponent, DependencySet,
|
||||
AssetPackage)
|
||||
from ba._generated.enums import (TimeType, Permission, TimeFormat, SpecialChar,
|
||||
InputType, UIScale)
|
||||
from ba._dependency import (
|
||||
Dependency,
|
||||
DependencyComponent,
|
||||
DependencySet,
|
||||
AssetPackage,
|
||||
)
|
||||
from ba._generated.enums import (
|
||||
TimeType,
|
||||
Permission,
|
||||
TimeFormat,
|
||||
SpecialChar,
|
||||
InputType,
|
||||
UIScale,
|
||||
)
|
||||
from ba._error import (
|
||||
print_exception, print_error, ContextError, NotFoundError,
|
||||
PlayerNotFoundError, SessionPlayerNotFoundError, NodeNotFoundError,
|
||||
ActorNotFoundError, InputDeviceNotFoundError, WidgetNotFoundError,
|
||||
ActivityNotFoundError, TeamNotFoundError, SessionTeamNotFoundError,
|
||||
SessionNotFoundError, DelegateNotFoundError, DependencyError)
|
||||
print_exception,
|
||||
print_error,
|
||||
ContextError,
|
||||
NotFoundError,
|
||||
PlayerNotFoundError,
|
||||
SessionPlayerNotFoundError,
|
||||
NodeNotFoundError,
|
||||
ActorNotFoundError,
|
||||
InputDeviceNotFoundError,
|
||||
WidgetNotFoundError,
|
||||
ActivityNotFoundError,
|
||||
TeamNotFoundError,
|
||||
SessionTeamNotFoundError,
|
||||
SessionNotFoundError,
|
||||
DelegateNotFoundError,
|
||||
DependencyError,
|
||||
)
|
||||
from ba._freeforallsession import FreeForAllSession
|
||||
from ba._gameactivity import GameActivity
|
||||
from ba._gameresults import GameResults
|
||||
from ba._settings import (Setting, IntSetting, FloatSetting, ChoiceSetting,
|
||||
BoolSetting, IntChoiceSetting, FloatChoiceSetting)
|
||||
from ba._settings import (
|
||||
Setting,
|
||||
IntSetting,
|
||||
FloatSetting,
|
||||
ChoiceSetting,
|
||||
BoolSetting,
|
||||
IntChoiceSetting,
|
||||
FloatChoiceSetting,
|
||||
)
|
||||
from ba._language import Lstr, LanguageSubsystem
|
||||
from ba._map import Map, getmaps
|
||||
from ba._session import Session
|
||||
@ -57,23 +132,53 @@ from ba._appconfig import AppConfig
|
||||
from ba._appdelegate import AppDelegate
|
||||
from ba._apputils import is_browser_likely_available, garbage_collect
|
||||
from ba._campaign import Campaign
|
||||
from ba._gameutils import (GameTip, animate, animate_array, show_damage_count,
|
||||
timestring, cameraflash)
|
||||
from ba._general import (WeakCall, Call, existing, Existable,
|
||||
verify_object_death, storagename, getclass)
|
||||
from ba._gameutils import (
|
||||
GameTip,
|
||||
animate,
|
||||
animate_array,
|
||||
show_damage_count,
|
||||
timestring,
|
||||
cameraflash,
|
||||
)
|
||||
from ba._general import (
|
||||
WeakCall,
|
||||
Call,
|
||||
existing,
|
||||
Existable,
|
||||
verify_object_death,
|
||||
storagename,
|
||||
getclass,
|
||||
)
|
||||
from ba._keyboard import Keyboard
|
||||
from ba._level import Level
|
||||
from ba._lobby import Lobby, Chooser
|
||||
from ba._math import normalized_color, is_point_in_box, vec3validate
|
||||
from ba._meta import MetadataSubsystem
|
||||
from ba._messages import (UNHANDLED, OutOfBoundsMessage, DeathType, DieMessage,
|
||||
PlayerDiedMessage, StandMessage, PickUpMessage,
|
||||
DropMessage, PickedUpMessage, DroppedMessage,
|
||||
ShouldShatterMessage, ImpactDamageMessage,
|
||||
FreezeMessage, ThawMessage, HitMessage,
|
||||
CelebrateMessage)
|
||||
from ba._music import (setmusic, MusicPlayer, MusicType, MusicPlayMode,
|
||||
MusicSubsystem)
|
||||
from ba._messages import (
|
||||
UNHANDLED,
|
||||
OutOfBoundsMessage,
|
||||
DeathType,
|
||||
DieMessage,
|
||||
PlayerDiedMessage,
|
||||
StandMessage,
|
||||
PickUpMessage,
|
||||
DropMessage,
|
||||
PickedUpMessage,
|
||||
DroppedMessage,
|
||||
ShouldShatterMessage,
|
||||
ImpactDamageMessage,
|
||||
FreezeMessage,
|
||||
ThawMessage,
|
||||
HitMessage,
|
||||
CelebrateMessage,
|
||||
)
|
||||
from ba._music import (
|
||||
setmusic,
|
||||
MusicPlayer,
|
||||
MusicType,
|
||||
MusicPlayMode,
|
||||
MusicSubsystem,
|
||||
)
|
||||
from ba._powerup import PowerupMessage, PowerupAcceptMessage
|
||||
from ba._multiteamsession import MultiTeamSession
|
||||
from ba.ui import Window, UIController, uicleanupcheck
|
||||
@ -82,47 +187,185 @@ from ba._collision import Collision, getcollision
|
||||
app: App
|
||||
|
||||
__all__ = [
|
||||
'Achievement', 'AchievementSubsystem', 'Activity', 'ActivityNotFoundError',
|
||||
'Actor', 'ActorNotFoundError', 'animate', 'animate_array', 'app', 'App',
|
||||
'AppConfig', 'AppDelegate', 'AssetPackage', 'BoolSetting', 'buttonwidget',
|
||||
'Call', 'cameraflash', 'camerashake', 'Campaign', 'CelebrateMessage',
|
||||
'charstr', 'checkboxwidget', 'ChoiceSetting', 'Chooser',
|
||||
'clipboard_get_text', 'clipboard_has_text', 'clipboard_is_supported',
|
||||
'clipboard_set_text', 'CollideModel', 'Collision', 'columnwidget',
|
||||
'containerwidget', 'Context', 'ContextCall', 'ContextError',
|
||||
'CloudSubsystem', 'CoopGameActivity', 'CoopSession', 'Data', 'DeathType',
|
||||
'DelegateNotFoundError', 'Dependency', 'DependencyComponent',
|
||||
'DependencyError', 'DependencySet', 'DieMessage', 'do_once', 'DropMessage',
|
||||
'DroppedMessage', 'DualTeamSession', 'emitfx', 'EmptyPlayer', 'EmptyTeam',
|
||||
'Existable', 'existing', 'FloatChoiceSetting', 'FloatSetting',
|
||||
'FreeForAllSession', 'FreezeMessage', 'GameActivity', 'GameResults',
|
||||
'GameTip', 'garbage_collect', 'getactivity', 'getclass', 'getcollidemodel',
|
||||
'getcollision', 'getdata', 'getmaps', 'getmodel', 'getnodes', 'getsession',
|
||||
'getsound', 'gettexture', 'HitMessage', 'hscrollwidget', 'imagewidget',
|
||||
'ImpactDamageMessage', 'in_logic_thread', 'InputDevice',
|
||||
'InputDeviceNotFoundError', 'InputType', 'IntChoiceSetting', 'IntSetting',
|
||||
'is_browser_likely_available', 'is_point_in_box', 'Keyboard',
|
||||
'LanguageSubsystem', 'Level', 'Lobby', 'Lstr', 'Map', 'Material',
|
||||
'MetadataSubsystem', 'Model', 'MultiTeamSession', 'MusicPlayer',
|
||||
'MusicPlayMode', 'MusicSubsystem', 'MusicType', 'newactivity', 'newnode',
|
||||
'Node', 'NodeActor', 'NodeNotFoundError', 'normalized_color',
|
||||
'NotFoundError', 'open_url', 'OutOfBoundsMessage', 'Permission',
|
||||
'PickedUpMessage', 'PickUpMessage', 'Player', 'PlayerDiedMessage',
|
||||
'PlayerInfo', 'PlayerNotFoundError', 'PlayerRecord', 'PlayerScoredMessage',
|
||||
'playsound', 'Plugin', 'PluginSubsystem', 'PotentialPlugin',
|
||||
'PowerupAcceptMessage', 'PowerupMessage', 'print_error', 'print_exception',
|
||||
'printnodes', 'printobjects', 'pushcall', 'quit', 'rowwidget', 'safecolor',
|
||||
'ScoreConfig', 'ScoreType', 'screenmessage', 'scrollwidget',
|
||||
'ServerController', 'Session', 'SessionNotFoundError', 'SessionPlayer',
|
||||
'SessionPlayerNotFoundError', 'SessionTeam', 'SessionTeamNotFoundError',
|
||||
'set_analytics_screen', 'setmusic', 'Setting', 'ShouldShatterMessage',
|
||||
'show_damage_count', 'Sound', 'SpecialChar', 'StandLocation',
|
||||
'StandMessage', 'Stats', 'storagename', 'Team', 'TeamGameActivity',
|
||||
'TeamNotFoundError', 'Texture', 'textwidget', 'ThawMessage', 'time',
|
||||
'TimeFormat', 'Timer', 'timer', 'timestring', 'TimeType', 'uicleanupcheck',
|
||||
'UIController', 'UIScale', 'UISubsystem', 'UNHANDLED', 'Vec3',
|
||||
'vec3validate', 'verify_object_death', 'WeakCall', 'Widget', 'widget',
|
||||
'WidgetNotFoundError', 'Window'
|
||||
'Achievement',
|
||||
'AchievementSubsystem',
|
||||
'Activity',
|
||||
'ActivityNotFoundError',
|
||||
'Actor',
|
||||
'ActorNotFoundError',
|
||||
'animate',
|
||||
'animate_array',
|
||||
'app',
|
||||
'App',
|
||||
'AppConfig',
|
||||
'AppDelegate',
|
||||
'AssetPackage',
|
||||
'BoolSetting',
|
||||
'buttonwidget',
|
||||
'Call',
|
||||
'cameraflash',
|
||||
'camerashake',
|
||||
'Campaign',
|
||||
'CelebrateMessage',
|
||||
'charstr',
|
||||
'checkboxwidget',
|
||||
'ChoiceSetting',
|
||||
'Chooser',
|
||||
'clipboard_get_text',
|
||||
'clipboard_has_text',
|
||||
'clipboard_is_supported',
|
||||
'clipboard_set_text',
|
||||
'CollideModel',
|
||||
'Collision',
|
||||
'columnwidget',
|
||||
'containerwidget',
|
||||
'Context',
|
||||
'ContextCall',
|
||||
'ContextError',
|
||||
'CloudSubsystem',
|
||||
'CoopGameActivity',
|
||||
'CoopSession',
|
||||
'Data',
|
||||
'DeathType',
|
||||
'DelegateNotFoundError',
|
||||
'Dependency',
|
||||
'DependencyComponent',
|
||||
'DependencyError',
|
||||
'DependencySet',
|
||||
'DieMessage',
|
||||
'do_once',
|
||||
'DropMessage',
|
||||
'DroppedMessage',
|
||||
'DualTeamSession',
|
||||
'emitfx',
|
||||
'EmptyPlayer',
|
||||
'EmptyTeam',
|
||||
'Existable',
|
||||
'existing',
|
||||
'FloatChoiceSetting',
|
||||
'FloatSetting',
|
||||
'FreeForAllSession',
|
||||
'FreezeMessage',
|
||||
'GameActivity',
|
||||
'GameResults',
|
||||
'GameTip',
|
||||
'garbage_collect',
|
||||
'getactivity',
|
||||
'getclass',
|
||||
'getcollidemodel',
|
||||
'getcollision',
|
||||
'getdata',
|
||||
'getmaps',
|
||||
'getmodel',
|
||||
'getnodes',
|
||||
'getsession',
|
||||
'getsound',
|
||||
'gettexture',
|
||||
'HitMessage',
|
||||
'hscrollwidget',
|
||||
'imagewidget',
|
||||
'ImpactDamageMessage',
|
||||
'in_logic_thread',
|
||||
'InputDevice',
|
||||
'InputDeviceNotFoundError',
|
||||
'InputType',
|
||||
'IntChoiceSetting',
|
||||
'IntSetting',
|
||||
'is_browser_likely_available',
|
||||
'is_point_in_box',
|
||||
'Keyboard',
|
||||
'LanguageSubsystem',
|
||||
'Level',
|
||||
'Lobby',
|
||||
'Lstr',
|
||||
'Map',
|
||||
'Material',
|
||||
'MetadataSubsystem',
|
||||
'Model',
|
||||
'MultiTeamSession',
|
||||
'MusicPlayer',
|
||||
'MusicPlayMode',
|
||||
'MusicSubsystem',
|
||||
'MusicType',
|
||||
'newactivity',
|
||||
'newnode',
|
||||
'Node',
|
||||
'NodeActor',
|
||||
'NodeNotFoundError',
|
||||
'normalized_color',
|
||||
'NotFoundError',
|
||||
'open_url',
|
||||
'OutOfBoundsMessage',
|
||||
'Permission',
|
||||
'PickedUpMessage',
|
||||
'PickUpMessage',
|
||||
'Player',
|
||||
'PlayerDiedMessage',
|
||||
'PlayerInfo',
|
||||
'PlayerNotFoundError',
|
||||
'PlayerRecord',
|
||||
'PlayerScoredMessage',
|
||||
'playsound',
|
||||
'Plugin',
|
||||
'PluginSubsystem',
|
||||
'PotentialPlugin',
|
||||
'PowerupAcceptMessage',
|
||||
'PowerupMessage',
|
||||
'print_error',
|
||||
'print_exception',
|
||||
'printnodes',
|
||||
'printobjects',
|
||||
'pushcall',
|
||||
'quit',
|
||||
'rowwidget',
|
||||
'safecolor',
|
||||
'ScoreConfig',
|
||||
'ScoreType',
|
||||
'screenmessage',
|
||||
'scrollwidget',
|
||||
'ServerController',
|
||||
'Session',
|
||||
'SessionNotFoundError',
|
||||
'SessionPlayer',
|
||||
'SessionPlayerNotFoundError',
|
||||
'SessionTeam',
|
||||
'SessionTeamNotFoundError',
|
||||
'set_analytics_screen',
|
||||
'setmusic',
|
||||
'Setting',
|
||||
'ShouldShatterMessage',
|
||||
'show_damage_count',
|
||||
'Sound',
|
||||
'SpecialChar',
|
||||
'StandLocation',
|
||||
'StandMessage',
|
||||
'Stats',
|
||||
'storagename',
|
||||
'Team',
|
||||
'TeamGameActivity',
|
||||
'TeamNotFoundError',
|
||||
'Texture',
|
||||
'textwidget',
|
||||
'ThawMessage',
|
||||
'time',
|
||||
'TimeFormat',
|
||||
'Timer',
|
||||
'timer',
|
||||
'timestring',
|
||||
'TimeType',
|
||||
'uicleanupcheck',
|
||||
'UIController',
|
||||
'UIScale',
|
||||
'UISubsystem',
|
||||
'UNHANDLED',
|
||||
'Vec3',
|
||||
'vec3validate',
|
||||
'verify_object_death',
|
||||
'WeakCall',
|
||||
'Widget',
|
||||
'widget',
|
||||
'WidgetNotFoundError',
|
||||
'Window',
|
||||
]
|
||||
|
||||
|
||||
@ -135,10 +378,12 @@ def _simplify_module_names() -> None:
|
||||
# so let's make an exception for it.
|
||||
if os.environ.get('BA_DOCS_GENERATION', '0') != '1':
|
||||
from efro.util import set_canonical_module
|
||||
|
||||
globs = globals()
|
||||
set_canonical_module(
|
||||
module_globals=globs,
|
||||
names=[n for n in globs.keys() if not n.startswith('_')])
|
||||
names=[n for n in globs.keys() if not n.startswith('_')],
|
||||
)
|
||||
|
||||
|
||||
_simplify_module_names()
|
||||
|
||||
@ -40,8 +40,10 @@ class AccountV1Subsystem:
|
||||
|
||||
# Auto-sign-in to a local account in a moment if we're set to.
|
||||
def do_auto_sign_in() -> None:
|
||||
if _ba.app.headless_mode or _ba.app.config.get(
|
||||
'Auto Account State') == 'Local':
|
||||
if (
|
||||
_ba.app.headless_mode
|
||||
or _ba.app.config.get('Auto Account State') == 'Local'
|
||||
):
|
||||
_internal.sign_in_v1('Local')
|
||||
|
||||
_ba.pushcall(do_auto_sign_in)
|
||||
@ -60,9 +62,14 @@ class AccountV1Subsystem:
|
||||
(internal)
|
||||
"""
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='getTicketsWindow.receivedTicketsText',
|
||||
subs=[('${COUNT}', str(count))]),
|
||||
color=(0, 1, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='getTicketsWindow.receivedTicketsText',
|
||||
subs=[('${COUNT}', str(count))],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
_ba.playsound(_ba.getsound('cashRegister'))
|
||||
|
||||
def cache_league_rank_data(self, data: Any) -> None:
|
||||
@ -73,9 +80,9 @@ class AccountV1Subsystem:
|
||||
"""(internal)"""
|
||||
return self.league_rank_cache.get('info', None)
|
||||
|
||||
def get_league_rank_points(self,
|
||||
data: dict[str, Any] | None,
|
||||
subset: str | None = None) -> int:
|
||||
def get_league_rank_points(
|
||||
self, data: dict[str, Any] | None, subset: str | None = None
|
||||
) -> int:
|
||||
"""(internal)"""
|
||||
if data is None:
|
||||
return 0
|
||||
@ -90,15 +97,23 @@ class AccountV1Subsystem:
|
||||
if ach.complete:
|
||||
total_ach_value += ach.power_ranking_value
|
||||
|
||||
trophies_total: int = (data['t0a'] * data['t0am'] +
|
||||
data['t0b'] * data['t0bm'] +
|
||||
data['t1'] * data['t1m'] +
|
||||
data['t2'] * data['t2m'] +
|
||||
data['t3'] * data['t3m'] +
|
||||
data['t4'] * data['t4m'])
|
||||
trophies_total: int = (
|
||||
data['t0a'] * data['t0am']
|
||||
+ data['t0b'] * data['t0bm']
|
||||
+ data['t1'] * data['t1m']
|
||||
+ data['t2'] * data['t2m']
|
||||
+ data['t3'] * data['t3m']
|
||||
+ data['t4'] * data['t4m']
|
||||
)
|
||||
if subset == 'trophyCount':
|
||||
val: int = (data['t0a'] + data['t0b'] + data['t1'] + data['t2'] +
|
||||
data['t3'] + data['t4'])
|
||||
val: int = (
|
||||
data['t0a']
|
||||
+ data['t0b']
|
||||
+ data['t1']
|
||||
+ data['t2']
|
||||
+ data['t3']
|
||||
+ data['t4']
|
||||
)
|
||||
assert isinstance(val, int)
|
||||
return val
|
||||
if subset == 'trophies':
|
||||
@ -108,41 +123,54 @@ class AccountV1Subsystem:
|
||||
raise ValueError('invalid subset value: ' + str(subset))
|
||||
|
||||
if data['p']:
|
||||
pro_mult = 1.0 + float(
|
||||
_internal.get_v1_account_misc_read_val('proPowerRankingBoost',
|
||||
0.0)) * 0.01
|
||||
pro_mult = (
|
||||
1.0
|
||||
+ float(
|
||||
_internal.get_v1_account_misc_read_val(
|
||||
'proPowerRankingBoost', 0.0
|
||||
)
|
||||
)
|
||||
* 0.01
|
||||
)
|
||||
else:
|
||||
pro_mult = 1.0
|
||||
|
||||
# For final value, apply our pro mult and activeness-mult.
|
||||
return int(
|
||||
(total_ach_value + trophies_total) *
|
||||
(data['act'] if data['act'] is not None else 1.0) * pro_mult)
|
||||
(total_ach_value + trophies_total)
|
||||
* (data['act'] if data['act'] is not None else 1.0)
|
||||
* pro_mult
|
||||
)
|
||||
|
||||
def cache_tournament_info(self, info: Any) -> None:
|
||||
"""(internal)"""
|
||||
from ba._generated.enums import TimeType, TimeFormat
|
||||
|
||||
for entry in info:
|
||||
cache_entry = self.tournament_info[entry['tournamentID']] = (
|
||||
copy.deepcopy(entry))
|
||||
cache_entry = self.tournament_info[
|
||||
entry['tournamentID']
|
||||
] = copy.deepcopy(entry)
|
||||
|
||||
# Also store the time we received this, so we can adjust
|
||||
# time-remaining values/etc.
|
||||
cache_entry['timeReceived'] = _ba.time(TimeType.REAL,
|
||||
TimeFormat.MILLISECONDS)
|
||||
cache_entry['timeReceived'] = _ba.time(
|
||||
TimeType.REAL, TimeFormat.MILLISECONDS
|
||||
)
|
||||
cache_entry['valid'] = True
|
||||
|
||||
def get_purchased_icons(self) -> list[str]:
|
||||
"""(internal)"""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba import _store
|
||||
|
||||
if _internal.get_v1_account_state() != 'signed_in':
|
||||
return []
|
||||
icons = []
|
||||
store_items = _store.get_store_items()
|
||||
for item_name, item in list(store_items.items()):
|
||||
if item_name.startswith('icons.') and _internal.get_purchased(
|
||||
item_name):
|
||||
item_name
|
||||
):
|
||||
icons.append(item['icon'])
|
||||
return icons
|
||||
|
||||
@ -160,23 +188,28 @@ class AccountV1Subsystem:
|
||||
# If the short version of our account name currently cant be
|
||||
# displayed by the game, cancel.
|
||||
if not _ba.have_chars(
|
||||
_internal.get_v1_account_display_string(full=False)):
|
||||
_internal.get_v1_account_display_string(full=False)
|
||||
):
|
||||
return
|
||||
|
||||
config = _ba.app.config
|
||||
if ('Player Profiles' not in config
|
||||
or '__account__' not in config['Player Profiles']):
|
||||
if (
|
||||
'Player Profiles' not in config
|
||||
or '__account__' not in config['Player Profiles']
|
||||
):
|
||||
|
||||
# Create a spaz with a nice default purply color.
|
||||
_internal.add_transaction({
|
||||
'type': 'ADD_PLAYER_PROFILE',
|
||||
'name': '__account__',
|
||||
'profile': {
|
||||
'character': 'Spaz',
|
||||
'color': [0.5, 0.25, 1.0],
|
||||
'highlight': [0.5, 0.25, 1.0]
|
||||
_internal.add_transaction(
|
||||
{
|
||||
'type': 'ADD_PLAYER_PROFILE',
|
||||
'name': '__account__',
|
||||
'profile': {
|
||||
'character': 'Spaz',
|
||||
'color': [0.5, 0.25, 1.0],
|
||||
'highlight': [0.5, 0.25, 1.0],
|
||||
},
|
||||
}
|
||||
})
|
||||
)
|
||||
_internal.run_transactions()
|
||||
|
||||
def have_pro(self) -> bool:
|
||||
@ -188,7 +221,8 @@ class AccountV1Subsystem:
|
||||
_internal.get_purchased('upgrades.pro')
|
||||
or _internal.get_purchased('static.pro')
|
||||
or _internal.get_purchased('static.pro_sale')
|
||||
or 'ballistica' + 'core' == _ba.appname())
|
||||
or 'ballistica' + 'core' == _ba.appname()
|
||||
)
|
||||
|
||||
def have_pro_options(self) -> bool:
|
||||
"""Return whether pro-options are present.
|
||||
@ -202,22 +236,31 @@ class AccountV1Subsystem:
|
||||
# or also if we've been grandfathered in or are using ballistica-core
|
||||
# builds.
|
||||
return self.have_pro() or bool(
|
||||
_internal.get_v1_account_misc_read_val_2('proOptionsUnlocked',
|
||||
False)
|
||||
or _ba.app.config.get('lc14292', 0) > 1)
|
||||
_internal.get_v1_account_misc_read_val_2(
|
||||
'proOptionsUnlocked', False
|
||||
)
|
||||
or _ba.app.config.get('lc14292', 0) > 1
|
||||
)
|
||||
|
||||
def show_post_purchase_message(self) -> None:
|
||||
"""(internal)"""
|
||||
from ba._language import Lstr
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
cur_time = _ba.time(TimeType.REAL)
|
||||
if (self.last_post_purchase_message_time is None
|
||||
or cur_time - self.last_post_purchase_message_time > 3.0):
|
||||
if (
|
||||
self.last_post_purchase_message_time is None
|
||||
or cur_time - self.last_post_purchase_message_time > 3.0
|
||||
):
|
||||
self.last_post_purchase_message_time = cur_time
|
||||
with _ba.Context('ui'):
|
||||
_ba.screenmessage(Lstr(resource='updatingAccountText',
|
||||
fallback_resource='purchasingText'),
|
||||
color=(0, 1, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='updatingAccountText',
|
||||
fallback_resource='purchasingText',
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
_ba.playsound(_ba.getsound('click01'))
|
||||
|
||||
def on_account_state_changed(self) -> None:
|
||||
@ -225,16 +268,21 @@ class AccountV1Subsystem:
|
||||
from ba._language import Lstr
|
||||
|
||||
# Run any pending promo codes we had queued up while not signed in.
|
||||
if _internal.get_v1_account_state(
|
||||
) == 'signed_in' and self.pending_promo_codes:
|
||||
if (
|
||||
_internal.get_v1_account_state() == 'signed_in'
|
||||
and self.pending_promo_codes
|
||||
):
|
||||
for code in self.pending_promo_codes:
|
||||
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
|
||||
color=(0, 1, 0))
|
||||
_internal.add_transaction({
|
||||
'type': 'PROMO_CODE',
|
||||
'expire_time': time.time() + 5,
|
||||
'code': code
|
||||
})
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0)
|
||||
)
|
||||
_internal.add_transaction(
|
||||
{
|
||||
'type': 'PROMO_CODE',
|
||||
'expire_time': time.time() + 5,
|
||||
'code': code,
|
||||
}
|
||||
)
|
||||
_internal.run_transactions()
|
||||
self.pending_promo_codes = []
|
||||
|
||||
@ -254,18 +302,18 @@ class AccountV1Subsystem:
|
||||
# If we're still not signed in and have pending codes,
|
||||
# inform the user that they need to sign in to use them.
|
||||
if self.pending_promo_codes:
|
||||
_ba.screenmessage(Lstr(resource='signInForPromoCodeText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0)
|
||||
)
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
|
||||
self.pending_promo_codes.append(code)
|
||||
_ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL)
|
||||
return
|
||||
_ba.screenmessage(Lstr(resource='submittingPromoCodeText'),
|
||||
color=(0, 1, 0))
|
||||
_internal.add_transaction({
|
||||
'type': 'PROMO_CODE',
|
||||
'expire_time': time.time() + 5,
|
||||
'code': code
|
||||
})
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0)
|
||||
)
|
||||
_internal.add_transaction(
|
||||
{'type': 'PROMO_CODE', 'expire_time': time.time() + 5, 'code': code}
|
||||
)
|
||||
_internal.run_transactions()
|
||||
|
||||
@ -55,8 +55,9 @@ class AccountV2Subsystem:
|
||||
"""Internal - should be overridden by subclass."""
|
||||
return None
|
||||
|
||||
def on_primary_account_changed(self,
|
||||
account: AccountV2Handle | None) -> None:
|
||||
def on_primary_account_changed(
|
||||
self, account: AccountV2Handle | None
|
||||
) -> None:
|
||||
"""Callback run after the primary account changes.
|
||||
|
||||
Will be called with None on log-outs or when new credentials
|
||||
@ -70,13 +71,16 @@ class AccountV2Subsystem:
|
||||
# informed when that process completes.
|
||||
if account.workspaceid is not None:
|
||||
assert account.workspacename is not None
|
||||
if (not self._initial_login_completed
|
||||
and not self._kicked_off_workspace_load):
|
||||
if (
|
||||
not self._initial_login_completed
|
||||
and not self._kicked_off_workspace_load
|
||||
):
|
||||
self._kicked_off_workspace_load = True
|
||||
_ba.app.workspaces.set_active_workspace(
|
||||
workspaceid=account.workspaceid,
|
||||
workspacename=account.workspacename,
|
||||
on_completed=self._on_set_active_workspace_completed)
|
||||
on_completed=self._on_set_active_workspace_completed,
|
||||
)
|
||||
else:
|
||||
# Don't activate workspaces if we've already told the game
|
||||
# that initial-log-in is done or if we've already kicked
|
||||
@ -84,7 +88,8 @@ class AccountV2Subsystem:
|
||||
_ba.screenmessage(
|
||||
f'\'{account.workspacename}\''
|
||||
f' will be activated at next app launch.',
|
||||
color=(1, 1, 0))
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
return
|
||||
|
||||
@ -124,8 +129,7 @@ class AccountV2Handle:
|
||||
self.workspaceid: str | None = None
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Support for "with" statement.
|
||||
"""
|
||||
"""Support for "with" statement."""
|
||||
|
||||
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> Any:
|
||||
"""Support for "with" statement."""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -9,8 +9,12 @@ from typing import TYPE_CHECKING, Generic, TypeVar
|
||||
import _ba
|
||||
from ba._team import Team
|
||||
from ba._player import Player
|
||||
from ba._error import (print_exception, SessionTeamNotFoundError,
|
||||
SessionPlayerNotFoundError, NodeNotFoundError)
|
||||
from ba._error import (
|
||||
print_exception,
|
||||
SessionTeamNotFoundError,
|
||||
SessionPlayerNotFoundError,
|
||||
NodeNotFoundError,
|
||||
)
|
||||
from ba._dependency import DependencyComponent
|
||||
from ba._general import Call, verify_object_death
|
||||
from ba._messages import UNHANDLED
|
||||
@ -199,8 +203,11 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
session = self._session()
|
||||
if session is not None:
|
||||
_ba.pushcall(
|
||||
Call(session.transitioning_out_activity_was_freed,
|
||||
self.can_show_ad_on_death))
|
||||
Call(
|
||||
session.transitioning_out_activity_was_freed,
|
||||
self.can_show_ad_on_death,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def globalsnode(self) -> ba.Node:
|
||||
@ -220,6 +227,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
"""
|
||||
if self._stats is None:
|
||||
from ba._error import NotFoundError
|
||||
|
||||
raise NotFoundError()
|
||||
return self._stats
|
||||
|
||||
@ -285,7 +293,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
5.0,
|
||||
Call(self._check_activity_death, ref, [0]),
|
||||
repeat=True,
|
||||
timetype=TimeType.REAL)
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
|
||||
# Run _expire in an empty context; nothing should be happening in
|
||||
# there except deleting things which requires no context.
|
||||
@ -296,8 +305,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
with _ba.Context('empty'):
|
||||
self._expire()
|
||||
else:
|
||||
raise RuntimeError(f'destroy() called when'
|
||||
f' already expired for {self}')
|
||||
raise RuntimeError(
|
||||
f'destroy() called when' f' already expired for {self}'
|
||||
)
|
||||
|
||||
def retain_actor(self, actor: ba.Actor) -> None:
|
||||
"""Add a strong-reference to a ba.Actor to this Activity.
|
||||
@ -308,6 +318,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
"""
|
||||
if __debug__:
|
||||
from ba._actor import Actor
|
||||
|
||||
assert isinstance(actor, Actor)
|
||||
self._actor_refs.append(actor)
|
||||
|
||||
@ -318,6 +329,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
"""
|
||||
if __debug__:
|
||||
from ba._actor import Actor
|
||||
|
||||
assert isinstance(actor, Actor)
|
||||
self._actor_weak_refs.append(weakref.ref(actor))
|
||||
|
||||
@ -330,6 +342,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
session = self._session()
|
||||
if session is None:
|
||||
from ba._error import SessionNotFoundError
|
||||
|
||||
raise SessionNotFoundError()
|
||||
return session
|
||||
|
||||
@ -381,7 +394,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
|
||||
def has_transitioned_in(self) -> bool:
|
||||
"""Return whether ba.Activity.on_transition_in()
|
||||
has been called."""
|
||||
has been called."""
|
||||
return self._has_transitioned_in
|
||||
|
||||
def has_begun(self) -> bool:
|
||||
@ -425,7 +438,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
if self.inherits_vr_overlay_center and prev_globals is not None:
|
||||
glb.vr_overlay_center = prev_globals.vr_overlay_center
|
||||
glb.vr_overlay_center_enabled = (
|
||||
prev_globals.vr_overlay_center_enabled)
|
||||
prev_globals.vr_overlay_center_enabled
|
||||
)
|
||||
|
||||
# If they want to inherit tint from the previous self.
|
||||
if self.inherits_tint and prev_globals is not None:
|
||||
@ -435,9 +449,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
|
||||
# Start pruning our various things periodically.
|
||||
self._prune_dead_actors()
|
||||
self._prune_dead_actors_timer = _ba.Timer(5.17,
|
||||
self._prune_dead_actors,
|
||||
repeat=True)
|
||||
self._prune_dead_actors_timer = _ba.Timer(
|
||||
5.17, self._prune_dead_actors, repeat=True
|
||||
)
|
||||
|
||||
_ba.timer(13.3, self._prune_delay_deletes, repeat=True)
|
||||
|
||||
@ -491,10 +505,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
# activity launch; just wanna be sure that is intentional.
|
||||
self.on_begin()
|
||||
|
||||
def end(self,
|
||||
results: Any = None,
|
||||
delay: float = 0.0,
|
||||
force: bool = False) -> None:
|
||||
def end(
|
||||
self, results: Any = None, delay: float = 0.0, force: bool = False
|
||||
) -> None:
|
||||
"""Commences Activity shutdown and delivers results to the ba.Session.
|
||||
|
||||
'delay' is the time delay before the Activity actually ends
|
||||
@ -543,7 +556,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
sessionplayer.setactivity(self)
|
||||
with _ba.Context(self):
|
||||
sessionplayer.activityplayer = player = self.create_player(
|
||||
sessionplayer)
|
||||
sessionplayer
|
||||
)
|
||||
player.postinit(sessionplayer)
|
||||
|
||||
assert player not in team.players
|
||||
@ -654,7 +668,8 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
self._teams_that_left.append(weakref.ref(team))
|
||||
|
||||
def _reset_session_player_for_no_activity(
|
||||
self, sessionplayer: ba.SessionPlayer) -> None:
|
||||
self, sessionplayer: ba.SessionPlayer
|
||||
) -> None:
|
||||
|
||||
# Let's be extra-defensive here: killing a node/input-call/etc
|
||||
# could trigger user-code resulting in errors, but we would still
|
||||
@ -664,13 +679,15 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
except Exception:
|
||||
print_exception(
|
||||
f'Error resetting SessionPlayer node on {sessionplayer}'
|
||||
f' for {self}.')
|
||||
f' for {self}.'
|
||||
)
|
||||
try:
|
||||
sessionplayer.resetinput()
|
||||
except Exception:
|
||||
print_exception(
|
||||
f'Error resetting SessionPlayer input on {sessionplayer}'
|
||||
f' for {self}.')
|
||||
f' for {self}.'
|
||||
)
|
||||
|
||||
# These should never fail I think...
|
||||
sessionplayer.setactivity(None)
|
||||
@ -691,21 +708,26 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
self._playertype = type(self).__orig_bases__[-1].__args__[0]
|
||||
if not isinstance(self._playertype, type):
|
||||
self._playertype = Player
|
||||
print(f'ERROR: {type(self)} was not passed a Player'
|
||||
f' type argument; please explicitly pass ba.Player'
|
||||
f' if you do not want to override it.')
|
||||
print(
|
||||
f'ERROR: {type(self)} was not passed a Player'
|
||||
f' type argument; please explicitly pass ba.Player'
|
||||
f' if you do not want to override it.'
|
||||
)
|
||||
self._teamtype = type(self).__orig_bases__[-1].__args__[1]
|
||||
if not isinstance(self._teamtype, type):
|
||||
self._teamtype = Team
|
||||
print(f'ERROR: {type(self)} was not passed a Team'
|
||||
f' type argument; please explicitly pass ba.Team'
|
||||
f' if you do not want to override it.')
|
||||
print(
|
||||
f'ERROR: {type(self)} was not passed a Team'
|
||||
f' type argument; please explicitly pass ba.Team'
|
||||
f' if you do not want to override it.'
|
||||
)
|
||||
assert issubclass(self._playertype, Player)
|
||||
assert issubclass(self._teamtype, Team)
|
||||
|
||||
@classmethod
|
||||
def _check_activity_death(cls, activity_ref: weakref.ref[Activity],
|
||||
counter: list[int]) -> None:
|
||||
def _check_activity_death(
|
||||
cls, activity_ref: weakref.ref[Activity], counter: list[int]
|
||||
) -> None:
|
||||
"""Sanity check to make sure an Activity was destroyed properly.
|
||||
|
||||
Receives a weakref to a ba.Activity which should have torn itself
|
||||
@ -715,9 +737,13 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
try:
|
||||
import gc
|
||||
import types
|
||||
|
||||
activity = activity_ref()
|
||||
print('ERROR: Activity is not dying when expected:', activity,
|
||||
'(warning ' + str(counter[0] + 1) + ')')
|
||||
print(
|
||||
'ERROR: Activity is not dying when expected:',
|
||||
activity,
|
||||
'(warning ' + str(counter[0] + 1) + ')',
|
||||
)
|
||||
print('This means something is still strong-referencing it.')
|
||||
counter[0] += 1
|
||||
|
||||
@ -784,8 +810,9 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]):
|
||||
try:
|
||||
actor.on_expire()
|
||||
except Exception:
|
||||
print_exception(f'Error in Actor.on_expire()'
|
||||
f' for {actor_ref()}.')
|
||||
print_exception(
|
||||
f'Error in Actor.on_expire()' f' for {actor_ref()}.'
|
||||
)
|
||||
|
||||
def _expire_players(self) -> None:
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import _ba
|
||||
from ba._activity import Activity
|
||||
from ba._music import setmusic, MusicType
|
||||
from ba._generated.enums import InputType, UIScale
|
||||
|
||||
# False-positive from pylint due to our class-generics-filter.
|
||||
from ba._player import EmptyPlayer # pylint: disable=W0611
|
||||
from ba._team import EmptyTeam # pylint: disable=W0611
|
||||
@ -40,6 +41,7 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.mainmenu import MainMenuSession
|
||||
from ba._general import Call
|
||||
|
||||
super().on_begin()
|
||||
_ba.unlock_all_input()
|
||||
_ba.app.ads.call_after_ad(Call(_ba.new_host_session, MainMenuSession))
|
||||
@ -72,10 +74,11 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.actor.tipstext import TipsText
|
||||
from bastd.actor.background import Background
|
||||
|
||||
super().on_transition_in()
|
||||
self._background = Background(fade_time=0.5,
|
||||
start_faded=True,
|
||||
show_logo=True)
|
||||
self._background = Background(
|
||||
fade_time=0.5, start_faded=True, show_logo=True
|
||||
)
|
||||
self._tips_text = TipsText()
|
||||
setmusic(MusicType.CHAR_SELECT)
|
||||
self._join_info = self.session.lobby.create_join_info()
|
||||
@ -103,10 +106,11 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
def on_transition_in(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.actor import background # FIXME: Don't use bastd from ba.
|
||||
|
||||
super().on_transition_in()
|
||||
self._background = background.Background(fade_time=0.5,
|
||||
start_faded=False,
|
||||
show_logo=False)
|
||||
self._background = background.Background(
|
||||
fade_time=0.5, start_faded=False, show_logo=False
|
||||
)
|
||||
|
||||
def on_begin(self) -> None:
|
||||
super().on_begin()
|
||||
@ -143,9 +147,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
|
||||
def on_player_join(self, player: EmptyPlayer) -> None:
|
||||
from ba._general import WeakCall
|
||||
|
||||
super().on_player_join(player)
|
||||
time_till_assign = max(
|
||||
0, self._birth_time + self._min_view_time - _ba.time())
|
||||
0, self._birth_time + self._min_view_time - _ba.time()
|
||||
)
|
||||
|
||||
# If we're still kicking at the end of our assign-delay, assign this
|
||||
# guy's input to trigger us.
|
||||
@ -154,10 +160,11 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
def on_transition_in(self) -> None:
|
||||
from bastd.actor.tipstext import TipsText
|
||||
from bastd.actor.background import Background
|
||||
|
||||
super().on_transition_in()
|
||||
self._background = Background(fade_time=0.5,
|
||||
start_faded=False,
|
||||
show_logo=True)
|
||||
self._background = Background(
|
||||
fade_time=0.5, start_faded=False, show_logo=True
|
||||
)
|
||||
if self._default_show_tips:
|
||||
self._tips_text = TipsText()
|
||||
setmusic(self.default_music)
|
||||
@ -166,6 +173,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.actor.text import Text
|
||||
from ba import _language
|
||||
|
||||
super().on_begin()
|
||||
|
||||
# Pop up a 'press any button to continue' statement after our
|
||||
@ -178,24 +186,30 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
else:
|
||||
sval = _language.Lstr(resource='pressAnyButtonText')
|
||||
|
||||
Text(self._custom_continue_message
|
||||
if self._custom_continue_message is not None else sval,
|
||||
v_attach=Text.VAttach.BOTTOM,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
flash=True,
|
||||
vr_depth=50,
|
||||
position=(0, 10),
|
||||
scale=0.8,
|
||||
color=(0.5, 0.7, 0.5, 0.5),
|
||||
transition=Text.Transition.IN_BOTTOM_SLOW,
|
||||
transition_delay=self._min_view_time).autoretain()
|
||||
Text(
|
||||
self._custom_continue_message
|
||||
if self._custom_continue_message is not None
|
||||
else sval,
|
||||
v_attach=Text.VAttach.BOTTOM,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
flash=True,
|
||||
vr_depth=50,
|
||||
position=(0, 10),
|
||||
scale=0.8,
|
||||
color=(0.5, 0.7, 0.5, 0.5),
|
||||
transition=Text.Transition.IN_BOTTOM_SLOW,
|
||||
transition_delay=self._min_view_time,
|
||||
).autoretain()
|
||||
|
||||
def _player_press(self) -> None:
|
||||
|
||||
# If this activity is a good 'end point', ask server-mode just once if
|
||||
# it wants to do anything special like switch sessions or kill the app.
|
||||
if (self._allow_server_transition and _ba.app.server is not None
|
||||
and self._server_transitioning is None):
|
||||
if (
|
||||
self._allow_server_transition
|
||||
and _ba.app.server is not None
|
||||
and self._server_transitioning is None
|
||||
):
|
||||
self._server_transitioning = _ba.app.server.handle_transition()
|
||||
assert isinstance(self._server_transitioning, bool)
|
||||
|
||||
@ -211,6 +225,12 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
|
||||
# Just to be extra careful, don't assign if we're transitioning out.
|
||||
# (though theoretically that should be ok).
|
||||
if not self.is_transitioning_out() and player:
|
||||
player.assigninput((InputType.JUMP_PRESS, InputType.PUNCH_PRESS,
|
||||
InputType.BOMB_PRESS, InputType.PICK_UP_PRESS),
|
||||
self._player_press)
|
||||
player.assigninput(
|
||||
(
|
||||
InputType.JUMP_PRESS,
|
||||
InputType.PUNCH_PRESS,
|
||||
InputType.BOMB_PRESS,
|
||||
InputType.PICK_UP_PRESS,
|
||||
),
|
||||
self._player_press,
|
||||
)
|
||||
|
||||
@ -38,31 +38,41 @@ class AdsSubsystem:
|
||||
|
||||
# Print this message once every 10 minutes at most.
|
||||
tval = _ba.time(TimeType.REAL)
|
||||
if (self.last_in_game_ad_remove_message_show_time is None or
|
||||
(tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)):
|
||||
if self.last_in_game_ad_remove_message_show_time is None or (
|
||||
tval - self.last_in_game_ad_remove_message_show_time > 60 * 10
|
||||
):
|
||||
self.last_in_game_ad_remove_message_show_time = tval
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(
|
||||
1.0,
|
||||
lambda: _ba.screenmessage(Lstr(
|
||||
resource='removeInGameAdsText',
|
||||
subs=[('${PRO}',
|
||||
Lstr(resource='store.bombSquadProNameText')),
|
||||
('${APP_NAME}', Lstr(resource='titleText'))]),
|
||||
color=(1, 1, 0)),
|
||||
timetype=TimeType.REAL)
|
||||
lambda: _ba.screenmessage(
|
||||
Lstr(
|
||||
resource='removeInGameAdsText',
|
||||
subs=[
|
||||
(
|
||||
'${PRO}',
|
||||
Lstr(resource='store.bombSquadProNameText'),
|
||||
),
|
||||
('${APP_NAME}', Lstr(resource='titleText')),
|
||||
],
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
|
||||
def show_ad(self,
|
||||
purpose: str,
|
||||
on_completion_call: Callable[[], Any] | None = None) -> None:
|
||||
def show_ad(
|
||||
self, purpose: str, on_completion_call: Callable[[], Any] | None = None
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
_ba.show_ad(purpose, on_completion_call)
|
||||
|
||||
def show_ad_2(
|
||||
self,
|
||||
purpose: str,
|
||||
on_completion_call: Callable[[bool], Any] | None = None) -> None:
|
||||
self,
|
||||
purpose: str,
|
||||
on_completion_call: Callable[[bool], Any] | None = None,
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
self.last_ad_purpose = purpose
|
||||
_ba.show_ad_2(purpose, on_completion_call)
|
||||
@ -73,6 +83,7 @@ class AdsSubsystem:
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
app = _ba.app
|
||||
show = True
|
||||
|
||||
@ -95,16 +106,22 @@ class AdsSubsystem:
|
||||
launch_count = app.config.get('launchCount', 0)
|
||||
|
||||
# If we're seeing short ads we may want to space them differently.
|
||||
interval_mult = (_internal.get_v1_account_misc_read_val(
|
||||
'ads.shortIntervalMult', 1.0)
|
||||
if self.last_ad_was_short else 1.0)
|
||||
interval_mult = (
|
||||
_internal.get_v1_account_misc_read_val(
|
||||
'ads.shortIntervalMult', 1.0
|
||||
)
|
||||
if self.last_ad_was_short
|
||||
else 1.0
|
||||
)
|
||||
if self.ad_amt is None:
|
||||
if launch_count <= 1:
|
||||
self.ad_amt = _internal.get_v1_account_misc_read_val(
|
||||
'ads.startVal1', 0.99)
|
||||
'ads.startVal1', 0.99
|
||||
)
|
||||
else:
|
||||
self.ad_amt = _internal.get_v1_account_misc_read_val(
|
||||
'ads.startVal2', 1.0)
|
||||
'ads.startVal2', 1.0
|
||||
)
|
||||
interval = None
|
||||
else:
|
||||
# So far we're cleared to show; now calc our
|
||||
@ -113,27 +130,33 @@ class AdsSubsystem:
|
||||
# playing).
|
||||
base = 'ads' if _ba.has_video_ads() else 'ads2'
|
||||
min_lc = _internal.get_v1_account_misc_read_val(
|
||||
base + '.minLC', 0.0)
|
||||
base + '.minLC', 0.0
|
||||
)
|
||||
max_lc = _internal.get_v1_account_misc_read_val(
|
||||
base + '.maxLC', 5.0)
|
||||
min_lc_scale = (_internal.get_v1_account_misc_read_val(
|
||||
base + '.minLCScale', 0.25))
|
||||
max_lc_scale = (_internal.get_v1_account_misc_read_val(
|
||||
base + '.maxLCScale', 0.34))
|
||||
min_lc_interval = (_internal.get_v1_account_misc_read_val(
|
||||
base + '.minLCInterval', 360))
|
||||
max_lc_interval = (_internal.get_v1_account_misc_read_val(
|
||||
base + '.maxLCInterval', 300))
|
||||
base + '.maxLC', 5.0
|
||||
)
|
||||
min_lc_scale = _internal.get_v1_account_misc_read_val(
|
||||
base + '.minLCScale', 0.25
|
||||
)
|
||||
max_lc_scale = _internal.get_v1_account_misc_read_val(
|
||||
base + '.maxLCScale', 0.34
|
||||
)
|
||||
min_lc_interval = _internal.get_v1_account_misc_read_val(
|
||||
base + '.minLCInterval', 360
|
||||
)
|
||||
max_lc_interval = _internal.get_v1_account_misc_read_val(
|
||||
base + '.maxLCInterval', 300
|
||||
)
|
||||
if launch_count < min_lc:
|
||||
lc_amt = 0.0
|
||||
elif launch_count > max_lc:
|
||||
lc_amt = 1.0
|
||||
else:
|
||||
lc_amt = ((float(launch_count) - min_lc) /
|
||||
(max_lc - min_lc))
|
||||
lc_amt = (float(launch_count) - min_lc) / (max_lc - min_lc)
|
||||
incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale
|
||||
interval = ((1.0 - lc_amt) * min_lc_interval +
|
||||
lc_amt * max_lc_interval)
|
||||
interval = (
|
||||
1.0 - lc_amt
|
||||
) * min_lc_interval + lc_amt * max_lc_interval
|
||||
self.ad_amt += incr
|
||||
assert self.ad_amt is not None
|
||||
if self.ad_amt >= 1.0:
|
||||
@ -143,12 +166,14 @@ class AdsSubsystem:
|
||||
# After we've reached the traditional show-threshold once,
|
||||
# try again whenever its been INTERVAL since our last successful
|
||||
# show.
|
||||
elif (
|
||||
self.attempted_first_ad and
|
||||
(self.last_ad_completion_time is None or
|
||||
(interval is not None
|
||||
and _ba.time(TimeType.REAL) - self.last_ad_completion_time >
|
||||
(interval * interval_mult)))):
|
||||
elif self.attempted_first_ad and (
|
||||
self.last_ad_completion_time is None
|
||||
or (
|
||||
interval is not None
|
||||
and _ba.time(TimeType.REAL) - self.last_ad_completion_time
|
||||
> (interval * interval_mult)
|
||||
)
|
||||
):
|
||||
# Reset our other counter too in this case.
|
||||
self.ad_amt = 0.0
|
||||
else:
|
||||
@ -161,7 +186,6 @@ class AdsSubsystem:
|
||||
# (in case some random ad network doesn't properly deliver its
|
||||
# completion callback).
|
||||
class _Payload:
|
||||
|
||||
def __init__(self, pcall: Callable[[], Any]):
|
||||
self._call = pcall
|
||||
self._ran = False
|
||||
@ -171,20 +195,31 @@ class AdsSubsystem:
|
||||
if not self._ran:
|
||||
if fallback:
|
||||
print(
|
||||
('ERROR: relying on fallback ad-callback! '
|
||||
'last network: ' + app.ads.last_ad_network +
|
||||
' (set ' + str(
|
||||
int(time.time() -
|
||||
app.ads.last_ad_network_set_time)) +
|
||||
's ago); purpose=' + app.ads.last_ad_purpose))
|
||||
(
|
||||
'ERROR: relying on fallback ad-callback! '
|
||||
'last network: '
|
||||
+ app.ads.last_ad_network
|
||||
+ ' (set '
|
||||
+ str(
|
||||
int(
|
||||
time.time()
|
||||
- app.ads.last_ad_network_set_time
|
||||
)
|
||||
)
|
||||
+ 's ago); purpose='
|
||||
+ app.ads.last_ad_purpose
|
||||
)
|
||||
)
|
||||
_ba.pushcall(self._call)
|
||||
self._ran = True
|
||||
|
||||
payload = _Payload(call)
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(5.0,
|
||||
lambda: payload.run(fallback=True),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
5.0,
|
||||
lambda: payload.run(fallback=True),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
self.show_ad('between_game', on_completion_call=payload.run)
|
||||
else:
|
||||
_ba.pushcall(call) # Just run the callback without the ad.
|
||||
|
||||
@ -20,6 +20,7 @@ def game_begin_analytics() -> None:
|
||||
from ba._freeforallsession import FreeForAllSession
|
||||
from ba._coopsession import CoopSession
|
||||
from ba._gameactivity import GameActivity
|
||||
|
||||
activity = _ba.getactivity(False)
|
||||
session = _ba.getsession(False)
|
||||
|
||||
@ -31,8 +32,11 @@ def game_begin_analytics() -> None:
|
||||
campaign = session.campaign
|
||||
assert campaign is not None
|
||||
_ba.set_analytics_screen(
|
||||
'Coop Game: ' + campaign.name + ' ' +
|
||||
campaign.getlevel(_ba.app.coop_session_args['level']).name)
|
||||
'Coop Game: '
|
||||
+ campaign.name
|
||||
+ ' '
|
||||
+ campaign.getlevel(_ba.app.coop_session_args['level']).name
|
||||
)
|
||||
_ba.increment_analytics_count('Co-op round start')
|
||||
if len(activity.players) == 1:
|
||||
_ba.increment_analytics_count('Co-op round start 1 human player')
|
||||
@ -49,9 +53,11 @@ def game_begin_analytics() -> None:
|
||||
if len(activity.players) == 1:
|
||||
_ba.increment_analytics_count('Teams round start 1 human player')
|
||||
elif 1 < len(activity.players) < 8:
|
||||
_ba.increment_analytics_count('Teams round start ' +
|
||||
str(len(activity.players)) +
|
||||
' human players')
|
||||
_ba.increment_analytics_count(
|
||||
'Teams round start '
|
||||
+ str(len(activity.players))
|
||||
+ ' human players'
|
||||
)
|
||||
elif len(activity.players) >= 8:
|
||||
_ba.increment_analytics_count('Teams round start 8+ human players')
|
||||
|
||||
@ -60,14 +66,18 @@ def game_begin_analytics() -> None:
|
||||
_ba.increment_analytics_count('Free-for-all round start')
|
||||
if len(activity.players) == 1:
|
||||
_ba.increment_analytics_count(
|
||||
'Free-for-all round start 1 human player')
|
||||
'Free-for-all round start 1 human player'
|
||||
)
|
||||
elif 1 < len(activity.players) < 8:
|
||||
_ba.increment_analytics_count('Free-for-all round start ' +
|
||||
str(len(activity.players)) +
|
||||
' human players')
|
||||
_ba.increment_analytics_count(
|
||||
'Free-for-all round start '
|
||||
+ str(len(activity.players))
|
||||
+ ' human players'
|
||||
)
|
||||
elif len(activity.players) >= 8:
|
||||
_ba.increment_analytics_count(
|
||||
'Free-for-all round start 8+ human players')
|
||||
'Free-for-all round start 8+ human players'
|
||||
)
|
||||
|
||||
# For some analytics tracking on the c layer.
|
||||
_ba.reset_game_activity_tracking()
|
||||
|
||||
@ -198,6 +198,7 @@ class App:
|
||||
accordingly and set to target the new API version number.
|
||||
"""
|
||||
from ba._meta import CURRENT_API_VERSION
|
||||
|
||||
return CURRENT_API_VERSION
|
||||
|
||||
@property
|
||||
@ -369,19 +370,33 @@ class App:
|
||||
|
||||
# FIXME: This should not be hard-coded.
|
||||
for maptype in [
|
||||
stdmaps.HockeyStadium, stdmaps.FootballStadium,
|
||||
stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout,
|
||||
stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad,
|
||||
stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop,
|
||||
stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts,
|
||||
stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage
|
||||
stdmaps.HockeyStadium,
|
||||
stdmaps.FootballStadium,
|
||||
stdmaps.Bridgit,
|
||||
stdmaps.BigG,
|
||||
stdmaps.Roundabout,
|
||||
stdmaps.MonkeyFace,
|
||||
stdmaps.ZigZag,
|
||||
stdmaps.ThePad,
|
||||
stdmaps.DoomShroom,
|
||||
stdmaps.LakeFrigid,
|
||||
stdmaps.TipTop,
|
||||
stdmaps.CragCastle,
|
||||
stdmaps.TowerD,
|
||||
stdmaps.HappyThoughts,
|
||||
stdmaps.StepRightUp,
|
||||
stdmaps.Courtyard,
|
||||
stdmaps.Rampage,
|
||||
]:
|
||||
_map.register_map(maptype)
|
||||
|
||||
# Non-test, non-debug builds should generally be blessed; warn if not.
|
||||
# (so I don't accidentally release a build that can't play tourneys)
|
||||
if (not self.debug_build and not self.test_build
|
||||
and not _internal.is_blessed()):
|
||||
if (
|
||||
not self.debug_build
|
||||
and not self.test_build
|
||||
and not _internal.is_blessed()
|
||||
):
|
||||
_ba.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))
|
||||
|
||||
# If there's a leftover log file, attempt to upload it to the
|
||||
@ -393,6 +408,7 @@ class App:
|
||||
if not self.config_file_healthy:
|
||||
if self.platform in ('mac', 'linux', 'windows'):
|
||||
from bastd.ui import configerror
|
||||
|
||||
configerror.ConfigErrorWindow()
|
||||
return
|
||||
|
||||
@ -418,10 +434,13 @@ class App:
|
||||
# pending special offer.
|
||||
def check_special_offer() -> None:
|
||||
from bastd.ui.specialoffer import show_offer
|
||||
|
||||
config = self.config
|
||||
if ('pendingSpecialOffer' in config
|
||||
and _internal.get_public_login_id()
|
||||
== config['pendingSpecialOffer']['a']):
|
||||
if (
|
||||
'pendingSpecialOffer' in config
|
||||
and _internal.get_public_login_id()
|
||||
== config['pendingSpecialOffer']['a']
|
||||
):
|
||||
self.special_offer = config['pendingSpecialOffer']['o']
|
||||
show_offer()
|
||||
|
||||
@ -436,8 +455,9 @@ class App:
|
||||
|
||||
# See note below in on_app_pause.
|
||||
if self.state != self.State.LAUNCHING:
|
||||
logging.error('on_app_launch found state %s; expected LAUNCHING.',
|
||||
self.state)
|
||||
logging.error(
|
||||
'on_app_launch found state %s; expected LAUNCHING.', self.state
|
||||
)
|
||||
|
||||
self._launch_completed = True
|
||||
self._update_state()
|
||||
@ -501,6 +521,7 @@ class App:
|
||||
def read_config(self) -> None:
|
||||
"""(internal)"""
|
||||
from ba._appconfig import read_config
|
||||
|
||||
self._config, self.config_file_healthy = read_config()
|
||||
|
||||
def pause(self) -> None:
|
||||
@ -510,8 +531,11 @@ class App:
|
||||
to pause ..we now no longer pause if there are connected clients.
|
||||
"""
|
||||
activity: ba.Activity | None = _ba.get_foreground_host_activity()
|
||||
if (activity is not None and activity.allow_pausing
|
||||
and not _ba.have_connected_clients()):
|
||||
if (
|
||||
activity is not None
|
||||
and activity.allow_pausing
|
||||
and not _ba.have_connected_clients()
|
||||
):
|
||||
from ba._language import Lstr
|
||||
from ba._nodeactor import NodeActor
|
||||
|
||||
@ -525,13 +549,16 @@ class App:
|
||||
|
||||
# FIXME: This should not be an attr on Actor.
|
||||
activity.paused_text = NodeActor(
|
||||
_ba.newnode('text',
|
||||
attrs={
|
||||
'text': Lstr(resource='pausedByHostText'),
|
||||
'client_only': True,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center'
|
||||
}))
|
||||
_ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': Lstr(resource='pausedByHostText'),
|
||||
'client_only': True,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def resume(self) -> None:
|
||||
"""Resume the game due to a user request or menu closing.
|
||||
@ -562,13 +589,15 @@ class App:
|
||||
# Make note to add it to our challenges UI.
|
||||
self.custom_coop_practice_games.append(f'Challenges:{level.name}')
|
||||
|
||||
def return_to_main_menu_session_gracefully(self,
|
||||
reset_ui: bool = True) -> None:
|
||||
def return_to_main_menu_session_gracefully(
|
||||
self, reset_ui: bool = True
|
||||
) -> None:
|
||||
"""Attempt to cleanly get back to the main menu."""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba import _benchmark
|
||||
from ba._general import Call
|
||||
from bastd.mainmenu import MainMenuSession
|
||||
|
||||
if reset_ui:
|
||||
_ba.app.ui.clear_main_menu_window()
|
||||
|
||||
@ -587,10 +616,9 @@ class App:
|
||||
|
||||
# Kick off a little transaction so we'll hopefully have all the
|
||||
# latest account state when we get back to the menu.
|
||||
_internal.add_transaction({
|
||||
'type': 'END_SESSION',
|
||||
'sType': str(type(host_session))
|
||||
})
|
||||
_internal.add_transaction(
|
||||
{'type': 'END_SESSION', 'sType': str(type(host_session))}
|
||||
)
|
||||
_internal.run_transactions()
|
||||
|
||||
host_session.end()
|
||||
@ -609,14 +637,14 @@ class App:
|
||||
else:
|
||||
self.main_menu_resume_callbacks.append(call)
|
||||
|
||||
def launch_coop_game(self,
|
||||
game: str,
|
||||
force: bool = False,
|
||||
args: dict | None = None) -> bool:
|
||||
def launch_coop_game(
|
||||
self, game: str, force: bool = False, args: dict | None = None
|
||||
) -> bool:
|
||||
"""High level way to launch a local co-op session."""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._campaign import getcampaign
|
||||
from bastd.ui.coop.level import CoopLevelLockedWindow
|
||||
|
||||
if args is None:
|
||||
args = {}
|
||||
if game == '':
|
||||
@ -633,7 +661,8 @@ class App:
|
||||
if not level.complete:
|
||||
CoopLevelLockedWindow(
|
||||
campaign.getlevel(levelname).displayname,
|
||||
campaign.getlevel(level.name).displayname)
|
||||
campaign.getlevel(level.name).displayname,
|
||||
)
|
||||
return False
|
||||
|
||||
# Ok, we're good to go.
|
||||
@ -646,12 +675,15 @@ class App:
|
||||
|
||||
def _fade_end() -> None:
|
||||
from ba import _coopsession
|
||||
|
||||
try:
|
||||
_ba.new_host_session(_coopsession.CoopSession)
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
from bastd.mainmenu import MainMenuSession
|
||||
|
||||
_ba.new_host_session(MainMenuSession)
|
||||
|
||||
_ba.fade_screen(False, endcall=_fade_end)
|
||||
@ -660,6 +692,7 @@ class App:
|
||||
def handle_deep_link(self, url: str) -> None:
|
||||
"""Handle a deep link URL."""
|
||||
from ba._language import Lstr
|
||||
|
||||
appname = _ba.appname()
|
||||
if url.startswith(f'{appname}://code/'):
|
||||
code = url.replace(f'{appname}://code/', '')
|
||||
|
||||
@ -115,16 +115,29 @@ def read_config() -> tuple[AppConfig, bool]:
|
||||
config_file_healthy = True
|
||||
|
||||
except Exception as exc:
|
||||
print(('error reading config file at time ' +
|
||||
str(_ba.time(TimeType.REAL)) + ': \'' + config_file_path +
|
||||
'\':\n'), exc)
|
||||
print(
|
||||
(
|
||||
'error reading config file at time '
|
||||
+ str(_ba.time(TimeType.REAL))
|
||||
+ ': \''
|
||||
+ config_file_path
|
||||
+ '\':\n'
|
||||
),
|
||||
exc,
|
||||
)
|
||||
|
||||
# Whenever this happens lets back up the broken one just in case it
|
||||
# gets overwritten accidentally.
|
||||
print(('backing up current config file to \'' + config_file_path +
|
||||
".broken\'"))
|
||||
print(
|
||||
(
|
||||
'backing up current config file to \''
|
||||
+ config_file_path
|
||||
+ ".broken\'"
|
||||
)
|
||||
)
|
||||
try:
|
||||
import shutil
|
||||
|
||||
shutil.copyfile(config_file_path, config_file_path + '.broken')
|
||||
except Exception as exc2:
|
||||
print('EXC copying broken config:', exc2)
|
||||
@ -154,8 +167,11 @@ def commit_app_config(force: bool = False) -> None:
|
||||
(internal)
|
||||
"""
|
||||
from ba._internal import mark_config_dirty
|
||||
|
||||
if not _ba.app.config_file_healthy and not force:
|
||||
print('Current config file is broken; '
|
||||
'skipping write to avoid losing settings.')
|
||||
print(
|
||||
'Current config file is broken; '
|
||||
'skipping write to avoid losing settings.'
|
||||
)
|
||||
return
|
||||
mark_config_dirty()
|
||||
|
||||
@ -17,9 +17,12 @@ class AppDelegate:
|
||||
"""
|
||||
|
||||
def create_default_game_settings_ui(
|
||||
self, gameclass: type[ba.GameActivity],
|
||||
sessiontype: type[ba.Session], settings: dict | None,
|
||||
completion_call: Callable[[dict | None], None]) -> None:
|
||||
self,
|
||||
gameclass: type[ba.GameActivity],
|
||||
sessiontype: type[ba.Session],
|
||||
settings: dict | None,
|
||||
completion_call: Callable[[dict | None], None],
|
||||
) -> None:
|
||||
"""Launch a UI to configure the given game config.
|
||||
|
||||
It should manipulate the contents of config and call completion_call
|
||||
@ -27,5 +30,7 @@ class AppDelegate:
|
||||
"""
|
||||
del gameclass, sessiontype, settings, completion_call # Unused.
|
||||
from ba import _error
|
||||
|
||||
_error.print_error(
|
||||
"create_default_game_settings_ui needs to be overridden")
|
||||
"create_default_game_settings_ui needs to be overridden"
|
||||
)
|
||||
|
||||
@ -42,6 +42,7 @@ def is_browser_likely_available() -> bool:
|
||||
def get_remote_app_name() -> ba.Lstr:
|
||||
"""(internal)"""
|
||||
from ba import _language
|
||||
|
||||
return _language.Lstr(resource='remote_app.app_name')
|
||||
|
||||
|
||||
@ -59,6 +60,7 @@ def handle_v1_cloud_log() -> None:
|
||||
from ba._net import master_server_post
|
||||
from ba._generated.enums import TimeType
|
||||
from ba._internal import get_news_show
|
||||
|
||||
app = _ba.app
|
||||
app.log_have_new = True
|
||||
if not app.log_upload_timer_started:
|
||||
@ -112,10 +114,12 @@ def handle_v1_cloud_log() -> None:
|
||||
|
||||
if not _ba.is_log_full():
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(600.0,
|
||||
_reset,
|
||||
timetype=TimeType.REAL,
|
||||
suppress_format_warning=True)
|
||||
_ba.timer(
|
||||
600.0,
|
||||
_reset,
|
||||
timetype=TimeType.REAL,
|
||||
suppress_format_warning=True,
|
||||
)
|
||||
|
||||
|
||||
def handle_leftover_v1_cloud_log_file() -> None:
|
||||
@ -125,8 +129,9 @@ def handle_leftover_v1_cloud_log_file() -> None:
|
||||
from ba._net import master_server_post
|
||||
|
||||
if os.path.exists(_ba.get_v1_cloud_log_file_path()):
|
||||
with open(_ba.get_v1_cloud_log_file_path(),
|
||||
encoding='utf-8') as infile:
|
||||
with open(
|
||||
_ba.get_v1_cloud_log_file_path(), encoding='utf-8'
|
||||
) as infile:
|
||||
info = json.loads(infile.read())
|
||||
infile.close()
|
||||
do_send = should_submit_debug_info()
|
||||
@ -150,6 +155,7 @@ def handle_leftover_v1_cloud_log_file() -> None:
|
||||
os.remove(_ba.get_v1_cloud_log_file_path())
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Error handling leftover log file.')
|
||||
|
||||
|
||||
@ -180,9 +186,10 @@ def garbage_collect() -> None:
|
||||
|
||||
|
||||
def print_live_object_warnings(
|
||||
when: Any,
|
||||
ignore_session: ba.Session | None = None,
|
||||
ignore_activity: ba.Activity | None = None) -> None:
|
||||
when: Any,
|
||||
ignore_session: ba.Session | None = None,
|
||||
ignore_activity: ba.Activity | None = None,
|
||||
) -> None:
|
||||
"""Print warnings for remaining objects in the current context."""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._session import Session
|
||||
@ -229,13 +236,17 @@ def print_corrupt_file_error() -> None:
|
||||
"""Print an error if a corrupt file is found."""
|
||||
from ba._general import Call
|
||||
from ba._generated.enums import TimeType
|
||||
_ba.timer(2.0,
|
||||
lambda: _ba.screenmessage(
|
||||
_ba.app.lang.get_resource('internal.corruptFileText').
|
||||
replace('${EMAIL}', 'support@froemling.net'),
|
||||
color=(1, 0, 0),
|
||||
),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(2.0,
|
||||
Call(_ba.playsound, _ba.getsound('error')),
|
||||
timetype=TimeType.REAL)
|
||||
|
||||
_ba.timer(
|
||||
2.0,
|
||||
lambda: _ba.screenmessage(
|
||||
_ba.app.lang.get_resource('internal.corruptFileText').replace(
|
||||
'${EMAIL}', 'support@froemling.net'
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
_ba.timer(
|
||||
2.0, Call(_ba.playsound, _ba.getsound('error')), timetype=TimeType.REAL
|
||||
)
|
||||
|
||||
@ -15,8 +15,12 @@ import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
from efro.dataclassio import (ioprepped, IOAttrs, dataclass_from_json,
|
||||
dataclass_to_json)
|
||||
from efro.dataclassio import (
|
||||
ioprepped,
|
||||
IOAttrs,
|
||||
dataclass_from_json,
|
||||
dataclass_to_json,
|
||||
)
|
||||
import _ba
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -34,8 +38,9 @@ class FileValue:
|
||||
class State:
|
||||
"""Holds all persistent state for the asset-manager."""
|
||||
|
||||
files: Annotated[dict[str, FileValue],
|
||||
IOAttrs('files')] = field(default_factory=dict)
|
||||
files: Annotated[dict[str, FileValue], IOAttrs('files')] = field(
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
|
||||
class AssetManager:
|
||||
@ -66,8 +71,14 @@ class AssetManager:
|
||||
account_token: str,
|
||||
) -> AssetGather:
|
||||
"""Spawn an asset-gather operation from this manager."""
|
||||
print('would gather', packages, 'and flavor', flavor, 'with token',
|
||||
account_token)
|
||||
print(
|
||||
'would gather',
|
||||
packages,
|
||||
'and flavor',
|
||||
flavor,
|
||||
'with token',
|
||||
account_token,
|
||||
)
|
||||
return AssetGather(self)
|
||||
|
||||
def update(self) -> None:
|
||||
@ -162,9 +173,7 @@ class AssetGather:
|
||||
|
||||
|
||||
def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
|
||||
"""Fetch a given url to a given filename for a given AssetGather.
|
||||
|
||||
"""
|
||||
"""Fetch a given url to a given filename for a given AssetGather."""
|
||||
# pylint: disable=consider-using-with
|
||||
import socket
|
||||
|
||||
@ -175,9 +184,7 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
|
||||
|
||||
# Pass a very short timeout to urllib so we have opportunities
|
||||
# to cancel even with network blockage.
|
||||
req = urllib.request.urlopen(url,
|
||||
context=_ba.app.net.sslcontext,
|
||||
timeout=1)
|
||||
req = urllib.request.urlopen(url, context=_ba.app.net.sslcontext, timeout=1)
|
||||
file_size = int(req.headers['Content-Length'])
|
||||
print(f'\nDownloading: {filename} Bytes: {file_size:,}')
|
||||
|
||||
@ -200,6 +207,7 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
|
||||
data = req.read(block_sz)
|
||||
except ValueError:
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
print('VALUEERROR', flush=True)
|
||||
break
|
||||
@ -210,8 +218,10 @@ def fetch_url(url: str, filename: Path, asset_gather: AssetGather) -> None:
|
||||
print('\n\n\nsorry -- try back later')
|
||||
os.unlink(filename)
|
||||
raise
|
||||
print('\nHmmm... little issue... '
|
||||
'I\'ll wait a couple of seconds')
|
||||
print(
|
||||
'\nHmmm... little issue... '
|
||||
'I\'ll wait a couple of seconds'
|
||||
)
|
||||
time.sleep(3)
|
||||
time_outs += 1
|
||||
continue
|
||||
|
||||
@ -69,13 +69,14 @@ def setup_asyncio() -> asyncio.AbstractEventLoop:
|
||||
if duration > warn_time:
|
||||
logging.warning(
|
||||
'Asyncio loop step took %.4fs; ideal max is %.4f',
|
||||
duration, warn_time)
|
||||
duration,
|
||||
warn_time,
|
||||
)
|
||||
|
||||
global _asyncio_timer # pylint: disable=invalid-name
|
||||
_asyncio_timer = _ba.Timer(1.0 / 30.0,
|
||||
run_cycle,
|
||||
timetype=TimeType.REAL,
|
||||
repeat=True)
|
||||
_asyncio_timer = _ba.Timer(
|
||||
1.0 / 30.0, run_cycle, timetype=TimeType.REAL, repeat=True
|
||||
)
|
||||
|
||||
if bool(False):
|
||||
|
||||
|
||||
@ -50,31 +50,42 @@ def run_cpu_benchmark() -> None:
|
||||
_ba.new_host_session(BenchmarkSession, benchmark_type='cpu')
|
||||
|
||||
|
||||
def run_stress_test(playlist_type: str = 'Random',
|
||||
playlist_name: str = '__default__',
|
||||
player_count: int = 8,
|
||||
round_duration: int = 30) -> None:
|
||||
def run_stress_test(
|
||||
playlist_type: str = 'Random',
|
||||
playlist_name: str = '__default__',
|
||||
player_count: int = 8,
|
||||
round_duration: int = 30,
|
||||
) -> None:
|
||||
"""Run a stress test."""
|
||||
from ba import modutils
|
||||
from ba._general import Call
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
_ba.screenmessage(
|
||||
'Beginning stress test.. use '
|
||||
"'End Test' to stop testing.",
|
||||
color=(1, 1, 0))
|
||||
"Beginning stress test.. use 'End Test' to stop testing.",
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
with _ba.Context('ui'):
|
||||
start_stress_test({
|
||||
'playlist_type': playlist_type,
|
||||
'playlist_name': playlist_name,
|
||||
'player_count': player_count,
|
||||
'round_duration': round_duration
|
||||
})
|
||||
_ba.timer(7.0,
|
||||
Call(_ba.screenmessage,
|
||||
('stats will be written to ' +
|
||||
modutils.get_human_readable_user_scripts_path() +
|
||||
'/stress_test_stats.csv')),
|
||||
timetype=TimeType.REAL)
|
||||
start_stress_test(
|
||||
{
|
||||
'playlist_type': playlist_type,
|
||||
'playlist_name': playlist_name,
|
||||
'player_count': player_count,
|
||||
'round_duration': round_duration,
|
||||
}
|
||||
)
|
||||
_ba.timer(
|
||||
7.0,
|
||||
Call(
|
||||
_ba.screenmessage,
|
||||
(
|
||||
'stats will be written to '
|
||||
+ modutils.get_human_readable_user_scripts_path()
|
||||
+ '/stress_test_stats.csv'
|
||||
),
|
||||
),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
|
||||
|
||||
def stop_stress_test() -> None:
|
||||
@ -94,6 +105,7 @@ def start_stress_test(args: dict[str, Any]) -> None:
|
||||
from ba._dualteamsession import DualTeamSession
|
||||
from ba._freeforallsession import FreeForAllSession
|
||||
from ba._generated.enums import TimeType, TimeFormat
|
||||
|
||||
appconfig = _ba.app.config
|
||||
playlist_type = args['playlist_type']
|
||||
if playlist_type == 'Random':
|
||||
@ -101,33 +113,42 @@ def start_stress_test(args: dict[str, Any]) -> None:
|
||||
playlist_type = 'Teams'
|
||||
else:
|
||||
playlist_type = 'Free-For-All'
|
||||
_ba.screenmessage('Running Stress Test (listType="' + playlist_type +
|
||||
'", listName="' + args['playlist_name'] + '")...')
|
||||
_ba.screenmessage(
|
||||
'Running Stress Test (listType="'
|
||||
+ playlist_type
|
||||
+ '", listName="'
|
||||
+ args['playlist_name']
|
||||
+ '")...'
|
||||
)
|
||||
if playlist_type == 'Teams':
|
||||
appconfig['Team Tournament Playlist Selection'] = args['playlist_name']
|
||||
appconfig['Team Tournament Playlist Randomize'] = 1
|
||||
_ba.timer(1.0,
|
||||
Call(_ba.pushcall, Call(_ba.new_host_session,
|
||||
DualTeamSession)),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
1.0,
|
||||
Call(_ba.pushcall, Call(_ba.new_host_session, DualTeamSession)),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
else:
|
||||
appconfig['Free-for-All Playlist Selection'] = args['playlist_name']
|
||||
appconfig['Free-for-All Playlist Randomize'] = 1
|
||||
_ba.timer(1.0,
|
||||
Call(_ba.pushcall,
|
||||
Call(_ba.new_host_session, FreeForAllSession)),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
1.0,
|
||||
Call(_ba.pushcall, Call(_ba.new_host_session, FreeForAllSession)),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
_ba.set_stress_testing(True, args['player_count'])
|
||||
_ba.app.stress_test_reset_timer = _ba.Timer(
|
||||
args['round_duration'] * 1000,
|
||||
Call(_reset_stress_test, args),
|
||||
timetype=TimeType.REAL,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
|
||||
|
||||
def _reset_stress_test(args: dict[str, Any]) -> None:
|
||||
from ba._general import Call
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
_ba.set_stress_testing(False, args['player_count'])
|
||||
_ba.screenmessage('Resetting stress test...')
|
||||
session = _ba.get_foreground_host_session()
|
||||
@ -146,27 +167,32 @@ def run_media_reload_benchmark() -> None:
|
||||
"""Kick off a benchmark to test media reloading speeds."""
|
||||
from ba._general import Call
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
_ba.reload_media()
|
||||
_ba.show_progress_bar()
|
||||
|
||||
def delay_add(start_time: float) -> None:
|
||||
|
||||
def doit(start_time_2: float) -> None:
|
||||
_ba.screenmessage(
|
||||
_ba.app.lang.get_resource(
|
||||
'debugWindow.totalReloadTimeText').replace(
|
||||
'${TIME}',
|
||||
str(_ba.time(TimeType.REAL) - start_time_2)))
|
||||
'debugWindow.totalReloadTimeText'
|
||||
).replace(
|
||||
'${TIME}', str(_ba.time(TimeType.REAL) - start_time_2)
|
||||
)
|
||||
)
|
||||
_ba.print_load_info()
|
||||
if _ba.app.config.resolve('Texture Quality') != 'High':
|
||||
_ba.screenmessage(_ba.app.lang.get_resource(
|
||||
'debugWindow.reloadBenchmarkBestResultsText'),
|
||||
color=(1, 1, 0))
|
||||
_ba.screenmessage(
|
||||
_ba.app.lang.get_resource(
|
||||
'debugWindow.reloadBenchmarkBestResultsText'
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
|
||||
_ba.add_clean_frame_callback(Call(doit, start_time))
|
||||
|
||||
# The reload starts (should add a completion callback to the
|
||||
# reload func to fix this).
|
||||
_ba.timer(0.05,
|
||||
Call(delay_add, _ba.time(TimeType.REAL)),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
0.05, Call(delay_add, _ba.time(TimeType.REAL)), timetype=TimeType.REAL
|
||||
)
|
||||
|
||||
@ -33,11 +33,13 @@ def bootstrap() -> None:
|
||||
# Python's stdout/stderr into it. Then we can at least debug problems
|
||||
# on systems where native stdout/stderr is not easily accessible
|
||||
# such as Android.
|
||||
log_handler = setup_logging(log_path=None,
|
||||
level=LogLevel.DEBUG,
|
||||
suppress_non_root_debug=True,
|
||||
log_stdout_stderr=True,
|
||||
cache_size_limit=1024 * 1024)
|
||||
log_handler = setup_logging(
|
||||
log_path=None,
|
||||
level=LogLevel.DEBUG,
|
||||
suppress_non_root_debug=True,
|
||||
log_stdout_stderr=True,
|
||||
cache_size_limit=1024 * 1024,
|
||||
)
|
||||
|
||||
log_handler.add_callback(_on_log)
|
||||
|
||||
@ -53,7 +55,8 @@ def bootstrap() -> None:
|
||||
f' Ballistica build {expected_build}.\n'
|
||||
f' You are running build {running_build}.'
|
||||
f' This might cause the app to error or misbehave.',
|
||||
file=sys.stderr)
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# In bootstrap_monolithic.py we told Python not to handle SIGINT itself
|
||||
# (because that must be done in the main thread). Now we finish the
|
||||
@ -69,7 +72,8 @@ def bootstrap() -> None:
|
||||
print(
|
||||
'ERROR: Python\'s UTF-8 mode is not set.'
|
||||
' This will likely result in errors.',
|
||||
file=sys.stderr)
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
debug_build = env['debug_build']
|
||||
|
||||
@ -78,20 +82,24 @@ def bootstrap() -> None:
|
||||
print(
|
||||
f'WARNING: Mismatch in debug_build {debug_build}'
|
||||
f' and sys.flags.dev_mode {sys.flags.dev_mode}',
|
||||
file=sys.stderr)
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
# In embedded situations (when we're providing our own Python) let's
|
||||
# also provide our own root certs so ssl works. We can consider overriding
|
||||
# this in particular embedded cases if we can verify that system certs
|
||||
# are working.
|
||||
# (We also allow forcing this via an env var if the user desires)
|
||||
if (_ba.contains_python_dist()
|
||||
or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1'):
|
||||
if (
|
||||
_ba.contains_python_dist()
|
||||
or os.environ.get('BA_USE_BUNDLED_ROOT_CERTS') == '1'
|
||||
):
|
||||
import certifi
|
||||
|
||||
# Let both OpenSSL and requests (if present) know to use this.
|
||||
os.environ['SSL_CERT_FILE'] = os.environ['REQUESTS_CA_BUNDLE'] = (
|
||||
certifi.where())
|
||||
os.environ['SSL_CERT_FILE'] = os.environ[
|
||||
'REQUESTS_CA_BUNDLE'
|
||||
] = certifi.where()
|
||||
|
||||
# On Windows I'm seeing the following error creating asyncio loops in
|
||||
# background threads with the default proactor setup:
|
||||
@ -104,6 +112,7 @@ def bootstrap() -> None:
|
||||
# to default to selector in that case?..
|
||||
if sys.platform == 'win32':
|
||||
import asyncio
|
||||
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
# pylint: disable=c-extension-no-member
|
||||
@ -122,6 +131,7 @@ def bootstrap() -> None:
|
||||
# Now spin up our App instance and store it on both _ba and ba.
|
||||
from ba._app import App
|
||||
import ba
|
||||
|
||||
_ba.app = ba.app = App()
|
||||
_ba.app.log_handler = log_handler
|
||||
|
||||
@ -138,6 +148,7 @@ class _CustomHelper:
|
||||
# (but then things mostly work). Let's get the ugly error out
|
||||
# of the way explicitly.
|
||||
import sysconfig
|
||||
|
||||
try:
|
||||
# This errors once but seems to run cleanly after, so let's
|
||||
# get the error out of the way.
|
||||
@ -146,13 +157,16 @@ class _CustomHelper:
|
||||
pass
|
||||
|
||||
import pydoc
|
||||
|
||||
# Disable pager and interactive help since neither works well
|
||||
# with our funky multi-threaded setup or in-game/cloud consoles.
|
||||
# Let's just do simple text dumps.
|
||||
pydoc.pager = pydoc.plainpager
|
||||
if not args and not kwds:
|
||||
print('Interactive help is not available in this environment.\n'
|
||||
'Type help(object) for help about object.')
|
||||
print(
|
||||
'Interactive help is not available in this environment.\n'
|
||||
'Type help(object) for help about object.'
|
||||
)
|
||||
return None
|
||||
return pydoc.help(*args, **kwds)
|
||||
|
||||
@ -170,6 +184,8 @@ def _on_log(entry: LogEntry) -> None:
|
||||
# We also want to feed some logs to the old V1-cloud-log system.
|
||||
# Let's go with anything warning or higher as well as the stdout/stderr
|
||||
# log messages that ba.app.log_handler creates for us.
|
||||
if entry.level.value >= LogLevel.WARNING.value or entry.name in ('stdout',
|
||||
'stderr'):
|
||||
if entry.level.value >= LogLevel.WARNING.value or entry.name in (
|
||||
'stdout',
|
||||
'stderr',
|
||||
):
|
||||
_ba.v1_cloud_log(entry.message)
|
||||
|
||||
@ -28,10 +28,12 @@ class Campaign:
|
||||
Category: **App Classes**
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
sequential: bool = True,
|
||||
levels: list[ba.Level] | None = None):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
sequential: bool = True,
|
||||
levels: list[ba.Level] | None = None,
|
||||
):
|
||||
self._name = name
|
||||
self._sequential = sequential
|
||||
self._levels: list[ba.Level] = []
|
||||
@ -67,12 +69,13 @@ class Campaign:
|
||||
def getlevel(self, name: str) -> ba.Level:
|
||||
"""Return a contained ba.Level by name."""
|
||||
from ba import _error
|
||||
|
||||
for level in self._levels:
|
||||
if level.name == name:
|
||||
return level
|
||||
raise _error.NotFoundError("Level '" + name +
|
||||
"' not found in campaign '" + self.name +
|
||||
"'")
|
||||
raise _error.NotFoundError(
|
||||
"Level '" + name + "' not found in campaign '" + self.name + "'"
|
||||
)
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset state for the Campaign."""
|
||||
@ -91,9 +94,9 @@ class Campaign:
|
||||
@property
|
||||
def configdict(self) -> dict[str, Any]:
|
||||
"""Return the live config dict for this campaign."""
|
||||
val: dict[str, Any] = (_ba.app.config.setdefault('Campaigns',
|
||||
{}).setdefault(
|
||||
self._name, {}))
|
||||
val: dict[str, Any] = _ba.app.config.setdefault(
|
||||
'Campaigns', {}
|
||||
).setdefault(self._name, {})
|
||||
assert isinstance(val, dict)
|
||||
return val
|
||||
|
||||
@ -121,92 +124,132 @@ def init_campaigns() -> None:
|
||||
Campaign(
|
||||
'Easy',
|
||||
levels=[
|
||||
Level('Onslaught Training',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'training_easy'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Rookie Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'rookie_easy'},
|
||||
preview_texture_name='courtyardPreview'),
|
||||
Level('Rookie Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'rookie_easy'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Pro Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Pro Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Pro Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Uber Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='courtyardPreview'),
|
||||
Level('Uber Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Uber Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='towerDPreview')
|
||||
Level(
|
||||
'Onslaught Training',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'training_easy'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Rookie Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'rookie_easy'},
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
Level(
|
||||
'Rookie Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'rookie_easy'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'pro_easy'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'uber_easy'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
],
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
# "hard" mode
|
||||
register_campaign(
|
||||
Campaign(
|
||||
'Default',
|
||||
levels=[
|
||||
Level('Onslaught Training',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'training'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Rookie Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'rookie'},
|
||||
preview_texture_name='courtyardPreview'),
|
||||
Level('Rookie Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'rookie'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Pro Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Pro Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Pro Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Uber Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='courtyardPreview'),
|
||||
Level('Uber Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Uber Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('The Last Stand',
|
||||
gametype=TheLastStandGame,
|
||||
settings={},
|
||||
preview_texture_name='rampagePreview')
|
||||
Level(
|
||||
'Onslaught Training',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'training'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Rookie Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'rookie'},
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
Level(
|
||||
'Rookie Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'rookie'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Football',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'uber'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'The Last Stand',
|
||||
gametype=TheLastStandGame,
|
||||
settings={},
|
||||
preview_texture_name='rampagePreview',
|
||||
),
|
||||
],
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
# challenges: our 'official' random extra co-op levels
|
||||
register_campaign(
|
||||
@ -214,121 +257,153 @@ def init_campaigns() -> None:
|
||||
'Challenges',
|
||||
sequential=False,
|
||||
levels=[
|
||||
Level('Infinite Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'endless'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Infinite Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'endless'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Race',
|
||||
displayname='${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={
|
||||
'map': 'Big G',
|
||||
'Laps': 3,
|
||||
'Bomb Spawning': 0
|
||||
},
|
||||
preview_texture_name='bigGPreview'),
|
||||
Level('Pro Race',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={
|
||||
'map': 'Big G',
|
||||
'Laps': 3,
|
||||
'Bomb Spawning': 1000
|
||||
},
|
||||
preview_texture_name='bigGPreview'),
|
||||
Level('Lake Frigid Race',
|
||||
displayname='${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={
|
||||
'map': 'Lake Frigid',
|
||||
'Laps': 6,
|
||||
'Mine Spawning': 2000,
|
||||
'Bomb Spawning': 0
|
||||
},
|
||||
preview_texture_name='lakeFrigidPreview'),
|
||||
Level('Football',
|
||||
displayname='${GAME}',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Pro Football',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'tournament_pro'},
|
||||
preview_texture_name='footballStadiumPreview'),
|
||||
Level('Runaround',
|
||||
displayname='${GAME}',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Uber Runaround',
|
||||
displayname='Uber ${GAME}',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'tournament_uber'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('The Last Stand',
|
||||
displayname='${GAME}',
|
||||
gametype=TheLastStandGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='rampagePreview'),
|
||||
Level('Tournament Infinite Onslaught',
|
||||
displayname='Infinite Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'endless_tournament'},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Tournament Infinite Runaround',
|
||||
displayname='Infinite Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'endless_tournament'},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Target Practice',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=TargetPracticeGame,
|
||||
settings={},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Target Practice B',
|
||||
displayname='${GAME}',
|
||||
gametype=TargetPracticeGame,
|
||||
settings={
|
||||
'Target Count': 2,
|
||||
'Enable Impact Bombs': False,
|
||||
'Enable Triple Bombs': False
|
||||
},
|
||||
preview_texture_name='doomShroomPreview'),
|
||||
Level('Meteor Shower',
|
||||
displayname='${GAME}',
|
||||
gametype=MeteorShowerGame,
|
||||
settings={},
|
||||
preview_texture_name='rampagePreview'),
|
||||
Level('Epic Meteor Shower',
|
||||
displayname='${GAME}',
|
||||
gametype=MeteorShowerGame,
|
||||
settings={'Epic Mode': True},
|
||||
preview_texture_name='rampagePreview'),
|
||||
Level('Easter Egg Hunt',
|
||||
displayname='${GAME}',
|
||||
gametype=EasterEggHuntGame,
|
||||
settings={},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level('Pro Easter Egg Hunt',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=EasterEggHuntGame,
|
||||
settings={'Pro Mode': True},
|
||||
preview_texture_name='towerDPreview'),
|
||||
Level(
|
||||
'Infinite Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'endless'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Infinite Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'endless'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Race',
|
||||
displayname='${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={'map': 'Big G', 'Laps': 3, 'Bomb Spawning': 0},
|
||||
preview_texture_name='bigGPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Race',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={'map': 'Big G', 'Laps': 3, 'Bomb Spawning': 1000},
|
||||
preview_texture_name='bigGPreview',
|
||||
),
|
||||
Level(
|
||||
'Lake Frigid Race',
|
||||
displayname='${GAME}',
|
||||
gametype=RaceGame,
|
||||
settings={
|
||||
'map': 'Lake Frigid',
|
||||
'Laps': 6,
|
||||
'Mine Spawning': 2000,
|
||||
'Bomb Spawning': 0,
|
||||
},
|
||||
preview_texture_name='lakeFrigidPreview',
|
||||
),
|
||||
Level(
|
||||
'Football',
|
||||
displayname='${GAME}',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Football',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=FootballCoopGame,
|
||||
settings={'preset': 'tournament_pro'},
|
||||
preview_texture_name='footballStadiumPreview',
|
||||
),
|
||||
Level(
|
||||
'Runaround',
|
||||
displayname='${GAME}',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Uber Runaround',
|
||||
displayname='Uber ${GAME}',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'tournament_uber'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'The Last Stand',
|
||||
displayname='${GAME}',
|
||||
gametype=TheLastStandGame,
|
||||
settings={'preset': 'tournament'},
|
||||
preview_texture_name='rampagePreview',
|
||||
),
|
||||
Level(
|
||||
'Tournament Infinite Onslaught',
|
||||
displayname='Infinite Onslaught',
|
||||
gametype=OnslaughtGame,
|
||||
settings={'preset': 'endless_tournament'},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Tournament Infinite Runaround',
|
||||
displayname='Infinite Runaround',
|
||||
gametype=RunaroundGame,
|
||||
settings={'preset': 'endless_tournament'},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Target Practice',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=TargetPracticeGame,
|
||||
settings={},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Target Practice B',
|
||||
displayname='${GAME}',
|
||||
gametype=TargetPracticeGame,
|
||||
settings={
|
||||
'Target Count': 2,
|
||||
'Enable Impact Bombs': False,
|
||||
'Enable Triple Bombs': False,
|
||||
},
|
||||
preview_texture_name='doomShroomPreview',
|
||||
),
|
||||
Level(
|
||||
'Meteor Shower',
|
||||
displayname='${GAME}',
|
||||
gametype=MeteorShowerGame,
|
||||
settings={},
|
||||
preview_texture_name='rampagePreview',
|
||||
),
|
||||
Level(
|
||||
'Epic Meteor Shower',
|
||||
displayname='${GAME}',
|
||||
gametype=MeteorShowerGame,
|
||||
settings={'Epic Mode': True},
|
||||
preview_texture_name='rampagePreview',
|
||||
),
|
||||
Level(
|
||||
'Easter Egg Hunt',
|
||||
displayname='${GAME}',
|
||||
gametype=EasterEggHuntGame,
|
||||
settings={},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
'Pro Easter Egg Hunt',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=EasterEggHuntGame,
|
||||
settings={'Pro Mode': True},
|
||||
preview_texture_name='towerDPreview',
|
||||
),
|
||||
Level(
|
||||
name='Ninja Fight', # (unique id not seen by player)
|
||||
displayname='${GAME}', # (readable name seen by player)
|
||||
gametype=NinjaFightGame,
|
||||
settings={'preset': 'regular'},
|
||||
preview_texture_name='courtyardPreview'),
|
||||
Level(name='Pro Ninja Fight',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=NinjaFightGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='courtyardPreview')
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
Level(
|
||||
name='Pro Ninja Fight',
|
||||
displayname='Pro ${GAME}',
|
||||
gametype=NinjaFightGame,
|
||||
settings={'preset': 'pro'},
|
||||
preview_texture_name='courtyardPreview',
|
||||
),
|
||||
],
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
@ -35,7 +35,8 @@ class CloudSubsystem:
|
||||
self,
|
||||
msg: bacommon.cloud.LoginProxyRequestMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.cloud.LoginProxyRequestResponse | Exception], None],
|
||||
[bacommon.cloud.LoginProxyRequestResponse | Exception], None
|
||||
],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@ -44,7 +45,8 @@ class CloudSubsystem:
|
||||
self,
|
||||
msg: bacommon.cloud.LoginProxyStateQueryMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.cloud.LoginProxyStateQueryResponse | Exception], None],
|
||||
[bacommon.cloud.LoginProxyStateQueryResponse | Exception], None
|
||||
],
|
||||
) -> None:
|
||||
...
|
||||
|
||||
@ -75,11 +77,15 @@ class CloudSubsystem:
|
||||
and passed either the response or the error that occurred.
|
||||
"""
|
||||
from ba._general import Call
|
||||
|
||||
del msg # Unused.
|
||||
|
||||
_ba.pushcall(
|
||||
Call(on_response,
|
||||
RuntimeError('Cloud functionality is not available.')))
|
||||
Call(
|
||||
on_response,
|
||||
RuntimeError('Cloud functionality is not available.'),
|
||||
)
|
||||
)
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
@ -89,8 +95,8 @@ class CloudSubsystem:
|
||||
|
||||
@overload
|
||||
def send_message(
|
||||
self,
|
||||
msg: bacommon.cloud.TestMessage) -> bacommon.cloud.TestResponse:
|
||||
self, msg: bacommon.cloud.TestMessage
|
||||
) -> bacommon.cloud.TestResponse:
|
||||
...
|
||||
|
||||
def send_message(self, msg: Message) -> Response | None:
|
||||
@ -107,6 +113,7 @@ def cloud_console_exec(code: str) -> None:
|
||||
import logging
|
||||
import __main__
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
try:
|
||||
|
||||
# First try it as eval.
|
||||
@ -118,7 +125,8 @@ def cloud_console_exec(code: str) -> None:
|
||||
# hmm; when we can't compile it as eval will we always get
|
||||
# syntax error?
|
||||
logging.exception(
|
||||
'unexpected error compiling code for cloud-console eval.')
|
||||
'unexpected error compiling code for cloud-console eval.'
|
||||
)
|
||||
evalcode = None
|
||||
if evalcode is not None:
|
||||
# pylint: disable=eval-used
|
||||
@ -135,6 +143,7 @@ def cloud_console_exec(code: str) -> None:
|
||||
exec(execcode, vars(__main__), vars(__main__))
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
apptime = _ba.time(TimeType.REAL)
|
||||
print(f'Exec error at time {apptime:.2f}.', file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
|
||||
@ -33,6 +33,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
@classmethod
|
||||
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
|
||||
from ba._coopsession import CoopSession
|
||||
|
||||
return issubclass(sessiontype, CoopSession)
|
||||
|
||||
def __init__(self, settings: dict):
|
||||
@ -55,12 +56,14 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
# Preload achievement images in case we get some.
|
||||
_ba.timer(2.0, WeakCall(self._preload_achievements))
|
||||
|
||||
def _show_standard_scores_to_beat_ui(self,
|
||||
scores: list[dict[str, Any]]) -> None:
|
||||
def _show_standard_scores_to_beat_ui(
|
||||
self, scores: list[dict[str, Any]]
|
||||
) -> None:
|
||||
from efro.util import asserttype
|
||||
from ba._gameutils import timestring, animate
|
||||
from ba._nodeactor import NodeActor
|
||||
from ba._generated.enums import TimeFormat
|
||||
|
||||
display_type = self.get_score_type()
|
||||
if scores is not None:
|
||||
|
||||
@ -70,26 +73,35 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
# Now make a display for the most recent challenge.
|
||||
for score in scores:
|
||||
if score['type'] == 'score_challenge':
|
||||
tval = (score['player'] + ': ' + timestring(
|
||||
int(score['value']) * 10,
|
||||
timeformat=TimeFormat.MILLISECONDS).evaluate()
|
||||
if display_type == 'time' else str(score['value']))
|
||||
tval = (
|
||||
score['player']
|
||||
+ ': '
|
||||
+ timestring(
|
||||
int(score['value']) * 10,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
).evaluate()
|
||||
if display_type == 'time'
|
||||
else str(score['value'])
|
||||
)
|
||||
hattach = 'center' if display_type == 'time' else 'left'
|
||||
halign = 'center' if display_type == 'time' else 'left'
|
||||
pos = (20, -70) if display_type == 'time' else (20, -130)
|
||||
txt = NodeActor(
|
||||
_ba.newnode('text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': hattach,
|
||||
'h_align': halign,
|
||||
'color': (0.7, 0.4, 1, 1),
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'position': pos,
|
||||
'scale': 0.6,
|
||||
'text': tval
|
||||
})).autoretain()
|
||||
_ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': hattach,
|
||||
'h_align': halign,
|
||||
'color': (0.7, 0.4, 1, 1),
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'position': pos,
|
||||
'scale': 0.6,
|
||||
'text': tval,
|
||||
},
|
||||
)
|
||||
).autoretain()
|
||||
assert txt.node is not None
|
||||
animate(txt.node, 'scale', {1.0: 0.0, 1.1: 0.7, 1.2: 0.6})
|
||||
break
|
||||
@ -104,8 +116,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
|
||||
def _get_coop_level_name(self) -> str:
|
||||
assert self.session.campaign is not None
|
||||
return self.session.campaign.name + ':' + str(
|
||||
self.settings_raw['name'])
|
||||
return self.session.campaign.name + ':' + str(self.settings_raw['name'])
|
||||
|
||||
def celebrate(self, duration: float) -> None:
|
||||
"""Tells all existing player-controlled characters to celebrate.
|
||||
@ -115,13 +126,15 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
duration is given in seconds.
|
||||
"""
|
||||
from ba._messages import CelebrateMessage
|
||||
|
||||
for player in self.players:
|
||||
if player.actor:
|
||||
player.actor.handlemessage(CelebrateMessage(duration))
|
||||
|
||||
def _preload_achievements(self) -> None:
|
||||
achievements = _ba.app.ach.achievements_for_coop_level(
|
||||
self._get_coop_level_name())
|
||||
self._get_coop_level_name()
|
||||
)
|
||||
for ach in achievements:
|
||||
ach.get_icon_texture(True)
|
||||
|
||||
@ -129,43 +142,52 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._language import Lstr
|
||||
from bastd.actor.text import Text
|
||||
|
||||
ts_h_offs = 30
|
||||
v_offs = -200
|
||||
achievements = [
|
||||
a for a in _ba.app.ach.achievements_for_coop_level(
|
||||
self._get_coop_level_name()) if not a.complete
|
||||
a
|
||||
for a in _ba.app.ach.achievements_for_coop_level(
|
||||
self._get_coop_level_name()
|
||||
)
|
||||
if not a.complete
|
||||
]
|
||||
vrmode = _ba.app.vr_mode
|
||||
if achievements:
|
||||
Text(Lstr(resource='achievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs - 10 + 40, v_offs - 10),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=1.1,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0),
|
||||
flatness=1.0 if vrmode else 0.6,
|
||||
shadow=1.0 if vrmode else 0.5,
|
||||
transition_delay=0.0,
|
||||
transition_out_delay=1.3
|
||||
if self.slow_motion else 4.0).autoretain()
|
||||
Text(
|
||||
Lstr(resource='achievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs - 10 + 40, v_offs - 10),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=1.1,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1.0, 1.0),
|
||||
flatness=1.0 if vrmode else 0.6,
|
||||
shadow=1.0 if vrmode else 0.5,
|
||||
transition_delay=0.0,
|
||||
transition_out_delay=1.3 if self.slow_motion else 4.0,
|
||||
).autoretain()
|
||||
hval = 70
|
||||
vval = -50
|
||||
tdelay = 0.0
|
||||
for ach in achievements:
|
||||
tdelay += 0.05
|
||||
ach.create_display(hval + 40,
|
||||
vval + v_offs,
|
||||
0 + tdelay,
|
||||
outdelay=1.3 if self.slow_motion else 4.0,
|
||||
style='in_game')
|
||||
ach.create_display(
|
||||
hval + 40,
|
||||
vval + v_offs,
|
||||
0 + tdelay,
|
||||
outdelay=1.3 if self.slow_motion else 4.0,
|
||||
style='in_game',
|
||||
)
|
||||
vval -= 55
|
||||
|
||||
def spawn_player_spaz(self,
|
||||
player: PlayerType,
|
||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
angle: float | None = None) -> PlayerSpaz:
|
||||
def spawn_player_spaz(
|
||||
self,
|
||||
player: PlayerType,
|
||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
angle: float | None = None,
|
||||
) -> PlayerSpaz:
|
||||
"""Spawn and wire up a standard player spaz."""
|
||||
spaz = super().spawn_player_spaz(player, position, angle)
|
||||
|
||||
@ -173,9 +195,9 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
spaz.play_big_death_sound = True
|
||||
return spaz
|
||||
|
||||
def _award_achievement(self,
|
||||
achievement_name: str,
|
||||
sound: bool = True) -> None:
|
||||
def _award_achievement(
|
||||
self, achievement_name: str, sound: bool = True
|
||||
) -> None:
|
||||
"""Award an achievement.
|
||||
|
||||
Returns True if a banner will be shown;
|
||||
@ -196,6 +218,7 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
return
|
||||
except Exception:
|
||||
from ba._error import print_exception
|
||||
|
||||
print_exception()
|
||||
|
||||
# If we haven't awarded this one, check to see if we've got it.
|
||||
@ -208,10 +231,9 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
_internal.report_achievement(achievement_name)
|
||||
|
||||
# ...and to our account.
|
||||
_internal.add_transaction({
|
||||
'type': 'ACHIEVEMENT',
|
||||
'name': achievement_name
|
||||
})
|
||||
_internal.add_transaction(
|
||||
{'type': 'ACHIEVEMENT', 'name': achievement_name}
|
||||
)
|
||||
|
||||
# Now bring up a celebration banner.
|
||||
ach.announce_completion(sound=sound)
|
||||
@ -219,14 +241,17 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
def fade_to_red(self) -> None:
|
||||
"""Fade the screen to red; (such as when the good guys have lost)."""
|
||||
from ba import _gameutils
|
||||
|
||||
c_existing = self.globalsnode.tint
|
||||
cnode = _ba.newnode('combine',
|
||||
attrs={
|
||||
'input0': c_existing[0],
|
||||
'input1': c_existing[1],
|
||||
'input2': c_existing[2],
|
||||
'size': 3
|
||||
})
|
||||
cnode = _ba.newnode(
|
||||
'combine',
|
||||
attrs={
|
||||
'input0': c_existing[0],
|
||||
'input1': c_existing[1],
|
||||
'input2': c_existing[2],
|
||||
'size': 3,
|
||||
},
|
||||
)
|
||||
_gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0})
|
||||
_gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0})
|
||||
cnode.connectattr('output', self.globalsnode, 'tint')
|
||||
@ -235,7 +260,8 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
"""Set up a beeping noise to play when any players are near death."""
|
||||
self._life_warning_beep = None
|
||||
self._life_warning_beep_timer = _ba.Timer(
|
||||
1.0, WeakCall(self._update_life_warning), repeat=True)
|
||||
1.0, WeakCall(self._update_life_warning), repeat=True
|
||||
)
|
||||
|
||||
def _update_life_warning(self) -> None:
|
||||
# Beep continuously if anyone is close to death.
|
||||
@ -249,12 +275,16 @@ class CoopGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
break
|
||||
if should_beep and self._life_warning_beep is None:
|
||||
from ba._nodeactor import NodeActor
|
||||
|
||||
self._life_warning_beep = NodeActor(
|
||||
_ba.newnode('sound',
|
||||
attrs={
|
||||
'sound': self._warn_beeps_sound,
|
||||
'positional': False,
|
||||
'loop': True
|
||||
}))
|
||||
_ba.newnode(
|
||||
'sound',
|
||||
attrs={
|
||||
'sound': self._warn_beeps_sound,
|
||||
'positional': False,
|
||||
'loop': True,
|
||||
},
|
||||
)
|
||||
)
|
||||
if self._life_warning_beep is not None and not should_beep:
|
||||
self._life_warning_beep = None
|
||||
|
||||
@ -60,15 +60,18 @@ class CoopSession(Session):
|
||||
# print('FIXME: COOP SESSION WOULD CALC DEPS.')
|
||||
depsets: Sequence[ba.DependencySet] = []
|
||||
|
||||
super().__init__(depsets,
|
||||
team_names=TEAM_NAMES,
|
||||
team_colors=TEAM_COLORS,
|
||||
min_players=min_players,
|
||||
max_players=max_players)
|
||||
super().__init__(
|
||||
depsets,
|
||||
team_names=TEAM_NAMES,
|
||||
team_colors=TEAM_COLORS,
|
||||
min_players=min_players,
|
||||
max_players=max_players,
|
||||
)
|
||||
|
||||
# Tournament-ID if we correspond to a co-op tournament (otherwise None)
|
||||
self.tournament_id: str | None = (
|
||||
app.coop_session_args.get('tournament_id'))
|
||||
self.tournament_id: str | None = app.coop_session_args.get(
|
||||
'tournament_id'
|
||||
)
|
||||
|
||||
self.campaign = getcampaign(app.coop_session_args['campaign'])
|
||||
self.campaign_level_name: str = app.coop_session_args['level']
|
||||
@ -151,10 +154,13 @@ class CoopSession(Session):
|
||||
# Special case:
|
||||
# If our current level is 'onslaught training', instantiate
|
||||
# our tutorial so its ready to go. (if we haven't run it yet).
|
||||
if (self.campaign_level_name == 'Onslaught Training'
|
||||
and self._tutorial_activity is None
|
||||
and not self._ran_tutorial_activity):
|
||||
if (
|
||||
self.campaign_level_name == 'Onslaught Training'
|
||||
and self._tutorial_activity is None
|
||||
and not self._ran_tutorial_activity
|
||||
):
|
||||
from bastd.tutorial import TutorialActivity
|
||||
|
||||
self._tutorial_activity = _ba.newactivity(TutorialActivity)
|
||||
|
||||
def get_custom_menu_entries(self) -> list[dict[str, Any]]:
|
||||
@ -162,6 +168,7 @@ class CoopSession(Session):
|
||||
|
||||
def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||
from ba._general import WeakCall
|
||||
|
||||
super().on_player_leave(sessionplayer)
|
||||
|
||||
_ba.timer(2.0, WeakCall(self._handle_empty_activity))
|
||||
@ -170,6 +177,7 @@ class CoopSession(Session):
|
||||
"""Handle cases where all players have left the current activity."""
|
||||
|
||||
from ba._gameactivity import GameActivity
|
||||
|
||||
activity = self.getactivity()
|
||||
if activity is None:
|
||||
return # Hmm what should we do in this case?
|
||||
@ -204,17 +212,21 @@ class CoopSession(Session):
|
||||
activity.end_game()
|
||||
|
||||
def _on_tournament_restart_menu_press(
|
||||
self, resume_callback: Callable[[], Any]) -> None:
|
||||
self, resume_callback: Callable[[], Any]
|
||||
) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.tournamententry import TournamentEntryWindow
|
||||
from ba._gameactivity import GameActivity
|
||||
|
||||
activity = self.getactivity()
|
||||
if activity is not None and not activity.expired:
|
||||
assert self.tournament_id is not None
|
||||
assert isinstance(activity, GameActivity)
|
||||
TournamentEntryWindow(tournament_id=self.tournament_id,
|
||||
tournament_activity=activity,
|
||||
on_close_call=resume_callback)
|
||||
TournamentEntryWindow(
|
||||
tournament_id=self.tournament_id,
|
||||
tournament_activity=activity,
|
||||
on_close_call=resume_callback,
|
||||
)
|
||||
|
||||
def restart(self) -> None:
|
||||
"""Restart the current game activity."""
|
||||
@ -277,8 +289,9 @@ class CoopSession(Session):
|
||||
|
||||
# If we're in a between-round activity or a restart-activity,
|
||||
# hop into a round.
|
||||
if (isinstance(activity,
|
||||
(JoinActivity, CoopScoreScreen, TransitionActivity))):
|
||||
if isinstance(
|
||||
activity, (JoinActivity, CoopScoreScreen, TransitionActivity)
|
||||
):
|
||||
|
||||
if outcome == 'next_level':
|
||||
if self._next_game_instance is None:
|
||||
@ -292,9 +305,11 @@ class CoopSession(Session):
|
||||
# Special case: if we're coming from a joining-activity
|
||||
# and will be going into onslaught-training, show the
|
||||
# tutorial first.
|
||||
if (isinstance(activity, JoinActivity)
|
||||
and self.campaign_level_name == 'Onslaught Training'
|
||||
and not (app.demo_mode or app.arcade_mode)):
|
||||
if (
|
||||
isinstance(activity, JoinActivity)
|
||||
and self.campaign_level_name == 'Onslaught Training'
|
||||
and not (app.demo_mode or app.arcade_mode)
|
||||
):
|
||||
if self._tutorial_activity is None:
|
||||
raise RuntimeError('Tutorial not preloaded properly.')
|
||||
self.setactivity(self._tutorial_activity)
|
||||
@ -319,20 +334,22 @@ class CoopSession(Session):
|
||||
|
||||
if not (app.demo_mode or app.arcade_mode):
|
||||
if self.tournament_id is not None:
|
||||
self._custom_menu_ui = [{
|
||||
'label':
|
||||
Lstr(resource='restartText'),
|
||||
'resume_on_call':
|
||||
False,
|
||||
'call':
|
||||
WeakCall(self._on_tournament_restart_menu_press
|
||||
)
|
||||
}]
|
||||
self._custom_menu_ui = [
|
||||
{
|
||||
'label': Lstr(resource='restartText'),
|
||||
'resume_on_call': False,
|
||||
'call': WeakCall(
|
||||
self._on_tournament_restart_menu_press
|
||||
),
|
||||
}
|
||||
]
|
||||
else:
|
||||
self._custom_menu_ui = [{
|
||||
'label': Lstr(resource='restartText'),
|
||||
'call': WeakCall(self.restart)
|
||||
}]
|
||||
self._custom_menu_ui = [
|
||||
{
|
||||
'label': Lstr(resource='restartText'),
|
||||
'call': WeakCall(self.restart),
|
||||
}
|
||||
]
|
||||
|
||||
# If we were in a tutorial, just pop a transition to get to the
|
||||
# actual round.
|
||||
@ -347,10 +364,13 @@ class CoopSession(Session):
|
||||
playerinfos = results.playerinfos
|
||||
score = results.get_sessionteam_score(results.sessionteams[0])
|
||||
fail_message = None
|
||||
score_order = ('decreasing'
|
||||
if results.lower_is_better else 'increasing')
|
||||
if results.scoretype in (ScoreType.SECONDS,
|
||||
ScoreType.MILLISECONDS):
|
||||
score_order = (
|
||||
'decreasing' if results.lower_is_better else 'increasing'
|
||||
)
|
||||
if results.scoretype in (
|
||||
ScoreType.SECONDS,
|
||||
ScoreType.MILLISECONDS,
|
||||
):
|
||||
scoretype = 'time'
|
||||
|
||||
# ScoreScreen wants hundredths of a second.
|
||||
@ -363,20 +383,28 @@ class CoopSession(Session):
|
||||
raise RuntimeError('FIXME')
|
||||
else:
|
||||
if results.scoretype is not ScoreType.POINTS:
|
||||
print(f'Unknown ScoreType:'
|
||||
f' "{results.scoretype}"')
|
||||
print(f'Unknown ScoreType:' f' "{results.scoretype}"')
|
||||
scoretype = 'points'
|
||||
|
||||
# Old coop-game-specific results; should migrate away from these.
|
||||
else:
|
||||
playerinfos = results.get('playerinfos')
|
||||
score = results['score'] if 'score' in results else None
|
||||
fail_message = (results['fail_message']
|
||||
if 'fail_message' in results else None)
|
||||
score_order = (results['score_order']
|
||||
if 'score_order' in results else 'increasing')
|
||||
activity_score_type = (activity.get_score_type() if isinstance(
|
||||
activity, CoopGameActivity) else None)
|
||||
fail_message = (
|
||||
results['fail_message']
|
||||
if 'fail_message' in results
|
||||
else None
|
||||
)
|
||||
score_order = (
|
||||
results['score_order']
|
||||
if 'score_order' in results
|
||||
else 'increasing'
|
||||
)
|
||||
activity_score_type = (
|
||||
activity.get_score_type()
|
||||
if isinstance(activity, CoopGameActivity)
|
||||
else None
|
||||
)
|
||||
assert activity_score_type is not None
|
||||
scoretype = activity_score_type
|
||||
|
||||
@ -394,7 +422,8 @@ class CoopSession(Session):
|
||||
else:
|
||||
self.setactivity(
|
||||
_ba.newactivity(
|
||||
CoopScoreScreen, {
|
||||
CoopScoreScreen,
|
||||
{
|
||||
'playerinfos': playerinfos,
|
||||
'score': score,
|
||||
'fail_message': fail_message,
|
||||
@ -402,8 +431,10 @@ class CoopSession(Session):
|
||||
'score_type': scoretype,
|
||||
'outcome': outcome,
|
||||
'campaign': self.campaign,
|
||||
'level': self.campaign_level_name
|
||||
}))
|
||||
'level': self.campaign_level_name,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
# No matter what, get the next 2 levels ready to go.
|
||||
self._update_on_deck_game_instances()
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
from typing import (Generic, TypeVar, TYPE_CHECKING)
|
||||
from typing import Generic, TypeVar, TYPE_CHECKING
|
||||
|
||||
import _ba
|
||||
|
||||
@ -44,6 +44,7 @@ class Dependency(Generic[T]):
|
||||
def get_hash(self) -> int:
|
||||
"""Return the dependency's hash, calculating it if necessary."""
|
||||
from efro.util import make_hash
|
||||
|
||||
if self._hash is None:
|
||||
self._hash = make_hash((self.cls, self.config))
|
||||
return self._hash
|
||||
@ -52,10 +53,12 @@ class Dependency(Generic[T]):
|
||||
if not isinstance(obj, DependencyComponent):
|
||||
if obj is None:
|
||||
raise TypeError(
|
||||
'Dependency must be accessed through an instance.')
|
||||
'Dependency must be accessed through an instance.'
|
||||
)
|
||||
raise TypeError(
|
||||
f'Dependency cannot be added to class of type {type(obj)}'
|
||||
' (class must inherit from ba.DependencyComponent).')
|
||||
' (class must inherit from ba.DependencyComponent).'
|
||||
)
|
||||
|
||||
# We expect to be instantiated from an already living
|
||||
# DependencyComponent with valid dep-data in place..
|
||||
@ -73,7 +76,8 @@ class Dependency(Generic[T]):
|
||||
|
||||
if not depset.resolved:
|
||||
raise RuntimeError(
|
||||
"Can't access data on an unresolved DependencySet.")
|
||||
"Can't access data on an unresolved DependencySet."
|
||||
)
|
||||
|
||||
# Look up the data in the set based on the hash for this Dependency.
|
||||
assert self._hash in depset.entries
|
||||
@ -157,8 +161,10 @@ class DependencyEntry:
|
||||
component = self.component
|
||||
assert isinstance(component, self.cls)
|
||||
if component is None:
|
||||
raise RuntimeError(f'Accessing DependencyComponent {self.cls} '
|
||||
'in an invalid state.')
|
||||
raise RuntimeError(
|
||||
f'Accessing DependencyComponent {self.cls} '
|
||||
'in an invalid state.'
|
||||
)
|
||||
return component
|
||||
|
||||
|
||||
@ -209,6 +215,7 @@ class DependencySet(Generic[T]):
|
||||
]
|
||||
if missing:
|
||||
from ba._error import DependencyError
|
||||
|
||||
raise DependencyError(missing)
|
||||
|
||||
self._resolved = True
|
||||
@ -278,7 +285,8 @@ class DependencySet(Generic[T]):
|
||||
|
||||
# Grab all Dependency instances we find in the class.
|
||||
subdeps = [
|
||||
cls for cls in dep.cls.__dict__.values()
|
||||
cls
|
||||
for cls in dep.cls.__dict__.values()
|
||||
if isinstance(cls, Dependency)
|
||||
]
|
||||
|
||||
@ -395,6 +403,7 @@ def test_depset() -> None:
|
||||
|
||||
def doit() -> None:
|
||||
from ba._error import DependencyError
|
||||
|
||||
depset = DependencySet(Dependency(TestClass))
|
||||
try:
|
||||
depset.resolve()
|
||||
@ -404,7 +413,8 @@ def test_depset() -> None:
|
||||
print('MISSING ASSET PACKAGE', dep.config)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f'Unknown dependency error for {dep.cls}') from exc
|
||||
f'Unknown dependency error for {dep.cls}'
|
||||
) from exc
|
||||
except Exception as exc:
|
||||
print('DependencySet resolve failed with exc type:', type(exc))
|
||||
if depset.resolved:
|
||||
|
||||
@ -33,10 +33,11 @@ class DualTeamSession(MultiTeamSession):
|
||||
def _switch_to_score_screen(self, results: ba.GameResults) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.activity.drawscore import DrawScoreScreenActivity
|
||||
from bastd.activity.dualteamscore import (
|
||||
TeamVictoryScoreScreenActivity)
|
||||
from bastd.activity.dualteamscore import TeamVictoryScoreScreenActivity
|
||||
from bastd.activity.multiteamvictory import (
|
||||
TeamSeriesVictoryScoreScreenActivity)
|
||||
TeamSeriesVictoryScoreScreenActivity,
|
||||
)
|
||||
|
||||
winnergroups = results.winnergroups
|
||||
|
||||
# If everyone has the same score, call it a draw.
|
||||
@ -49,9 +50,13 @@ class DualTeamSession(MultiTeamSession):
|
||||
# If a team has won, show final victory screen.
|
||||
if winner.customdata['score'] >= (self._series_length - 1) / 2 + 1:
|
||||
self.setactivity(
|
||||
_ba.newactivity(TeamSeriesVictoryScoreScreenActivity,
|
||||
{'winner': winner}))
|
||||
_ba.newactivity(
|
||||
TeamSeriesVictoryScoreScreenActivity, {'winner': winner}
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.setactivity(
|
||||
_ba.newactivity(TeamVictoryScoreScreenActivity,
|
||||
{'winner': winner}))
|
||||
_ba.newactivity(
|
||||
TeamVictoryScoreScreenActivity, {'winner': winner}
|
||||
)
|
||||
)
|
||||
|
||||
@ -141,6 +141,7 @@ def print_exception(*args: Any, **keywds: Any) -> None:
|
||||
one time from an exact calling location.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
if keywds:
|
||||
allowed_keywds = ['once']
|
||||
if any(keywd not in allowed_keywds for keywd in keywds):
|
||||
@ -181,6 +182,7 @@ def print_error(err_str: str, once: bool = False) -> None:
|
||||
one time from an exact calling location.
|
||||
"""
|
||||
import traceback
|
||||
|
||||
try:
|
||||
# If we're only printing once and already have, bail.
|
||||
if once:
|
||||
|
||||
@ -18,6 +18,7 @@ class FreeForAllSession(MultiTeamSession):
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
"""
|
||||
|
||||
use_teams = False
|
||||
use_team_colors = False
|
||||
_playlist_selection_var = 'Free-for-All Playlist Selection'
|
||||
@ -55,42 +56,54 @@ class FreeForAllSession(MultiTeamSession):
|
||||
from efro.util import asserttype
|
||||
from bastd.activity.drawscore import DrawScoreScreenActivity
|
||||
from bastd.activity.multiteamvictory import (
|
||||
TeamSeriesVictoryScoreScreenActivity)
|
||||
TeamSeriesVictoryScoreScreenActivity,
|
||||
)
|
||||
from bastd.activity.freeforallvictory import (
|
||||
FreeForAllVictoryScoreScreenActivity)
|
||||
FreeForAllVictoryScoreScreenActivity,
|
||||
)
|
||||
|
||||
winners = results.winnergroups
|
||||
|
||||
# If there's multiple players and everyone has the same score,
|
||||
# call it a draw.
|
||||
if len(self.sessionplayers) > 1 and len(winners) < 2:
|
||||
self.setactivity(
|
||||
_ba.newactivity(DrawScoreScreenActivity, {'results': results}))
|
||||
_ba.newactivity(DrawScoreScreenActivity, {'results': results})
|
||||
)
|
||||
else:
|
||||
# Award different point amounts based on number of players.
|
||||
point_awards = self.get_ffa_point_awards()
|
||||
|
||||
for i, winner in enumerate(winners):
|
||||
for team in winner.teams:
|
||||
points = (point_awards[i] if i in point_awards else 0)
|
||||
team.customdata['previous_score'] = (
|
||||
team.customdata['score'])
|
||||
points = point_awards[i] if i in point_awards else 0
|
||||
team.customdata['previous_score'] = team.customdata['score']
|
||||
team.customdata['score'] += points
|
||||
|
||||
series_winners = [
|
||||
team for team in self.sessionteams
|
||||
team
|
||||
for team in self.sessionteams
|
||||
if team.customdata['score'] >= self._ffa_series_length
|
||||
]
|
||||
series_winners.sort(
|
||||
reverse=True,
|
||||
key=lambda t: asserttype(t.customdata['score'], int))
|
||||
if (len(series_winners) == 1
|
||||
or (len(series_winners) > 1
|
||||
and series_winners[0].customdata['score'] !=
|
||||
series_winners[1].customdata['score'])):
|
||||
key=lambda t: asserttype(t.customdata['score'], int),
|
||||
)
|
||||
if len(series_winners) == 1 or (
|
||||
len(series_winners) > 1
|
||||
and series_winners[0].customdata['score']
|
||||
!= series_winners[1].customdata['score']
|
||||
):
|
||||
self.setactivity(
|
||||
_ba.newactivity(TeamSeriesVictoryScoreScreenActivity,
|
||||
{'winner': series_winners[0]}))
|
||||
_ba.newactivity(
|
||||
TeamSeriesVictoryScoreScreenActivity,
|
||||
{'winner': series_winners[0]},
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.setactivity(
|
||||
_ba.newactivity(FreeForAllVictoryScoreScreenActivity,
|
||||
{'results': results}))
|
||||
_ba.newactivity(
|
||||
FreeForAllVictoryScoreScreenActivity,
|
||||
{'results': results},
|
||||
)
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@ if TYPE_CHECKING:
|
||||
@dataclass
|
||||
class WinnerGroup:
|
||||
"""Entry for a winning team or teams calculated by game-results."""
|
||||
|
||||
score: int | None
|
||||
teams: Sequence[ba.SessionTeam]
|
||||
|
||||
@ -35,8 +36,9 @@ class GameResults:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._game_set = False
|
||||
self._scores: dict[int, tuple[weakref.ref[ba.SessionTeam],
|
||||
int | None]] = {}
|
||||
self._scores: dict[
|
||||
int, tuple[weakref.ref[ba.SessionTeam], int | None]
|
||||
] = {}
|
||||
self._sessionteams: list[weakref.ref[ba.SessionTeam]] | None = None
|
||||
self._playerinfos: list[ba.PlayerInfo] | None = None
|
||||
self._lower_is_better: bool | None = None
|
||||
@ -96,8 +98,7 @@ class GameResults:
|
||||
"""Return whether there is a score for a given session-team."""
|
||||
return any(s[0]() is sessionteam for s in self._scores.values())
|
||||
|
||||
def get_sessionteam_score_str(self,
|
||||
sessionteam: ba.SessionTeam) -> ba.Lstr:
|
||||
def get_sessionteam_score_str(self, sessionteam: ba.SessionTeam) -> ba.Lstr:
|
||||
"""Return the score for the given session-team as an Lstr.
|
||||
|
||||
(properly formatted for the score type.)
|
||||
@ -106,6 +107,7 @@ class GameResults:
|
||||
from ba._language import Lstr
|
||||
from ba._generated.enums import TimeFormat
|
||||
from ba._score import ScoreType
|
||||
|
||||
if not self._game_set:
|
||||
raise RuntimeError("Can't get team-score-str until game is set.")
|
||||
for score in list(self._scores.values()):
|
||||
@ -113,13 +115,15 @@ class GameResults:
|
||||
if score[1] is None:
|
||||
return Lstr(value='-')
|
||||
if self._scoretype is ScoreType.SECONDS:
|
||||
return timestring(score[1] * 1000,
|
||||
centi=False,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
return timestring(
|
||||
score[1] * 1000,
|
||||
centi=False,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
if self._scoretype is ScoreType.MILLISECONDS:
|
||||
return timestring(score[1],
|
||||
centi=True,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
return timestring(
|
||||
score[1], centi=True, timeformat=TimeFormat.MILLISECONDS
|
||||
)
|
||||
return Lstr(value=str(score[1]))
|
||||
return Lstr(value='-')
|
||||
|
||||
@ -174,7 +178,8 @@ class GameResults:
|
||||
# Group by best scoring teams.
|
||||
winners: dict[int, list[ba.SessionTeam]] = {}
|
||||
scores = [
|
||||
score for score in self._scores.values()
|
||||
score
|
||||
for score in self._scores.values()
|
||||
if score[0]() is not None and score[1] is not None
|
||||
]
|
||||
for score in scores:
|
||||
@ -183,10 +188,13 @@ class GameResults:
|
||||
team = score[0]()
|
||||
assert team is not None
|
||||
sval.append(team)
|
||||
results: list[tuple[int | None,
|
||||
list[ba.SessionTeam]]] = list(winners.items())
|
||||
results.sort(reverse=not self._lower_is_better,
|
||||
key=lambda x: asserttype(x[0], int))
|
||||
results: list[tuple[int | None, list[ba.SessionTeam]]] = list(
|
||||
winners.items()
|
||||
)
|
||||
results.sort(
|
||||
reverse=not self._lower_is_better,
|
||||
key=lambda x: asserttype(x[0], int),
|
||||
)
|
||||
|
||||
# Also group the 'None' scores.
|
||||
none_sessionteams: list[ba.SessionTeam] = []
|
||||
|
||||
@ -21,7 +21,7 @@ TROPHY_CHARS = {
|
||||
'3': SpecialChar.TROPHY3,
|
||||
'0a': SpecialChar.TROPHY0A,
|
||||
'0b': SpecialChar.TROPHY0B,
|
||||
'4': SpecialChar.TROPHY4
|
||||
'4': SpecialChar.TROPHY4,
|
||||
}
|
||||
|
||||
|
||||
@ -31,6 +31,7 @@ class GameTip:
|
||||
|
||||
Category: **Gameplay Classes**
|
||||
"""
|
||||
|
||||
text: str
|
||||
icon: ba.Texture | None = None
|
||||
sound: ba.Sound | None = None
|
||||
@ -43,14 +44,16 @@ def get_trophy_string(trophy_id: str) -> str:
|
||||
return '?'
|
||||
|
||||
|
||||
def animate(node: ba.Node,
|
||||
attr: str,
|
||||
keys: dict[float, float],
|
||||
loop: bool = False,
|
||||
offset: float = 0,
|
||||
timetype: ba.TimeType = TimeType.SIM,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False) -> ba.Node:
|
||||
def animate(
|
||||
node: ba.Node,
|
||||
attr: str,
|
||||
keys: dict[float, float],
|
||||
loop: bool = False,
|
||||
offset: float = 0,
|
||||
timetype: ba.TimeType = TimeType.SIM,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False,
|
||||
) -> ba.Node:
|
||||
"""Animate values on a target ba.Node.
|
||||
|
||||
Category: **Gameplay Functions**
|
||||
@ -76,9 +79,11 @@ def animate(node: ba.Node,
|
||||
for item in items:
|
||||
_ba.time_format_check(timeformat, item[0])
|
||||
|
||||
curve = _ba.newnode('animcurve',
|
||||
owner=node,
|
||||
name='Driving ' + str(node) + ' \'' + attr + '\'')
|
||||
curve = _ba.newnode(
|
||||
'animcurve',
|
||||
owner=node,
|
||||
name='Driving ' + str(node) + ' \'' + attr + '\'',
|
||||
)
|
||||
|
||||
if timeformat is TimeFormat.SECONDS:
|
||||
mult = 1000
|
||||
@ -89,7 +94,8 @@ def animate(node: ba.Node,
|
||||
|
||||
curve.times = [int(mult * time) for time, val in items]
|
||||
curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int(
|
||||
mult * offset)
|
||||
mult * offset
|
||||
)
|
||||
curve.values = [val for time, val in items]
|
||||
curve.loop = loop
|
||||
|
||||
@ -99,9 +105,11 @@ def animate(node: ba.Node,
|
||||
# get disconnected.
|
||||
if not loop:
|
||||
# noinspection PyUnresolvedReferences
|
||||
_ba.timer(int(mult * items[-1][0]) + 1000,
|
||||
curve.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
_ba.timer(
|
||||
int(mult * items[-1][0]) + 1000,
|
||||
curve.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
|
||||
# Do the connects last so all our attrs are in place when we push initial
|
||||
# values through.
|
||||
@ -117,15 +125,17 @@ def animate(node: ba.Node,
|
||||
return curve
|
||||
|
||||
|
||||
def animate_array(node: ba.Node,
|
||||
attr: str,
|
||||
size: int,
|
||||
keys: dict[float, Sequence[float]],
|
||||
loop: bool = False,
|
||||
offset: float = 0,
|
||||
timetype: ba.TimeType = TimeType.SIM,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False) -> None:
|
||||
def animate_array(
|
||||
node: ba.Node,
|
||||
attr: str,
|
||||
size: int,
|
||||
keys: dict[float, Sequence[float]],
|
||||
loop: bool = False,
|
||||
offset: float = 0,
|
||||
timetype: ba.TimeType = TimeType.SIM,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False,
|
||||
) -> None:
|
||||
"""Animate an array of values on a target ba.Node.
|
||||
|
||||
Category: **Gameplay Functions**
|
||||
@ -163,15 +173,19 @@ def animate_array(node: ba.Node,
|
||||
globalsnode = _ba.getsession().sessionglobalsnode
|
||||
|
||||
for i in range(size):
|
||||
curve = _ba.newnode('animcurve',
|
||||
owner=node,
|
||||
name=('Driving ' + str(node) + ' \'' + attr +
|
||||
'\' member ' + str(i)))
|
||||
curve = _ba.newnode(
|
||||
'animcurve',
|
||||
owner=node,
|
||||
name=(
|
||||
'Driving ' + str(node) + ' \'' + attr + '\' member ' + str(i)
|
||||
),
|
||||
)
|
||||
globalsnode.connectattr(driver, curve, 'in')
|
||||
curve.times = [int(mult * time) for time, val in items]
|
||||
curve.values = [val[i] for time, val in items]
|
||||
curve.offset = _ba.time(timeformat=TimeFormat.MILLISECONDS) + int(
|
||||
mult * offset)
|
||||
mult * offset
|
||||
)
|
||||
curve.loop = loop
|
||||
curve.connectattr('out', combine, 'input' + str(i))
|
||||
|
||||
@ -180,9 +194,11 @@ def animate_array(node: ba.Node,
|
||||
if not loop:
|
||||
# (PyCharm seems to think item is a float, not a tuple)
|
||||
# noinspection PyUnresolvedReferences
|
||||
_ba.timer(int(mult * items[-1][0]) + 1000,
|
||||
curve.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
_ba.timer(
|
||||
int(mult * items[-1][0]) + 1000,
|
||||
curve.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
combine.connectattr('output', node, attr)
|
||||
|
||||
# If we're not looping, set a timer to kill the combine once
|
||||
@ -192,13 +208,16 @@ def animate_array(node: ba.Node,
|
||||
if not loop:
|
||||
# (PyCharm seems to think item is a float, not a tuple)
|
||||
# noinspection PyUnresolvedReferences
|
||||
_ba.timer(int(mult * items[-1][0]) + 1000,
|
||||
combine.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
_ba.timer(
|
||||
int(mult * items[-1][0]) + 1000,
|
||||
combine.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
|
||||
|
||||
def show_damage_count(damage: str, position: Sequence[float],
|
||||
direction: Sequence[float]) -> None:
|
||||
def show_damage_count(
|
||||
damage: str, position: Sequence[float], direction: Sequence[float]
|
||||
) -> None:
|
||||
"""Pop up a damage count at a position in space.
|
||||
|
||||
Category: **Gameplay Functions**
|
||||
@ -210,16 +229,18 @@ def show_damage_count(damage: str, position: Sequence[float],
|
||||
# (connected clients may have differing configs so they won't
|
||||
# get the intended results).
|
||||
do_big = app.ui.uiscale is UIScale.SMALL or app.vr_mode
|
||||
txtnode = _ba.newnode('text',
|
||||
attrs={
|
||||
'text': damage,
|
||||
'in_world': True,
|
||||
'h_align': 'center',
|
||||
'flatness': 1.0,
|
||||
'shadow': 1.0 if do_big else 0.7,
|
||||
'color': (1, 0.25, 0.25, 1),
|
||||
'scale': 0.015 if do_big else 0.01
|
||||
})
|
||||
txtnode = _ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': damage,
|
||||
'in_world': True,
|
||||
'h_align': 'center',
|
||||
'flatness': 1.0,
|
||||
'shadow': 1.0 if do_big else 0.7,
|
||||
'color': (1, 0.25, 0.25, 1),
|
||||
'scale': 0.015 if do_big else 0.01,
|
||||
},
|
||||
)
|
||||
# Translate upward.
|
||||
tcombine = _ba.newnode('combine', owner=txtnode, attrs={'size': 3})
|
||||
tcombine.connectattr('output', txtnode, 'position')
|
||||
@ -233,27 +254,35 @@ def show_damage_count(damage: str, position: Sequence[float],
|
||||
vval *= 0.5
|
||||
p_start = position[0]
|
||||
p_dir = direction[0]
|
||||
animate(tcombine, 'input0',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1]
|
||||
for i in v_vals})
|
||||
animate(
|
||||
tcombine,
|
||||
'input0',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals},
|
||||
)
|
||||
p_start = position[1]
|
||||
p_dir = direction[1]
|
||||
animate(tcombine, 'input1',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1]
|
||||
for i in v_vals})
|
||||
animate(
|
||||
tcombine,
|
||||
'input1',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals},
|
||||
)
|
||||
p_start = position[2]
|
||||
p_dir = direction[2]
|
||||
animate(tcombine, 'input2',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1]
|
||||
for i in v_vals})
|
||||
animate(
|
||||
tcombine,
|
||||
'input2',
|
||||
{i[0] * lifespan: p_start + p_dir * i[1] for i in v_vals},
|
||||
)
|
||||
animate(txtnode, 'opacity', {0.7 * lifespan: 1.0, lifespan: 0.0})
|
||||
_ba.timer(lifespan, txtnode.delete)
|
||||
|
||||
|
||||
def timestring(timeval: float,
|
||||
centi: bool = True,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False) -> ba.Lstr:
|
||||
def timestring(
|
||||
timeval: float,
|
||||
centi: bool = True,
|
||||
timeformat: ba.TimeFormat = TimeFormat.SECONDS,
|
||||
suppress_format_warning: bool = False,
|
||||
) -> ba.Lstr:
|
||||
"""Generate a ba.Lstr for displaying a time value.
|
||||
|
||||
Category: **General Utility Functions**
|
||||
@ -292,32 +321,56 @@ def timestring(timeval: float,
|
||||
hval = (timeval // 1000) // (60 * 60)
|
||||
if hval != 0:
|
||||
bits.append('${H}')
|
||||
subs.append(('${H}',
|
||||
Lstr(resource='timeSuffixHoursText',
|
||||
subs=[('${COUNT}', str(hval))])))
|
||||
subs.append(
|
||||
(
|
||||
'${H}',
|
||||
Lstr(
|
||||
resource='timeSuffixHoursText',
|
||||
subs=[('${COUNT}', str(hval))],
|
||||
),
|
||||
)
|
||||
)
|
||||
mval = ((timeval // 1000) // 60) % 60
|
||||
if mval != 0:
|
||||
bits.append('${M}')
|
||||
subs.append(('${M}',
|
||||
Lstr(resource='timeSuffixMinutesText',
|
||||
subs=[('${COUNT}', str(mval))])))
|
||||
subs.append(
|
||||
(
|
||||
'${M}',
|
||||
Lstr(
|
||||
resource='timeSuffixMinutesText',
|
||||
subs=[('${COUNT}', str(mval))],
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
# We add seconds if its non-zero *or* we haven't added anything else.
|
||||
if centi:
|
||||
# pylint: disable=consider-using-f-string
|
||||
sval = (timeval / 1000.0 % 60.0)
|
||||
sval = timeval / 1000.0 % 60.0
|
||||
if sval >= 0.005 or not bits:
|
||||
bits.append('${S}')
|
||||
subs.append(('${S}',
|
||||
Lstr(resource='timeSuffixSecondsText',
|
||||
subs=[('${COUNT}', ('%.2f' % sval))])))
|
||||
subs.append(
|
||||
(
|
||||
'${S}',
|
||||
Lstr(
|
||||
resource='timeSuffixSecondsText',
|
||||
subs=[('${COUNT}', ('%.2f' % sval))],
|
||||
),
|
||||
)
|
||||
)
|
||||
else:
|
||||
sval = (timeval // 1000 % 60)
|
||||
sval = timeval // 1000 % 60
|
||||
if sval != 0 or not bits:
|
||||
bits.append('${S}')
|
||||
subs.append(('${S}',
|
||||
Lstr(resource='timeSuffixSecondsText',
|
||||
subs=[('${COUNT}', str(sval))])))
|
||||
subs.append(
|
||||
(
|
||||
'${S}',
|
||||
Lstr(
|
||||
resource='timeSuffixSecondsText',
|
||||
subs=[('${COUNT}', str(sval))],
|
||||
),
|
||||
)
|
||||
)
|
||||
return Lstr(value=' '.join(bits), subs=subs)
|
||||
|
||||
|
||||
@ -332,11 +385,17 @@ def cameraflash(duration: float = 999.0) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
import random
|
||||
from ba._nodeactor import NodeActor
|
||||
|
||||
x_spread = 10
|
||||
y_spread = 5
|
||||
positions = [[-x_spread, -y_spread], [0, -y_spread], [0, y_spread],
|
||||
[x_spread, -y_spread], [x_spread, y_spread],
|
||||
[-x_spread, y_spread]]
|
||||
positions = [
|
||||
[-x_spread, -y_spread],
|
||||
[0, -y_spread],
|
||||
[0, y_spread],
|
||||
[x_spread, -y_spread],
|
||||
[x_spread, y_spread],
|
||||
[-x_spread, y_spread],
|
||||
]
|
||||
times = [0, 2700, 1000, 1800, 500, 1400]
|
||||
|
||||
# Store this on the current activity so we only have one at a time.
|
||||
@ -345,57 +404,73 @@ def cameraflash(duration: float = 999.0) -> None:
|
||||
activity.camera_flash_data = [] # type: ignore
|
||||
for i in range(6):
|
||||
light = NodeActor(
|
||||
_ba.newnode('light',
|
||||
attrs={
|
||||
'position': (positions[i][0], 0, positions[i][1]),
|
||||
'radius': 1.0,
|
||||
'lights_volumes': False,
|
||||
'height_attenuated': False,
|
||||
'color': (0.2, 0.2, 0.8)
|
||||
}))
|
||||
_ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': (positions[i][0], 0, positions[i][1]),
|
||||
'radius': 1.0,
|
||||
'lights_volumes': False,
|
||||
'height_attenuated': False,
|
||||
'color': (0.2, 0.2, 0.8),
|
||||
},
|
||||
)
|
||||
)
|
||||
sval = 1.87
|
||||
iscale = 1.3
|
||||
tcombine = _ba.newnode('combine',
|
||||
owner=light.node,
|
||||
attrs={
|
||||
'size': 3,
|
||||
'input0': positions[i][0],
|
||||
'input1': 0,
|
||||
'input2': positions[i][1]
|
||||
})
|
||||
tcombine = _ba.newnode(
|
||||
'combine',
|
||||
owner=light.node,
|
||||
attrs={
|
||||
'size': 3,
|
||||
'input0': positions[i][0],
|
||||
'input1': 0,
|
||||
'input2': positions[i][1],
|
||||
},
|
||||
)
|
||||
assert light.node
|
||||
tcombine.connectattr('output', light.node, 'position')
|
||||
xval = positions[i][0]
|
||||
yval = positions[i][1]
|
||||
spd = 0.5 + random.random()
|
||||
spd2 = 0.5 + random.random()
|
||||
animate(tcombine,
|
||||
'input0', {
|
||||
0.0: xval + 0,
|
||||
0.069 * spd: xval + 10.0,
|
||||
0.143 * spd: xval - 10.0,
|
||||
0.201 * spd: xval + 0
|
||||
},
|
||||
loop=True)
|
||||
animate(tcombine,
|
||||
'input2', {
|
||||
0.0: yval + 0,
|
||||
0.15 * spd2: yval + 10.0,
|
||||
0.287 * spd2: yval - 10.0,
|
||||
0.398 * spd2: yval + 0
|
||||
},
|
||||
loop=True)
|
||||
animate(light.node,
|
||||
'intensity', {
|
||||
0.0: 0,
|
||||
0.02 * sval: 0,
|
||||
0.05 * sval: 0.8 * iscale,
|
||||
0.08 * sval: 0,
|
||||
0.1 * sval: 0
|
||||
},
|
||||
loop=True,
|
||||
offset=times[i])
|
||||
_ba.timer((times[i] + random.randint(1, int(duration)) * 40 * sval),
|
||||
light.node.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS)
|
||||
animate(
|
||||
tcombine,
|
||||
'input0',
|
||||
{
|
||||
0.0: xval + 0,
|
||||
0.069 * spd: xval + 10.0,
|
||||
0.143 * spd: xval - 10.0,
|
||||
0.201 * spd: xval + 0,
|
||||
},
|
||||
loop=True,
|
||||
)
|
||||
animate(
|
||||
tcombine,
|
||||
'input2',
|
||||
{
|
||||
0.0: yval + 0,
|
||||
0.15 * spd2: yval + 10.0,
|
||||
0.287 * spd2: yval - 10.0,
|
||||
0.398 * spd2: yval + 0,
|
||||
},
|
||||
loop=True,
|
||||
)
|
||||
animate(
|
||||
light.node,
|
||||
'intensity',
|
||||
{
|
||||
0.0: 0,
|
||||
0.02 * sval: 0,
|
||||
0.05 * sval: 0.8 * iscale,
|
||||
0.08 * sval: 0,
|
||||
0.1 * sval: 0,
|
||||
},
|
||||
loop=True,
|
||||
offset=times[i],
|
||||
)
|
||||
_ba.timer(
|
||||
(times[i] + random.randint(1, int(duration)) * 40 * sval),
|
||||
light.node.delete,
|
||||
timeformat=TimeFormat.MILLISECONDS,
|
||||
)
|
||||
activity.camera_flash_data.append(light) # type: ignore
|
||||
|
||||
@ -64,6 +64,7 @@ def getclass(name: str, subclassof: type[T]) -> type[T]:
|
||||
'subclassof' class, and a TypeError will be raised if not.
|
||||
"""
|
||||
import importlib
|
||||
|
||||
splits = name.split('.')
|
||||
modulename = '.'.join(splits[:-1])
|
||||
classname = splits[-1]
|
||||
@ -84,8 +85,10 @@ def json_prep(data: Any) -> Any:
|
||||
"""
|
||||
|
||||
if isinstance(data, dict):
|
||||
return dict((json_prep(key), json_prep(value))
|
||||
for key, value in list(data.items()))
|
||||
return dict(
|
||||
(json_prep(key), json_prep(value))
|
||||
for key, value in list(data.items())
|
||||
)
|
||||
if isinstance(data, list):
|
||||
return [json_prep(element) for element in data]
|
||||
if isinstance(data, tuple):
|
||||
@ -96,19 +99,23 @@ def json_prep(data: Any) -> Any:
|
||||
return data.decode(errors='ignore')
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
print_error('json_prep encountered utf-8 decode error', once=True)
|
||||
return data.decode(errors='ignore')
|
||||
if not isinstance(data, (str, float, bool, type(None), int)):
|
||||
print_error('got unsupported type in json_prep:' + str(type(data)),
|
||||
once=True)
|
||||
print_error(
|
||||
'got unsupported type in json_prep:' + str(type(data)), once=True
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
def utf8_all(data: Any) -> Any:
|
||||
"""Convert any unicode data in provided sequence(s) to utf8 bytes."""
|
||||
if isinstance(data, dict):
|
||||
return dict((utf8_all(key), utf8_all(value))
|
||||
for key, value in list(data.items()))
|
||||
return dict(
|
||||
(utf8_all(key), utf8_all(value))
|
||||
for key, value in list(data.items())
|
||||
)
|
||||
if isinstance(data, list):
|
||||
return [utf8_all(element) for element in data]
|
||||
if isinstance(data, tuple):
|
||||
@ -190,11 +197,17 @@ class _WeakCall:
|
||||
else:
|
||||
app = _ba.app
|
||||
if not app.did_weak_call_warning:
|
||||
print(('Warning: callable passed to ba.WeakCall() is not'
|
||||
' weak-referencable (' + str(args[0]) +
|
||||
'); use ba.Call() instead to avoid this '
|
||||
'warning. Stack-trace:'))
|
||||
print(
|
||||
(
|
||||
'Warning: callable passed to ba.WeakCall() is not'
|
||||
' weak-referencable ('
|
||||
+ str(args[0])
|
||||
+ '); use ba.Call() instead to avoid this '
|
||||
'warning. Stack-trace:'
|
||||
)
|
||||
)
|
||||
import traceback
|
||||
|
||||
traceback.print_stack()
|
||||
app.did_weak_call_warning = True
|
||||
self._call = args[0]
|
||||
@ -205,8 +218,15 @@ class _WeakCall:
|
||||
return self._call(*self._args + args_extra, **self._keywds)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ('<ba.WeakCall object; _call=' + str(self._call) + ' _args=' +
|
||||
str(self._args) + ' _keywds=' + str(self._keywds) + '>')
|
||||
return (
|
||||
'<ba.WeakCall object; _call='
|
||||
+ str(self._call)
|
||||
+ ' _args='
|
||||
+ str(self._args)
|
||||
+ ' _keywds='
|
||||
+ str(self._keywds)
|
||||
+ '>'
|
||||
)
|
||||
|
||||
|
||||
class _Call:
|
||||
@ -244,8 +264,15 @@ class _Call:
|
||||
return self._call(*self._args + args_extra, **self._keywds)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return ('<ba.Call object; _call=' + str(self._call) + ' _args=' +
|
||||
str(self._args) + ' _keywds=' + str(self._keywds) + '>')
|
||||
return (
|
||||
'<ba.Call object; _call='
|
||||
+ str(self._call)
|
||||
+ ' _args='
|
||||
+ str(self._args)
|
||||
+ ' _keywds='
|
||||
+ str(self._keywds)
|
||||
+ '>'
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -278,7 +305,7 @@ class WeakMethod:
|
||||
obj = self._obj()
|
||||
if obj is None:
|
||||
return None
|
||||
return self._func(*((obj, ) + args), **keywds)
|
||||
return self._func(*((obj,) + args), **keywds)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return '<ba.WeakMethod object; call=' + str(self._func) + '>'
|
||||
@ -300,9 +327,9 @@ def verify_object_death(obj: object) -> None:
|
||||
# if we queue a lot of them.
|
||||
delay = random.uniform(2.0, 5.5)
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(delay,
|
||||
lambda: _verify_object_death(ref),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
delay, lambda: _verify_object_death(ref), timetype=TimeType.REAL
|
||||
)
|
||||
|
||||
|
||||
def print_active_refs(obj: Any) -> None:
|
||||
@ -314,6 +341,7 @@ def print_active_refs(obj: Any) -> None:
|
||||
"""
|
||||
# pylint: disable=too-many-nested-blocks
|
||||
from types import FrameType, TracebackType
|
||||
|
||||
refs = list(gc.get_referrers(obj))
|
||||
print(f'{Clr.YLW}Active referrers to {obj}:{Clr.RST}')
|
||||
for i, ref in enumerate(refs):
|
||||
@ -330,20 +358,28 @@ def print_active_refs(obj: Any) -> None:
|
||||
# Can go further down the rabbit-hole if needed...
|
||||
if bool(False):
|
||||
if isinstance(ref2, TracebackType):
|
||||
print(f'{Clr.YLW} '
|
||||
f'Active referrers to #a{j+1}:{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.YLW} '
|
||||
f'Active referrers to #a{j+1}:{Clr.RST}'
|
||||
)
|
||||
refs3 = list(gc.get_referrers(ref2))
|
||||
for k, ref3 in enumerate(refs3):
|
||||
print(f'{Clr.YLW} '
|
||||
f'#b{k+1}:{Clr.BLU} {ref3}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.YLW} '
|
||||
f'#b{k+1}:{Clr.BLU} {ref3}{Clr.RST}'
|
||||
)
|
||||
|
||||
if isinstance(ref3, BaseException):
|
||||
print(f'{Clr.YLW} Active referrers to'
|
||||
f' #b{k+1}:{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.YLW} Active referrers to'
|
||||
f' #b{k+1}:{Clr.RST}'
|
||||
)
|
||||
refs4 = list(gc.get_referrers(ref3))
|
||||
for x, ref4 in enumerate(refs4):
|
||||
print(f'{Clr.YLW} #c{x+1}:{Clr.BLU}'
|
||||
f' {ref4}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.YLW} #c{x+1}:{Clr.BLU}'
|
||||
f' {ref4}{Clr.RST}'
|
||||
)
|
||||
|
||||
|
||||
def _verify_object_death(wref: weakref.ref) -> None:
|
||||
@ -357,8 +393,10 @@ def _verify_object_death(wref: weakref.ref) -> None:
|
||||
print(f'Note: unable to get type name for {obj}')
|
||||
name = 'object'
|
||||
|
||||
print(f'{Clr.RED}Error: {name} not dying when expected to:'
|
||||
f' {Clr.BLD}{obj}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.RED}Error: {name} not dying when expected to:'
|
||||
f' {Clr.BLD}{obj}{Clr.RST}'
|
||||
)
|
||||
print_active_refs(obj)
|
||||
|
||||
|
||||
|
||||
@ -54,87 +54,113 @@ def set_config_fullscreen_off() -> None:
|
||||
|
||||
def not_signed_in_screen_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.screenmessage(Lstr(resource='notSignedInErrorText'))
|
||||
|
||||
|
||||
def connecting_to_party_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='internal.connectingToPartyText'),
|
||||
color=(1, 1, 1))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.connectingToPartyText'), color=(1, 1, 1)
|
||||
)
|
||||
|
||||
|
||||
def rejecting_invite_already_in_party_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.rejectingInviteAlreadyInPartyText'),
|
||||
color=(1, 0.5, 0))
|
||||
color=(1, 0.5, 0),
|
||||
)
|
||||
|
||||
|
||||
def connection_failed_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='internal.connectionFailedText'),
|
||||
color=(1, 0.5, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.connectionFailedText'), color=(1, 0.5, 0)
|
||||
)
|
||||
|
||||
|
||||
def temporarily_unavailable_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.unavailableTemporarilyText'),
|
||||
color=(1, 0, 0))
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
def in_progress_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='getTicketsWindow.inProgressText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.inProgressText'), color=(1, 0, 0)
|
||||
)
|
||||
|
||||
|
||||
def error_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
|
||||
|
||||
def purchase_not_valid_error() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='store.purchaseNotValidError',
|
||||
subs=[('${EMAIL}', 'support@froemling.net')]),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='store.purchaseNotValidError',
|
||||
subs=[('${EMAIL}', 'support@froemling.net')],
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
|
||||
|
||||
def purchase_already_in_progress_error() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='store.purchaseAlreadyInProgressText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='store.purchaseAlreadyInProgressText'), color=(1, 0, 0)
|
||||
)
|
||||
|
||||
|
||||
def gear_vr_controller_warning() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='usesExternalControllerText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='usesExternalControllerText'), color=(1, 0, 0)
|
||||
)
|
||||
|
||||
|
||||
def uuid_str() -> str:
|
||||
import uuid
|
||||
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def orientation_reset_cb_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.vrOrientationResetCardboardText'),
|
||||
color=(0, 1, 0))
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
|
||||
|
||||
def orientation_reset_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='internal.vrOrientationResetText'),
|
||||
color=(0, 1, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.vrOrientationResetText'), color=(0, 1, 0)
|
||||
)
|
||||
|
||||
|
||||
def on_app_pause() -> None:
|
||||
@ -147,12 +173,14 @@ def on_app_resume() -> None:
|
||||
|
||||
def launch_main_menu_session() -> None:
|
||||
from bastd.mainmenu import MainMenuSession
|
||||
|
||||
_ba.new_host_session(MainMenuSession)
|
||||
|
||||
|
||||
def language_test_toggle() -> None:
|
||||
_ba.app.lang.setlanguage('Gibberish' if _ba.app.lang.language ==
|
||||
'English' else 'English')
|
||||
_ba.app.lang.setlanguage(
|
||||
'Gibberish' if _ba.app.lang.language == 'English' else 'English'
|
||||
)
|
||||
|
||||
|
||||
def award_in_control_achievement() -> None:
|
||||
@ -173,8 +201,10 @@ def launch_coop_game(name: str) -> None:
|
||||
|
||||
def purchases_restored_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='getTicketsWindow.purchasesRestoredText'),
|
||||
color=(0, 1, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.purchasesRestoredText'), color=(0, 1, 0)
|
||||
)
|
||||
|
||||
|
||||
def dismiss_wii_remotes_window() -> None:
|
||||
@ -185,8 +215,10 @@ def dismiss_wii_remotes_window() -> None:
|
||||
|
||||
def unavailable_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='getTicketsWindow.unavailableText'),
|
||||
color=(1, 0, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='getTicketsWindow.unavailableText'), color=(1, 0, 0)
|
||||
)
|
||||
|
||||
|
||||
def submit_analytics_counts(sval: str) -> None:
|
||||
@ -196,19 +228,23 @@ def submit_analytics_counts(sval: str) -> None:
|
||||
|
||||
def set_last_ad_network(sval: str) -> None:
|
||||
import time
|
||||
|
||||
_ba.app.ads.last_ad_network = sval
|
||||
_ba.app.ads.last_ad_network_set_time = time.time()
|
||||
|
||||
|
||||
def no_game_circle_message() -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
_ba.screenmessage(Lstr(resource='noGameCircleText'), color=(1, 0, 0))
|
||||
|
||||
|
||||
def google_play_purchases_not_available_message() -> None:
|
||||
from ba._language import Lstr
|
||||
_ba.screenmessage(Lstr(resource='googlePlayPurchasesNotAvailableText'),
|
||||
color=(1, 0, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='googlePlayPurchasesNotAvailableText'), color=(1, 0, 0)
|
||||
)
|
||||
|
||||
|
||||
def empty_call() -> None:
|
||||
@ -229,8 +265,10 @@ def coin_icon_press() -> None:
|
||||
|
||||
def ticket_icon_press() -> None:
|
||||
from bastd.ui.resourcetypeinfo import ResourceTypeInfoWindow
|
||||
|
||||
ResourceTypeInfoWindow(
|
||||
origin_widget=_ba.get_special_widget('tickets_info_button'))
|
||||
origin_widget=_ba.get_special_widget('tickets_info_button')
|
||||
)
|
||||
|
||||
|
||||
def back_button_press() -> None:
|
||||
@ -243,6 +281,7 @@ def friends_button_press() -> None:
|
||||
|
||||
def print_trace() -> None:
|
||||
import traceback
|
||||
|
||||
print('Python Traceback (most recent call last):')
|
||||
traceback.print_stack()
|
||||
|
||||
@ -256,6 +295,7 @@ def toggle_fullscreen() -> None:
|
||||
def party_icon_activate(origin: Sequence[float]) -> None:
|
||||
import weakref
|
||||
from bastd.ui.party import PartyWindow
|
||||
|
||||
app = _ba.app
|
||||
_ba.playsound(_ba.getsound('swish'))
|
||||
|
||||
@ -276,13 +316,16 @@ def ui_remote_press() -> None:
|
||||
|
||||
# Can be called without a context; need a context for getsound.
|
||||
with _ba.Context('ui'):
|
||||
_ba.screenmessage(Lstr(resource='internal.controllerForMenusOnlyText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.controllerForMenusOnlyText'),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
|
||||
|
||||
def quit_window() -> None:
|
||||
from bastd.ui.confirm import QuitWindow
|
||||
|
||||
QuitWindow()
|
||||
|
||||
|
||||
@ -292,6 +335,7 @@ def remove_in_game_ads_message() -> None:
|
||||
|
||||
def telnet_access_request() -> None:
|
||||
from bastd.ui.telnet import TelnetAccessRequestWindow
|
||||
|
||||
TelnetAccessRequestWindow()
|
||||
|
||||
|
||||
@ -305,11 +349,13 @@ def shutdown() -> None:
|
||||
|
||||
def gc_disable() -> None:
|
||||
import gc
|
||||
|
||||
gc.disable()
|
||||
|
||||
|
||||
def device_menu_press(device: ba.InputDevice) -> None:
|
||||
from bastd.ui.mainmenu import MainMenuWindow
|
||||
|
||||
in_main_menu = _ba.app.ui.has_main_menu_window()
|
||||
if not in_main_menu:
|
||||
_ba.set_ui_input_device(device)
|
||||
@ -319,6 +365,7 @@ def device_menu_press(device: ba.InputDevice) -> None:
|
||||
|
||||
def show_url_window(address: str) -> None:
|
||||
from bastd.ui.url import ShowURLWindow
|
||||
|
||||
ShowURLWindow(address)
|
||||
|
||||
|
||||
@ -328,8 +375,9 @@ def party_invite_revoke(invite_id: str) -> None:
|
||||
for winref in _ba.app.invite_confirm_windows:
|
||||
win = winref()
|
||||
if win is not None and win.ew_party_invite_id == invite_id:
|
||||
_ba.containerwidget(edit=win.get_root_widget(),
|
||||
transition='out_right')
|
||||
_ba.containerwidget(
|
||||
edit=win.get_root_widget(), transition='out_right'
|
||||
)
|
||||
|
||||
|
||||
def filter_chat_message(msg: str, client_id: int) -> str | None:
|
||||
@ -345,8 +393,10 @@ def filter_chat_message(msg: str, client_id: int) -> str | None:
|
||||
|
||||
|
||||
def local_chat_message(msg: str) -> None:
|
||||
if (_ba.app.ui.party_window is not None
|
||||
and _ba.app.ui.party_window() is not None):
|
||||
if (
|
||||
_ba.app.ui.party_window is not None
|
||||
and _ba.app.ui.party_window() is not None
|
||||
):
|
||||
_ba.app.ui.party_window().on_chat_message(msg)
|
||||
|
||||
|
||||
@ -356,13 +406,14 @@ def get_player_icon(sessionplayer: ba.SessionPlayer) -> dict[str, Any]:
|
||||
'texture': _ba.gettexture(info['texture']),
|
||||
'tint_texture': _ba.gettexture(info['tint_texture']),
|
||||
'tint_color': info['tint_color'],
|
||||
'tint2_color': info['tint2_color']
|
||||
'tint2_color': info['tint2_color'],
|
||||
}
|
||||
|
||||
|
||||
def hash_strings(inputs: list[str]) -> str:
|
||||
"""Hash provided strings into a short output string."""
|
||||
import hashlib
|
||||
|
||||
sha = hashlib.sha1()
|
||||
for inp in inputs:
|
||||
sha.update(inp.encode())
|
||||
|
||||
@ -48,7 +48,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonUp': 20,
|
||||
'buttonDown': 21,
|
||||
'buttonVRReorient': 110
|
||||
'buttonVRReorient': 110,
|
||||
}.get(name, -1)
|
||||
|
||||
# If there's an entry in our config for this controller, use it.
|
||||
@ -79,7 +79,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRun2': 5,
|
||||
'buttonRun1': 6,
|
||||
'buttonJump': 1,
|
||||
'buttonIgnored': 11
|
||||
'buttonIgnored': 11,
|
||||
}.get(name, -1)
|
||||
|
||||
# Ps4 controller.
|
||||
@ -94,7 +94,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonRun2': 5,
|
||||
'buttonRun1': 6,
|
||||
'triggerRun1': 5
|
||||
'triggerRun1': 5,
|
||||
}.get(name, -1)
|
||||
|
||||
# Look for some exact types.
|
||||
@ -112,7 +112,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 100,
|
||||
'buttonRun2': 103,
|
||||
'buttonRun1': 104,
|
||||
'triggerRun1': 24
|
||||
'triggerRun1': 24,
|
||||
}.get(name, -1)
|
||||
if devicename == 'NYKO PLAYPAD PRO':
|
||||
return {
|
||||
@ -126,7 +126,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonDown': 21
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Logitech Dual Action':
|
||||
return {
|
||||
@ -136,7 +136,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonBomb': 101,
|
||||
'buttonJump': 100,
|
||||
'buttonStart': 109,
|
||||
'buttonPunch': 97
|
||||
'buttonPunch': 97,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Xbox 360 Wireless Receiver':
|
||||
return {
|
||||
@ -150,7 +150,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100,
|
||||
'buttonDown': 21
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Microsoft X-Box 360 pad':
|
||||
return {
|
||||
@ -160,11 +160,12 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonBomb': 98,
|
||||
'buttonJump': 97,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 100
|
||||
'buttonPunch': 100,
|
||||
}.get(name, -1)
|
||||
if devicename in [
|
||||
'Amazon Remote', 'Amazon Bluetooth Dev',
|
||||
'Amazon Fire TV Remote'
|
||||
'Amazon Remote',
|
||||
'Amazon Bluetooth Dev',
|
||||
'Amazon Fire TV Remote',
|
||||
]:
|
||||
return {
|
||||
'triggerRun2': 23,
|
||||
@ -178,7 +179,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonStart': 83,
|
||||
'buttonPunch': 90,
|
||||
'buttonDown': 21
|
||||
'buttonDown': 21,
|
||||
}.get(name, -1)
|
||||
|
||||
elif 'NVIDIA SHIELD;' in useragentstring:
|
||||
@ -193,7 +194,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonStart': 109,
|
||||
'buttonPunch': 100,
|
||||
'buttonIgnored': 184,
|
||||
'buttonIgnored2': 86
|
||||
'buttonIgnored2': 86,
|
||||
}.get(name, -1)
|
||||
elif platform == 'mac':
|
||||
if devicename == 'PLAYSTATION(R)3 Controller':
|
||||
@ -207,7 +208,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonBomb': 14,
|
||||
'buttonPickUp': 13,
|
||||
'buttonStart': 4,
|
||||
'buttonIgnored': 17
|
||||
'buttonIgnored': 17,
|
||||
}.get(name, -1)
|
||||
if devicename in ['Wireless 360 Controller', 'Controller']:
|
||||
|
||||
@ -225,16 +226,18 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonUp': 1,
|
||||
'triggerRun1': 5,
|
||||
'triggerRun2': 6,
|
||||
'buttonIgnored': 11
|
||||
'buttonIgnored': 11,
|
||||
}.get(name, -1)
|
||||
if (devicename
|
||||
in ['Logitech Dual Action', 'Logitech Cordless RumblePad 2']):
|
||||
if devicename in [
|
||||
'Logitech Dual Action',
|
||||
'Logitech Cordless RumblePad 2',
|
||||
]:
|
||||
return {
|
||||
'buttonJump': 2,
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
# Old gravis gamepad.
|
||||
@ -244,7 +247,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
if devicename == 'Microsoft SideWinder Plug & Play Game Pad':
|
||||
@ -253,7 +256,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 3,
|
||||
'buttonBomb': 2,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 6
|
||||
'buttonStart': 6,
|
||||
}.get(name, -1)
|
||||
|
||||
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
|
||||
@ -263,7 +266,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 4,
|
||||
'buttonPickUp': 2,
|
||||
'buttonStart': 11
|
||||
'buttonStart': 11,
|
||||
}.get(name, -1)
|
||||
|
||||
# Some crazy 'Senze' dual gamepad.
|
||||
@ -288,7 +291,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonStart': 10,
|
||||
'buttonStart_B': 22,
|
||||
'enableSecondary': 1,
|
||||
'unassignedButtonsRun': False
|
||||
'unassignedButtonsRun': False,
|
||||
}.get(name, -1)
|
||||
if devicename == 'USB Gamepad ': # some weird 'JITE' gamepad
|
||||
return {
|
||||
@ -298,7 +301,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 4,
|
||||
'buttonBomb': 2,
|
||||
'buttonPickUp': 1,
|
||||
'buttonStart': 10
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
default_android_mapping = {
|
||||
@ -317,7 +320,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonUp': 20,
|
||||
'buttonDown': 21,
|
||||
'buttonVRReorient': 110
|
||||
'buttonVRReorient': 110,
|
||||
}
|
||||
|
||||
# Generic android...
|
||||
@ -341,7 +344,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 23,
|
||||
'buttonUp': 20,
|
||||
'buttonDown': 21,
|
||||
'buttonVRReorient': 108
|
||||
'buttonVRReorient': 108,
|
||||
}.get(name, -1)
|
||||
|
||||
# Adt-1 gamepad (use funky 'mode' button for start).
|
||||
@ -357,7 +360,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'buttonRun2': 104,
|
||||
'buttonRun1': 103,
|
||||
'triggerRun1': 18
|
||||
'triggerRun1': 18,
|
||||
}.get(name, -1)
|
||||
# Nexus player remote.
|
||||
if devicename == 'Nexus Remote':
|
||||
@ -376,7 +379,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 24,
|
||||
'buttonRun2': 104,
|
||||
'buttonRun1': 103,
|
||||
'triggerRun1': 18
|
||||
'triggerRun1': 18,
|
||||
}.get(name, -1)
|
||||
|
||||
if devicename == 'virtual-remote':
|
||||
@ -397,13 +400,15 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRun1': 103,
|
||||
'buttonDown': 21,
|
||||
'startButtonActivatesDefaultWidget': False,
|
||||
'uiOnly': True
|
||||
'uiOnly': True,
|
||||
}.get(name, -1)
|
||||
|
||||
# flag particular gamepads to use exact android defaults..
|
||||
# (so they don't even ask to configure themselves)
|
||||
if devicename in ['Samsung Game Pad EI-GP20', 'ASUS Gamepad'
|
||||
] or devicename.startswith('Freefly VR Glide'):
|
||||
if devicename in [
|
||||
'Samsung Game Pad EI-GP20',
|
||||
'ASUS Gamepad',
|
||||
] or devicename.startswith('Freefly VR Glide'):
|
||||
return default_android_mapping.get(name, -1)
|
||||
|
||||
# Nvidia controller is default, but gets some strange
|
||||
@ -423,7 +428,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 100,
|
||||
'buttonRun2': 104,
|
||||
'buttonRun1': 103,
|
||||
'triggerRun1': 18
|
||||
'triggerRun1': 18,
|
||||
}.get(name, -1)
|
||||
|
||||
# Default keyboard vals across platforms..
|
||||
@ -438,7 +443,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonDown': 274,
|
||||
'buttonLeft': 276,
|
||||
'buttonRight': 275,
|
||||
'buttonStart': 263
|
||||
'buttonStart': 263,
|
||||
}.get(name, -1)
|
||||
return {
|
||||
'buttonPickUp': 1073741917,
|
||||
@ -449,7 +454,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonRight': 1073741903,
|
||||
'buttonStart': 1073741919,
|
||||
'buttonPunch': 1073741913,
|
||||
'buttonDown': 1073741905
|
||||
'buttonDown': 1073741905,
|
||||
}.get(name, -1)
|
||||
if devicename == 'Keyboard' and unique_id == '#1':
|
||||
return {
|
||||
@ -460,7 +465,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonUp': 119,
|
||||
'buttonDown': 115,
|
||||
'buttonLeft': 97,
|
||||
'buttonRight': 100
|
||||
'buttonRight': 100,
|
||||
}.get(name, -1)
|
||||
|
||||
# Ok, this gamepad's not in our specific preset list;
|
||||
@ -479,7 +484,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 16,
|
||||
'buttonBomb': 14,
|
||||
'buttonPickUp': 13,
|
||||
'buttonStart': 4
|
||||
'buttonStart': 4,
|
||||
}.get(name, -1)
|
||||
|
||||
# Dual Action Config - hopefully applies to more...
|
||||
@ -489,7 +494,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
|
||||
@ -499,7 +504,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 4,
|
||||
'buttonPickUp': 2,
|
||||
'buttonStart': 11
|
||||
'buttonStart': 11,
|
||||
}.get(name, -1)
|
||||
|
||||
# Gravis stuff?...
|
||||
@ -509,7 +514,7 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 1,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 10
|
||||
'buttonStart': 10,
|
||||
}.get(name, -1)
|
||||
|
||||
# Reasonable defaults.
|
||||
@ -542,21 +547,23 @@ def get_device_value(device: ba.InputDevice, name: str) -> Any:
|
||||
'buttonPunch': 2,
|
||||
'buttonBomb': 3,
|
||||
'buttonPickUp': 4,
|
||||
'buttonStart': 5
|
||||
'buttonStart': 5,
|
||||
}.get(name, -1)
|
||||
|
||||
|
||||
def _gen_android_input_hash() -> str:
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
md5 = hashlib.md5()
|
||||
|
||||
# Currently we just do a single hash of *all* inputs on android
|
||||
# and that's it.. good enough.
|
||||
# (grabbing mappings for a specific device looks to be non-trivial)
|
||||
for dirname in [
|
||||
'/system/usr/keylayout', '/data/usr/keylayout',
|
||||
'/data/system/devices/keylayout'
|
||||
'/system/usr/keylayout',
|
||||
'/data/usr/keylayout',
|
||||
'/data/system/devices/keylayout',
|
||||
]:
|
||||
try:
|
||||
if os.path.isdir(dirname):
|
||||
@ -573,8 +580,10 @@ def _gen_android_input_hash() -> str:
|
||||
pass
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception(
|
||||
'error in _gen_android_input_hash inner loop')
|
||||
'error in _gen_android_input_hash inner loop'
|
||||
)
|
||||
return md5.hexdigest()
|
||||
|
||||
|
||||
@ -597,12 +606,14 @@ def get_input_map_hash(inputdevice: ba.InputDevice) -> str:
|
||||
return app.input_map_hash
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Exception in get_input_map_hash')
|
||||
return ''
|
||||
|
||||
|
||||
def get_input_device_config(device: ba.InputDevice,
|
||||
default: bool) -> tuple[dict, str]:
|
||||
def get_input_device_config(
|
||||
device: ba.InputDevice, default: bool
|
||||
) -> tuple[dict, str]:
|
||||
"""Given an input device, return its config dict in the app config.
|
||||
|
||||
The dict will be created if it does not exist.
|
||||
@ -634,8 +645,10 @@ def get_last_player_name_from_input_device(device: ba.InputDevice) -> str:
|
||||
# otherwise default to their current random name.
|
||||
profilename = '_random'
|
||||
key_name = device.name + ' ' + device.unique_identifier
|
||||
if ('Default Player Profiles' in appconfig
|
||||
and key_name in appconfig['Default Player Profiles']):
|
||||
if (
|
||||
'Default Player Profiles' in appconfig
|
||||
and key_name in appconfig['Default Player Profiles']
|
||||
):
|
||||
profilename = appconfig['Default Player Profiles'][key_name]
|
||||
if profilename == '_random':
|
||||
profilename = device.get_default_player_name()
|
||||
|
||||
@ -14,6 +14,7 @@ from typing import TYPE_CHECKING
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
import _bainternal
|
||||
|
||||
HAVE_INTERNAL = True
|
||||
except ImportError:
|
||||
HAVE_INTERNAL = False
|
||||
@ -26,6 +27,7 @@ if TYPE_CHECKING:
|
||||
# to account for its absence should call this to draw attention to itself.
|
||||
def _no_bainternal_warning() -> None:
|
||||
import logging
|
||||
|
||||
logging.warning('INTERNAL CALL RUN WITHOUT INTERNAL PRESENT.')
|
||||
|
||||
|
||||
@ -47,8 +49,9 @@ def get_master_server_address(source: int = -1, version: int = 1) -> str:
|
||||
Return the address of the master server.
|
||||
"""
|
||||
if HAVE_INTERNAL:
|
||||
return _bainternal.get_master_server_address(source=source,
|
||||
version=version)
|
||||
return _bainternal.get_master_server_address(
|
||||
source=source, version=version
|
||||
)
|
||||
raise _no_bainternal_error()
|
||||
|
||||
|
||||
@ -75,8 +78,9 @@ def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||
for it on the game service.
|
||||
"""
|
||||
if HAVE_INTERNAL:
|
||||
return _bainternal.game_service_has_leaderboard(game=game,
|
||||
config=config)
|
||||
return _bainternal.game_service_has_leaderboard(
|
||||
game=game, config=config
|
||||
)
|
||||
# Harmless to always just say no here.
|
||||
return False
|
||||
|
||||
@ -84,8 +88,9 @@ def game_service_has_leaderboard(game: str, config: str) -> bool:
|
||||
def report_achievement(achievement: str, pass_to_account: bool = True) -> None:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
_bainternal.report_achievement(achievement=achievement,
|
||||
pass_to_account=pass_to_account)
|
||||
_bainternal.report_achievement(
|
||||
achievement=achievement, pass_to_account=pass_to_account
|
||||
)
|
||||
return
|
||||
|
||||
# Need to see if this actually still works as expected.. warning for now.
|
||||
@ -93,17 +98,19 @@ def report_achievement(achievement: str, pass_to_account: bool = True) -> None:
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def submit_score(game: str,
|
||||
config: str,
|
||||
name: Any,
|
||||
score: int | None,
|
||||
callback: Callable,
|
||||
friend_callback: Callable | None,
|
||||
order: str = 'increasing',
|
||||
tournament_id: str | None = None,
|
||||
score_type: str = 'points',
|
||||
campaign: str | None = None,
|
||||
level: str | None = None) -> None:
|
||||
def submit_score(
|
||||
game: str,
|
||||
config: str,
|
||||
name: Any,
|
||||
score: int | None,
|
||||
callback: Callable,
|
||||
friend_callback: Callable | None,
|
||||
order: str = 'increasing',
|
||||
tournament_id: str | None = None,
|
||||
score_type: str = 'points',
|
||||
campaign: str | None = None,
|
||||
level: str | None = None,
|
||||
) -> None:
|
||||
"""(internal)
|
||||
|
||||
Submit a score to the server; callback will be called with the results.
|
||||
@ -112,24 +119,27 @@ def submit_score(game: str,
|
||||
score server more mischief-proof.
|
||||
"""
|
||||
if HAVE_INTERNAL:
|
||||
_bainternal.submit_score(game=game,
|
||||
config=config,
|
||||
name=name,
|
||||
score=score,
|
||||
callback=callback,
|
||||
friend_callback=friend_callback,
|
||||
order=order,
|
||||
tournament_id=tournament_id,
|
||||
score_type=score_type,
|
||||
campaign=campaign,
|
||||
level=level)
|
||||
_bainternal.submit_score(
|
||||
game=game,
|
||||
config=config,
|
||||
name=name,
|
||||
score=score,
|
||||
callback=callback,
|
||||
friend_callback=friend_callback,
|
||||
order=order,
|
||||
tournament_id=tournament_id,
|
||||
score_type=score_type,
|
||||
campaign=campaign,
|
||||
level=level,
|
||||
)
|
||||
return
|
||||
# This technically breaks since callback will never be called/etc.
|
||||
raise _no_bainternal_error()
|
||||
|
||||
|
||||
def tournament_query(callback: Callable[[dict | None], None],
|
||||
args: dict) -> None:
|
||||
def tournament_query(
|
||||
callback: Callable[[dict | None], None], args: dict
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
_bainternal.tournament_query(callback=callback, args=args)
|
||||
@ -212,8 +222,9 @@ def in_game_purchase(item: str, price: int) -> None:
|
||||
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def add_transaction(transaction: dict,
|
||||
callback: Callable | None = None) -> None:
|
||||
def add_transaction(
|
||||
transaction: dict, callback: Callable | None = None
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
_bainternal.add_transaction(transaction=transaction, callback=callback)
|
||||
@ -265,7 +276,8 @@ def get_v1_account_misc_read_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
return _bainternal.get_v1_account_misc_read_val(
|
||||
name=name, default_value=default_value)
|
||||
name=name, default_value=default_value
|
||||
)
|
||||
raise _no_bainternal_error()
|
||||
|
||||
|
||||
@ -273,15 +285,17 @@ def get_v1_account_misc_read_val_2(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
return _bainternal.get_v1_account_misc_read_val_2(
|
||||
name=name, default_value=default_value)
|
||||
name=name, default_value=default_value
|
||||
)
|
||||
raise _no_bainternal_error()
|
||||
|
||||
|
||||
def get_v1_account_misc_val(name: str, default_value: Any) -> Any:
|
||||
"""(internal)"""
|
||||
if HAVE_INTERNAL:
|
||||
return _bainternal.get_v1_account_misc_val(name=name,
|
||||
default_value=default_value)
|
||||
return _bainternal.get_v1_account_misc_val(
|
||||
name=name, default_value=default_value
|
||||
)
|
||||
raise _no_bainternal_error()
|
||||
|
||||
|
||||
|
||||
@ -35,10 +35,21 @@ class LanguageSubsystem:
|
||||
"""
|
||||
|
||||
# We don't yet support full unicode display on windows or linux :-(.
|
||||
if (language in {
|
||||
'Chinese', 'ChineseTraditional', 'Persian', 'Korean', 'Arabic',
|
||||
'Hindi', 'Vietnamese', 'Thai', 'Tamil'
|
||||
} and not _ba.can_display_full_unicode()):
|
||||
if (
|
||||
language
|
||||
in {
|
||||
'Chinese',
|
||||
'ChineseTraditional',
|
||||
'Persian',
|
||||
'Korean',
|
||||
'Arabic',
|
||||
'Hindi',
|
||||
'Vietnamese',
|
||||
'Thai',
|
||||
'Tamil',
|
||||
}
|
||||
and not _ba.can_display_full_unicode()
|
||||
):
|
||||
return False
|
||||
return True
|
||||
|
||||
@ -130,18 +141,22 @@ class LanguageSubsystem:
|
||||
names[i] = 'ChineseTraditional'
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
names = []
|
||||
for name in names:
|
||||
if self._can_display_language(name):
|
||||
langs.add(name)
|
||||
return sorted(name for name in names
|
||||
if self._can_display_language(name))
|
||||
return sorted(
|
||||
name for name in names if self._can_display_language(name)
|
||||
)
|
||||
|
||||
def setlanguage(self,
|
||||
language: str | None,
|
||||
print_change: bool = True,
|
||||
store_to_config: bool = True) -> None:
|
||||
def setlanguage(
|
||||
self,
|
||||
language: str | None,
|
||||
print_change: bool = True,
|
||||
store_to_config: bool = True,
|
||||
) -> None:
|
||||
"""Set the active language used for the game.
|
||||
|
||||
Pass None to use OS default language.
|
||||
@ -164,8 +179,9 @@ class LanguageSubsystem:
|
||||
else:
|
||||
switched = False
|
||||
|
||||
with open('ba_data/data/languages/english.json',
|
||||
encoding='utf-8') as infile:
|
||||
with open(
|
||||
'ba_data/data/languages/english.json', encoding='utf-8'
|
||||
) as infile:
|
||||
lenglishvalues = json.loads(infile.read())
|
||||
|
||||
# None implies default.
|
||||
@ -175,16 +191,21 @@ class LanguageSubsystem:
|
||||
if language == 'English':
|
||||
lmodvalues = None
|
||||
else:
|
||||
lmodfile = 'ba_data/data/languages/' + language.lower(
|
||||
) + '.json'
|
||||
lmodfile = (
|
||||
'ba_data/data/languages/' + language.lower() + '.json'
|
||||
)
|
||||
with open(lmodfile, encoding='utf-8') as infile:
|
||||
lmodvalues = json.loads(infile.read())
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Exception importing language:', language)
|
||||
_ba.screenmessage("Error setting language to '" + language +
|
||||
"'; see log for details",
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
"Error setting language to '"
|
||||
+ language
|
||||
+ "'; see log for details",
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
switched = False
|
||||
lmodvalues = None
|
||||
|
||||
@ -193,8 +214,8 @@ class LanguageSubsystem:
|
||||
langtarget = self.language_target
|
||||
assert langtarget is not None
|
||||
_add_to_attr_dict(
|
||||
langtarget,
|
||||
lmodvalues if lmodvalues is not None else lenglishvalues)
|
||||
langtarget, lmodvalues if lmodvalues is not None else lenglishvalues
|
||||
)
|
||||
|
||||
# Create an attrdict of our target language overlaid
|
||||
# on our base (english).
|
||||
@ -209,20 +230,22 @@ class LanguageSubsystem:
|
||||
# Pass some keys/values in for low level code to use;
|
||||
# start with everything in their 'internal' section.
|
||||
internal_vals = [
|
||||
v for v in list(lfull['internal'].items())
|
||||
if isinstance(v[1], str)
|
||||
v for v in list(lfull['internal'].items()) if isinstance(v[1], str)
|
||||
]
|
||||
|
||||
# Cherry-pick various other values to include.
|
||||
# (should probably get rid of the 'internal' section
|
||||
# and do everything this way)
|
||||
for value in [
|
||||
'replayNameDefaultText', 'replayWriteErrorText',
|
||||
'replayVersionErrorText', 'replayReadErrorText'
|
||||
'replayNameDefaultText',
|
||||
'replayWriteErrorText',
|
||||
'replayVersionErrorText',
|
||||
'replayReadErrorText',
|
||||
]:
|
||||
internal_vals.append((value, lfull[value]))
|
||||
internal_vals.append(
|
||||
('axisText', lfull['configGamepadWindow']['axisText']))
|
||||
('axisText', lfull['configGamepadWindow']['axisText'])
|
||||
)
|
||||
internal_vals.append(('buttonText', lfull['buttonText']))
|
||||
lmerged = self.language_merged
|
||||
assert lmerged is not None
|
||||
@ -232,16 +255,22 @@ class LanguageSubsystem:
|
||||
random_names = [n for n in random_names if n != '']
|
||||
_ba.set_internal_language_keys(internal_vals, random_names)
|
||||
if switched and print_change:
|
||||
_ba.screenmessage(Lstr(resource='languageSetText',
|
||||
subs=[('${LANGUAGE}',
|
||||
Lstr(translate=('languages',
|
||||
language)))]),
|
||||
color=(0, 1, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='languageSetText',
|
||||
subs=[
|
||||
('${LANGUAGE}', Lstr(translate=('languages', language)))
|
||||
],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
|
||||
def get_resource(self,
|
||||
resource: str,
|
||||
fallback_resource: str | None = None,
|
||||
fallback_value: Any = None) -> Any:
|
||||
def get_resource(
|
||||
self,
|
||||
resource: str,
|
||||
fallback_resource: str | None = None,
|
||||
fallback_value: Any = None,
|
||||
) -> Any:
|
||||
"""Return a translation resource by name.
|
||||
|
||||
DEPRECATED; use ba.Lstr functionality for these purposes.
|
||||
@ -251,24 +280,29 @@ class LanguageSubsystem:
|
||||
if self.language_merged is None:
|
||||
language = self.language
|
||||
try:
|
||||
self.setlanguage(language,
|
||||
print_change=False,
|
||||
store_to_config=False)
|
||||
self.setlanguage(
|
||||
language, print_change=False, store_to_config=False
|
||||
)
|
||||
except Exception:
|
||||
from ba import _error
|
||||
_error.print_exception('exception setting language to',
|
||||
language)
|
||||
|
||||
_error.print_exception(
|
||||
'exception setting language to', language
|
||||
)
|
||||
|
||||
# Try english as a fallback.
|
||||
if language != 'English':
|
||||
print('Resorting to fallback language (English)')
|
||||
try:
|
||||
self.setlanguage('English',
|
||||
print_change=False,
|
||||
store_to_config=False)
|
||||
self.setlanguage(
|
||||
'English',
|
||||
print_change=False,
|
||||
store_to_config=False,
|
||||
)
|
||||
except Exception:
|
||||
_error.print_exception(
|
||||
'error setting language to english fallback')
|
||||
'error setting language to english fallback'
|
||||
)
|
||||
|
||||
# If they provided a fallback_resource value, try the
|
||||
# target-language-only dict first and then fall back to trying the
|
||||
@ -325,16 +359,20 @@ class LanguageSubsystem:
|
||||
# anywhere. Now if we've been given a fallback value, return it;
|
||||
# otherwise fail.
|
||||
from ba import _error
|
||||
|
||||
if fallback_value is not None:
|
||||
return fallback_value
|
||||
raise _error.NotFoundError(
|
||||
f"Resource not found: '{resource}'") from None
|
||||
f"Resource not found: '{resource}'"
|
||||
) from None
|
||||
|
||||
def translate(self,
|
||||
category: str,
|
||||
strval: str,
|
||||
raise_exceptions: bool = False,
|
||||
print_errors: bool = False) -> str:
|
||||
def translate(
|
||||
self,
|
||||
category: str,
|
||||
strval: str,
|
||||
raise_exceptions: bool = False,
|
||||
print_errors: bool = False,
|
||||
) -> str:
|
||||
"""Translate a value (or return the value if no translation available)
|
||||
|
||||
DEPRECATED; use ba.Lstr functionality for these purposes.
|
||||
@ -345,8 +383,17 @@ class LanguageSubsystem:
|
||||
if raise_exceptions:
|
||||
raise
|
||||
if print_errors:
|
||||
print(('Translate error: category=\'' + category +
|
||||
'\' name=\'' + strval + '\' exc=' + str(exc) + ''))
|
||||
print(
|
||||
(
|
||||
'Translate error: category=\''
|
||||
+ category
|
||||
+ '\' name=\''
|
||||
+ strval
|
||||
+ '\' exc='
|
||||
+ str(exc)
|
||||
+ ''
|
||||
)
|
||||
)
|
||||
translated = None
|
||||
translated_out: str
|
||||
if translated is None:
|
||||
@ -403,28 +450,31 @@ class Lstr:
|
||||
# pylint: disable=dangerous-default-value
|
||||
# noinspection PyDefaultArgument
|
||||
@overload
|
||||
def __init__(self,
|
||||
*,
|
||||
resource: str,
|
||||
fallback_resource: str = '',
|
||||
fallback_value: str = '',
|
||||
subs: Sequence[tuple[str, str | Lstr]] = []) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
resource: str,
|
||||
fallback_resource: str = '',
|
||||
fallback_value: str = '',
|
||||
subs: Sequence[tuple[str, str | Lstr]] = [],
|
||||
) -> None:
|
||||
"""Create an Lstr from a string resource."""
|
||||
|
||||
# noinspection PyShadowingNames,PyDefaultArgument
|
||||
@overload
|
||||
def __init__(self,
|
||||
*,
|
||||
translate: tuple[str, str],
|
||||
subs: Sequence[tuple[str, str | Lstr]] = []) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
translate: tuple[str, str],
|
||||
subs: Sequence[tuple[str, str | Lstr]] = [],
|
||||
) -> None:
|
||||
"""Create an Lstr by translating a string in a category."""
|
||||
|
||||
# noinspection PyDefaultArgument
|
||||
@overload
|
||||
def __init__(self,
|
||||
*,
|
||||
value: str,
|
||||
subs: Sequence[tuple[str, str | Lstr]] = []) -> None:
|
||||
def __init__(
|
||||
self, *, value: str, subs: Sequence[tuple[str, str | Lstr]] = []
|
||||
) -> None:
|
||||
"""Create an Lstr from a raw string value."""
|
||||
|
||||
# pylint: enable=redefined-outer-name, dangerous-default-value
|
||||
@ -478,10 +528,12 @@ class Lstr:
|
||||
del keywds['value']
|
||||
if 'fallback' in keywds:
|
||||
from ba import _error
|
||||
|
||||
_error.print_error(
|
||||
'deprecated "fallback" arg passed to Lstr(); use '
|
||||
'either "fallback_resource" or "fallback_value"',
|
||||
once=True)
|
||||
once=True,
|
||||
)
|
||||
keywds['f'] = keywds['fallback']
|
||||
del keywds['fallback']
|
||||
if 'fallback_resource' in keywds:
|
||||
@ -517,6 +569,7 @@ class Lstr:
|
||||
return json.dumps(self.args, separators=(',', ':'))
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('_get_json failed for', self.args)
|
||||
return 'JSON_ERR'
|
||||
|
||||
@ -542,13 +595,20 @@ def _add_to_attr_dict(dst: AttrDict, src: dict) -> None:
|
||||
except Exception:
|
||||
dst_dict = dst[key] = AttrDict()
|
||||
if not isinstance(dst_dict, AttrDict):
|
||||
raise RuntimeError("language key '" + key +
|
||||
"' is defined both as a dict and value")
|
||||
raise RuntimeError(
|
||||
"language key '"
|
||||
+ key
|
||||
+ "' is defined both as a dict and value"
|
||||
)
|
||||
_add_to_attr_dict(dst_dict, value)
|
||||
else:
|
||||
if not isinstance(value, (float, int, bool, str, str, type(None))):
|
||||
raise TypeError("invalid value type for res '" + key + "': " +
|
||||
str(type(value)))
|
||||
raise TypeError(
|
||||
"invalid value type for res '"
|
||||
+ key
|
||||
+ "': "
|
||||
+ str(type(value))
|
||||
)
|
||||
dst[key] = value
|
||||
|
||||
|
||||
|
||||
@ -20,12 +20,14 @@ class Level:
|
||||
Category: **Gameplay Classes**
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
name: str,
|
||||
gametype: type[ba.GameActivity],
|
||||
settings: dict,
|
||||
preview_texture_name: str,
|
||||
displayname: str | None = None):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
gametype: type[ba.GameActivity],
|
||||
settings: dict,
|
||||
preview_texture_name: str,
|
||||
displayname: str | None = None,
|
||||
):
|
||||
self._name = name
|
||||
self._gametype = gametype
|
||||
self._settings = settings
|
||||
@ -66,11 +68,18 @@ class Level:
|
||||
def displayname(self) -> ba.Lstr:
|
||||
"""The localized name for this Level."""
|
||||
from ba import _language
|
||||
|
||||
return _language.Lstr(
|
||||
translate=('coopLevelNames', self._displayname
|
||||
if self._displayname is not None else self._name),
|
||||
subs=[('${GAME}',
|
||||
self._gametype.get_display_string(self._settings))])
|
||||
translate=(
|
||||
'coopLevelNames',
|
||||
self._displayname
|
||||
if self._displayname is not None
|
||||
else self._name,
|
||||
),
|
||||
subs=[
|
||||
('${GAME}', self._gametype.get_display_string(self._settings))
|
||||
],
|
||||
)
|
||||
|
||||
@property
|
||||
def gametype(self) -> type[ba.GameActivity]:
|
||||
@ -156,10 +165,9 @@ class Level:
|
||||
if campaign is None:
|
||||
raise RuntimeError('Level is not in a campaign.')
|
||||
configdict = campaign.configdict
|
||||
val: dict[str, Any] = configdict.setdefault(self._name, {
|
||||
'Rating': 0.0,
|
||||
'Complete': False
|
||||
})
|
||||
val: dict[str, Any] = configdict.setdefault(
|
||||
self._name, {'Rating': 0.0, 'Complete': False}
|
||||
)
|
||||
assert isinstance(val, dict)
|
||||
return val
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Implements lobby system for gathering before games, char select, etc."""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -31,15 +32,20 @@ class JoinInfo:
|
||||
def __init__(self, lobby: ba.Lobby):
|
||||
from ba._nodeactor import NodeActor
|
||||
from ba._general import WeakCall
|
||||
|
||||
self._state = 0
|
||||
self._press_to_punch: str | ba.Lstr = ('C' if _ba.app.iircade_mode else
|
||||
_ba.charstr(
|
||||
SpecialChar.LEFT_BUTTON))
|
||||
self._press_to_bomb: str | ba.Lstr = ('B' if _ba.app.iircade_mode else
|
||||
_ba.charstr(
|
||||
SpecialChar.RIGHT_BUTTON))
|
||||
self._press_to_punch: str | ba.Lstr = (
|
||||
'C'
|
||||
if _ba.app.iircade_mode
|
||||
else _ba.charstr(SpecialChar.LEFT_BUTTON)
|
||||
)
|
||||
self._press_to_bomb: str | ba.Lstr = (
|
||||
'B'
|
||||
if _ba.app.iircade_mode
|
||||
else _ba.charstr(SpecialChar.RIGHT_BUTTON)
|
||||
)
|
||||
self._joinmsg = Lstr(resource='pressAnyButtonToJoinText')
|
||||
can_switch_teams = (len(lobby.sessionteams) > 1)
|
||||
can_switch_teams = len(lobby.sessionteams) > 1
|
||||
|
||||
# If we have a keyboard, grab keys for punch and pickup.
|
||||
# FIXME: This of course is only correct on the local device;
|
||||
@ -50,59 +56,97 @@ class JoinInfo:
|
||||
|
||||
flatness = 1.0 if _ba.app.vr_mode else 0.0
|
||||
self._text = NodeActor(
|
||||
_ba.newnode('text',
|
||||
attrs={
|
||||
'position': (0, -40),
|
||||
'h_attach': 'center',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'center',
|
||||
'color': (0.7, 0.7, 0.95, 1.0),
|
||||
'flatness': flatness,
|
||||
'text': self._joinmsg
|
||||
}))
|
||||
_ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'position': (0, -40),
|
||||
'h_attach': 'center',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'center',
|
||||
'color': (0.7, 0.7, 0.95, 1.0),
|
||||
'flatness': flatness,
|
||||
'text': self._joinmsg,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
if _ba.app.demo_mode or _ba.app.arcade_mode:
|
||||
self._messages = [self._joinmsg]
|
||||
else:
|
||||
msg1 = Lstr(resource='pressToSelectProfileText',
|
||||
subs=[
|
||||
('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) +
|
||||
' ' + _ba.charstr(SpecialChar.DOWN_ARROW))
|
||||
])
|
||||
msg2 = Lstr(resource='pressToOverrideCharacterText',
|
||||
subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))])
|
||||
msg3 = Lstr(value='${A} < ${B} >',
|
||||
subs=[('${A}', msg2), ('${B}', self._press_to_bomb)])
|
||||
self._messages = (([
|
||||
Lstr(
|
||||
resource='pressToSelectTeamText',
|
||||
subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) +
|
||||
' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))],
|
||||
msg1 = Lstr(
|
||||
resource='pressToSelectProfileText',
|
||||
subs=[
|
||||
(
|
||||
'${BUTTONS}',
|
||||
_ba.charstr(SpecialChar.UP_ARROW)
|
||||
+ ' '
|
||||
+ _ba.charstr(SpecialChar.DOWN_ARROW),
|
||||
)
|
||||
],
|
||||
)
|
||||
msg2 = Lstr(
|
||||
resource='pressToOverrideCharacterText',
|
||||
subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))],
|
||||
)
|
||||
msg3 = Lstr(
|
||||
value='${A} < ${B} >',
|
||||
subs=[('${A}', msg2), ('${B}', self._press_to_bomb)],
|
||||
)
|
||||
self._messages = (
|
||||
(
|
||||
[
|
||||
Lstr(
|
||||
resource='pressToSelectTeamText',
|
||||
subs=[
|
||||
(
|
||||
'${BUTTONS}',
|
||||
_ba.charstr(SpecialChar.LEFT_ARROW)
|
||||
+ ' '
|
||||
+ _ba.charstr(SpecialChar.RIGHT_ARROW),
|
||||
)
|
||||
],
|
||||
)
|
||||
]
|
||||
if can_switch_teams
|
||||
else []
|
||||
)
|
||||
] if can_switch_teams else []) + [msg1] + [msg3] + [self._joinmsg])
|
||||
+ [msg1]
|
||||
+ [msg3]
|
||||
+ [self._joinmsg]
|
||||
)
|
||||
|
||||
self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)
|
||||
|
||||
def _update_for_keyboard(self, keyboard: ba.InputDevice) -> None:
|
||||
from ba import _input
|
||||
|
||||
punch_key = keyboard.get_button_name(
|
||||
_input.get_device_value(keyboard, 'buttonPunch'))
|
||||
self._press_to_punch = Lstr(resource='orText',
|
||||
subs=[('${A}',
|
||||
Lstr(value='\'${K}\'',
|
||||
subs=[('${K}', punch_key)])),
|
||||
('${B}', self._press_to_punch)])
|
||||
_input.get_device_value(keyboard, 'buttonPunch')
|
||||
)
|
||||
self._press_to_punch = Lstr(
|
||||
resource='orText',
|
||||
subs=[
|
||||
('${A}', Lstr(value='\'${K}\'', subs=[('${K}', punch_key)])),
|
||||
('${B}', self._press_to_punch),
|
||||
],
|
||||
)
|
||||
bomb_key = keyboard.get_button_name(
|
||||
_input.get_device_value(keyboard, 'buttonBomb'))
|
||||
self._press_to_bomb = Lstr(resource='orText',
|
||||
subs=[('${A}',
|
||||
Lstr(value='\'${K}\'',
|
||||
subs=[('${K}', bomb_key)])),
|
||||
('${B}', self._press_to_bomb)])
|
||||
self._joinmsg = Lstr(value='${A} < ${B} >',
|
||||
subs=[('${A}',
|
||||
Lstr(resource='pressPunchToJoinText')),
|
||||
('${B}', self._press_to_punch)])
|
||||
_input.get_device_value(keyboard, 'buttonBomb')
|
||||
)
|
||||
self._press_to_bomb = Lstr(
|
||||
resource='orText',
|
||||
subs=[
|
||||
('${A}', Lstr(value='\'${K}\'', subs=[('${K}', bomb_key)])),
|
||||
('${B}', self._press_to_bomb),
|
||||
],
|
||||
)
|
||||
self._joinmsg = Lstr(
|
||||
value='${A} < ${B} >',
|
||||
subs=[
|
||||
('${A}', Lstr(resource='pressPunchToJoinText')),
|
||||
('${B}', self._press_to_punch),
|
||||
],
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
assert self._text.node
|
||||
@ -113,12 +157,14 @@ class JoinInfo:
|
||||
@dataclass
|
||||
class PlayerReadyMessage:
|
||||
"""Tells an object a player has been selected from the given chooser."""
|
||||
|
||||
chooser: ba.Chooser
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChangeMessage:
|
||||
"""Tells an object that a selection is being changed."""
|
||||
|
||||
what: str
|
||||
value: int
|
||||
|
||||
@ -135,8 +181,9 @@ class Chooser:
|
||||
if self._text_node:
|
||||
self._text_node.delete()
|
||||
|
||||
def __init__(self, vpos: float, sessionplayer: _ba.SessionPlayer,
|
||||
lobby: 'Lobby') -> None:
|
||||
def __init__(
|
||||
self, vpos: float, sessionplayer: _ba.SessionPlayer, lobby: 'Lobby'
|
||||
) -> None:
|
||||
self._deek_sound = _ba.getsound('deek')
|
||||
self._click_sound = _ba.getsound('click01')
|
||||
self._punchsound = _ba.getsound('punch01')
|
||||
@ -170,48 +217,54 @@ class Chooser:
|
||||
# for the '_random' profile. Let's use their input_device id to seed
|
||||
# it. This will give a persistent character for them between games
|
||||
# and will distribute characters nicely if everyone is random.
|
||||
self._random_color, self._random_highlight = (
|
||||
get_player_profile_colors(None))
|
||||
self._random_color, self._random_highlight = get_player_profile_colors(
|
||||
None
|
||||
)
|
||||
|
||||
# To calc our random character we pick a random one out of our
|
||||
# unlocked list and then locate that character's index in the full
|
||||
# list.
|
||||
char_index_offset = app.lobby_random_char_index_offset
|
||||
self._random_character_index = (
|
||||
(sessionplayer.inputdevice.id + char_index_offset) %
|
||||
len(self._character_names))
|
||||
sessionplayer.inputdevice.id + char_index_offset
|
||||
) % len(self._character_names)
|
||||
|
||||
# Attempt to set an initial profile based on what was used previously
|
||||
# for this input-device, etc.
|
||||
self._profileindex = self._select_initial_profile()
|
||||
self._profilename = self._profilenames[self._profileindex]
|
||||
|
||||
self._text_node = _ba.newnode('text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'position': (-100, self._vpos),
|
||||
'maxwidth': 160,
|
||||
'shadow': 0.5,
|
||||
'vr_depth': -20,
|
||||
'h_align': 'left',
|
||||
'v_align': 'center',
|
||||
'v_attach': 'top'
|
||||
})
|
||||
self._text_node = _ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'position': (-100, self._vpos),
|
||||
'maxwidth': 160,
|
||||
'shadow': 0.5,
|
||||
'vr_depth': -20,
|
||||
'h_align': 'left',
|
||||
'v_align': 'center',
|
||||
'v_attach': 'top',
|
||||
},
|
||||
)
|
||||
animate(self._text_node, 'scale', {0: 0, 0.1: 1.0})
|
||||
self.icon = _ba.newnode('image',
|
||||
owner=self._text_node,
|
||||
attrs={
|
||||
'position': (-130, self._vpos + 20),
|
||||
'mask_texture': self._mask_texture,
|
||||
'vr_depth': -10,
|
||||
'attach': 'topCenter'
|
||||
})
|
||||
self.icon = _ba.newnode(
|
||||
'image',
|
||||
owner=self._text_node,
|
||||
attrs={
|
||||
'position': (-130, self._vpos + 20),
|
||||
'mask_texture': self._mask_texture,
|
||||
'vr_depth': -10,
|
||||
'attach': 'topCenter',
|
||||
},
|
||||
)
|
||||
|
||||
animate_array(self.icon, 'scale', 2, {0: (0, 0), 0.1: (45, 45)})
|
||||
|
||||
# Set our initial name to '<choosing player>' in case anyone asks.
|
||||
self._sessionplayer.setname(
|
||||
Lstr(resource='choosingPlayerText').evaluate(), real=False)
|
||||
Lstr(resource='choosingPlayerText').evaluate(), real=False
|
||||
)
|
||||
|
||||
# Init these to our rando but they should get switched to the
|
||||
# selected profile (if any) right after.
|
||||
@ -232,32 +285,40 @@ class Chooser:
|
||||
|
||||
# If we've got a set profile name for this device, work backwards
|
||||
# from that to get our index.
|
||||
dprofilename = (app.config.get('Default Player Profiles',
|
||||
{}).get(inputdevice.name + ' ' +
|
||||
inputdevice.unique_identifier))
|
||||
dprofilename = app.config.get('Default Player Profiles', {}).get(
|
||||
inputdevice.name + ' ' + inputdevice.unique_identifier
|
||||
)
|
||||
if dprofilename is not None and dprofilename in profilenames:
|
||||
# If we got '__account__' and its local and we haven't marked
|
||||
# anyone as the 'account profile' device yet, mark this guy as
|
||||
# it. (prevents the next joiner from getting the account
|
||||
# profile too).
|
||||
if (dprofilename == '__account__'
|
||||
and not inputdevice.is_remote_client
|
||||
and app.lobby_account_profile_device_id is None):
|
||||
if (
|
||||
dprofilename == '__account__'
|
||||
and not inputdevice.is_remote_client
|
||||
and app.lobby_account_profile_device_id is None
|
||||
):
|
||||
app.lobby_account_profile_device_id = inputdevice.id
|
||||
return profilenames.index(dprofilename)
|
||||
|
||||
# We want to mark the first local input-device in the game
|
||||
# as the 'account profile' device.
|
||||
if (not inputdevice.is_remote_client
|
||||
and not inputdevice.is_controller_app):
|
||||
if (app.lobby_account_profile_device_id is None
|
||||
and '__account__' in profilenames):
|
||||
if (
|
||||
not inputdevice.is_remote_client
|
||||
and not inputdevice.is_controller_app
|
||||
):
|
||||
if (
|
||||
app.lobby_account_profile_device_id is None
|
||||
and '__account__' in profilenames
|
||||
):
|
||||
app.lobby_account_profile_device_id = inputdevice.id
|
||||
|
||||
# If this is the designated account-profile-device, try to default
|
||||
# to the account profile.
|
||||
if (inputdevice.id == app.lobby_account_profile_device_id
|
||||
and '__account__' in profilenames):
|
||||
if (
|
||||
inputdevice.id == app.lobby_account_profile_device_id
|
||||
and '__account__' in profilenames
|
||||
):
|
||||
return profilenames.index('__account__')
|
||||
|
||||
# If this is the controller app, it defaults to using a random
|
||||
@ -274,9 +335,13 @@ class Chooser:
|
||||
|
||||
# Cycle through our non-random profiles once; after
|
||||
# that, everyone gets random.
|
||||
while (app.lobby_random_profile_index < len(profilenames)
|
||||
and profilenames[app.lobby_random_profile_index]
|
||||
in ('_random', '__account__', '_edit')):
|
||||
while app.lobby_random_profile_index < len(
|
||||
profilenames
|
||||
) and profilenames[app.lobby_random_profile_index] in (
|
||||
'_random',
|
||||
'__account__',
|
||||
'_edit',
|
||||
):
|
||||
app.lobby_random_profile_index += 1
|
||||
if app.lobby_random_profile_index < len(profilenames):
|
||||
profileindex = app.lobby_random_profile_index
|
||||
@ -340,18 +405,22 @@ class Chooser:
|
||||
# character to others they own, but profile characters
|
||||
# should work (and we validate profiles on the master server
|
||||
# so no exploit opportunities)
|
||||
if (character not in self._character_names
|
||||
and character in _ba.app.spaz_appearances):
|
||||
if (
|
||||
character not in self._character_names
|
||||
and character in _ba.app.spaz_appearances
|
||||
):
|
||||
self._character_names.append(character)
|
||||
self._character_index = self._character_names.index(character)
|
||||
self._color, self._highlight = (get_player_profile_colors(
|
||||
self._profilename, profiles=self._profiles))
|
||||
self._color, self._highlight = get_player_profile_colors(
|
||||
self._profilename, profiles=self._profiles
|
||||
)
|
||||
self._update_icon()
|
||||
self._update_text()
|
||||
|
||||
def reload_profiles(self) -> None:
|
||||
"""Reload all player profiles."""
|
||||
from ba._general import json_prep
|
||||
|
||||
app = _ba.app
|
||||
|
||||
# Re-construct our profile index and other stuff since the profile
|
||||
@ -396,8 +465,11 @@ class Chooser:
|
||||
|
||||
# For local devices, add it an 'edit' option which will pop up
|
||||
# the profile window.
|
||||
if not is_remote and not is_test_input and not (app.demo_mode
|
||||
or app.arcade_mode):
|
||||
if (
|
||||
not is_remote
|
||||
and not is_test_input
|
||||
and not (app.demo_mode or app.arcade_mode)
|
||||
):
|
||||
self._profiles['_edit'] = {}
|
||||
|
||||
# Build a sorted name list we can iterate through.
|
||||
@ -417,18 +489,25 @@ class Chooser:
|
||||
assert self._text_node
|
||||
spacing = 350
|
||||
sessionteams = self.lobby.sessionteams
|
||||
offs = (spacing * -0.5 * len(sessionteams) +
|
||||
spacing * self._selected_team_index + 250)
|
||||
offs = (
|
||||
spacing * -0.5 * len(sessionteams)
|
||||
+ spacing * self._selected_team_index
|
||||
+ 250
|
||||
)
|
||||
if len(sessionteams) > 1:
|
||||
offs -= 35
|
||||
animate_array(self._text_node, 'position', 2, {
|
||||
0: self._text_node.position,
|
||||
0.1: (-100 + offs, self._vpos + 23)
|
||||
})
|
||||
animate_array(self.icon, 'position', 2, {
|
||||
0: self.icon.position,
|
||||
0.1: (-130 + offs, self._vpos + 22)
|
||||
})
|
||||
animate_array(
|
||||
self._text_node,
|
||||
'position',
|
||||
2,
|
||||
{0: self._text_node.position, 0.1: (-100 + offs, self._vpos + 23)},
|
||||
)
|
||||
animate_array(
|
||||
self.icon,
|
||||
'position',
|
||||
2,
|
||||
{0: self.icon.position, 0.1: (-130 + offs, self._vpos + 22)},
|
||||
)
|
||||
|
||||
def get_character_name(self) -> str:
|
||||
"""Return the selected character name."""
|
||||
@ -442,16 +521,14 @@ class Chooser:
|
||||
clamp = False
|
||||
if name == '_random':
|
||||
try:
|
||||
name = (
|
||||
self._sessionplayer.inputdevice.get_default_player_name())
|
||||
name = self._sessionplayer.inputdevice.get_default_player_name()
|
||||
except Exception:
|
||||
print_exception('Error getting _random chooser name.')
|
||||
name = 'Invalid'
|
||||
clamp = not full
|
||||
elif name == '__account__':
|
||||
try:
|
||||
name = self._sessionplayer.inputdevice.get_v1_account_name(
|
||||
full)
|
||||
name = self._sessionplayer.inputdevice.get_v1_account_name(full)
|
||||
except Exception:
|
||||
print_exception('Error getting account name for chooser.')
|
||||
name = 'Invalid'
|
||||
@ -459,18 +536,21 @@ class Chooser:
|
||||
elif name == '_edit':
|
||||
# Explicitly flattening this to a str; it's only relevant on
|
||||
# the host so that's ok.
|
||||
name = (Lstr(
|
||||
name = Lstr(
|
||||
resource='createEditPlayerText',
|
||||
fallback_resource='editProfileWindow.titleNewText').evaluate())
|
||||
fallback_resource='editProfileWindow.titleNewText',
|
||||
).evaluate()
|
||||
else:
|
||||
# If we have a regular profile marked as global with an icon,
|
||||
# use it (for full only).
|
||||
if full:
|
||||
try:
|
||||
if self._profiles[name_raw].get('global', False):
|
||||
icon = (self._profiles[name_raw]['icon']
|
||||
if 'icon' in self._profiles[name_raw] else
|
||||
_ba.charstr(SpecialChar.LOGO))
|
||||
icon = (
|
||||
self._profiles[name_raw]['icon']
|
||||
if 'icon' in self._profiles[name_raw]
|
||||
else _ba.charstr(SpecialChar.LOGO)
|
||||
)
|
||||
name = icon + name
|
||||
except Exception:
|
||||
print_exception('Error applying global icon.')
|
||||
@ -488,6 +568,7 @@ class Chooser:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.profile import browser as pbrowser
|
||||
from ba._general import Call
|
||||
|
||||
profilename = self._profilenames[self._profileindex]
|
||||
|
||||
# Handle '_edit' as a special case.
|
||||
@ -503,50 +584,71 @@ class Chooser:
|
||||
if not ready:
|
||||
self._sessionplayer.assigninput(
|
||||
InputType.LEFT_PRESS,
|
||||
Call(self.handlemessage, ChangeMessage('team', -1)))
|
||||
Call(self.handlemessage, ChangeMessage('team', -1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
InputType.RIGHT_PRESS,
|
||||
Call(self.handlemessage, ChangeMessage('team', 1)))
|
||||
Call(self.handlemessage, ChangeMessage('team', 1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
InputType.BOMB_PRESS,
|
||||
Call(self.handlemessage, ChangeMessage('character', 1)))
|
||||
Call(self.handlemessage, ChangeMessage('character', 1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
InputType.UP_PRESS,
|
||||
Call(self.handlemessage, ChangeMessage('profileindex', -1)))
|
||||
Call(self.handlemessage, ChangeMessage('profileindex', -1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
InputType.DOWN_PRESS,
|
||||
Call(self.handlemessage, ChangeMessage('profileindex', 1)))
|
||||
Call(self.handlemessage, ChangeMessage('profileindex', 1)),
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
(InputType.JUMP_PRESS, InputType.PICK_UP_PRESS,
|
||||
InputType.PUNCH_PRESS),
|
||||
Call(self.handlemessage, ChangeMessage('ready', 1)))
|
||||
(
|
||||
InputType.JUMP_PRESS,
|
||||
InputType.PICK_UP_PRESS,
|
||||
InputType.PUNCH_PRESS,
|
||||
),
|
||||
Call(self.handlemessage, ChangeMessage('ready', 1)),
|
||||
)
|
||||
self._ready = False
|
||||
self._update_text()
|
||||
self._sessionplayer.setname('untitled', real=False)
|
||||
else:
|
||||
self._sessionplayer.assigninput(
|
||||
(InputType.LEFT_PRESS, InputType.RIGHT_PRESS,
|
||||
InputType.UP_PRESS, InputType.DOWN_PRESS,
|
||||
InputType.JUMP_PRESS, InputType.BOMB_PRESS,
|
||||
InputType.PICK_UP_PRESS), self._do_nothing)
|
||||
(
|
||||
InputType.LEFT_PRESS,
|
||||
InputType.RIGHT_PRESS,
|
||||
InputType.UP_PRESS,
|
||||
InputType.DOWN_PRESS,
|
||||
InputType.JUMP_PRESS,
|
||||
InputType.BOMB_PRESS,
|
||||
InputType.PICK_UP_PRESS,
|
||||
),
|
||||
self._do_nothing,
|
||||
)
|
||||
self._sessionplayer.assigninput(
|
||||
(InputType.JUMP_PRESS, InputType.BOMB_PRESS,
|
||||
InputType.PICK_UP_PRESS, InputType.PUNCH_PRESS),
|
||||
Call(self.handlemessage, ChangeMessage('ready', 0)))
|
||||
(
|
||||
InputType.JUMP_PRESS,
|
||||
InputType.BOMB_PRESS,
|
||||
InputType.PICK_UP_PRESS,
|
||||
InputType.PUNCH_PRESS,
|
||||
),
|
||||
Call(self.handlemessage, ChangeMessage('ready', 0)),
|
||||
)
|
||||
|
||||
# Store the last profile picked by this input for reuse.
|
||||
input_device = self._sessionplayer.inputdevice
|
||||
name = input_device.name
|
||||
unique_id = input_device.unique_identifier
|
||||
device_profiles = _ba.app.config.setdefault(
|
||||
'Default Player Profiles', {})
|
||||
'Default Player Profiles', {}
|
||||
)
|
||||
|
||||
# Make an exception if we have no custom profiles and are set
|
||||
# to random; in that case we'll want to start picking up custom
|
||||
# profiles if/when one is made so keep our setting cleared.
|
||||
special = ('_random', '_edit', '__account__')
|
||||
have_custom_profiles = any(p not in special
|
||||
for p in self._profiles)
|
||||
have_custom_profiles = any(p not in special for p in self._profiles)
|
||||
|
||||
profilekey = name + ' ' + unique_id
|
||||
if profilename == '_random' and not have_custom_profiles:
|
||||
@ -557,9 +659,9 @@ class Chooser:
|
||||
_ba.app.config.commit()
|
||||
|
||||
# Set this player's short and full name.
|
||||
self._sessionplayer.setname(self._getname(),
|
||||
self._getname(full=True),
|
||||
real=True)
|
||||
self._sessionplayer.setname(
|
||||
self._getname(), self._getname(full=True), real=True
|
||||
)
|
||||
self._ready = True
|
||||
self._update_text()
|
||||
|
||||
@ -583,18 +685,21 @@ class Chooser:
|
||||
team_player_counts = {}
|
||||
for sessionteam in sessionteams:
|
||||
team_player_counts[sessionteam.id] = len(
|
||||
sessionteam.players)
|
||||
sessionteam.players
|
||||
)
|
||||
for chooser in lobby.choosers:
|
||||
if chooser.ready:
|
||||
team_player_counts[chooser.sessionteam.id] += 1
|
||||
largest_team_size = max(team_player_counts.values())
|
||||
smallest_team_size = (min(team_player_counts.values()))
|
||||
smallest_team_size = min(team_player_counts.values())
|
||||
|
||||
# Force switch if we're on the biggest sessionteam
|
||||
# and there's a smaller one available.
|
||||
if (largest_team_size != smallest_team_size
|
||||
and team_player_counts[self.sessionteam.id] >=
|
||||
largest_team_size):
|
||||
if (
|
||||
largest_team_size != smallest_team_size
|
||||
and team_player_counts[self.sessionteam.id]
|
||||
>= largest_team_size
|
||||
):
|
||||
force_team_switch = True
|
||||
|
||||
# Either force switch teams, or actually for realsies do the set-ready.
|
||||
@ -612,8 +717,7 @@ class Chooser:
|
||||
if now - self._last_change[0] < QUICK_CHANGE_INTERVAL:
|
||||
count += 1
|
||||
if count > MAX_QUICK_CHANGE_COUNT:
|
||||
_ba.disconnect_client(
|
||||
self._sessionplayer.inputdevice.client_id)
|
||||
_ba.disconnect_client(self._sessionplayer.inputdevice.client_id)
|
||||
elif now - self._last_change[0] > QUICK_CHANGE_RESET_INTERVAL:
|
||||
count = 0
|
||||
self._last_change = (now, count)
|
||||
@ -638,8 +742,8 @@ class Chooser:
|
||||
if len(sessionteams) > 1:
|
||||
_ba.playsound(self._swish_sound)
|
||||
self._selected_team_index = (
|
||||
(self._selected_team_index + msg.value) %
|
||||
len(sessionteams))
|
||||
self._selected_team_index + msg.value
|
||||
) % len(sessionteams)
|
||||
self._update_text()
|
||||
self.update_position()
|
||||
self._update_icon()
|
||||
@ -655,15 +759,17 @@ class Chooser:
|
||||
# Pick the next player profile and assign our name
|
||||
# and character based on that.
|
||||
_ba.playsound(self._deek_sound)
|
||||
self._profileindex = ((self._profileindex + msg.value) %
|
||||
len(self._profilenames))
|
||||
self._profileindex = (self._profileindex + msg.value) % len(
|
||||
self._profilenames
|
||||
)
|
||||
self.update_from_profile()
|
||||
|
||||
elif msg.what == 'character':
|
||||
_ba.playsound(self._click_sound)
|
||||
# update our index in our local list of characters
|
||||
self._character_index = ((self._character_index + msg.value) %
|
||||
len(self._character_names))
|
||||
self._character_index = (
|
||||
self._character_index + msg.value
|
||||
) % len(self._character_names)
|
||||
self._update_text()
|
||||
self._update_icon()
|
||||
|
||||
@ -677,30 +783,34 @@ class Chooser:
|
||||
# Once we're ready, we've saved the name, so lets ask the system
|
||||
# for it so we get appended numbers and stuff.
|
||||
text = Lstr(value=self._sessionplayer.getname(full=True))
|
||||
text = Lstr(value='${A} (${B})',
|
||||
subs=[('${A}', text),
|
||||
('${B}', Lstr(resource='readyText'))])
|
||||
text = Lstr(
|
||||
value='${A} (${B})',
|
||||
subs=[('${A}', text), ('${B}', Lstr(resource='readyText'))],
|
||||
)
|
||||
else:
|
||||
text = Lstr(value=self._getname(full=True))
|
||||
|
||||
can_switch_teams = len(self.lobby.sessionteams) > 1
|
||||
|
||||
# Flash as we're coming in.
|
||||
fin_color = _ba.safecolor(self.get_color()) + (1, )
|
||||
fin_color = _ba.safecolor(self.get_color()) + (1,)
|
||||
if not self._inited:
|
||||
animate_array(self._text_node, 'color', 4, {
|
||||
0.15: fin_color,
|
||||
0.25: (2, 2, 2, 1),
|
||||
0.35: fin_color
|
||||
})
|
||||
animate_array(
|
||||
self._text_node,
|
||||
'color',
|
||||
4,
|
||||
{0.15: fin_color, 0.25: (2, 2, 2, 1), 0.35: fin_color},
|
||||
)
|
||||
else:
|
||||
|
||||
# Blend if we're in teams mode; switch instantly otherwise.
|
||||
if can_switch_teams:
|
||||
animate_array(self._text_node, 'color', 4, {
|
||||
0: self._text_node.color,
|
||||
0.1: fin_color
|
||||
})
|
||||
animate_array(
|
||||
self._text_node,
|
||||
'color',
|
||||
4,
|
||||
{0: self._text_node.color, 0.1: fin_color},
|
||||
)
|
||||
else:
|
||||
self._text_node.color = fin_color
|
||||
|
||||
@ -740,9 +850,11 @@ class Chooser:
|
||||
max_val = sessionteam.color[j]
|
||||
max_index = j
|
||||
that_color_for_us = highlight[max_index]
|
||||
our_second_biggest = max(highlight[(max_index + 1) % 3],
|
||||
highlight[(max_index + 2) % 3])
|
||||
diff = (that_color_for_us - our_second_biggest)
|
||||
our_second_biggest = max(
|
||||
highlight[(max_index + 1) % 3],
|
||||
highlight[(max_index + 2) % 3],
|
||||
)
|
||||
diff = that_color_for_us - our_second_biggest
|
||||
if diff > 0:
|
||||
highlight[max_index] -= diff * 0.6
|
||||
highlight[(max_index + 1) % 3] += diff * 0.3
|
||||
@ -764,10 +876,12 @@ class Chooser:
|
||||
return
|
||||
|
||||
try:
|
||||
tex_name = (_ba.app.spaz_appearances[self._character_names[
|
||||
self._character_index]].icon_texture)
|
||||
tint_tex_name = (_ba.app.spaz_appearances[self._character_names[
|
||||
self._character_index]].icon_mask_texture)
|
||||
tex_name = _ba.app.spaz_appearances[
|
||||
self._character_names[self._character_index]
|
||||
].icon_texture
|
||||
tint_tex_name = _ba.app.spaz_appearances[
|
||||
self._character_names[self._character_index]
|
||||
].icon_mask_texture
|
||||
except Exception:
|
||||
print_exception('Error updating char icon list')
|
||||
tex_name = 'neoSpazIcon'
|
||||
@ -786,18 +900,18 @@ class Chooser:
|
||||
|
||||
# If we're initing, flash.
|
||||
if not self._inited:
|
||||
animate_array(self.icon, 'color', 3, {
|
||||
0.15: (1, 1, 1),
|
||||
0.25: (2, 2, 2),
|
||||
0.35: (1, 1, 1)
|
||||
})
|
||||
animate_array(
|
||||
self.icon,
|
||||
'color',
|
||||
3,
|
||||
{0.15: (1, 1, 1), 0.25: (2, 2, 2), 0.35: (1, 1, 1)},
|
||||
)
|
||||
|
||||
# Blend in teams mode; switch instantly in ffa-mode.
|
||||
if can_switch_teams:
|
||||
animate_array(self.icon, 'tint_color', 3, {
|
||||
0: self.icon.tint_color,
|
||||
0.1: clr
|
||||
})
|
||||
animate_array(
|
||||
self.icon, 'tint_color', 3, {0: self.icon.tint_color, 0.1: clr}
|
||||
)
|
||||
else:
|
||||
self.icon.tint_color = clr
|
||||
self.icon.tint2_color = clr2
|
||||
@ -825,6 +939,7 @@ class Lobby:
|
||||
def __init__(self) -> None:
|
||||
from ba._team import SessionTeam
|
||||
from ba._coopsession import CoopSession
|
||||
|
||||
session = _ba.getsession()
|
||||
self._use_team_colors = session.use_team_colors
|
||||
if session.use_teams:
|
||||
@ -834,7 +949,7 @@ class Lobby:
|
||||
else:
|
||||
self._dummy_teams = SessionTeam()
|
||||
self._sessionteams = [weakref.ref(self._dummy_teams)]
|
||||
v_offset = (-150 if isinstance(session, CoopSession) else -50)
|
||||
v_offset = -150 if isinstance(session, CoopSession) else -50
|
||||
self.choosers: list[Chooser] = []
|
||||
self.base_v_offset = v_offset
|
||||
self.update_positions()
|
||||
@ -916,9 +1031,11 @@ class Lobby:
|
||||
def add_chooser(self, sessionplayer: ba.SessionPlayer) -> None:
|
||||
"""Add a chooser to the lobby for the provided player."""
|
||||
self.choosers.append(
|
||||
Chooser(vpos=self._vpos, sessionplayer=sessionplayer, lobby=self))
|
||||
Chooser(vpos=self._vpos, sessionplayer=sessionplayer, lobby=self)
|
||||
)
|
||||
self._next_add_team = (self._next_add_team + 1) % len(
|
||||
self._sessionteams)
|
||||
self._sessionteams
|
||||
)
|
||||
self._vpos -= 48
|
||||
|
||||
def remove_chooser(self, player: ba.SessionPlayer) -> None:
|
||||
|
||||
@ -49,6 +49,7 @@ def get_map_display_string(name: str) -> ba.Lstr:
|
||||
Category: **Asset Functions**
|
||||
"""
|
||||
from ba import _language
|
||||
|
||||
return _language.Lstr(translate=('mapsNames', name))
|
||||
|
||||
|
||||
@ -97,8 +98,11 @@ def getmaps(playtype: str) -> list[str]:
|
||||
For racing games where players much touch each region in order.
|
||||
Has two or more 'race_point' locations.
|
||||
"""
|
||||
return sorted(key for key, val in _ba.app.maps.items()
|
||||
if playtype in val.get_play_types())
|
||||
return sorted(
|
||||
key
|
||||
for key, val in _ba.app.maps.items()
|
||||
if playtype in val.get_play_types()
|
||||
)
|
||||
|
||||
|
||||
def get_map_class(name: str) -> type[ba.Map]:
|
||||
@ -111,6 +115,7 @@ def get_map_class(name: str) -> type[ba.Map]:
|
||||
return _ba.app.maps[name]
|
||||
except KeyError:
|
||||
from ba import _error
|
||||
|
||||
raise _error.NotFoundError(f"Map not found: '{name}'") from None
|
||||
|
||||
|
||||
@ -122,6 +127,7 @@ class Map(Actor):
|
||||
Consists of a collection of terrain nodes, metadata, and other
|
||||
functionality comprising a game map.
|
||||
"""
|
||||
|
||||
defs: Any = None
|
||||
name = 'Map'
|
||||
_playtypes: list[str] = []
|
||||
@ -170,8 +176,9 @@ class Map(Actor):
|
||||
"""
|
||||
return None
|
||||
|
||||
def __init__(self,
|
||||
vr_overlay_offset: Sequence[float] | None = None) -> None:
|
||||
def __init__(
|
||||
self, vr_overlay_offset: Sequence[float] | None = None
|
||||
) -> None:
|
||||
"""Instantiate a map."""
|
||||
super().__init__()
|
||||
|
||||
@ -185,10 +192,13 @@ class Map(Actor):
|
||||
self.preloaddata = _ba.getactivity().preloads[type(self)]
|
||||
except Exception as exc:
|
||||
from ba import _error
|
||||
|
||||
raise _error.NotFoundError(
|
||||
'Preload data not found for ' + str(type(self)) +
|
||||
'; make sure to call the type\'s preload()'
|
||||
' staticmethod in the activity constructor') from exc
|
||||
'Preload data not found for '
|
||||
+ str(type(self))
|
||||
+ '; make sure to call the type\'s preload()'
|
||||
' staticmethod in the activity constructor'
|
||||
) from exc
|
||||
|
||||
# Set various globals.
|
||||
gnode = _ba.getactivity().globalsnode
|
||||
@ -210,9 +220,12 @@ class Map(Actor):
|
||||
# Set shadow ranges.
|
||||
try:
|
||||
gnode.shadow_range = [
|
||||
self.defs.points[v][1] for v in [
|
||||
'shadow_lower_bottom', 'shadow_lower_top',
|
||||
'shadow_upper_bottom', 'shadow_upper_top'
|
||||
self.defs.points[v][1]
|
||||
for v in [
|
||||
'shadow_lower_bottom',
|
||||
'shadow_lower_top',
|
||||
'shadow_upper_bottom',
|
||||
'shadow_upper_top',
|
||||
]
|
||||
]
|
||||
except Exception:
|
||||
@ -220,36 +233,42 @@ class Map(Actor):
|
||||
|
||||
# In vr, set a fixed point in space for the overlay to show up at.
|
||||
# By default we use the bounds center but allow the map to override it.
|
||||
center = ((aoi_bounds[0] + aoi_bounds[3]) * 0.5,
|
||||
(aoi_bounds[1] + aoi_bounds[4]) * 0.5,
|
||||
(aoi_bounds[2] + aoi_bounds[5]) * 0.5)
|
||||
center = (
|
||||
(aoi_bounds[0] + aoi_bounds[3]) * 0.5,
|
||||
(aoi_bounds[1] + aoi_bounds[4]) * 0.5,
|
||||
(aoi_bounds[2] + aoi_bounds[5]) * 0.5,
|
||||
)
|
||||
if vr_overlay_offset is not None:
|
||||
center = (center[0] + vr_overlay_offset[0],
|
||||
center[1] + vr_overlay_offset[1],
|
||||
center[2] + vr_overlay_offset[2])
|
||||
center = (
|
||||
center[0] + vr_overlay_offset[0],
|
||||
center[1] + vr_overlay_offset[1],
|
||||
center[2] + vr_overlay_offset[2],
|
||||
)
|
||||
gnode.vr_overlay_center = center
|
||||
gnode.vr_overlay_center_enabled = True
|
||||
|
||||
self.spawn_points = (self.get_def_points('spawn')
|
||||
or [(0, 0, 0, 0, 0, 0)])
|
||||
self.ffa_spawn_points = (self.get_def_points('ffa_spawn')
|
||||
or [(0, 0, 0, 0, 0, 0)])
|
||||
self.spawn_by_flag_points = (self.get_def_points('spawn_by_flag')
|
||||
or [(0, 0, 0, 0, 0, 0)])
|
||||
self.spawn_points = self.get_def_points('spawn') or [(0, 0, 0, 0, 0, 0)]
|
||||
self.ffa_spawn_points = self.get_def_points('ffa_spawn') or [
|
||||
(0, 0, 0, 0, 0, 0)
|
||||
]
|
||||
self.spawn_by_flag_points = self.get_def_points('spawn_by_flag') or [
|
||||
(0, 0, 0, 0, 0, 0)
|
||||
]
|
||||
self.flag_points = self.get_def_points('flag') or [(0, 0, 0)]
|
||||
|
||||
# We just want points.
|
||||
self.flag_points = [p[:3] for p in self.flag_points]
|
||||
self.flag_points_default = (self.get_def_point('flag_default')
|
||||
or (0, 1, 0))
|
||||
self.flag_points_default = self.get_def_point('flag_default') or (
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
)
|
||||
self.powerup_spawn_points = self.get_def_points('powerup_spawn') or [
|
||||
(0, 0, 0)
|
||||
]
|
||||
|
||||
# We just want points.
|
||||
self.powerup_spawn_points = ([
|
||||
p[:3] for p in self.powerup_spawn_points
|
||||
])
|
||||
self.powerup_spawn_points = [p[:3] for p in self.powerup_spawn_points]
|
||||
self.tnt_points = self.get_def_points('tnt') or []
|
||||
|
||||
# We just want points.
|
||||
@ -262,11 +281,10 @@ class Map(Actor):
|
||||
# Let's select random index for first spawn point,
|
||||
# so that no one is offended by the constant spawn on the edge.
|
||||
self._next_ffa_start_index = random.randrange(
|
||||
len(self.ffa_spawn_points))
|
||||
len(self.ffa_spawn_points)
|
||||
)
|
||||
|
||||
def is_point_near_edge(self,
|
||||
point: ba.Vec3,
|
||||
running: bool = False) -> bool:
|
||||
def is_point_near_edge(self, point: ba.Vec3, running: bool = False) -> bool:
|
||||
"""Return whether the provided point is near an edge of the map.
|
||||
|
||||
Simple bot logic uses this call to determine if they
|
||||
@ -278,22 +296,32 @@ class Map(Actor):
|
||||
return False
|
||||
|
||||
def get_def_bound_box(
|
||||
self, name: str
|
||||
self, name: str
|
||||
) -> tuple[float, float, float, float, float, float] | None:
|
||||
"""Return a 6 member bounds tuple or None if it is not defined."""
|
||||
try:
|
||||
box = self.defs.boxes[name]
|
||||
return (box[0] - box[6] / 2.0, box[1] - box[7] / 2.0,
|
||||
box[2] - box[8] / 2.0, box[0] + box[6] / 2.0,
|
||||
box[1] + box[7] / 2.0, box[2] + box[8] / 2.0)
|
||||
return (
|
||||
box[0] - box[6] / 2.0,
|
||||
box[1] - box[7] / 2.0,
|
||||
box[2] - box[8] / 2.0,
|
||||
box[0] + box[6] / 2.0,
|
||||
box[1] + box[7] / 2.0,
|
||||
box[2] + box[8] / 2.0,
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_def_point(self, name: str) -> Sequence[float] | None:
|
||||
"""Return a single defined point or a default value in its absence."""
|
||||
val = self.defs.points.get(name)
|
||||
return (None if val is None else
|
||||
_math.vec3validate(val) if __debug__ else val)
|
||||
return (
|
||||
None
|
||||
if val is None
|
||||
else _math.vec3validate(val)
|
||||
if __debug__
|
||||
else val
|
||||
)
|
||||
|
||||
def get_def_points(self, name: str) -> list[Sequence[float]]:
|
||||
"""Return a list of named points.
|
||||
@ -320,12 +348,16 @@ class Map(Actor):
|
||||
pnt = self.spawn_points[team_index % len(self.spawn_points)]
|
||||
x_range = (-0.5, 0.5) if pnt[3] == 0.0 else (-pnt[3], pnt[3])
|
||||
z_range = (-0.5, 0.5) if pnt[5] == 0.0 else (-pnt[5], pnt[5])
|
||||
pnt = (pnt[0] + random.uniform(*x_range), pnt[1],
|
||||
pnt[2] + random.uniform(*z_range))
|
||||
pnt = (
|
||||
pnt[0] + random.uniform(*x_range),
|
||||
pnt[1],
|
||||
pnt[2] + random.uniform(*z_range),
|
||||
)
|
||||
return pnt
|
||||
|
||||
def get_ffa_start_position(
|
||||
self, players: Sequence[ba.Player]) -> Sequence[float]:
|
||||
self, players: Sequence[ba.Player]
|
||||
) -> Sequence[float]:
|
||||
"""Return a random starting position in one of the FFA spawn areas.
|
||||
|
||||
If a list of ba.Player-s is provided; the returned points will be
|
||||
@ -340,12 +372,16 @@ class Map(Actor):
|
||||
|
||||
def _getpt() -> Sequence[float]:
|
||||
point = self.ffa_spawn_points[self._next_ffa_start_index]
|
||||
self._next_ffa_start_index = ((self._next_ffa_start_index + 1) %
|
||||
len(self.ffa_spawn_points))
|
||||
self._next_ffa_start_index = (self._next_ffa_start_index + 1) % len(
|
||||
self.ffa_spawn_points
|
||||
)
|
||||
x_range = (-0.5, 0.5) if point[3] == 0.0 else (-point[3], point[3])
|
||||
z_range = (-0.5, 0.5) if point[5] == 0.0 else (-point[5], point[5])
|
||||
point = (point[0] + random.uniform(*x_range), point[1],
|
||||
point[2] + random.uniform(*z_range))
|
||||
point = (
|
||||
point[0] + random.uniform(*x_range),
|
||||
point[1],
|
||||
point[2] + random.uniform(*z_range),
|
||||
)
|
||||
return point
|
||||
|
||||
if not player_pts:
|
||||
@ -368,8 +404,9 @@ class Map(Actor):
|
||||
assert farthestpt is not None
|
||||
return tuple(farthestpt)
|
||||
|
||||
def get_flag_position(self,
|
||||
team_index: int | None = None) -> Sequence[float]:
|
||||
def get_flag_position(
|
||||
self, team_index: int | None = None
|
||||
) -> Sequence[float]:
|
||||
"""Return a flag position on the map for the given team index.
|
||||
|
||||
Pass None to get the default flag point.
|
||||
@ -384,6 +421,7 @@ class Map(Actor):
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
from ba import _messages
|
||||
|
||||
if isinstance(msg, _messages.DieMessage):
|
||||
if self.node:
|
||||
self.node.delete()
|
||||
|
||||
@ -24,6 +24,7 @@ def vec3validate(value: Sequence[float]) -> Sequence[float]:
|
||||
to keep runtime overhead minimal.
|
||||
"""
|
||||
from numbers import Number
|
||||
|
||||
if not isinstance(value, abc.Sequence):
|
||||
raise TypeError(f"Expected a sequence; got {type(value)}")
|
||||
if len(value) != 3:
|
||||
@ -40,9 +41,11 @@ def is_point_in_box(pnt: Sequence[float], box: Sequence[float]) -> bool:
|
||||
|
||||
For use with standard def boxes (position|rotate|scale).
|
||||
"""
|
||||
return ((abs(pnt[0] - box[0]) <= box[6] * 0.5)
|
||||
and (abs(pnt[1] - box[1]) <= box[7] * 0.5)
|
||||
and (abs(pnt[2] - box[2]) <= box[8] * 0.5))
|
||||
return (
|
||||
(abs(pnt[0] - box[0]) <= box[6] * 0.5)
|
||||
and (abs(pnt[1] - box[1]) <= box[7] * 0.5)
|
||||
and (abs(pnt[2] - box[2]) <= box[8] * 0.5)
|
||||
)
|
||||
|
||||
|
||||
def normalized_color(color: Sequence[float]) -> tuple[float, ...]:
|
||||
|
||||
@ -38,6 +38,7 @@ class DeathType(Enum):
|
||||
|
||||
Category: Enums
|
||||
"""
|
||||
|
||||
GENERIC = 'generic'
|
||||
OUT_OF_BOUNDS = 'out_of_bounds'
|
||||
IMPACT = 'impact'
|
||||
@ -83,8 +84,13 @@ class PlayerDiedMessage:
|
||||
how: ba.DeathType
|
||||
"""The particular type of death."""
|
||||
|
||||
def __init__(self, player: ba.Player, was_killed: bool,
|
||||
killerplayer: ba.Player | None, how: ba.DeathType):
|
||||
def __init__(
|
||||
self,
|
||||
player: ba.Player,
|
||||
was_killed: bool,
|
||||
killerplayer: ba.Player | None,
|
||||
how: ba.DeathType,
|
||||
):
|
||||
"""Instantiate a message with the given values."""
|
||||
|
||||
# Invalid refs should never be passed as args.
|
||||
@ -97,8 +103,9 @@ class PlayerDiedMessage:
|
||||
self.killed = was_killed
|
||||
self.how = how
|
||||
|
||||
def getkillerplayer(self,
|
||||
playertype: type[PlayerType]) -> PlayerType | None:
|
||||
def getkillerplayer(
|
||||
self, playertype: type[PlayerType]
|
||||
) -> PlayerType | None:
|
||||
"""Return the ba.Player responsible for the killing, if any.
|
||||
|
||||
Pass the Player type being used by the current game.
|
||||
@ -235,19 +242,21 @@ class HitMessage:
|
||||
their effect to a target.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
srcnode: ba.Node | None = None,
|
||||
pos: Sequence[float] | None = None,
|
||||
velocity: Sequence[float] | None = None,
|
||||
magnitude: float = 1.0,
|
||||
velocity_magnitude: float = 0.0,
|
||||
radius: float = 1.0,
|
||||
source_player: ba.Player | None = None,
|
||||
kick_back: float = 1.0,
|
||||
flat_damage: float | None = None,
|
||||
hit_type: str = 'generic',
|
||||
force_direction: Sequence[float] | None = None,
|
||||
hit_subtype: str = 'default'):
|
||||
def __init__(
|
||||
self,
|
||||
srcnode: ba.Node | None = None,
|
||||
pos: Sequence[float] | None = None,
|
||||
velocity: Sequence[float] | None = None,
|
||||
magnitude: float = 1.0,
|
||||
velocity_magnitude: float = 0.0,
|
||||
radius: float = 1.0,
|
||||
source_player: ba.Player | None = None,
|
||||
kick_back: float = 1.0,
|
||||
flat_damage: float | None = None,
|
||||
hit_type: str = 'generic',
|
||||
force_direction: Sequence[float] | None = None,
|
||||
hit_subtype: str = 'default',
|
||||
):
|
||||
"""Instantiate a message with given values."""
|
||||
|
||||
self.srcnode = srcnode
|
||||
@ -264,11 +273,13 @@ class HitMessage:
|
||||
self.flat_damage = flat_damage
|
||||
self.hit_type = hit_type
|
||||
self.hit_subtype = hit_subtype
|
||||
self.force_direction = (force_direction
|
||||
if force_direction is not None else velocity)
|
||||
self.force_direction = (
|
||||
force_direction if force_direction is not None else velocity
|
||||
)
|
||||
|
||||
def get_source_player(self,
|
||||
playertype: type[PlayerType]) -> PlayerType | None:
|
||||
def get_source_player(
|
||||
self, playertype: type[PlayerType]
|
||||
) -> PlayerType | None:
|
||||
"""Return the source-player if one exists and is the provided type."""
|
||||
player: Any = self._source_player
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ T = TypeVar('T')
|
||||
@dataclass
|
||||
class ScanResults:
|
||||
"""Final results from a meta-scan."""
|
||||
|
||||
exports: dict[str, list[str]] = field(default_factory=dict)
|
||||
errors: list[str] = field(default_factory=list)
|
||||
warnings: list[str] = field(default_factory=list)
|
||||
@ -80,7 +81,8 @@ class MetadataSubsystem:
|
||||
|
||||
self._scan_complete_cb = scan_complete_cb
|
||||
self._scan = DirectoryScan(
|
||||
[_ba.app.python_directory_app, _ba.app.python_directory_user])
|
||||
[_ba.app.python_directory_app, _ba.app.python_directory_user]
|
||||
)
|
||||
|
||||
Thread(target=self._run_scan_in_bg, daemon=True).start()
|
||||
|
||||
@ -111,8 +113,12 @@ class MetadataSubsystem:
|
||||
loading work happens, pass completion_cb_in_bg_thread=True.
|
||||
"""
|
||||
Thread(
|
||||
target=tpartial(self._load_exported_classes, cls, completion_cb,
|
||||
completion_cb_in_bg_thread),
|
||||
target=tpartial(
|
||||
self._load_exported_classes,
|
||||
cls,
|
||||
completion_cb,
|
||||
completion_cb_in_bg_thread,
|
||||
),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
@ -123,6 +129,7 @@ class MetadataSubsystem:
|
||||
completion_cb_in_bg_thread: bool,
|
||||
) -> None:
|
||||
from ba._general import getclass
|
||||
|
||||
classes: list[type[T]] = []
|
||||
try:
|
||||
classnames = self._wait_for_scan_results().exports_of_class(cls)
|
||||
@ -148,7 +155,8 @@ class MetadataSubsystem:
|
||||
logging.warning(
|
||||
'ba.meta._wait_for_scan_results()'
|
||||
' called in logic thread before scan completed;'
|
||||
' this can cause hitches.')
|
||||
' this can cause hitches.'
|
||||
)
|
||||
|
||||
# Now wait a bit for the scan to complete.
|
||||
# Eventually error though if it doesn't.
|
||||
@ -157,7 +165,8 @@ class MetadataSubsystem:
|
||||
time.sleep(0.05)
|
||||
if time.time() - starttime > 10.0:
|
||||
raise TimeoutError(
|
||||
'timeout waiting for meta scan to complete.')
|
||||
'timeout waiting for meta scan to complete.'
|
||||
)
|
||||
return self.scanresults
|
||||
|
||||
def _run_scan_in_bg(self) -> None:
|
||||
@ -177,6 +186,7 @@ class MetadataSubsystem:
|
||||
def _handle_scan_results(self) -> None:
|
||||
"""Called in the logic thread with results of a completed scan."""
|
||||
from ba._language import Lstr
|
||||
|
||||
assert _ba.in_logic_thread()
|
||||
|
||||
results = self.scanresults
|
||||
@ -188,16 +198,20 @@ class MetadataSubsystem:
|
||||
# Errors are more serious and will get included in the regular log.
|
||||
if results.warnings or results.errors:
|
||||
import textwrap
|
||||
_ba.screenmessage(Lstr(resource='scanScriptsErrorText'),
|
||||
color=(1, 0, 0))
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
if results.warnings:
|
||||
allwarnings = textwrap.indent('\n'.join(results.warnings),
|
||||
'Warning (meta-scan): ')
|
||||
allwarnings = textwrap.indent(
|
||||
'\n'.join(results.warnings), 'Warning (meta-scan): '
|
||||
)
|
||||
logging.warning(allwarnings)
|
||||
if results.errors:
|
||||
allerrors = textwrap.indent('\n'.join(results.errors),
|
||||
'Error (meta-scan): ')
|
||||
allerrors = textwrap.indent(
|
||||
'\n'.join(results.errors), 'Error (meta-scan): '
|
||||
)
|
||||
logging.error(allerrors)
|
||||
|
||||
# Let the game know we're done.
|
||||
@ -244,23 +258,28 @@ class DirectoryScan:
|
||||
self._scan_module(moduledir, subpath)
|
||||
except Exception:
|
||||
import traceback
|
||||
|
||||
self.results.warnings.append(
|
||||
f"Error scanning '{subpath}': " +
|
||||
traceback.format_exc())
|
||||
f"Error scanning '{subpath}': " + traceback.format_exc()
|
||||
)
|
||||
|
||||
# Sort our results
|
||||
for exportlist in self.results.exports.values():
|
||||
exportlist.sort()
|
||||
|
||||
def _get_path_module_entries(self, path: Path, subpath: str | Path,
|
||||
modules: list[tuple[Path, Path]]) -> None:
|
||||
def _get_path_module_entries(
|
||||
self, path: Path, subpath: str | Path, modules: list[tuple[Path, Path]]
|
||||
) -> None:
|
||||
"""Scan provided path and add module entries to provided list."""
|
||||
try:
|
||||
# Special case: let's save some time and skip the whole 'ba'
|
||||
# package since we know it doesn't contain any meta tags.
|
||||
fullpath = Path(path, subpath)
|
||||
entries = [(path, Path(subpath, name))
|
||||
for name in os.listdir(fullpath) if name != 'ba']
|
||||
entries = [
|
||||
(path, Path(subpath, name))
|
||||
for name in os.listdir(fullpath)
|
||||
if name != 'ba'
|
||||
]
|
||||
except PermissionError:
|
||||
# Expected sometimes.
|
||||
entries = []
|
||||
@ -273,8 +292,10 @@ class DirectoryScan:
|
||||
for entry in entries:
|
||||
if entry[1].name.endswith('.py'):
|
||||
modules.append(entry)
|
||||
elif (Path(entry[0], entry[1]).is_dir()
|
||||
and Path(entry[0], entry[1], '__init__.py').is_file()):
|
||||
elif (
|
||||
Path(entry[0], entry[1]).is_dir()
|
||||
and Path(entry[0], entry[1], '__init__.py').is_file()
|
||||
):
|
||||
modules.append(entry)
|
||||
|
||||
def _scan_module(self, moduledir: Path, subpath: Path) -> None:
|
||||
@ -289,11 +310,13 @@ class DirectoryScan:
|
||||
flines = infile.readlines()
|
||||
meta_lines = {
|
||||
lnum: l[1:].split()
|
||||
for lnum, l in enumerate(flines) if '# ba_meta ' in l
|
||||
for lnum, l in enumerate(flines)
|
||||
if '# ba_meta ' in l
|
||||
}
|
||||
is_top_level = len(subpath.parts) <= 1
|
||||
required_api = self._get_api_requirement(subpath, meta_lines,
|
||||
is_top_level)
|
||||
required_api = self._get_api_requirement(
|
||||
subpath, meta_lines, is_top_level
|
||||
)
|
||||
|
||||
# Top level modules with no discernible api version get ignored.
|
||||
if is_top_level and required_api is None:
|
||||
@ -304,7 +327,8 @@ class DirectoryScan:
|
||||
if required_api is not None and required_api != CURRENT_API_VERSION:
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath} requires api {required_api} but'
|
||||
f' we are running {CURRENT_API_VERSION}; ignoring module.')
|
||||
f' we are running {CURRENT_API_VERSION}; ignoring module.'
|
||||
)
|
||||
return
|
||||
|
||||
# Ok; can proceed with a full scan of this module.
|
||||
@ -320,11 +344,14 @@ class DirectoryScan:
|
||||
self._scan_module(submodule[0], submodule[1])
|
||||
except Exception:
|
||||
import traceback
|
||||
self.results.warnings.append(
|
||||
f"Error scanning '{subpath}': {traceback.format_exc()}")
|
||||
|
||||
def _process_module_meta_tags(self, subpath: Path, flines: list[str],
|
||||
meta_lines: dict[int, list[str]]) -> None:
|
||||
self.results.warnings.append(
|
||||
f"Error scanning '{subpath}': {traceback.format_exc()}"
|
||||
)
|
||||
|
||||
def _process_module_meta_tags(
|
||||
self, subpath: Path, flines: list[str], meta_lines: dict[int, list[str]]
|
||||
) -> None:
|
||||
"""Pull data from a module based on its ba_meta tags."""
|
||||
for lindex, mline in meta_lines.items():
|
||||
# meta_lines is just anything containing '# ba_meta '; make sure
|
||||
@ -332,9 +359,11 @@ class DirectoryScan:
|
||||
if mline[0] != 'ba_meta':
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath}:'
|
||||
f' malformed ba_meta statement on line {lindex + 1}.')
|
||||
elif (len(mline) == 4 and mline[1] == 'require'
|
||||
and mline[2] == 'api'):
|
||||
f' malformed ba_meta statement on line {lindex + 1}.'
|
||||
)
|
||||
elif (
|
||||
len(mline) == 4 and mline[1] == 'require' and mline[2] == 'api'
|
||||
):
|
||||
# Ignore 'require api X' lines in this pass.
|
||||
pass
|
||||
elif len(mline) != 3 or mline[1] != 'export':
|
||||
@ -342,7 +371,8 @@ class DirectoryScan:
|
||||
# complain for anything else we see.
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath}'
|
||||
f': unrecognized ba_meta statement on line {lindex + 1}.')
|
||||
f': unrecognized ba_meta statement on line {lindex + 1}.'
|
||||
)
|
||||
else:
|
||||
# Looks like we've got a valid export line!
|
||||
modulename = '.'.join(subpath.parts)
|
||||
@ -350,7 +380,8 @@ class DirectoryScan:
|
||||
modulename = modulename[:-3]
|
||||
exporttypestr = mline[2]
|
||||
export_class_name = self._get_export_class_name(
|
||||
subpath, flines, lindex)
|
||||
subpath, flines, lindex
|
||||
)
|
||||
if export_class_name is not None:
|
||||
classname = modulename + '.' + export_class_name
|
||||
|
||||
@ -360,11 +391,13 @@ class DirectoryScan:
|
||||
exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr)
|
||||
if exporttype is None:
|
||||
exporttype = exporttypestr
|
||||
self.results.exports.setdefault(exporttype,
|
||||
[]).append(classname)
|
||||
self.results.exports.setdefault(exporttype, []).append(
|
||||
classname
|
||||
)
|
||||
|
||||
def _get_export_class_name(self, subpath: Path, lines: list[str],
|
||||
lindex: int) -> str | None:
|
||||
def _get_export_class_name(
|
||||
self, subpath: Path, lines: list[str], lindex: int
|
||||
) -> str | None:
|
||||
"""Given line num of an export tag, returns its operand class name."""
|
||||
lindexorig = lindex
|
||||
classname = None
|
||||
@ -385,7 +418,8 @@ class DirectoryScan:
|
||||
if classname is None:
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath}: class definition not found below'
|
||||
f' "ba_meta export" statement on line {lindexorig + 1}.')
|
||||
f' "ba_meta export" statement on line {lindexorig + 1}.'
|
||||
)
|
||||
return classname
|
||||
|
||||
def _get_api_requirement(
|
||||
@ -399,8 +433,13 @@ class DirectoryScan:
|
||||
Malformed api requirement strings will be logged as warnings.
|
||||
"""
|
||||
lines = [
|
||||
l for l in meta_lines.values() if len(l) == 4 and l[0] == 'ba_meta'
|
||||
and l[1] == 'require' and l[2] == 'api' and l[3].isdigit()
|
||||
l
|
||||
for l in meta_lines.values()
|
||||
if len(l) == 4
|
||||
and l[0] == 'ba_meta'
|
||||
and l[1] == 'require'
|
||||
and l[2] == 'api'
|
||||
and l[3].isdigit()
|
||||
]
|
||||
|
||||
# We're successful if we find exactly one properly formatted line.
|
||||
@ -412,12 +451,14 @@ class DirectoryScan:
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath}: multiple'
|
||||
' "# ba_meta require api <NUM>" lines found;'
|
||||
' ignoring module.')
|
||||
' ignoring module.'
|
||||
)
|
||||
elif not lines and toplevel and meta_lines:
|
||||
# If we're a top-level module containing meta lines but
|
||||
# no valid "require api" line found, complain.
|
||||
self.results.warnings.append(
|
||||
f'Warning: {subpath}:'
|
||||
' no valid "# ba_meta require api <NUM>" line found;'
|
||||
' ignoring module.')
|
||||
' ignoring module.'
|
||||
)
|
||||
return None
|
||||
|
||||
@ -38,6 +38,7 @@ class MultiTeamSession(Session):
|
||||
# pylint: disable=cyclic-import
|
||||
from ba import _playlist
|
||||
from bastd.activity.multiteamjoin import MultiTeamJoinActivity
|
||||
|
||||
app = _ba.app
|
||||
cfg = app.config
|
||||
|
||||
@ -51,11 +52,13 @@ class MultiTeamSession(Session):
|
||||
# print('FIXME: TEAM BASE SESSION WOULD CALC DEPS.')
|
||||
depsets: Sequence[ba.DependencySet] = []
|
||||
|
||||
super().__init__(depsets,
|
||||
team_names=team_names,
|
||||
team_colors=team_colors,
|
||||
min_players=1,
|
||||
max_players=self.get_max_players())
|
||||
super().__init__(
|
||||
depsets,
|
||||
team_names=team_names,
|
||||
team_colors=team_colors,
|
||||
min_players=1,
|
||||
max_players=self.get_max_players(),
|
||||
)
|
||||
|
||||
self._series_length = app.teams_series_length
|
||||
self._ffa_series_length = app.ffa_series_length
|
||||
@ -67,13 +70,13 @@ class MultiTeamSession(Session):
|
||||
from bastd.tutorial import TutorialActivity
|
||||
|
||||
# Get this loading.
|
||||
self._tutorial_activity_instance = _ba.newactivity(
|
||||
TutorialActivity)
|
||||
self._tutorial_activity_instance = _ba.newactivity(TutorialActivity)
|
||||
else:
|
||||
self._tutorial_activity_instance = None
|
||||
|
||||
self._playlist_name = cfg.get(self._playlist_selection_var,
|
||||
'__default__')
|
||||
self._playlist_name = cfg.get(
|
||||
self._playlist_selection_var, '__default__'
|
||||
)
|
||||
self._playlist_randomize = cfg.get(self._playlist_randomize_var, False)
|
||||
|
||||
# Which game activity we're on.
|
||||
@ -81,8 +84,10 @@ class MultiTeamSession(Session):
|
||||
|
||||
playlists = cfg.get(self._playlists_var, {})
|
||||
|
||||
if (self._playlist_name != '__default__'
|
||||
and self._playlist_name in playlists):
|
||||
if (
|
||||
self._playlist_name != '__default__'
|
||||
and self._playlist_name in playlists
|
||||
):
|
||||
|
||||
# Make sure to copy this, as we muck with it in place once we've
|
||||
# got it and we don't want that to affect our config.
|
||||
@ -98,19 +103,22 @@ class MultiTeamSession(Session):
|
||||
playlist,
|
||||
sessiontype=type(self),
|
||||
add_resolved_type=True,
|
||||
name='default teams' if self.use_teams else 'default ffa')
|
||||
name='default teams' if self.use_teams else 'default ffa',
|
||||
)
|
||||
|
||||
if not playlist_resolved:
|
||||
raise RuntimeError('Playlist contains no valid games.')
|
||||
|
||||
self._playlist = ShuffleList(playlist_resolved,
|
||||
shuffle=self._playlist_randomize)
|
||||
self._playlist = ShuffleList(
|
||||
playlist_resolved, shuffle=self._playlist_randomize
|
||||
)
|
||||
|
||||
# Get a game on deck ready to go.
|
||||
self._current_game_spec: dict[str, Any] | None = None
|
||||
self._next_game_spec: dict[str, Any] = self._playlist.pull_next()
|
||||
self._next_game: type[ba.GameActivity] = (
|
||||
self._next_game_spec['resolved_type'])
|
||||
self._next_game: type[ba.GameActivity] = self._next_game_spec[
|
||||
'resolved_type'
|
||||
]
|
||||
|
||||
# Go ahead and instantiate the next game we'll
|
||||
# use so it has lots of time to load.
|
||||
@ -131,6 +139,7 @@ class MultiTeamSession(Session):
|
||||
"""Returns a description of the next game on deck."""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._gameactivity import GameActivity
|
||||
|
||||
gametype: type[GameActivity] = self._next_game_spec['resolved_type']
|
||||
assert issubclass(gametype, GameActivity)
|
||||
return gametype.get_settings_display_string(self._next_game_spec)
|
||||
@ -151,15 +160,20 @@ class MultiTeamSession(Session):
|
||||
def _instantiate_next_game(self) -> None:
|
||||
self._next_game_instance = _ba.newactivity(
|
||||
self._next_game_spec['resolved_type'],
|
||||
self._next_game_spec['settings'])
|
||||
self._next_game_spec['settings'],
|
||||
)
|
||||
|
||||
def on_activity_end(self, activity: ba.Activity, results: Any) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.tutorial import TutorialActivity
|
||||
from bastd.activity.multiteamvictory import (
|
||||
TeamSeriesVictoryScoreScreenActivity)
|
||||
from ba._activitytypes import (TransitionActivity, JoinActivity,
|
||||
ScoreScreenActivity)
|
||||
TeamSeriesVictoryScoreScreenActivity,
|
||||
)
|
||||
from ba._activitytypes import (
|
||||
TransitionActivity,
|
||||
JoinActivity,
|
||||
ScoreScreenActivity,
|
||||
)
|
||||
|
||||
# If we have a tutorial to show, that's the first thing we do no
|
||||
# matter what.
|
||||
@ -176,8 +190,8 @@ class MultiTeamSession(Session):
|
||||
# If we're in a between-round activity or a restart-activity, hop
|
||||
# into a round.
|
||||
elif isinstance(
|
||||
activity,
|
||||
(JoinActivity, TransitionActivity, ScoreScreenActivity)):
|
||||
activity, (JoinActivity, TransitionActivity, ScoreScreenActivity)
|
||||
):
|
||||
|
||||
# If we're coming from a series-end activity, reset scores.
|
||||
if isinstance(activity, TeamSeriesVictoryScoreScreenActivity):
|
||||
@ -204,7 +218,7 @@ class MultiTeamSession(Session):
|
||||
# ..but only ones who have been placed on a team
|
||||
# (ie: no longer sitting in the lobby).
|
||||
try:
|
||||
has_team = (player.sessionteam is not None)
|
||||
has_team = player.sessionteam is not None
|
||||
except NotFoundError:
|
||||
has_team = False
|
||||
if has_team:
|
||||
@ -223,11 +237,13 @@ class MultiTeamSession(Session):
|
||||
del results # Unused arg.
|
||||
print_error('this should be overridden')
|
||||
|
||||
def announce_game_results(self,
|
||||
activity: ba.GameActivity,
|
||||
results: ba.GameResults,
|
||||
delay: float,
|
||||
announce_winning_team: bool = True) -> None:
|
||||
def announce_game_results(
|
||||
self,
|
||||
activity: ba.GameActivity,
|
||||
results: ba.GameResults,
|
||||
delay: float,
|
||||
announce_winning_team: bool = True,
|
||||
) -> None:
|
||||
"""Show basic game result at the end of a game.
|
||||
|
||||
(before transitioning to a score screen).
|
||||
@ -243,6 +259,7 @@ class MultiTeamSession(Session):
|
||||
from ba._language import Lstr
|
||||
from ba._freeforallsession import FreeForAllSession
|
||||
from ba._messages import CelebrateMessage
|
||||
|
||||
_ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell')))
|
||||
|
||||
if announce_winning_team:
|
||||
@ -261,8 +278,10 @@ class MultiTeamSession(Session):
|
||||
wins_resource = 'winsPlayerText'
|
||||
else:
|
||||
wins_resource = 'winsTeamText'
|
||||
wins_text = Lstr(resource=wins_resource,
|
||||
subs=[('${NAME}', winning_sessionteam.name)])
|
||||
wins_text = Lstr(
|
||||
resource=wins_resource,
|
||||
subs=[('${NAME}', winning_sessionteam.name)],
|
||||
)
|
||||
activity.show_zoom_message(
|
||||
wins_text,
|
||||
scale=0.85,
|
||||
@ -300,8 +319,10 @@ class ShuffleList:
|
||||
# If the new one is the same map or game-type as the previous,
|
||||
# lets try to keep looking.
|
||||
if len(self.shuffle_list) > 1 and self.last_gotten is not None:
|
||||
if (test_obj['settings']['map'] ==
|
||||
self.last_gotten['settings']['map']):
|
||||
if (
|
||||
test_obj['settings']['map']
|
||||
== self.last_gotten['settings']['map']
|
||||
):
|
||||
continue
|
||||
if test_obj['type'] == self.last_gotten['type']:
|
||||
continue
|
||||
|
||||
@ -24,6 +24,7 @@ class MusicType(Enum):
|
||||
'situations'. The actual music played for each type can be overridden
|
||||
by the game or by the user.
|
||||
"""
|
||||
|
||||
MENU = 'Menu'
|
||||
VICTORY = 'Victory'
|
||||
CHAR_SELECT = 'CharSelect'
|
||||
@ -53,6 +54,7 @@ class MusicPlayMode(Enum):
|
||||
|
||||
Category: **Enums**
|
||||
"""
|
||||
|
||||
REGULAR = 'regular'
|
||||
TEST = 'test'
|
||||
|
||||
@ -63,6 +65,7 @@ class AssetSoundtrackEntry:
|
||||
|
||||
Category: **App Classes**
|
||||
"""
|
||||
|
||||
assetname: str
|
||||
volume: float = 1.0
|
||||
loop: bool = True
|
||||
@ -70,50 +73,38 @@ class AssetSoundtrackEntry:
|
||||
|
||||
# What gets played by default for our different music types:
|
||||
ASSET_SOUNDTRACK_ENTRIES: dict[MusicType, AssetSoundtrackEntry] = {
|
||||
MusicType.MENU:
|
||||
AssetSoundtrackEntry('menuMusic'),
|
||||
MusicType.VICTORY:
|
||||
AssetSoundtrackEntry('victoryMusic', volume=1.2, loop=False),
|
||||
MusicType.CHAR_SELECT:
|
||||
AssetSoundtrackEntry('charSelectMusic', volume=0.4),
|
||||
MusicType.RUN_AWAY:
|
||||
AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.ONSLAUGHT:
|
||||
AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.KEEP_AWAY:
|
||||
AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.RACE:
|
||||
AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.EPIC_RACE:
|
||||
AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SCORES:
|
||||
AssetSoundtrackEntry('scoresEpicMusic', volume=0.6, loop=False),
|
||||
MusicType.GRAND_ROMP:
|
||||
AssetSoundtrackEntry('grandRompMusic', volume=1.2),
|
||||
MusicType.TO_THE_DEATH:
|
||||
AssetSoundtrackEntry('toTheDeathMusic', volume=1.2),
|
||||
MusicType.CHOSEN_ONE:
|
||||
AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.FORWARD_MARCH:
|
||||
AssetSoundtrackEntry('forwardMarchMusic', volume=0.8),
|
||||
MusicType.FLAG_CATCHER:
|
||||
AssetSoundtrackEntry('flagCatcherMusic', volume=1.2),
|
||||
MusicType.SURVIVAL:
|
||||
AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.EPIC:
|
||||
AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SPORTS:
|
||||
AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.HOCKEY:
|
||||
AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FOOTBALL:
|
||||
AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FLYING:
|
||||
AssetSoundtrackEntry('flyingMusic', volume=0.8),
|
||||
MusicType.SCARY:
|
||||
AssetSoundtrackEntry('scaryMusic', volume=0.8),
|
||||
MusicType.MARCHING:
|
||||
AssetSoundtrackEntry('whenJohnnyComesMarchingHomeMusic', volume=0.8),
|
||||
MusicType.MENU: AssetSoundtrackEntry('menuMusic'),
|
||||
MusicType.VICTORY: AssetSoundtrackEntry(
|
||||
'victoryMusic', volume=1.2, loop=False
|
||||
),
|
||||
MusicType.CHAR_SELECT: AssetSoundtrackEntry('charSelectMusic', volume=0.4),
|
||||
MusicType.RUN_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.ONSLAUGHT: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.KEEP_AWAY: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.RACE: AssetSoundtrackEntry('runAwayMusic', volume=1.2),
|
||||
MusicType.EPIC_RACE: AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SCORES: AssetSoundtrackEntry(
|
||||
'scoresEpicMusic', volume=0.6, loop=False
|
||||
),
|
||||
MusicType.GRAND_ROMP: AssetSoundtrackEntry('grandRompMusic', volume=1.2),
|
||||
MusicType.TO_THE_DEATH: AssetSoundtrackEntry('toTheDeathMusic', volume=1.2),
|
||||
MusicType.CHOSEN_ONE: AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.FORWARD_MARCH: AssetSoundtrackEntry(
|
||||
'forwardMarchMusic', volume=0.8
|
||||
),
|
||||
MusicType.FLAG_CATCHER: AssetSoundtrackEntry(
|
||||
'flagCatcherMusic', volume=1.2
|
||||
),
|
||||
MusicType.SURVIVAL: AssetSoundtrackEntry('survivalMusic', volume=0.8),
|
||||
MusicType.EPIC: AssetSoundtrackEntry('slowEpicMusic', volume=1.2),
|
||||
MusicType.SPORTS: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.HOCKEY: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FOOTBALL: AssetSoundtrackEntry('sportsMusic', volume=0.8),
|
||||
MusicType.FLYING: AssetSoundtrackEntry('flyingMusic', volume=0.8),
|
||||
MusicType.SCARY: AssetSoundtrackEntry('scaryMusic', volume=0.8),
|
||||
MusicType.MARCHING: AssetSoundtrackEntry(
|
||||
'whenJohnnyComesMarchingHomeMusic', volume=0.8
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@ -133,7 +124,7 @@ class MusicSubsystem:
|
||||
self._music_player_type: type[MusicPlayer] | None = None
|
||||
self.music_types: dict[MusicPlayMode, MusicType | None] = {
|
||||
MusicPlayMode.REGULAR: None,
|
||||
MusicPlayMode.TEST: None
|
||||
MusicPlayMode.TEST: None,
|
||||
}
|
||||
|
||||
# Set up custom music players for platforms that support them.
|
||||
@ -143,9 +134,11 @@ class MusicSubsystem:
|
||||
# instead of a special case.
|
||||
if self.supports_soundtrack_entry_type('musicFile'):
|
||||
from ba.osmusic import OSMusicPlayer
|
||||
|
||||
self._music_player_type = OSMusicPlayer
|
||||
elif self.supports_soundtrack_entry_type('iTunesPlaylist'):
|
||||
from ba.macmusicapp import MacMusicAppMusicPlayer
|
||||
|
||||
self._music_player_type = MacMusicAppMusicPlayer
|
||||
|
||||
def on_app_launch(self) -> None:
|
||||
@ -156,11 +149,14 @@ class MusicSubsystem:
|
||||
# out than later).
|
||||
try:
|
||||
cfg = _ba.app.config
|
||||
if ('Soundtrack' in cfg and cfg['Soundtrack']
|
||||
not in ['__default__', 'Default Soundtrack']):
|
||||
if 'Soundtrack' in cfg and cfg['Soundtrack'] not in [
|
||||
'__default__',
|
||||
'Default Soundtrack',
|
||||
]:
|
||||
self.get_music_player()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error prepping music-player')
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
@ -185,9 +181,9 @@ class MusicSubsystem:
|
||||
if self._music_player is not None:
|
||||
self._music_player.set_volume(val)
|
||||
|
||||
def set_music_play_mode(self,
|
||||
mode: MusicPlayMode,
|
||||
force_restart: bool = False) -> None:
|
||||
def set_music_play_mode(
|
||||
self, mode: MusicPlayMode, force_restart: bool = False
|
||||
) -> None:
|
||||
"""Sets music play mode; used for soundtrack testing/etc."""
|
||||
old_mode = self._music_mode
|
||||
self._music_mode = mode
|
||||
@ -210,8 +206,10 @@ class MusicSubsystem:
|
||||
if entry_type == 'iTunesPlaylist':
|
||||
return 'Mac' in uas
|
||||
if entry_type in ('musicFile', 'musicFolder'):
|
||||
return ('android' in uas
|
||||
and _ba.android_get_external_files_dir() is not None)
|
||||
return (
|
||||
'android' in uas
|
||||
and _ba.android_get_external_files_dir() is not None
|
||||
)
|
||||
if entry_type == 'default':
|
||||
return True
|
||||
return False
|
||||
@ -228,18 +226,28 @@ class MusicSubsystem:
|
||||
entry_type = 'iTunesPlaylist'
|
||||
|
||||
# For other entries we expect type and name strings in a dict.
|
||||
elif (isinstance(entry, dict) and 'type' in entry
|
||||
and isinstance(entry['type'], str) and 'name' in entry
|
||||
and isinstance(entry['name'], str)):
|
||||
elif (
|
||||
isinstance(entry, dict)
|
||||
and 'type' in entry
|
||||
and isinstance(entry['type'], str)
|
||||
and 'name' in entry
|
||||
and isinstance(entry['name'], str)
|
||||
):
|
||||
entry_type = entry['type']
|
||||
else:
|
||||
raise TypeError('invalid soundtrack entry: ' + str(entry) +
|
||||
' (type ' + str(type(entry)) + ')')
|
||||
raise TypeError(
|
||||
'invalid soundtrack entry: '
|
||||
+ str(entry)
|
||||
+ ' (type '
|
||||
+ str(type(entry))
|
||||
+ ')'
|
||||
)
|
||||
if self.supports_soundtrack_entry_type(entry_type):
|
||||
return entry_type
|
||||
raise ValueError('invalid soundtrack entry:' + str(entry))
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
return 'default'
|
||||
|
||||
@ -254,13 +262,18 @@ class MusicSubsystem:
|
||||
return entry
|
||||
|
||||
# For other entries we expect type and name strings in a dict.
|
||||
if (isinstance(entry, dict) and 'type' in entry
|
||||
and isinstance(entry['type'], str) and 'name' in entry
|
||||
and isinstance(entry['name'], str)):
|
||||
if (
|
||||
isinstance(entry, dict)
|
||||
and 'type' in entry
|
||||
and isinstance(entry['type'], str)
|
||||
and 'name' in entry
|
||||
and isinstance(entry['name'], str)
|
||||
):
|
||||
return entry['name']
|
||||
raise ValueError('invalid soundtrack entry:' + str(entry))
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
return 'default'
|
||||
|
||||
@ -269,11 +282,13 @@ class MusicSubsystem:
|
||||
if _ba.is_os_playing_music():
|
||||
self.do_play_music(None)
|
||||
|
||||
def do_play_music(self,
|
||||
musictype: MusicType | str | None,
|
||||
continuous: bool = False,
|
||||
mode: MusicPlayMode = MusicPlayMode.REGULAR,
|
||||
testsoundtrack: dict[str, Any] | None = None) -> None:
|
||||
def do_play_music(
|
||||
self,
|
||||
musictype: MusicType | str | None,
|
||||
continuous: bool = False,
|
||||
mode: MusicPlayMode = MusicPlayMode.REGULAR,
|
||||
testsoundtrack: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Plays the requested music type/mode.
|
||||
|
||||
For most cases, setmusic() is the proper call to use, which itself
|
||||
@ -378,8 +393,9 @@ class MusicSubsystem:
|
||||
'positional': False,
|
||||
'music': True,
|
||||
'volume': entry.volume * 5.0,
|
||||
'loop': entry.loop
|
||||
})
|
||||
'loop': entry.loop,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
class MusicPlayer:
|
||||
@ -397,11 +413,16 @@ class MusicPlayer:
|
||||
self._volume = 1.0
|
||||
self._actually_playing = False
|
||||
|
||||
def select_entry(self, callback: Callable[[Any], None], current_entry: Any,
|
||||
selection_target_name: str) -> Any:
|
||||
def select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
"""Summons a UI to select a new soundtrack entry."""
|
||||
return self.on_select_entry(callback, current_entry,
|
||||
selection_target_name)
|
||||
return self.on_select_entry(
|
||||
callback, current_entry, selection_target_name
|
||||
)
|
||||
|
||||
def set_volume(self, volume: float) -> None:
|
||||
"""Set player volume (value should be between 0 and 1)."""
|
||||
@ -435,8 +456,12 @@ class MusicPlayer:
|
||||
"""Shutdown music playback completely."""
|
||||
self.on_app_shutdown()
|
||||
|
||||
def on_select_entry(self, callback: Callable[[Any], None],
|
||||
current_entry: Any, selection_target_name: str) -> Any:
|
||||
def on_select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
"""Present a GUI to select an entry.
|
||||
|
||||
The callback should be called with a valid entry or None to
|
||||
@ -464,8 +489,9 @@ class MusicPlayer:
|
||||
self.on_play(self._entry_to_play)
|
||||
self._actually_playing = True
|
||||
else:
|
||||
if self._actually_playing and (self._entry_to_play is None
|
||||
or self._volume <= 0.0):
|
||||
if self._actually_playing and (
|
||||
self._entry_to_play is None or self._volume <= 0.0
|
||||
):
|
||||
self.on_stop()
|
||||
self._actually_playing = False
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ import _ba
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
import socket
|
||||
|
||||
MasterServerCallback = Callable[[None | dict[str, Any]], None]
|
||||
|
||||
# Timeout for standard functions talking to the master-server/etc.
|
||||
@ -61,6 +62,7 @@ class NetworkSubsystem:
|
||||
def get_ip_address_type(addr: str) -> socket.AddressFamily:
|
||||
"""Return socket.AF_INET6 or socket.AF_INET4 for the provided address."""
|
||||
import socket
|
||||
|
||||
socket_type = None
|
||||
|
||||
# First try it as an ipv4 address.
|
||||
@ -84,16 +86,21 @@ def get_ip_address_type(addr: str) -> socket.AddressFamily:
|
||||
|
||||
class MasterServerResponseType(Enum):
|
||||
"""How to interpret responses from the master-server."""
|
||||
|
||||
JSON = 0
|
||||
|
||||
|
||||
class MasterServerCallThread(threading.Thread):
|
||||
"""Thread to communicate with the master-server."""
|
||||
|
||||
def __init__(self, request: str, request_type: str,
|
||||
data: dict[str, Any] | None,
|
||||
callback: MasterServerCallback | None,
|
||||
response_type: MasterServerResponseType):
|
||||
def __init__(
|
||||
self,
|
||||
request: str,
|
||||
request_type: str,
|
||||
data: dict[str, Any] | None,
|
||||
callback: MasterServerCallback | None,
|
||||
response_type: MasterServerResponseType,
|
||||
):
|
||||
super().__init__()
|
||||
self._request = request
|
||||
self._request_type = request_type
|
||||
@ -106,8 +113,7 @@ class MasterServerCallThread(threading.Thread):
|
||||
|
||||
# Save and restore the context we were created from.
|
||||
activity = _ba.getactivity(doraise=False)
|
||||
self._activity = weakref.ref(
|
||||
activity) if activity is not None else None
|
||||
self._activity = weakref.ref(activity) if activity is not None else None
|
||||
|
||||
def _run_callback(self, arg: None | dict[str, Any]) -> None:
|
||||
# If we were created in an activity context and that activity has
|
||||
@ -143,22 +149,31 @@ class MasterServerCallThread(threading.Thread):
|
||||
self._data = utf8_all(self._data)
|
||||
_ba.set_thread_name('BA_ServerCallThread')
|
||||
if self._request_type == 'get':
|
||||
url = (get_master_server_address() + '/' + self._request +
|
||||
'?' + urllib.parse.urlencode(self._data))
|
||||
url = (
|
||||
get_master_server_address()
|
||||
+ '/'
|
||||
+ self._request
|
||||
+ '?'
|
||||
+ urllib.parse.urlencode(self._data)
|
||||
)
|
||||
response = urllib.request.urlopen(
|
||||
urllib.request.Request(
|
||||
url, None, {'User-Agent': _ba.app.user_agent_string}),
|
||||
url, None, {'User-Agent': _ba.app.user_agent_string}
|
||||
),
|
||||
context=_ba.app.net.sslcontext,
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
elif self._request_type == 'post':
|
||||
url = get_master_server_address() + '/' + self._request
|
||||
response = urllib.request.urlopen(
|
||||
urllib.request.Request(
|
||||
url,
|
||||
urllib.parse.urlencode(self._data).encode(),
|
||||
{'User-Agent': _ba.app.user_agent_string}),
|
||||
{'User-Agent': _ba.app.user_agent_string},
|
||||
),
|
||||
context=_ba.app.net.sslcontext,
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS)
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
else:
|
||||
raise TypeError('Invalid request_type: ' + self._request_type)
|
||||
|
||||
@ -180,37 +195,43 @@ class MasterServerCallThread(threading.Thread):
|
||||
|
||||
# Ignore common network errors; note unexpected ones.
|
||||
if not is_urllib_communication_error(exc, url=url):
|
||||
print(f'Error in MasterServerCallThread'
|
||||
f' (url={url},'
|
||||
f' response-type={self._response_type},'
|
||||
f' response-data={response_data}):')
|
||||
print(
|
||||
f'Error in MasterServerCallThread'
|
||||
f' (url={url},'
|
||||
f' response-type={self._response_type},'
|
||||
f' response-data={response_data}):'
|
||||
)
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
|
||||
response_data = None
|
||||
|
||||
if self._callback is not None:
|
||||
_ba.pushcall(Call(self._run_callback, response_data),
|
||||
from_other_thread=True)
|
||||
_ba.pushcall(
|
||||
Call(self._run_callback, response_data), from_other_thread=True
|
||||
)
|
||||
|
||||
|
||||
def master_server_get(
|
||||
request: str,
|
||||
data: dict[str, Any],
|
||||
callback: MasterServerCallback | None = None,
|
||||
response_type: MasterServerResponseType = MasterServerResponseType.JSON
|
||||
response_type: MasterServerResponseType = MasterServerResponseType.JSON,
|
||||
) -> None:
|
||||
"""Make a call to the master server via a http GET."""
|
||||
MasterServerCallThread(request, 'get', data, callback,
|
||||
response_type).start()
|
||||
MasterServerCallThread(
|
||||
request, 'get', data, callback, response_type
|
||||
).start()
|
||||
|
||||
|
||||
def master_server_post(
|
||||
request: str,
|
||||
data: dict[str, Any],
|
||||
callback: MasterServerCallback | None = None,
|
||||
response_type: MasterServerResponseType = MasterServerResponseType.JSON
|
||||
response_type: MasterServerResponseType = MasterServerResponseType.JSON,
|
||||
) -> None:
|
||||
"""Make a call to the master server via a http POST."""
|
||||
MasterServerCallThread(request, 'post', data, callback,
|
||||
response_type).start()
|
||||
MasterServerCallThread(
|
||||
request, 'post', data, callback, response_type
|
||||
).start()
|
||||
|
||||
@ -8,8 +8,11 @@ from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, TypeVar, Generic, cast
|
||||
|
||||
import _ba
|
||||
from ba._error import (SessionPlayerNotFoundError, print_exception,
|
||||
ActorNotFoundError)
|
||||
from ba._error import (
|
||||
SessionPlayerNotFoundError,
|
||||
print_exception,
|
||||
ActorNotFoundError,
|
||||
)
|
||||
from ba._messages import DeathType, DieMessage
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -28,6 +31,7 @@ class PlayerInfo:
|
||||
|
||||
Category: Gameplay Classes
|
||||
"""
|
||||
|
||||
name: str
|
||||
character: str
|
||||
|
||||
@ -38,6 +42,7 @@ class StandLocation:
|
||||
|
||||
Category: Gameplay Classes
|
||||
"""
|
||||
|
||||
position: ba.Vec3
|
||||
angle: float | None = None
|
||||
|
||||
@ -90,7 +95,8 @@ class Player(Generic[TeamType]):
|
||||
f' operator (__eq__) which will break internal'
|
||||
f' logic. Please remove it.\n'
|
||||
f'For dataclasses you can do "dataclass(eq=False)"'
|
||||
f' in the class decorator.')
|
||||
f' in the class decorator.'
|
||||
)
|
||||
|
||||
self.actor = None
|
||||
self.character = ''
|
||||
@ -249,8 +255,9 @@ class Player(Generic[TeamType]):
|
||||
assert not self._expired
|
||||
return self._sessionplayer.get_icon()
|
||||
|
||||
def assigninput(self, inputtype: ba.InputType | tuple[ba.InputType, ...],
|
||||
call: Callable) -> None:
|
||||
def assigninput(
|
||||
self, inputtype: ba.InputType | tuple[ba.InputType, ...], call: Callable
|
||||
) -> None:
|
||||
"""
|
||||
Set the python callable to be run for one or more types of input.
|
||||
"""
|
||||
@ -311,8 +318,9 @@ def playercast(totype: type[PlayerType], player: ba.Player) -> PlayerType:
|
||||
# NOTE: ideally we should have a single playercast() call and use overloads
|
||||
# for the optional variety, but that currently seems to not be working.
|
||||
# See: https://github.com/python/mypy/issues/8800
|
||||
def playercast_o(totype: type[PlayerType],
|
||||
player: ba.Player | None) -> PlayerType | None:
|
||||
def playercast_o(
|
||||
totype: type[PlayerType], player: ba.Player | None
|
||||
) -> PlayerType | None:
|
||||
"""A variant of ba.playercast() for use with optional ba.Player values.
|
||||
|
||||
Category: Gameplay Functions
|
||||
|
||||
@ -15,12 +15,14 @@ if TYPE_CHECKING:
|
||||
PlaylistType = list[dict[str, Any]]
|
||||
|
||||
|
||||
def filter_playlist(playlist: PlaylistType,
|
||||
sessiontype: type[_session.Session],
|
||||
add_resolved_type: bool = False,
|
||||
remove_unowned: bool = True,
|
||||
mark_unowned: bool = False,
|
||||
name: str = '?') -> PlaylistType:
|
||||
def filter_playlist(
|
||||
playlist: PlaylistType,
|
||||
sessiontype: type[_session.Session],
|
||||
add_resolved_type: bool = False,
|
||||
remove_unowned: bool = True,
|
||||
mark_unowned: bool = False,
|
||||
name: str = '?',
|
||||
) -> PlaylistType:
|
||||
"""Return a filtered version of a playlist.
|
||||
|
||||
Strips out or replaces invalid or unowned game types, makes sure all
|
||||
@ -34,6 +36,7 @@ def filter_playlist(playlist: PlaylistType,
|
||||
from ba._store import get_unowned_maps, get_unowned_game_types
|
||||
from ba._general import getclass
|
||||
from ba._gameactivity import GameActivity
|
||||
|
||||
goodlist: list[dict] = []
|
||||
unowned_maps: Sequence[str]
|
||||
if remove_unowned or mark_unowned:
|
||||
@ -56,7 +59,8 @@ def filter_playlist(playlist: PlaylistType,
|
||||
|
||||
# Update old map names to new ones.
|
||||
entry['settings']['map'] = get_filtered_map_name(
|
||||
entry['settings']['map'])
|
||||
entry['settings']['map']
|
||||
)
|
||||
if remove_unowned and entry['settings']['map'] in unowned_maps:
|
||||
continue
|
||||
|
||||
@ -67,60 +71,89 @@ def filter_playlist(playlist: PlaylistType,
|
||||
raise TypeError('invalid entry format')
|
||||
try:
|
||||
# Do some type filters for backwards compat.
|
||||
if entry['type'] in ('Assault.AssaultGame',
|
||||
'Happy_Thoughts.HappyThoughtsGame',
|
||||
'bsAssault.AssaultGame',
|
||||
'bs_assault.AssaultGame'):
|
||||
if entry['type'] in (
|
||||
'Assault.AssaultGame',
|
||||
'Happy_Thoughts.HappyThoughtsGame',
|
||||
'bsAssault.AssaultGame',
|
||||
'bs_assault.AssaultGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.assault.AssaultGame'
|
||||
if entry['type'] in ('King_of_the_Hill.KingOfTheHillGame',
|
||||
'bsKingOfTheHill.KingOfTheHillGame',
|
||||
'bs_king_of_the_hill.KingOfTheHillGame'):
|
||||
if entry['type'] in (
|
||||
'King_of_the_Hill.KingOfTheHillGame',
|
||||
'bsKingOfTheHill.KingOfTheHillGame',
|
||||
'bs_king_of_the_hill.KingOfTheHillGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.kingofthehill.KingOfTheHillGame'
|
||||
if entry['type'] in ('Capture_the_Flag.CTFGame',
|
||||
'bsCaptureTheFlag.CTFGame',
|
||||
'bs_capture_the_flag.CTFGame'):
|
||||
entry['type'] = (
|
||||
'bastd.game.capturetheflag.CaptureTheFlagGame')
|
||||
if entry['type'] in ('Death_Match.DeathMatchGame',
|
||||
'bsDeathMatch.DeathMatchGame',
|
||||
'bs_death_match.DeathMatchGame'):
|
||||
if entry['type'] in (
|
||||
'Capture_the_Flag.CTFGame',
|
||||
'bsCaptureTheFlag.CTFGame',
|
||||
'bs_capture_the_flag.CTFGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.capturetheflag.CaptureTheFlagGame'
|
||||
if entry['type'] in (
|
||||
'Death_Match.DeathMatchGame',
|
||||
'bsDeathMatch.DeathMatchGame',
|
||||
'bs_death_match.DeathMatchGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.deathmatch.DeathMatchGame'
|
||||
if entry['type'] in ('ChosenOne.ChosenOneGame',
|
||||
'bsChosenOne.ChosenOneGame',
|
||||
'bs_chosen_one.ChosenOneGame'):
|
||||
if entry['type'] in (
|
||||
'ChosenOne.ChosenOneGame',
|
||||
'bsChosenOne.ChosenOneGame',
|
||||
'bs_chosen_one.ChosenOneGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.chosenone.ChosenOneGame'
|
||||
if entry['type'] in ('Conquest.Conquest', 'Conquest.ConquestGame',
|
||||
'bsConquest.ConquestGame',
|
||||
'bs_conquest.ConquestGame'):
|
||||
if entry['type'] in (
|
||||
'Conquest.Conquest',
|
||||
'Conquest.ConquestGame',
|
||||
'bsConquest.ConquestGame',
|
||||
'bs_conquest.ConquestGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.conquest.ConquestGame'
|
||||
if entry['type'] in ('Elimination.EliminationGame',
|
||||
'bsElimination.EliminationGame',
|
||||
'bs_elimination.EliminationGame'):
|
||||
if entry['type'] in (
|
||||
'Elimination.EliminationGame',
|
||||
'bsElimination.EliminationGame',
|
||||
'bs_elimination.EliminationGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.elimination.EliminationGame'
|
||||
if entry['type'] in ('Football.FootballGame',
|
||||
'bsFootball.FootballTeamGame',
|
||||
'bs_football.FootballTeamGame'):
|
||||
if entry['type'] in (
|
||||
'Football.FootballGame',
|
||||
'bsFootball.FootballTeamGame',
|
||||
'bs_football.FootballTeamGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.football.FootballTeamGame'
|
||||
if entry['type'] in ('Hockey.HockeyGame', 'bsHockey.HockeyGame',
|
||||
'bs_hockey.HockeyGame'):
|
||||
if entry['type'] in (
|
||||
'Hockey.HockeyGame',
|
||||
'bsHockey.HockeyGame',
|
||||
'bs_hockey.HockeyGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.hockey.HockeyGame'
|
||||
if entry['type'] in ('Keep_Away.KeepAwayGame',
|
||||
'bsKeepAway.KeepAwayGame',
|
||||
'bs_keep_away.KeepAwayGame'):
|
||||
if entry['type'] in (
|
||||
'Keep_Away.KeepAwayGame',
|
||||
'bsKeepAway.KeepAwayGame',
|
||||
'bs_keep_away.KeepAwayGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.keepaway.KeepAwayGame'
|
||||
if entry['type'] in ('Race.RaceGame', 'bsRace.RaceGame',
|
||||
'bs_race.RaceGame'):
|
||||
if entry['type'] in (
|
||||
'Race.RaceGame',
|
||||
'bsRace.RaceGame',
|
||||
'bs_race.RaceGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.race.RaceGame'
|
||||
if entry['type'] in ('bsEasterEggHunt.EasterEggHuntGame',
|
||||
'bs_easter_egg_hunt.EasterEggHuntGame'):
|
||||
if entry['type'] in (
|
||||
'bsEasterEggHunt.EasterEggHuntGame',
|
||||
'bs_easter_egg_hunt.EasterEggHuntGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.easteregghunt.EasterEggHuntGame'
|
||||
if entry['type'] in ('bsMeteorShower.MeteorShowerGame',
|
||||
'bs_meteor_shower.MeteorShowerGame'):
|
||||
if entry['type'] in (
|
||||
'bsMeteorShower.MeteorShowerGame',
|
||||
'bs_meteor_shower.MeteorShowerGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.meteorshower.MeteorShowerGame'
|
||||
if entry['type'] in ('bsTargetPractice.TargetPracticeGame',
|
||||
'bs_target_practice.TargetPracticeGame'):
|
||||
entry['type'] = (
|
||||
'bastd.game.targetpractice.TargetPracticeGame')
|
||||
if entry['type'] in (
|
||||
'bsTargetPractice.TargetPracticeGame',
|
||||
'bs_target_practice.TargetPracticeGame',
|
||||
):
|
||||
entry['type'] = 'bastd.game.targetpractice.TargetPracticeGame'
|
||||
|
||||
gameclass = getclass(entry['type'], GameActivity)
|
||||
|
||||
@ -140,10 +173,12 @@ def filter_playlist(playlist: PlaylistType,
|
||||
entry['settings'][setting.name] = setting.default
|
||||
goodlist.append(entry)
|
||||
except ImportError as exc:
|
||||
logging.warning('Import failed while scanning playlist \'%s\': %s',
|
||||
name, exc)
|
||||
logging.warning(
|
||||
'Import failed while scanning playlist \'%s\': %s', name, exc
|
||||
)
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
return goodlist
|
||||
|
||||
@ -155,123 +190,134 @@ def get_default_free_for_all_playlist() -> PlaylistType:
|
||||
# but filtering translates them properly to the new ones.
|
||||
# (is kinda a handy way to ensure filtering is working).
|
||||
# Eventually should update these though.
|
||||
return [{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Doom Shroom'
|
||||
return [
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Doom Shroom',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Chosen One Gets Gloves': True,
|
||||
'Chosen One Gets Shield': False,
|
||||
'Chosen One Time': 30,
|
||||
'Epic Mode': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face'
|
||||
{
|
||||
'settings': {
|
||||
'Chosen One Gets Gloves': True,
|
||||
'Chosen One Gets Shield': False,
|
||||
'Chosen One Time': 30,
|
||||
'Epic Mode': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face',
|
||||
},
|
||||
'type': 'bs_chosen_one.ChosenOneGame',
|
||||
},
|
||||
'type': 'bs_chosen_one.ChosenOneGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Zigzag'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Zigzag',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'map': 'Rampage'
|
||||
{
|
||||
'settings': {'Epic Mode': False, 'map': 'Rampage'},
|
||||
'type': 'bs_meteor_shower.MeteorShowerGame',
|
||||
},
|
||||
'type': 'bs_meteor_shower.MeteorShowerGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': 1,
|
||||
'Lives Per Player': 1,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 120,
|
||||
'map': 'Tip Top'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': 1,
|
||||
'Lives Per Player': 1,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 120,
|
||||
'map': 'Tip Top',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'The Pad'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'The Pad',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 0.25,
|
||||
'Time Limit': 120,
|
||||
'map': 'Rampage'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 0.25,
|
||||
'Time Limit': 120,
|
||||
'map': 'Rampage',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Bomb Spawning': 1000,
|
||||
'Epic Mode': False,
|
||||
'Laps': 3,
|
||||
'Mine Spawn Interval': 4000,
|
||||
'Mine Spawning': 4000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Big G'
|
||||
{
|
||||
'settings': {
|
||||
'Bomb Spawning': 1000,
|
||||
'Epic Mode': False,
|
||||
'Laps': 3,
|
||||
'Mine Spawn Interval': 4000,
|
||||
'Mine Spawning': 4000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Big G',
|
||||
},
|
||||
'type': 'bs_race.RaceGame',
|
||||
},
|
||||
'type': 'bs_race.RaceGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Happy Thoughts'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Happy Thoughts',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Enable Impact Bombs': 1,
|
||||
'Enable Triple Bombs': False,
|
||||
'Target Count': 2,
|
||||
'map': 'Doom Shroom'
|
||||
{
|
||||
'settings': {
|
||||
'Enable Impact Bombs': 1,
|
||||
'Enable Triple Bombs': False,
|
||||
'Target Count': 2,
|
||||
'map': 'Doom Shroom',
|
||||
},
|
||||
'type': 'bs_target_practice.TargetPracticeGame',
|
||||
},
|
||||
'type': 'bs_target_practice.TargetPracticeGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Step Right Up'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Step Right Up',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 10,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'map': 'Lake Frigid',
|
||||
'settings': {
|
||||
'Bomb Spawning': 0,
|
||||
'Epic Mode': False,
|
||||
'Laps': 6,
|
||||
'Mine Spawning': 2000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Lake Frigid'
|
||||
{
|
||||
'map': 'Lake Frigid',
|
||||
'settings': {
|
||||
'Bomb Spawning': 0,
|
||||
'Epic Mode': False,
|
||||
'Laps': 6,
|
||||
'Mine Spawning': 2000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Lake Frigid',
|
||||
},
|
||||
'type': 'bs_race.RaceGame',
|
||||
},
|
||||
'type': 'bs_race.RaceGame'
|
||||
}]
|
||||
]
|
||||
|
||||
|
||||
def get_default_teams_playlist() -> PlaylistType:
|
||||
@ -281,217 +327,238 @@ def get_default_teams_playlist() -> PlaylistType:
|
||||
# but filtering translates them properly to the new ones.
|
||||
# (is kinda a handy way to ensure filtering is working).
|
||||
# Eventually should update these though.
|
||||
return [{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 600,
|
||||
'map': 'Bridgit'
|
||||
return [
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 600,
|
||||
'map': 'Bridgit',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 600,
|
||||
'map': 'Step Right Up'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 600,
|
||||
'map': 'Step Right Up',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': True,
|
||||
'Time Limit': 600,
|
||||
'map': 'Rampage'
|
||||
{
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': True,
|
||||
'Time Limit': 600,
|
||||
'map': 'Rampage',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Roundabout'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Roundabout',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 1,
|
||||
'Time Limit': 600,
|
||||
'map': 'Hockey Stadium'
|
||||
{
|
||||
'settings': {
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 1,
|
||||
'Time Limit': 600,
|
||||
'map': 'Hockey Stadium',
|
||||
},
|
||||
'type': 'bs_hockey.HockeyGame',
|
||||
},
|
||||
'type': 'bs_hockey.HockeyGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': True,
|
||||
'Lives Per Player': 1,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': False,
|
||||
'Time Limit': 120,
|
||||
'map': 'Tip Top'
|
||||
{
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': True,
|
||||
'Lives Per Player': 1,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': False,
|
||||
'Time Limit': 120,
|
||||
'map': 'Tip Top',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Doom Shroom'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Doom Shroom',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'map': 'Rampage'
|
||||
{
|
||||
'settings': {'Epic Mode': False, 'map': 'Rampage'},
|
||||
'type': 'bs_meteor_shower.MeteorShowerGame',
|
||||
},
|
||||
'type': 'bs_meteor_shower.MeteorShowerGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 600,
|
||||
'map': 'Roundabout'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 600,
|
||||
'map': 'Roundabout',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 21,
|
||||
'Time Limit': 600,
|
||||
'map': 'Football Stadium'
|
||||
{
|
||||
'settings': {
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 21,
|
||||
'Time Limit': 600,
|
||||
'map': 'Football Stadium',
|
||||
},
|
||||
'type': 'bs_football.FootballTeamGame',
|
||||
},
|
||||
'type': 'bs_football.FootballTeamGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Respawn Times': 0.25,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 120,
|
||||
'map': 'Bridgit'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Respawn Times': 0.25,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 120,
|
||||
'map': 'Bridgit',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame'
|
||||
}, {
|
||||
'map': 'Doom Shroom',
|
||||
'settings': {
|
||||
'Enable Impact Bombs': 1,
|
||||
'Enable Triple Bombs': False,
|
||||
'Target Count': 2,
|
||||
'map': 'Doom Shroom'
|
||||
{
|
||||
'map': 'Doom Shroom',
|
||||
'settings': {
|
||||
'Enable Impact Bombs': 1,
|
||||
'Enable Triple Bombs': False,
|
||||
'Target Count': 2,
|
||||
'map': 'Doom Shroom',
|
||||
},
|
||||
'type': 'bs_target_practice.TargetPracticeGame',
|
||||
},
|
||||
'type': 'bs_target_practice.TargetPracticeGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Tip Top'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Tip Top',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame',
|
||||
},
|
||||
'type': 'bs_king_of_the_hill.KingOfTheHillGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 300,
|
||||
'map': 'Zigzag'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 300,
|
||||
'map': 'Zigzag',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame',
|
||||
},
|
||||
'type': 'bs_assault.AssaultGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 300,
|
||||
'map': 'Happy Thoughts'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 0,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 3,
|
||||
'Time Limit': 300,
|
||||
'map': 'Happy Thoughts',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Bomb Spawning': 1000,
|
||||
'Epic Mode': True,
|
||||
'Laps': 1,
|
||||
'Mine Spawning': 2000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Big G'
|
||||
{
|
||||
'settings': {
|
||||
'Bomb Spawning': 1000,
|
||||
'Epic Mode': True,
|
||||
'Laps': 1,
|
||||
'Mine Spawning': 2000,
|
||||
'Time Limit': 300,
|
||||
'map': 'Big G',
|
||||
},
|
||||
'type': 'bs_race.RaceGame',
|
||||
},
|
||||
'type': 'bs_race.RaceGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Kills to Win Per Player': 5,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Monkey Face',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame',
|
||||
},
|
||||
'type': 'bs_death_match.DeathMatchGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Lake Frigid'
|
||||
{
|
||||
'settings': {
|
||||
'Hold Time': 30,
|
||||
'Respawn Times': 1.0,
|
||||
'Time Limit': 300,
|
||||
'map': 'Lake Frigid',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame',
|
||||
},
|
||||
'type': 'bs_keep_away.KeepAwayGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 300,
|
||||
'map': 'Tip Top'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': False,
|
||||
'Flag Idle Return Time': 30,
|
||||
'Flag Touch Return Time': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Score to Win': 2,
|
||||
'Time Limit': 300,
|
||||
'map': 'Tip Top',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame',
|
||||
},
|
||||
'type': 'bs_capture_the_flag.CTFGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': False,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle'
|
||||
{
|
||||
'settings': {
|
||||
'Balance Total Lives': False,
|
||||
'Epic Mode': False,
|
||||
'Lives Per Player': 3,
|
||||
'Respawn Times': 1.0,
|
||||
'Solo Mode': False,
|
||||
'Time Limit': 300,
|
||||
'map': 'Crag Castle',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame',
|
||||
},
|
||||
'type': 'bs_elimination.EliminationGame'
|
||||
}, {
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Respawn Times': 0.25,
|
||||
'Time Limit': 120,
|
||||
'map': 'Zigzag'
|
||||
{
|
||||
'settings': {
|
||||
'Epic Mode': True,
|
||||
'Respawn Times': 0.25,
|
||||
'Time Limit': 120,
|
||||
'map': 'Zigzag',
|
||||
},
|
||||
'type': 'bs_conquest.ConquestGame',
|
||||
},
|
||||
'type': 'bs_conquest.ConquestGame'
|
||||
}]
|
||||
]
|
||||
|
||||
@ -42,9 +42,12 @@ class PluginSubsystem:
|
||||
# Create a potential-plugin for each class we found in the scan.
|
||||
for class_path in results.exports_of_class(Plugin):
|
||||
plugs.potential_plugins.append(
|
||||
PotentialPlugin(display_name=Lstr(value=class_path),
|
||||
class_path=class_path,
|
||||
available=True))
|
||||
PotentialPlugin(
|
||||
display_name=Lstr(value=class_path),
|
||||
class_path=class_path,
|
||||
available=True,
|
||||
)
|
||||
)
|
||||
if class_path not in plugstates:
|
||||
# Go ahead and enable new plugins by default, but we'll
|
||||
# inform the user that they need to restart to pick them up.
|
||||
@ -59,8 +62,9 @@ class PluginSubsystem:
|
||||
# plugins, so we don't need the message about 'restart to activate'
|
||||
# anymore.
|
||||
if found_new and bool(False):
|
||||
_ba.screenmessage(Lstr(resource='pluginsDetectedText'),
|
||||
color=(0, 1, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='pluginsDetectedText'), color=(0, 1, 0)
|
||||
)
|
||||
_ba.playsound(_ba.getsound('ding'))
|
||||
|
||||
if config_changed:
|
||||
@ -75,6 +79,7 @@ class PluginSubsystem:
|
||||
plugin.on_app_running()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_running()')
|
||||
|
||||
def on_app_pause(self) -> None:
|
||||
@ -84,6 +89,7 @@ class PluginSubsystem:
|
||||
plugin.on_app_pause()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_pause()')
|
||||
|
||||
def on_app_resume(self) -> None:
|
||||
@ -93,6 +99,7 @@ class PluginSubsystem:
|
||||
plugin.on_app_resume()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_resume()')
|
||||
|
||||
def on_app_shutdown(self) -> None:
|
||||
@ -102,6 +109,7 @@ class PluginSubsystem:
|
||||
plugin.on_app_shutdown()
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('Error in plugin on_app_shutdown()')
|
||||
|
||||
def load_plugins(self) -> None:
|
||||
@ -113,8 +121,9 @@ class PluginSubsystem:
|
||||
# in the app config. Its not our job to look at meta stuff here.
|
||||
plugstates: dict[str, dict] = _ba.app.config.get('Plugins', {})
|
||||
assert isinstance(plugstates, dict)
|
||||
plugkeys: list[str] = sorted(key for key, val in plugstates.items()
|
||||
if val.get('enabled', False))
|
||||
plugkeys: list[str] = sorted(
|
||||
key for key, val in plugstates.items() if val.get('enabled', False)
|
||||
)
|
||||
disappeared_plugs: set[str] = set()
|
||||
for plugkey in plugkeys:
|
||||
try:
|
||||
@ -124,10 +133,13 @@ class PluginSubsystem:
|
||||
continue
|
||||
except Exception as exc:
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='pluginClassLoadErrorText',
|
||||
subs=[('${PLUGIN}', plugkey),
|
||||
('${ERROR}', str(exc))]),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='pluginClassLoadErrorText',
|
||||
subs=[('${PLUGIN}', plugkey), ('${ERROR}', str(exc))],
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
logging.exception("Error loading plugin class '%s'", plugkey)
|
||||
continue
|
||||
try:
|
||||
@ -136,11 +148,15 @@ class PluginSubsystem:
|
||||
self.active_plugins[plugkey] = plugin
|
||||
except Exception as exc:
|
||||
from ba import _error
|
||||
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='pluginInitErrorText',
|
||||
subs=[('${PLUGIN}', plugkey),
|
||||
('${ERROR}', str(exc))]),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='pluginInitErrorText',
|
||||
subs=[('${PLUGIN}', plugkey), ('${ERROR}', str(exc))],
|
||||
),
|
||||
color=(1, 0, 0),
|
||||
)
|
||||
_error.print_exception(f"Error initing plugin: '{plugkey}'.")
|
||||
|
||||
# If plugins disappeared, let the user know gently and remove them
|
||||
@ -150,13 +166,18 @@ class PluginSubsystem:
|
||||
if disappeared_plugs:
|
||||
_ba.playsound(_ba.getsound('shieldDown'))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='pluginsRemovedText',
|
||||
subs=[('${NUM}', str(len(disappeared_plugs)))]),
|
||||
Lstr(
|
||||
resource='pluginsRemovedText',
|
||||
subs=[('${NUM}', str(len(disappeared_plugs)))],
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
plugnames = ', '.join(disappeared_plugs)
|
||||
logging.warning('%d plugin(s) no longer found: %s.',
|
||||
len(disappeared_plugs), plugnames)
|
||||
logging.warning(
|
||||
'%d plugin(s) no longer found: %s.',
|
||||
len(disappeared_plugs),
|
||||
plugnames,
|
||||
)
|
||||
for goneplug in disappeared_plugs:
|
||||
del _ba.app.config['Plugins'][goneplug]
|
||||
_ba.app.config.commit()
|
||||
@ -173,6 +194,7 @@ class PotentialPlugin:
|
||||
were previously set to be loaded but which were unable to be
|
||||
for some reason. In that case, 'available' will be set to False.
|
||||
"""
|
||||
|
||||
display_name: ba.Lstr
|
||||
class_path: str
|
||||
available: bool
|
||||
|
||||
@ -45,6 +45,14 @@ class PowerupAcceptMessage:
|
||||
|
||||
def get_default_powerup_distribution() -> Sequence[tuple[str, int]]:
|
||||
"""Standard set of powerups."""
|
||||
return (('triple_bombs', 3), ('ice_bombs', 3), ('punch', 3),
|
||||
('impact_bombs', 3), ('land_mines', 2), ('sticky_bombs', 3),
|
||||
('shield', 2), ('health', 1), ('curse', 1))
|
||||
return (
|
||||
('triple_bombs', 3),
|
||||
('ice_bombs', 3),
|
||||
('punch', 3),
|
||||
('impact_bombs', 3),
|
||||
('land_mines', 2),
|
||||
('sticky_bombs', 3),
|
||||
('shield', 2),
|
||||
('health', 1),
|
||||
('curse', 1),
|
||||
)
|
||||
|
||||
@ -13,11 +13,24 @@ if TYPE_CHECKING:
|
||||
|
||||
# NOTE: player color options are enforced server-side for non-pro accounts
|
||||
# so don't change these or they won't stick...
|
||||
PLAYER_COLORS = [(1, 0.15, 0.15), (0.2, 1, 0.2), (0.1, 0.1, 1), (0.2, 1, 1),
|
||||
(0.5, 0.25, 1.0), (1, 1, 0), (1, 0.5, 0), (1, 0.3, 0.5),
|
||||
(0.1, 0.1, 0.5), (0.4, 0.2, 0.1), (0.1, 0.35, 0.1),
|
||||
(1, 0.8, 0.5), (0.4, 0.05, 0.05), (0.13, 0.13, 0.13),
|
||||
(0.5, 0.5, 0.5), (1, 1, 1)]
|
||||
PLAYER_COLORS = [
|
||||
(1, 0.15, 0.15),
|
||||
(0.2, 1, 0.2),
|
||||
(0.1, 0.1, 1),
|
||||
(0.2, 1, 1),
|
||||
(0.5, 0.25, 1.0),
|
||||
(1, 1, 0),
|
||||
(1, 0.5, 0),
|
||||
(1, 0.3, 0.5),
|
||||
(0.1, 0.1, 0.5),
|
||||
(0.4, 0.2, 0.1),
|
||||
(0.1, 0.35, 0.1),
|
||||
(1, 0.8, 0.5),
|
||||
(0.4, 0.05, 0.05),
|
||||
(0.13, 0.13, 0.13),
|
||||
(0.5, 0.5, 0.5),
|
||||
(1, 1, 1),
|
||||
]
|
||||
|
||||
|
||||
def get_player_colors() -> list[tuple[float, float, float]]:
|
||||
@ -49,8 +62,7 @@ def get_player_profile_icon(profilename: str) -> str:
|
||||
|
||||
|
||||
def get_player_profile_colors(
|
||||
profilename: str | None,
|
||||
profiles: dict[str, dict[str, Any]] | None = None
|
||||
profilename: str | None, profiles: dict[str, dict[str, Any]] | None = None
|
||||
) -> tuple[tuple[float, float, float], tuple[float, float, float]]:
|
||||
"""Given a profile, return colors for them."""
|
||||
appconfig = _ba.app.config
|
||||
@ -83,11 +95,13 @@ def get_player_profile_colors(
|
||||
if profilename is None:
|
||||
# Last 2 are grey and white; ignore those or we
|
||||
# get lots of old-looking players.
|
||||
highlight = PLAYER_COLORS[random.randrange(
|
||||
len(PLAYER_COLORS) - 2)]
|
||||
highlight = PLAYER_COLORS[
|
||||
random.randrange(len(PLAYER_COLORS) - 2)
|
||||
]
|
||||
else:
|
||||
highlight = PLAYER_COLORS[sum(ord(c) + 1
|
||||
for c in profilename) %
|
||||
(len(PLAYER_COLORS) - 2)]
|
||||
highlight = PLAYER_COLORS[
|
||||
sum(ord(c) + 1 for c in profilename)
|
||||
% (len(PLAYER_COLORS) - 2)
|
||||
]
|
||||
|
||||
return color, highlight
|
||||
|
||||
@ -18,6 +18,7 @@ class ScoreType(Enum):
|
||||
|
||||
Category: **Enums**
|
||||
"""
|
||||
|
||||
SECONDS = 's'
|
||||
MILLISECONDS = 'ms'
|
||||
POINTS = 'p'
|
||||
|
||||
@ -9,13 +9,18 @@ import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from efro.terminal import Clr
|
||||
from bacommon.servermanager import (ServerCommand, StartServerModeCommand,
|
||||
ShutdownCommand, ShutdownReason,
|
||||
ChatMessageCommand, ScreenMessageCommand,
|
||||
ClientListCommand, KickCommand)
|
||||
from bacommon.servermanager import (
|
||||
ServerCommand,
|
||||
StartServerModeCommand,
|
||||
ShutdownCommand,
|
||||
ShutdownReason,
|
||||
ChatMessageCommand,
|
||||
ScreenMessageCommand,
|
||||
ClientListCommand,
|
||||
KickCommand,
|
||||
)
|
||||
import _ba
|
||||
from ba._internal import (add_transaction, run_transactions,
|
||||
get_v1_account_state)
|
||||
from ba._internal import add_transaction, run_transactions, get_v1_account_state
|
||||
from ba._generated.enums import TimeType
|
||||
from ba._freeforallsession import FreeForAllSession
|
||||
from ba._dualteamsession import DualTeamSession
|
||||
@ -31,6 +36,7 @@ if TYPE_CHECKING:
|
||||
def _cmd(command_data: bytes) -> None:
|
||||
"""Handle commands coming in from our server manager parent process."""
|
||||
import pickle
|
||||
|
||||
command = pickle.loads(command_data)
|
||||
assert isinstance(command, ServerCommand)
|
||||
|
||||
@ -41,8 +47,9 @@ def _cmd(command_data: bytes) -> None:
|
||||
|
||||
if isinstance(command, ShutdownCommand):
|
||||
assert _ba.app.server is not None
|
||||
_ba.app.server.shutdown(reason=command.reason,
|
||||
immediate=command.immediate)
|
||||
_ba.app.server.shutdown(
|
||||
reason=command.reason, immediate=command.immediate
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(command, ChatMessageCommand):
|
||||
@ -56,10 +63,12 @@ def _cmd(command_data: bytes) -> None:
|
||||
# Note: we have to do transient messages if
|
||||
# clients is specified, so they won't show up
|
||||
# in replays.
|
||||
_ba.screenmessage(command.message,
|
||||
color=command.color,
|
||||
clients=command.clients,
|
||||
transient=command.clients is not None)
|
||||
_ba.screenmessage(
|
||||
command.message,
|
||||
color=command.color,
|
||||
clients=command.clients,
|
||||
transient=command.clients is not None,
|
||||
)
|
||||
return
|
||||
|
||||
if isinstance(command, ClientListCommand):
|
||||
@ -69,12 +78,15 @@ def _cmd(command_data: bytes) -> None:
|
||||
|
||||
if isinstance(command, KickCommand):
|
||||
assert _ba.app.server is not None
|
||||
_ba.app.server.kick(client_id=command.client_id,
|
||||
ban_time=command.ban_time)
|
||||
_ba.app.server.kick(
|
||||
client_id=command.client_id, ban_time=command.ban_time
|
||||
)
|
||||
return
|
||||
|
||||
print(f'{Clr.SRED}ERROR: server process'
|
||||
f' got unknown command: {type(command)}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.SRED}ERROR: server process'
|
||||
f' got unknown command: {type(command)}{Clr.RST}'
|
||||
)
|
||||
|
||||
|
||||
class ServerController:
|
||||
@ -105,23 +117,28 @@ class ServerController:
|
||||
# account sign-in or fetching playlists; this will kick off the
|
||||
# session once done.
|
||||
with _ba.Context('ui'):
|
||||
self._prep_timer = _ba.Timer(0.25,
|
||||
self._prepare_to_serve,
|
||||
timetype=TimeType.REAL,
|
||||
repeat=True)
|
||||
self._prep_timer = _ba.Timer(
|
||||
0.25,
|
||||
self._prepare_to_serve,
|
||||
timetype=TimeType.REAL,
|
||||
repeat=True,
|
||||
)
|
||||
|
||||
def print_client_list(self) -> None:
|
||||
"""Print info about all connected clients."""
|
||||
import json
|
||||
|
||||
roster = _ba.get_game_roster()
|
||||
title1 = 'Client ID'
|
||||
title2 = 'Account Name'
|
||||
title3 = 'Players'
|
||||
col1 = 10
|
||||
col2 = 16
|
||||
out = (f'{Clr.BLD}'
|
||||
f'{title1:<{col1}} {title2:<{col2}} {title3}'
|
||||
f'{Clr.RST}')
|
||||
out = (
|
||||
f'{Clr.BLD}'
|
||||
f'{title1:<{col1}} {title2:<{col2}} {title3}'
|
||||
f'{Clr.RST}'
|
||||
)
|
||||
for client in roster:
|
||||
if client['client_id'] == -1:
|
||||
continue
|
||||
@ -153,9 +170,11 @@ class ServerController:
|
||||
print(f'{Clr.SBLU}Immediate shutdown initiated.{Clr.RST}')
|
||||
self._execute_shutdown()
|
||||
else:
|
||||
print(f'{Clr.SBLU}Shutdown initiated;'
|
||||
f' server process will exit at the next clean opportunity.'
|
||||
f'{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.SBLU}Shutdown initiated;'
|
||||
f' server process will exit at the next clean opportunity.'
|
||||
f'{Clr.RST}'
|
||||
)
|
||||
|
||||
def handle_transition(self) -> bool:
|
||||
"""Handle transitioning to a new ba.Session or quitting the app.
|
||||
@ -172,37 +191,45 @@ class ServerController:
|
||||
|
||||
def _execute_shutdown(self) -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
if self._executing_shutdown:
|
||||
return
|
||||
self._executing_shutdown = True
|
||||
timestrval = time.strftime('%c')
|
||||
if self._shutdown_reason is ShutdownReason.RESTARTING:
|
||||
_ba.screenmessage(Lstr(resource='internal.serverRestartingText'),
|
||||
color=(1, 0.5, 0.0))
|
||||
print(f'{Clr.SBLU}Exiting for server-restart'
|
||||
f' at {timestrval}.{Clr.RST}')
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.serverRestartingText'),
|
||||
color=(1, 0.5, 0.0),
|
||||
)
|
||||
print(
|
||||
f'{Clr.SBLU}Exiting for server-restart'
|
||||
f' at {timestrval}.{Clr.RST}'
|
||||
)
|
||||
else:
|
||||
_ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'),
|
||||
color=(1, 0.5, 0.0))
|
||||
print(f'{Clr.SBLU}Exiting for server-shutdown'
|
||||
f' at {timestrval}.{Clr.RST}')
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='internal.serverShuttingDownText'),
|
||||
color=(1, 0.5, 0.0),
|
||||
)
|
||||
print(
|
||||
f'{Clr.SBLU}Exiting for server-shutdown'
|
||||
f' at {timestrval}.{Clr.RST}'
|
||||
)
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
|
||||
|
||||
def _run_access_check(self) -> None:
|
||||
"""Check with the master server to see if we're likely joinable."""
|
||||
from ba._net import master_server_get
|
||||
|
||||
master_server_get(
|
||||
'bsAccessCheck',
|
||||
{
|
||||
'port': _ba.get_game_port(),
|
||||
'b': _ba.app.build_number
|
||||
},
|
||||
{'port': _ba.get_game_port(), 'b': _ba.app.build_number},
|
||||
callback=self._access_check_response,
|
||||
)
|
||||
|
||||
def _access_check_response(self, data: dict[str, Any] | None) -> None:
|
||||
import os
|
||||
|
||||
if data is None:
|
||||
print('error on UDP port access check (internet down?)')
|
||||
else:
|
||||
@ -216,17 +243,22 @@ class ServerController:
|
||||
addrstr = ''
|
||||
poststr = (
|
||||
'\nSet environment variable BA_ACCESS_CHECK_VERBOSE=1'
|
||||
' for more info.')
|
||||
' for more info.'
|
||||
)
|
||||
if data['accessible']:
|
||||
print(f'{Clr.SBLU}Master server access check of{addrstr}'
|
||||
f' udp port {port} succeeded.\n'
|
||||
f'Your server appears to be'
|
||||
f' joinable from the internet.{poststr}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.SBLU}Master server access check of{addrstr}'
|
||||
f' udp port {port} succeeded.\n'
|
||||
f'Your server appears to be'
|
||||
f' joinable from the internet.{poststr}{Clr.RST}'
|
||||
)
|
||||
else:
|
||||
print(f'{Clr.SRED}Master server access check of{addrstr}'
|
||||
f' udp port {port} failed.\n'
|
||||
f'Your server does not appear to be'
|
||||
f' joinable from the internet.{poststr}{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.SRED}Master server access check of{addrstr}'
|
||||
f' udp port {port} failed.\n'
|
||||
f'Your server does not appear to be'
|
||||
f' joinable from the internet.{poststr}{Clr.RST}'
|
||||
)
|
||||
|
||||
def _prepare_to_serve(self) -> None:
|
||||
"""Run in a timer to do prep before beginning to serve."""
|
||||
@ -248,15 +280,18 @@ class ServerController:
|
||||
can_launch = True
|
||||
else:
|
||||
if not self._playlist_fetch_sent_request:
|
||||
print(f'{Clr.SBLU}Requesting shared-playlist'
|
||||
f' {self._config.playlist_code}...{Clr.RST}')
|
||||
print(
|
||||
f'{Clr.SBLU}Requesting shared-playlist'
|
||||
f' {self._config.playlist_code}...{Clr.RST}'
|
||||
)
|
||||
add_transaction(
|
||||
{
|
||||
'type': 'IMPORT_PLAYLIST',
|
||||
'code': str(self._config.playlist_code),
|
||||
'overwrite': True
|
||||
'overwrite': True,
|
||||
},
|
||||
callback=self._on_playlist_fetch_response)
|
||||
callback=self._on_playlist_fetch_response,
|
||||
)
|
||||
run_transactions()
|
||||
self._playlist_fetch_sent_request = True
|
||||
|
||||
@ -278,13 +313,17 @@ class ServerController:
|
||||
|
||||
# Once we get here, simply modify our config to use this playlist.
|
||||
typename = (
|
||||
'teams' if result['playlistType'] == 'Team Tournament' else
|
||||
'ffa' if result['playlistType'] == 'Free-for-All' else '??')
|
||||
'teams'
|
||||
if result['playlistType'] == 'Team Tournament'
|
||||
else 'ffa'
|
||||
if result['playlistType'] == 'Free-for-All'
|
||||
else '??'
|
||||
)
|
||||
plistname = result['playlistName']
|
||||
print(f'{Clr.SBLU}Got playlist: "{plistname}" ({typename}).{Clr.RST}')
|
||||
self._playlist_fetch_got_response = True
|
||||
self._config.session_type = typename
|
||||
self._playlist_name = (result['playlistName'])
|
||||
self._playlist_name = result['playlistName']
|
||||
|
||||
def _get_session_type(self) -> type[ba.Session]:
|
||||
# Convert string session type to the class.
|
||||
@ -296,7 +335,8 @@ class ServerController:
|
||||
if self._config.session_type == 'coop':
|
||||
return CoopSession
|
||||
raise RuntimeError(
|
||||
f'Invalid session_type: "{self._config.session_type}"')
|
||||
f'Invalid session_type: "{self._config.session_type}"'
|
||||
)
|
||||
|
||||
def _launch_server_session(self) -> None:
|
||||
"""Kick off a host-session based on the current server config."""
|
||||
@ -306,13 +346,17 @@ class ServerController:
|
||||
sessiontype = self._get_session_type()
|
||||
|
||||
if get_v1_account_state() != 'signed_in':
|
||||
print('WARNING: launch_server_session() expects to run '
|
||||
'with a signed in server account')
|
||||
print(
|
||||
'WARNING: launch_server_session() expects to run '
|
||||
'with a signed in server account'
|
||||
)
|
||||
|
||||
# If we didn't fetch a playlist but there's an inline one in the
|
||||
# server-config, pull it in to the game config and use it.
|
||||
if (self._config.playlist_code is None
|
||||
and self._config.playlist_inline is not None):
|
||||
if (
|
||||
self._config.playlist_code is None
|
||||
and self._config.playlist_inline is not None
|
||||
):
|
||||
self._playlist_name = 'ServerModePlaylist'
|
||||
if sessiontype is FreeForAllSession:
|
||||
ptypename = 'Free-for-All'
|
||||
@ -325,12 +369,14 @@ class ServerController:
|
||||
|
||||
# Need to add this in a transaction instead of just setting
|
||||
# it directly or it will get overwritten by the master-server.
|
||||
add_transaction({
|
||||
'type': 'ADD_PLAYLIST',
|
||||
'playlistType': ptypename,
|
||||
'playlistName': self._playlist_name,
|
||||
'playlist': self._config.playlist_inline
|
||||
})
|
||||
add_transaction(
|
||||
{
|
||||
'type': 'ADD_PLAYLIST',
|
||||
'playlistType': ptypename,
|
||||
'playlistName': self._playlist_name,
|
||||
'playlist': self._config.playlist_inline,
|
||||
}
|
||||
)
|
||||
run_transactions()
|
||||
|
||||
if self._first_run:
|
||||
@ -338,17 +384,20 @@ class ServerController:
|
||||
startupmsg = (
|
||||
f'{Clr.BLD}{Clr.BLU}{_ba.appnameupper()} {app.version}'
|
||||
f' ({app.build_number})'
|
||||
f' entering server-mode {curtimestr}{Clr.RST}')
|
||||
f' entering server-mode {curtimestr}{Clr.RST}'
|
||||
)
|
||||
logging.info(startupmsg)
|
||||
|
||||
if sessiontype is 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 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 CoopSession:
|
||||
app.coop_session_args = {
|
||||
'campaign': self._config.coop_campaign,
|
||||
@ -363,7 +412,8 @@ class ServerController:
|
||||
_ba.set_authenticate_clients(self._config.authenticate_clients)
|
||||
|
||||
_ba.set_enable_default_kick_voting(
|
||||
self._config.enable_default_kick_voting)
|
||||
self._config.enable_default_kick_voting
|
||||
)
|
||||
_ba.set_admins(self._config.admins)
|
||||
|
||||
# Call set-enabled last (will push state to the cloud).
|
||||
@ -376,10 +426,13 @@ class ServerController:
|
||||
if self._config.stress_test_players is not None:
|
||||
# Special case: run a stress test.
|
||||
from ba.internal import run_stress_test
|
||||
run_stress_test(playlist_type='Random',
|
||||
playlist_name='__default__',
|
||||
player_count=self._config.stress_test_players,
|
||||
round_duration=30)
|
||||
|
||||
run_stress_test(
|
||||
playlist_type='Random',
|
||||
playlist_name='__default__',
|
||||
player_count=self._config.stress_test_players,
|
||||
round_duration=30,
|
||||
)
|
||||
else:
|
||||
_ba.new_host_session(sessiontype)
|
||||
|
||||
|
||||
@ -70,12 +70,14 @@ class Session:
|
||||
"""All the ba.SessionTeams in the Session. Most things should use the
|
||||
list of ba.Team-s in ba.Activity; not this."""
|
||||
|
||||
def __init__(self,
|
||||
depsets: Sequence[ba.DependencySet],
|
||||
team_names: Sequence[str] | None = None,
|
||||
team_colors: Sequence[Sequence[float]] | None = None,
|
||||
min_players: int = 1,
|
||||
max_players: int = 8):
|
||||
def __init__(
|
||||
self,
|
||||
depsets: Sequence[ba.DependencySet],
|
||||
team_names: Sequence[str] | None = None,
|
||||
team_colors: Sequence[Sequence[float]] | None = None,
|
||||
min_players: int = 1,
|
||||
max_players: int = 8,
|
||||
):
|
||||
"""Instantiate a session.
|
||||
|
||||
depsets should be a sequence of successfully resolved ba.DependencySet
|
||||
@ -116,10 +118,12 @@ class Session:
|
||||
|
||||
# Throw a combined exception if we found anything missing.
|
||||
if missing_asset_packages:
|
||||
raise DependencyError([
|
||||
Dependency(AssetPackage, set_id)
|
||||
for set_id in missing_asset_packages
|
||||
])
|
||||
raise DependencyError(
|
||||
[
|
||||
Dependency(AssetPackage, set_id)
|
||||
for set_id in missing_asset_packages
|
||||
]
|
||||
)
|
||||
|
||||
# Ok; looks like our dependencies check out.
|
||||
# Now give the engine a list of asset-set-ids to pass along to clients.
|
||||
@ -152,27 +156,33 @@ class Session:
|
||||
self._wants_to_end = False
|
||||
self._ending = False
|
||||
self._activity_should_end_immediately = False
|
||||
self._activity_should_end_immediately_results: (ba.GameResults
|
||||
| None) = None
|
||||
self._activity_should_end_immediately_results: (
|
||||
ba.GameResults | None
|
||||
) = None
|
||||
self._activity_should_end_immediately_delay = 0.0
|
||||
|
||||
# Create static teams if we're using them.
|
||||
if self.use_teams:
|
||||
if team_names is None:
|
||||
raise RuntimeError(
|
||||
'use_teams is True but team_names not provided.')
|
||||
'use_teams is True but team_names not provided.'
|
||||
)
|
||||
if team_colors is None:
|
||||
raise RuntimeError(
|
||||
'use_teams is True but team_colors not provided.')
|
||||
'use_teams is True but team_colors not provided.'
|
||||
)
|
||||
if len(team_colors) != len(team_names):
|
||||
raise RuntimeError(f'Got {len(team_names)} team_names'
|
||||
f' and {len(team_colors)} team_colors;'
|
||||
f' these numbers must match.')
|
||||
raise RuntimeError(
|
||||
f'Got {len(team_names)} team_names'
|
||||
f' and {len(team_colors)} team_colors;'
|
||||
f' these numbers must match.'
|
||||
)
|
||||
for i, color in enumerate(team_colors):
|
||||
team = SessionTeam(team_id=self._next_team_id,
|
||||
name=GameActivity.get_team_display_string(
|
||||
team_names[i]),
|
||||
color=color)
|
||||
team = SessionTeam(
|
||||
team_id=self._next_team_id,
|
||||
name=GameActivity.get_team_display_string(team_names[i]),
|
||||
color=color,
|
||||
)
|
||||
self.sessionteams.append(team)
|
||||
self._next_team_id += 1
|
||||
try:
|
||||
@ -218,12 +228,15 @@ class Session:
|
||||
# Print a rejection message *only* to the client trying to
|
||||
# join (prevents spamming everyone else in the game).
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='playerLimitReachedText',
|
||||
subs=[('${COUNT}',
|
||||
str(self.max_players))]),
|
||||
color=(0.8, 0.0, 0.0),
|
||||
clients=[player.inputdevice.client_id],
|
||||
transient=True)
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='playerLimitReachedText',
|
||||
subs=[('${COUNT}', str(self.max_players))],
|
||||
),
|
||||
color=(0.8, 0.0, 0.0),
|
||||
clients=[player.inputdevice.client_id],
|
||||
transient=True,
|
||||
)
|
||||
return False
|
||||
|
||||
_ba.playsound(_ba.getsound('dripity'))
|
||||
@ -233,8 +246,10 @@ class Session:
|
||||
"""Called when a previously-accepted ba.SessionPlayer leaves."""
|
||||
|
||||
if sessionplayer not in self.sessionplayers:
|
||||
print('ERROR: Session.on_player_leave called'
|
||||
' for player not in our list.')
|
||||
print(
|
||||
'ERROR: Session.on_player_leave called'
|
||||
' for player not in our list.'
|
||||
)
|
||||
return
|
||||
|
||||
_ba.playsound(_ba.getsound('playerLeft'))
|
||||
@ -256,15 +271,20 @@ class Session:
|
||||
assert sessionteam is not None
|
||||
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='playerLeftText',
|
||||
subs=[('${PLAYER}', sessionplayer.getname(full=True))]))
|
||||
Lstr(
|
||||
resource='playerLeftText',
|
||||
subs=[('${PLAYER}', sessionplayer.getname(full=True))],
|
||||
)
|
||||
)
|
||||
|
||||
# Remove them from their SessionTeam.
|
||||
if sessionplayer in sessionteam.players:
|
||||
sessionteam.players.remove(sessionplayer)
|
||||
else:
|
||||
print('SessionPlayer not found in SessionTeam'
|
||||
' in on_player_leave.')
|
||||
print(
|
||||
'SessionPlayer not found in SessionTeam'
|
||||
' in on_player_leave.'
|
||||
)
|
||||
|
||||
# Grab their activity-specific player instance.
|
||||
player = sessionplayer.activityplayer
|
||||
@ -284,8 +304,9 @@ class Session:
|
||||
# Now remove them from the session list.
|
||||
self.sessionplayers.remove(sessionplayer)
|
||||
|
||||
def _remove_player_team(self, sessionteam: ba.SessionTeam,
|
||||
activity: ba.Activity | None) -> None:
|
||||
def _remove_player_team(
|
||||
self, sessionteam: ba.SessionTeam, activity: ba.Activity | None
|
||||
) -> None:
|
||||
"""Remove the player-specific team in non-teams mode."""
|
||||
|
||||
# They should have been the only one on their team.
|
||||
@ -306,14 +327,17 @@ class Session:
|
||||
self.on_team_leave(sessionteam)
|
||||
except Exception:
|
||||
print_exception(
|
||||
f'Error in on_team_leave for Session {self}.')
|
||||
f'Error in on_team_leave for Session {self}.'
|
||||
)
|
||||
else:
|
||||
print('Team no in Session teams in on_player_leave.')
|
||||
try:
|
||||
sessionteam.leave()
|
||||
except Exception:
|
||||
print_exception(f'Error clearing sessiondata'
|
||||
f' for team {sessionteam} in session {self}.')
|
||||
print_exception(
|
||||
f'Error clearing sessiondata'
|
||||
f' for team {sessionteam} in session {self}.'
|
||||
)
|
||||
|
||||
def end(self) -> None:
|
||||
"""Initiates an end to the session and a return to the main menu.
|
||||
@ -329,17 +353,20 @@ class Session:
|
||||
"""(internal)"""
|
||||
from ba._activitytypes import EndSessionActivity
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
with _ba.Context(self):
|
||||
curtime = _ba.time(TimeType.REAL)
|
||||
if self._ending:
|
||||
# Ignore repeats unless its been a while.
|
||||
assert self._launch_end_session_activity_time is not None
|
||||
since_last = (curtime - self._launch_end_session_activity_time)
|
||||
since_last = curtime - self._launch_end_session_activity_time
|
||||
if since_last < 30.0:
|
||||
return
|
||||
print_error(
|
||||
'_launch_end_session_activity called twice (since_last=' +
|
||||
str(since_last) + ')')
|
||||
'_launch_end_session_activity called twice (since_last='
|
||||
+ str(since_last)
|
||||
+ ')'
|
||||
)
|
||||
self._launch_end_session_activity_time = curtime
|
||||
self.setactivity(_ba.newactivity(EndSessionActivity))
|
||||
self._wants_to_end = False
|
||||
@ -351,8 +378,9 @@ class Session:
|
||||
def on_team_leave(self, team: ba.SessionTeam) -> None:
|
||||
"""Called when a ba.Team is leaving the session."""
|
||||
|
||||
def end_activity(self, activity: ba.Activity, results: Any, delay: float,
|
||||
force: bool) -> None:
|
||||
def end_activity(
|
||||
self, activity: ba.Activity, results: Any, delay: float, force: bool
|
||||
) -> None:
|
||||
"""Commence shutdown of a ba.Activity (if not already occurring).
|
||||
|
||||
'delay' is the time delay before the Activity actually ends
|
||||
@ -385,7 +413,8 @@ class Session:
|
||||
self._activity_end_timer = _ba.Timer(
|
||||
delay,
|
||||
Call(self._complete_end_activity, activity, results),
|
||||
timetype=TimeType.BASE)
|
||||
timetype=TimeType.BASE,
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
"""General message handling; can be passed any message object."""
|
||||
@ -407,7 +436,6 @@ class Session:
|
||||
return None
|
||||
|
||||
class _SetActivityScopedLock:
|
||||
|
||||
def __init__(self, session: ba.Session) -> None:
|
||||
self._session = session
|
||||
if session._in_set_activity:
|
||||
@ -442,12 +470,16 @@ class Session:
|
||||
return
|
||||
|
||||
if self._next_activity is not None:
|
||||
raise RuntimeError('Activity switch already in progress (to ' +
|
||||
str(self._next_activity) + ')')
|
||||
raise RuntimeError(
|
||||
'Activity switch already in progress (to '
|
||||
+ str(self._next_activity)
|
||||
+ ')'
|
||||
)
|
||||
|
||||
prev_activity = self._activity_retained
|
||||
prev_globals = (prev_activity.globalsnode
|
||||
if prev_activity is not None else None)
|
||||
prev_globals = (
|
||||
prev_activity.globalsnode if prev_activity is not None else None
|
||||
)
|
||||
|
||||
# Let the activity do its thing.
|
||||
activity.transition_in(prev_globals)
|
||||
@ -476,9 +508,11 @@ class Session:
|
||||
# will trigger the next activity to run).
|
||||
if prev_activity is not None:
|
||||
with _ba.Context('ui'):
|
||||
_ba.timer(max(0.0, activity.transition_time),
|
||||
prev_activity.expire,
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
max(0.0, activity.transition_time),
|
||||
prev_activity.expire,
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
self._in_set_activity = False
|
||||
|
||||
def getactivity(self) -> ba.Activity | None:
|
||||
@ -495,15 +529,18 @@ class Session:
|
||||
"""
|
||||
return []
|
||||
|
||||
def _complete_end_activity(self, activity: ba.Activity,
|
||||
results: Any) -> None:
|
||||
def _complete_end_activity(
|
||||
self, activity: ba.Activity, results: Any
|
||||
) -> None:
|
||||
# Run the subclass callback in the session context.
|
||||
try:
|
||||
with _ba.Context(self):
|
||||
self.on_activity_end(activity, results)
|
||||
except Exception:
|
||||
print_exception(f'Error in on_activity_end() for session {self}'
|
||||
f' activity {activity} with results {results}')
|
||||
print_exception(
|
||||
f'Error in on_activity_end() for session {self}'
|
||||
f' activity {activity} with results {results}'
|
||||
)
|
||||
|
||||
def _request_player(self, sessionplayer: ba.SessionPlayer) -> bool:
|
||||
"""Called by the native layer when a player wants to join."""
|
||||
@ -571,7 +608,8 @@ class Session:
|
||||
if self._activity_should_end_immediately:
|
||||
self._activity_retained.end(
|
||||
self._activity_should_end_immediately_results,
|
||||
self._activity_should_end_immediately_delay)
|
||||
self._activity_should_end_immediately_delay,
|
||||
)
|
||||
|
||||
def _on_player_ready(self, chooser: ba.Chooser) -> None:
|
||||
"""Called when a ba.Player has checked themself ready."""
|
||||
@ -601,8 +639,10 @@ class Session:
|
||||
self._complete_end_activity(activity, {})
|
||||
else:
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='notEnoughPlayersText',
|
||||
subs=[('${COUNT}', str(min_players))]),
|
||||
Lstr(
|
||||
resource='notEnoughPlayersText',
|
||||
subs=[('${COUNT}', str(min_players))],
|
||||
),
|
||||
color=(1, 1, 0),
|
||||
)
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
@ -613,7 +653,8 @@ class Session:
|
||||
lobby.remove_chooser(chooser.getplayer())
|
||||
|
||||
def transitioning_out_activity_was_freed(
|
||||
self, can_show_ad_on_death: bool) -> None:
|
||||
self, can_show_ad_on_death: bool
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._apputils import garbage_collect
|
||||
@ -632,10 +673,12 @@ class Session:
|
||||
|
||||
def _add_chosen_player(self, chooser: ba.Chooser) -> ba.SessionPlayer:
|
||||
from ba._team import SessionTeam
|
||||
|
||||
sessionplayer = chooser.getplayer()
|
||||
assert sessionplayer in self.sessionplayers, (
|
||||
'SessionPlayer not found in session '
|
||||
'player-list after chooser selection.')
|
||||
'player-list after chooser selection.'
|
||||
)
|
||||
|
||||
activity = self._activity_weak()
|
||||
assert activity is not None
|
||||
@ -646,20 +689,26 @@ class Session:
|
||||
|
||||
# We can pass it to the current activity if it has already begun
|
||||
# (otherwise it'll get passed once begin is called).
|
||||
pass_to_activity = (activity.has_begun()
|
||||
and not activity.is_joining_activity)
|
||||
pass_to_activity = (
|
||||
activity.has_begun() and not activity.is_joining_activity
|
||||
)
|
||||
|
||||
# However, if we're not allowing mid-game joins, don't actually pass;
|
||||
# just announce the arrival and say they'll partake next round.
|
||||
if pass_to_activity:
|
||||
if not (activity.allow_mid_activity_joins
|
||||
and self.should_allow_mid_activity_joins(activity)):
|
||||
if not (
|
||||
activity.allow_mid_activity_joins
|
||||
and self.should_allow_mid_activity_joins(activity)
|
||||
):
|
||||
pass_to_activity = False
|
||||
with _ba.Context(self):
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='playerDelayedJoinText',
|
||||
subs=[('${PLAYER}',
|
||||
sessionplayer.getname(full=True))]),
|
||||
Lstr(
|
||||
resource='playerDelayedJoinText',
|
||||
subs=[
|
||||
('${PLAYER}', sessionplayer.getname(full=True))
|
||||
],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
|
||||
@ -691,10 +740,12 @@ class Session:
|
||||
|
||||
assert sessionplayer not in sessionteam.players
|
||||
sessionteam.players.append(sessionplayer)
|
||||
sessionplayer.setdata(team=sessionteam,
|
||||
character=chooser.get_character_name(),
|
||||
color=chooser.get_color(),
|
||||
highlight=chooser.get_highlight())
|
||||
sessionplayer.setdata(
|
||||
team=sessionteam,
|
||||
character=chooser.get_character_name(),
|
||||
color=chooser.get_color(),
|
||||
highlight=chooser.get_highlight(),
|
||||
)
|
||||
|
||||
self.stats.register_sessionplayer(sessionplayer)
|
||||
if pass_to_activity:
|
||||
|
||||
@ -28,6 +28,7 @@ class BoolSetting(Setting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
default: bool
|
||||
|
||||
|
||||
@ -37,6 +38,7 @@ class IntSetting(Setting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
default: int
|
||||
min_value: int = 0
|
||||
max_value: int = 9999
|
||||
@ -49,6 +51,7 @@ class FloatSetting(Setting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
default: float
|
||||
min_value: float = 0.0
|
||||
max_value: float = 9999.0
|
||||
@ -61,6 +64,7 @@ class ChoiceSetting(Setting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
choices: list[tuple[str, Any]]
|
||||
|
||||
|
||||
@ -70,6 +74,7 @@ class IntChoiceSetting(ChoiceSetting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
default: int
|
||||
choices: list[tuple[str, int]]
|
||||
|
||||
@ -80,5 +85,6 @@ class FloatChoiceSetting(ChoiceSetting):
|
||||
|
||||
Category: Settings Classes
|
||||
"""
|
||||
|
||||
default: float
|
||||
choices: list[tuple[str, float]]
|
||||
|
||||
@ -9,8 +9,13 @@ from typing import TYPE_CHECKING
|
||||
from dataclasses import dataclass
|
||||
|
||||
import _ba
|
||||
from ba._error import (print_exception, print_error, SessionTeamNotFoundError,
|
||||
SessionPlayerNotFoundError, NotFoundError)
|
||||
from ba._error import (
|
||||
print_exception,
|
||||
print_error,
|
||||
SessionTeamNotFoundError,
|
||||
SessionPlayerNotFoundError,
|
||||
NotFoundError,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import ba
|
||||
@ -37,10 +42,16 @@ class PlayerRecord:
|
||||
still present (stats may be retained for players that leave
|
||||
mid-game)
|
||||
"""
|
||||
|
||||
character: str
|
||||
|
||||
def __init__(self, name: str, name_full: str,
|
||||
sessionplayer: ba.SessionPlayer, stats: ba.Stats):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
name_full: str,
|
||||
sessionplayer: ba.SessionPlayer,
|
||||
stats: ba.Stats,
|
||||
):
|
||||
self.name = name
|
||||
self.name_full = name_full
|
||||
self.score = 0
|
||||
@ -105,8 +116,9 @@ class PlayerRecord:
|
||||
return stats.getactivity()
|
||||
return None
|
||||
|
||||
def associate_with_sessionplayer(self,
|
||||
sessionplayer: ba.SessionPlayer) -> None:
|
||||
def associate_with_sessionplayer(
|
||||
self, sessionplayer: ba.SessionPlayer
|
||||
) -> None:
|
||||
"""Associate this entry with a ba.SessionPlayer."""
|
||||
self._sessionteam = weakref.ref(sessionplayer.sessionteam)
|
||||
self.character = sessionplayer.character
|
||||
@ -129,6 +141,7 @@ class PlayerRecord:
|
||||
# pylint: disable=too-many-statements
|
||||
from ba._language import Lstr
|
||||
from ba._general import Call
|
||||
|
||||
self._multi_kill_count += 1
|
||||
stats = self._stats()
|
||||
assert stats
|
||||
@ -169,16 +182,23 @@ class PlayerRecord:
|
||||
sound = stats.orchestrahitsound4
|
||||
else:
|
||||
score = 100
|
||||
name = Lstr(resource='multiKillText',
|
||||
subs=[('${COUNT}', str(self._multi_kill_count))])
|
||||
name = Lstr(
|
||||
resource='multiKillText',
|
||||
subs=[('${COUNT}', str(self._multi_kill_count))],
|
||||
)
|
||||
color = (1.0, 0.5, 0.0, 1)
|
||||
scale = 1.3
|
||||
delay = 1.0
|
||||
sound = stats.orchestrahitsound4
|
||||
|
||||
def _apply(name2: Lstr, score2: int, showpoints2: bool,
|
||||
color2: tuple[float, float, float, float], scale2: float,
|
||||
sound2: ba.Sound | None) -> None:
|
||||
def _apply(
|
||||
name2: Lstr,
|
||||
score2: int,
|
||||
showpoints2: bool,
|
||||
color2: tuple[float, float, float, float],
|
||||
scale2: float,
|
||||
sound2: ba.Sound | None,
|
||||
) -> None:
|
||||
from bastd.actor.popuptext import PopupText
|
||||
|
||||
# Only award this if they're still alive and we can get
|
||||
@ -194,18 +214,23 @@ class PlayerRecord:
|
||||
return
|
||||
|
||||
# Jitter position a bit since these often come in clusters.
|
||||
our_pos = _ba.Vec3(our_pos[0] + (random.random() - 0.5) * 2.0,
|
||||
our_pos[1] + (random.random() - 0.5) * 2.0,
|
||||
our_pos[2] + (random.random() - 0.5) * 2.0)
|
||||
our_pos = _ba.Vec3(
|
||||
our_pos[0] + (random.random() - 0.5) * 2.0,
|
||||
our_pos[1] + (random.random() - 0.5) * 2.0,
|
||||
our_pos[2] + (random.random() - 0.5) * 2.0,
|
||||
)
|
||||
activity = self.getactivity()
|
||||
if activity is not None:
|
||||
PopupText(Lstr(
|
||||
value=(('+' + str(score2) + ' ') if showpoints2 else '') +
|
||||
'${N}',
|
||||
subs=[('${N}', name2)]),
|
||||
color=color2,
|
||||
scale=scale2,
|
||||
position=our_pos).autoretain()
|
||||
PopupText(
|
||||
Lstr(
|
||||
value=(('+' + str(score2) + ' ') if showpoints2 else '')
|
||||
+ '${N}',
|
||||
subs=[('${N}', name2)],
|
||||
),
|
||||
color=color2,
|
||||
scale=scale2,
|
||||
position=our_pos,
|
||||
).autoretain()
|
||||
if sound2:
|
||||
_ba.playsound(sound2)
|
||||
|
||||
@ -219,7 +244,8 @@ class PlayerRecord:
|
||||
if name is not None:
|
||||
_ba.timer(
|
||||
0.3 + delay,
|
||||
Call(_apply, name, score, showpoints, color, scale, sound))
|
||||
Call(_apply, name, score, showpoints, color, scale, sound),
|
||||
)
|
||||
|
||||
# Keep the tally rollin'...
|
||||
# set a timer for a bit in the future.
|
||||
@ -296,8 +322,9 @@ class Stats:
|
||||
self._player_records[name].associate_with_sessionplayer(player)
|
||||
else:
|
||||
name_full = player.getname(full=True)
|
||||
self._player_records[name] = PlayerRecord(name, name_full, player,
|
||||
self)
|
||||
self._player_records[name] = PlayerRecord(
|
||||
name, name_full, player, self
|
||||
)
|
||||
|
||||
def get_records(self) -> dict[str, ba.PlayerRecord]:
|
||||
"""Get PlayerRecord corresponding to still-existing players."""
|
||||
@ -311,20 +338,22 @@ class Stats:
|
||||
records[record_id] = record
|
||||
return records
|
||||
|
||||
def player_scored(self,
|
||||
player: ba.Player,
|
||||
base_points: int = 1,
|
||||
target: Sequence[float] | None = None,
|
||||
kill: bool = False,
|
||||
victim_player: ba.Player | None = None,
|
||||
scale: float = 1.0,
|
||||
color: Sequence[float] | None = None,
|
||||
title: str | ba.Lstr | None = None,
|
||||
screenmessage: bool = True,
|
||||
display: bool = True,
|
||||
importance: int = 1,
|
||||
showpoints: bool = True,
|
||||
big_message: bool = False) -> int:
|
||||
def player_scored(
|
||||
self,
|
||||
player: ba.Player,
|
||||
base_points: int = 1,
|
||||
target: Sequence[float] | None = None,
|
||||
kill: bool = False,
|
||||
victim_player: ba.Player | None = None,
|
||||
scale: float = 1.0,
|
||||
color: Sequence[float] | None = None,
|
||||
title: str | ba.Lstr | None = None,
|
||||
screenmessage: bool = True,
|
||||
display: bool = True,
|
||||
importance: int = 1,
|
||||
showpoints: bool = True,
|
||||
big_message: bool = False,
|
||||
) -> int:
|
||||
"""Register a score for the player.
|
||||
|
||||
Return value is actual score with multipliers and such factored in.
|
||||
@ -338,6 +367,7 @@ class Stats:
|
||||
from ba import _math
|
||||
from ba._gameactivity import GameActivity
|
||||
from ba._language import Lstr
|
||||
|
||||
del victim_player # Currently unused.
|
||||
name = player.getname()
|
||||
s_player = self._player_records[name]
|
||||
@ -361,9 +391,12 @@ class Stats:
|
||||
if isinstance(activity, GameActivity):
|
||||
name_full = player.getname(full=True, icon=False)
|
||||
activity.show_zoom_message(
|
||||
Lstr(resource='nameScoresText',
|
||||
subs=[('${NAME}', name_full)]),
|
||||
color=_math.normalized_color(player.team.color))
|
||||
Lstr(
|
||||
resource='nameScoresText',
|
||||
subs=[('${NAME}', name_full)],
|
||||
),
|
||||
color=_math.normalized_color(player.team.color),
|
||||
)
|
||||
except Exception:
|
||||
print_exception('error showing big_message')
|
||||
|
||||
@ -376,21 +409,26 @@ class Stats:
|
||||
|
||||
# If display-pos is *way* lower than us, raise it up
|
||||
# (so we can still see scores from dudes that fell off cliffs).
|
||||
display_pos = (target[0], max(target[1], our_pos[1] - 2.0),
|
||||
min(target[2], our_pos[2] + 2.0))
|
||||
display_pos = (
|
||||
target[0],
|
||||
max(target[1], our_pos[1] - 2.0),
|
||||
min(target[2], our_pos[2] + 2.0),
|
||||
)
|
||||
activity = self.getactivity()
|
||||
if activity is not None:
|
||||
if title is not None:
|
||||
sval = Lstr(value='+${A} ${B}',
|
||||
subs=[('${A}', str(points)),
|
||||
('${B}', title)])
|
||||
sval = Lstr(
|
||||
value='+${A} ${B}',
|
||||
subs=[('${A}', str(points)), ('${B}', title)],
|
||||
)
|
||||
else:
|
||||
sval = Lstr(value='+${A}',
|
||||
subs=[('${A}', str(points))])
|
||||
PopupText(sval,
|
||||
color=display_color,
|
||||
scale=1.2 * scale,
|
||||
position=display_pos).autoretain()
|
||||
sval = Lstr(value='+${A}', subs=[('${A}', str(points))])
|
||||
PopupText(
|
||||
sval,
|
||||
color=display_color,
|
||||
scale=1.2 * scale,
|
||||
position=display_pos,
|
||||
).autoretain()
|
||||
|
||||
# Tally kills.
|
||||
if kill:
|
||||
@ -400,11 +438,12 @@ class Stats:
|
||||
# Report non-kill scorings.
|
||||
try:
|
||||
if screenmessage and not kill:
|
||||
_ba.screenmessage(Lstr(resource='nameScoresText',
|
||||
subs=[('${NAME}', name)]),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon())
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='nameScoresText', subs=[('${NAME}', name)]),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon(),
|
||||
)
|
||||
except Exception:
|
||||
print_exception('error announcing score')
|
||||
|
||||
@ -419,12 +458,15 @@ class Stats:
|
||||
|
||||
return points
|
||||
|
||||
def player_was_killed(self,
|
||||
player: ba.Player,
|
||||
killed: bool = False,
|
||||
killer: ba.Player | None = None) -> None:
|
||||
def player_was_killed(
|
||||
self,
|
||||
player: ba.Player,
|
||||
killed: bool = False,
|
||||
killer: ba.Player | None = None,
|
||||
) -> None:
|
||||
"""Should be called when a player is killed."""
|
||||
from ba._language import Lstr
|
||||
|
||||
name = player.getname()
|
||||
prec = self._player_records[name]
|
||||
prec.streak = 0
|
||||
@ -434,33 +476,47 @@ class Stats:
|
||||
try:
|
||||
if killed and _ba.getactivity().announce_player_deaths:
|
||||
if killer is player:
|
||||
_ba.screenmessage(Lstr(resource='nameSuicideText',
|
||||
subs=[('${NAME}', name)]),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon())
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='nameSuicideText', subs=[('${NAME}', name)]
|
||||
),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon(),
|
||||
)
|
||||
elif killer is not None:
|
||||
if killer.team is player.team:
|
||||
_ba.screenmessage(Lstr(resource='nameBetrayedText',
|
||||
subs=[('${NAME}',
|
||||
killer.getname()),
|
||||
('${VICTIM}', name)]),
|
||||
top=True,
|
||||
color=killer.color,
|
||||
image=killer.get_icon())
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='nameBetrayedText',
|
||||
subs=[
|
||||
('${NAME}', killer.getname()),
|
||||
('${VICTIM}', name),
|
||||
],
|
||||
),
|
||||
top=True,
|
||||
color=killer.color,
|
||||
image=killer.get_icon(),
|
||||
)
|
||||
else:
|
||||
_ba.screenmessage(Lstr(resource='nameKilledText',
|
||||
subs=[('${NAME}',
|
||||
killer.getname()),
|
||||
('${VICTIM}', name)]),
|
||||
top=True,
|
||||
color=killer.color,
|
||||
image=killer.get_icon())
|
||||
_ba.screenmessage(
|
||||
Lstr(
|
||||
resource='nameKilledText',
|
||||
subs=[
|
||||
('${NAME}', killer.getname()),
|
||||
('${VICTIM}', name),
|
||||
],
|
||||
),
|
||||
top=True,
|
||||
color=killer.color,
|
||||
image=killer.get_icon(),
|
||||
)
|
||||
else:
|
||||
_ba.screenmessage(Lstr(resource='nameDiedText',
|
||||
subs=[('${NAME}', name)]),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon())
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='nameDiedText', subs=[('${NAME}', name)]),
|
||||
top=True,
|
||||
color=player.color,
|
||||
image=player.get_icon(),
|
||||
)
|
||||
except Exception:
|
||||
print_exception('error announcing kill')
|
||||
|
||||
@ -24,14 +24,17 @@ def get_store_item_name_translated(item_name: str) -> ba.Lstr:
|
||||
# pylint: disable=cyclic-import
|
||||
from ba import _language
|
||||
from ba import _map
|
||||
|
||||
item_info = get_store_item(item_name)
|
||||
if item_name.startswith('characters.'):
|
||||
return _language.Lstr(translate=('characterNames',
|
||||
item_info['character']))
|
||||
return _language.Lstr(
|
||||
translate=('characterNames', item_info['character'])
|
||||
)
|
||||
if item_name in ['upgrades.pro', 'pro']:
|
||||
return _language.Lstr(resource='store.bombSquadProNameText',
|
||||
subs=[('${APP_NAME}',
|
||||
_language.Lstr(resource='titleText'))])
|
||||
return _language.Lstr(
|
||||
resource='store.bombSquadProNameText',
|
||||
subs=[('${APP_NAME}', _language.Lstr(resource='titleText'))],
|
||||
)
|
||||
if item_name.startswith('maps.'):
|
||||
map_type: type[ba.Map] = item_info['map_type']
|
||||
return _map.get_map_display_string(map_type.name)
|
||||
@ -64,6 +67,7 @@ def get_store_items() -> dict[str, dict]:
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._generated.enums import SpecialChar
|
||||
from bastd import maps
|
||||
|
||||
if _ba.app.store_items is None:
|
||||
from bastd.game import ninjafight
|
||||
from bastd.game import meteorshower
|
||||
@ -73,114 +77,64 @@ def get_store_items() -> dict[str, dict]:
|
||||
# IMPORTANT - need to keep this synced with the master server.
|
||||
# (doing so manually for now)
|
||||
_ba.app.store_items = {
|
||||
'characters.kronk': {
|
||||
'character': 'Kronk'
|
||||
},
|
||||
'characters.zoe': {
|
||||
'character': 'Zoe'
|
||||
},
|
||||
'characters.jackmorgan': {
|
||||
'character': 'Jack Morgan'
|
||||
},
|
||||
'characters.mel': {
|
||||
'character': 'Mel'
|
||||
},
|
||||
'characters.snakeshadow': {
|
||||
'character': 'Snake Shadow'
|
||||
},
|
||||
'characters.bones': {
|
||||
'character': 'Bones'
|
||||
},
|
||||
'characters.kronk': {'character': 'Kronk'},
|
||||
'characters.zoe': {'character': 'Zoe'},
|
||||
'characters.jackmorgan': {'character': 'Jack Morgan'},
|
||||
'characters.mel': {'character': 'Mel'},
|
||||
'characters.snakeshadow': {'character': 'Snake Shadow'},
|
||||
'characters.bones': {'character': 'Bones'},
|
||||
'characters.bernard': {
|
||||
'character': 'Bernard',
|
||||
'highlight': (0.6, 0.5, 0.8)
|
||||
},
|
||||
'characters.pixie': {
|
||||
'character': 'Pixel'
|
||||
},
|
||||
'characters.wizard': {
|
||||
'character': 'Grumbledorf'
|
||||
},
|
||||
'characters.frosty': {
|
||||
'character': 'Frosty'
|
||||
},
|
||||
'characters.pascal': {
|
||||
'character': 'Pascal'
|
||||
},
|
||||
'characters.cyborg': {
|
||||
'character': 'B-9000'
|
||||
},
|
||||
'characters.agent': {
|
||||
'character': 'Agent Johnson'
|
||||
},
|
||||
'characters.taobaomascot': {
|
||||
'character': 'Taobao Mascot'
|
||||
},
|
||||
'characters.santa': {
|
||||
'character': 'Santa Claus'
|
||||
},
|
||||
'characters.bunny': {
|
||||
'character': 'Easter Bunny'
|
||||
'highlight': (0.6, 0.5, 0.8),
|
||||
},
|
||||
'characters.pixie': {'character': 'Pixel'},
|
||||
'characters.wizard': {'character': 'Grumbledorf'},
|
||||
'characters.frosty': {'character': 'Frosty'},
|
||||
'characters.pascal': {'character': 'Pascal'},
|
||||
'characters.cyborg': {'character': 'B-9000'},
|
||||
'characters.agent': {'character': 'Agent Johnson'},
|
||||
'characters.taobaomascot': {'character': 'Taobao Mascot'},
|
||||
'characters.santa': {'character': 'Santa Claus'},
|
||||
'characters.bunny': {'character': 'Easter Bunny'},
|
||||
'pro': {},
|
||||
'maps.lake_frigid': {
|
||||
'map_type': maps.LakeFrigid
|
||||
},
|
||||
'maps.lake_frigid': {'map_type': maps.LakeFrigid},
|
||||
'games.ninja_fight': {
|
||||
'gametype': ninjafight.NinjaFightGame,
|
||||
'previewTex': 'courtyardPreview'
|
||||
'previewTex': 'courtyardPreview',
|
||||
},
|
||||
'games.meteor_shower': {
|
||||
'gametype': meteorshower.MeteorShowerGame,
|
||||
'previewTex': 'rampagePreview'
|
||||
'previewTex': 'rampagePreview',
|
||||
},
|
||||
'games.target_practice': {
|
||||
'gametype': targetpractice.TargetPracticeGame,
|
||||
'previewTex': 'doomShroomPreview'
|
||||
'previewTex': 'doomShroomPreview',
|
||||
},
|
||||
'games.easter_egg_hunt': {
|
||||
'gametype': easteregghunt.EasterEggHuntGame,
|
||||
'previewTex': 'towerDPreview'
|
||||
'previewTex': 'towerDPreview',
|
||||
},
|
||||
'icons.flag_us': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_UNITED_STATES)
|
||||
},
|
||||
'icons.flag_mexico': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_MEXICO)
|
||||
},
|
||||
'icons.flag_mexico': {'icon': _ba.charstr(SpecialChar.FLAG_MEXICO)},
|
||||
'icons.flag_germany': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_GERMANY)
|
||||
},
|
||||
'icons.flag_brazil': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_BRAZIL)
|
||||
},
|
||||
'icons.flag_russia': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_RUSSIA)
|
||||
},
|
||||
'icons.flag_china': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_CHINA)
|
||||
},
|
||||
'icons.flag_brazil': {'icon': _ba.charstr(SpecialChar.FLAG_BRAZIL)},
|
||||
'icons.flag_russia': {'icon': _ba.charstr(SpecialChar.FLAG_RUSSIA)},
|
||||
'icons.flag_china': {'icon': _ba.charstr(SpecialChar.FLAG_CHINA)},
|
||||
'icons.flag_uk': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_UNITED_KINGDOM)
|
||||
},
|
||||
'icons.flag_canada': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_CANADA)
|
||||
},
|
||||
'icons.flag_india': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_INDIA)
|
||||
},
|
||||
'icons.flag_japan': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_JAPAN)
|
||||
},
|
||||
'icons.flag_france': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_FRANCE)
|
||||
},
|
||||
'icons.flag_canada': {'icon': _ba.charstr(SpecialChar.FLAG_CANADA)},
|
||||
'icons.flag_india': {'icon': _ba.charstr(SpecialChar.FLAG_INDIA)},
|
||||
'icons.flag_japan': {'icon': _ba.charstr(SpecialChar.FLAG_JAPAN)},
|
||||
'icons.flag_france': {'icon': _ba.charstr(SpecialChar.FLAG_FRANCE)},
|
||||
'icons.flag_indonesia': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_INDONESIA)
|
||||
},
|
||||
'icons.flag_italy': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_ITALY)
|
||||
},
|
||||
'icons.flag_italy': {'icon': _ba.charstr(SpecialChar.FLAG_ITALY)},
|
||||
'icons.flag_south_korea': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_SOUTH_KOREA)
|
||||
},
|
||||
@ -190,15 +144,9 @@ def get_store_items() -> dict[str, dict]:
|
||||
'icons.flag_uae': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_UNITED_ARAB_EMIRATES)
|
||||
},
|
||||
'icons.flag_qatar': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_QATAR)
|
||||
},
|
||||
'icons.flag_egypt': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_EGYPT)
|
||||
},
|
||||
'icons.flag_kuwait': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_KUWAIT)
|
||||
},
|
||||
'icons.flag_qatar': {'icon': _ba.charstr(SpecialChar.FLAG_QATAR)},
|
||||
'icons.flag_egypt': {'icon': _ba.charstr(SpecialChar.FLAG_EGYPT)},
|
||||
'icons.flag_kuwait': {'icon': _ba.charstr(SpecialChar.FLAG_KUWAIT)},
|
||||
'icons.flag_algeria': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_ALGERIA)
|
||||
},
|
||||
@ -217,69 +165,33 @@ def get_store_items() -> dict[str, dict]:
|
||||
'icons.flag_singapore': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_SINGAPORE)
|
||||
},
|
||||
'icons.flag_iran': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_IRAN)
|
||||
},
|
||||
'icons.flag_poland': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_POLAND)
|
||||
},
|
||||
'icons.flag_iran': {'icon': _ba.charstr(SpecialChar.FLAG_IRAN)},
|
||||
'icons.flag_poland': {'icon': _ba.charstr(SpecialChar.FLAG_POLAND)},
|
||||
'icons.flag_argentina': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_ARGENTINA)
|
||||
},
|
||||
'icons.flag_philippines': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_PHILIPPINES)
|
||||
},
|
||||
'icons.flag_chile': {
|
||||
'icon': _ba.charstr(SpecialChar.FLAG_CHILE)
|
||||
},
|
||||
'icons.fedora': {
|
||||
'icon': _ba.charstr(SpecialChar.FEDORA)
|
||||
},
|
||||
'icons.hal': {
|
||||
'icon': _ba.charstr(SpecialChar.HAL)
|
||||
},
|
||||
'icons.crown': {
|
||||
'icon': _ba.charstr(SpecialChar.CROWN)
|
||||
},
|
||||
'icons.yinyang': {
|
||||
'icon': _ba.charstr(SpecialChar.YIN_YANG)
|
||||
},
|
||||
'icons.eyeball': {
|
||||
'icon': _ba.charstr(SpecialChar.EYE_BALL)
|
||||
},
|
||||
'icons.skull': {
|
||||
'icon': _ba.charstr(SpecialChar.SKULL)
|
||||
},
|
||||
'icons.heart': {
|
||||
'icon': _ba.charstr(SpecialChar.HEART)
|
||||
},
|
||||
'icons.dragon': {
|
||||
'icon': _ba.charstr(SpecialChar.DRAGON)
|
||||
},
|
||||
'icons.helmet': {
|
||||
'icon': _ba.charstr(SpecialChar.HELMET)
|
||||
},
|
||||
'icons.mushroom': {
|
||||
'icon': _ba.charstr(SpecialChar.MUSHROOM)
|
||||
},
|
||||
'icons.ninja_star': {
|
||||
'icon': _ba.charstr(SpecialChar.NINJA_STAR)
|
||||
},
|
||||
'icons.flag_chile': {'icon': _ba.charstr(SpecialChar.FLAG_CHILE)},
|
||||
'icons.fedora': {'icon': _ba.charstr(SpecialChar.FEDORA)},
|
||||
'icons.hal': {'icon': _ba.charstr(SpecialChar.HAL)},
|
||||
'icons.crown': {'icon': _ba.charstr(SpecialChar.CROWN)},
|
||||
'icons.yinyang': {'icon': _ba.charstr(SpecialChar.YIN_YANG)},
|
||||
'icons.eyeball': {'icon': _ba.charstr(SpecialChar.EYE_BALL)},
|
||||
'icons.skull': {'icon': _ba.charstr(SpecialChar.SKULL)},
|
||||
'icons.heart': {'icon': _ba.charstr(SpecialChar.HEART)},
|
||||
'icons.dragon': {'icon': _ba.charstr(SpecialChar.DRAGON)},
|
||||
'icons.helmet': {'icon': _ba.charstr(SpecialChar.HELMET)},
|
||||
'icons.mushroom': {'icon': _ba.charstr(SpecialChar.MUSHROOM)},
|
||||
'icons.ninja_star': {'icon': _ba.charstr(SpecialChar.NINJA_STAR)},
|
||||
'icons.viking_helmet': {
|
||||
'icon': _ba.charstr(SpecialChar.VIKING_HELMET)
|
||||
},
|
||||
'icons.moon': {
|
||||
'icon': _ba.charstr(SpecialChar.MOON)
|
||||
},
|
||||
'icons.spider': {
|
||||
'icon': _ba.charstr(SpecialChar.SPIDER)
|
||||
},
|
||||
'icons.fireball': {
|
||||
'icon': _ba.charstr(SpecialChar.FIREBALL)
|
||||
},
|
||||
'icons.mikirog': {
|
||||
'icon': _ba.charstr(SpecialChar.MIKIROG)
|
||||
},
|
||||
'icons.moon': {'icon': _ba.charstr(SpecialChar.MOON)},
|
||||
'icons.spider': {'icon': _ba.charstr(SpecialChar.SPIDER)},
|
||||
'icons.fireball': {'icon': _ba.charstr(SpecialChar.FIREBALL)},
|
||||
'icons.mikirog': {'icon': _ba.charstr(SpecialChar.MIKIROG)},
|
||||
}
|
||||
store_items = _ba.app.store_items
|
||||
assert store_items is not None
|
||||
@ -289,97 +201,107 @@ def get_store_items() -> dict[str, dict]:
|
||||
def get_store_layout() -> dict[str, list[dict[str, Any]]]:
|
||||
"""Return what's available in the store at a given time.
|
||||
|
||||
Categorized by tab and by section."""
|
||||
Categorized by tab and by section."""
|
||||
if _ba.app.store_layout is None:
|
||||
_ba.app.store_layout = {
|
||||
'characters': [{
|
||||
'items': []
|
||||
}],
|
||||
'extras': [{
|
||||
'items': ['pro']
|
||||
}],
|
||||
'maps': [{
|
||||
'items': ['maps.lake_frigid']
|
||||
}],
|
||||
'characters': [{'items': []}],
|
||||
'extras': [{'items': ['pro']}],
|
||||
'maps': [{'items': ['maps.lake_frigid']}],
|
||||
'minigames': [],
|
||||
'icons': [{
|
||||
'items': [
|
||||
'icons.mushroom',
|
||||
'icons.heart',
|
||||
'icons.eyeball',
|
||||
'icons.yinyang',
|
||||
'icons.hal',
|
||||
'icons.flag_us',
|
||||
'icons.flag_mexico',
|
||||
'icons.flag_germany',
|
||||
'icons.flag_brazil',
|
||||
'icons.flag_russia',
|
||||
'icons.flag_china',
|
||||
'icons.flag_uk',
|
||||
'icons.flag_canada',
|
||||
'icons.flag_india',
|
||||
'icons.flag_japan',
|
||||
'icons.flag_france',
|
||||
'icons.flag_indonesia',
|
||||
'icons.flag_italy',
|
||||
'icons.flag_south_korea',
|
||||
'icons.flag_netherlands',
|
||||
'icons.flag_uae',
|
||||
'icons.flag_qatar',
|
||||
'icons.flag_egypt',
|
||||
'icons.flag_kuwait',
|
||||
'icons.flag_algeria',
|
||||
'icons.flag_saudi_arabia',
|
||||
'icons.flag_malaysia',
|
||||
'icons.flag_czech_republic',
|
||||
'icons.flag_australia',
|
||||
'icons.flag_singapore',
|
||||
'icons.flag_iran',
|
||||
'icons.flag_poland',
|
||||
'icons.flag_argentina',
|
||||
'icons.flag_philippines',
|
||||
'icons.flag_chile',
|
||||
'icons.moon',
|
||||
'icons.fedora',
|
||||
'icons.spider',
|
||||
'icons.ninja_star',
|
||||
'icons.skull',
|
||||
'icons.dragon',
|
||||
'icons.viking_helmet',
|
||||
'icons.fireball',
|
||||
'icons.helmet',
|
||||
'icons.crown',
|
||||
]
|
||||
}]
|
||||
'icons': [
|
||||
{
|
||||
'items': [
|
||||
'icons.mushroom',
|
||||
'icons.heart',
|
||||
'icons.eyeball',
|
||||
'icons.yinyang',
|
||||
'icons.hal',
|
||||
'icons.flag_us',
|
||||
'icons.flag_mexico',
|
||||
'icons.flag_germany',
|
||||
'icons.flag_brazil',
|
||||
'icons.flag_russia',
|
||||
'icons.flag_china',
|
||||
'icons.flag_uk',
|
||||
'icons.flag_canada',
|
||||
'icons.flag_india',
|
||||
'icons.flag_japan',
|
||||
'icons.flag_france',
|
||||
'icons.flag_indonesia',
|
||||
'icons.flag_italy',
|
||||
'icons.flag_south_korea',
|
||||
'icons.flag_netherlands',
|
||||
'icons.flag_uae',
|
||||
'icons.flag_qatar',
|
||||
'icons.flag_egypt',
|
||||
'icons.flag_kuwait',
|
||||
'icons.flag_algeria',
|
||||
'icons.flag_saudi_arabia',
|
||||
'icons.flag_malaysia',
|
||||
'icons.flag_czech_republic',
|
||||
'icons.flag_australia',
|
||||
'icons.flag_singapore',
|
||||
'icons.flag_iran',
|
||||
'icons.flag_poland',
|
||||
'icons.flag_argentina',
|
||||
'icons.flag_philippines',
|
||||
'icons.flag_chile',
|
||||
'icons.moon',
|
||||
'icons.fedora',
|
||||
'icons.spider',
|
||||
'icons.ninja_star',
|
||||
'icons.skull',
|
||||
'icons.dragon',
|
||||
'icons.viking_helmet',
|
||||
'icons.fireball',
|
||||
'icons.helmet',
|
||||
'icons.crown',
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
store_layout = _ba.app.store_layout
|
||||
assert store_layout is not None
|
||||
store_layout['characters'] = [{
|
||||
'items': [
|
||||
'characters.kronk', 'characters.zoe', 'characters.jackmorgan',
|
||||
'characters.mel', 'characters.snakeshadow', 'characters.bones',
|
||||
'characters.bernard', 'characters.agent', 'characters.frosty',
|
||||
'characters.pascal', 'characters.pixie'
|
||||
]
|
||||
}]
|
||||
store_layout['minigames'] = [{
|
||||
'items': [
|
||||
'games.ninja_fight', 'games.meteor_shower', 'games.target_practice'
|
||||
]
|
||||
}]
|
||||
store_layout['characters'] = [
|
||||
{
|
||||
'items': [
|
||||
'characters.kronk',
|
||||
'characters.zoe',
|
||||
'characters.jackmorgan',
|
||||
'characters.mel',
|
||||
'characters.snakeshadow',
|
||||
'characters.bones',
|
||||
'characters.bernard',
|
||||
'characters.agent',
|
||||
'characters.frosty',
|
||||
'characters.pascal',
|
||||
'characters.pixie',
|
||||
]
|
||||
}
|
||||
]
|
||||
store_layout['minigames'] = [
|
||||
{
|
||||
'items': [
|
||||
'games.ninja_fight',
|
||||
'games.meteor_shower',
|
||||
'games.target_practice',
|
||||
]
|
||||
}
|
||||
]
|
||||
if _internal.get_v1_account_misc_read_val('xmas', False):
|
||||
store_layout['characters'][0]['items'].append('characters.santa')
|
||||
store_layout['characters'][0]['items'].append('characters.wizard')
|
||||
store_layout['characters'][0]['items'].append('characters.cyborg')
|
||||
if _internal.get_v1_account_misc_read_val('easter', False):
|
||||
store_layout['characters'].append({
|
||||
'title': 'store.holidaySpecialText',
|
||||
'items': ['characters.bunny']
|
||||
})
|
||||
store_layout['minigames'].append({
|
||||
'title': 'store.holidaySpecialText',
|
||||
'items': ['games.easter_egg_hunt']
|
||||
})
|
||||
store_layout['characters'].append(
|
||||
{'title': 'store.holidaySpecialText', 'items': ['characters.bunny']}
|
||||
)
|
||||
store_layout['minigames'].append(
|
||||
{
|
||||
'title': 'store.holidaySpecialText',
|
||||
'items': ['games.easter_egg_hunt'],
|
||||
}
|
||||
)
|
||||
return store_layout
|
||||
|
||||
|
||||
@ -394,7 +316,7 @@ def get_clean_price(price_string: str) -> str:
|
||||
'$4.99': '$5.00',
|
||||
'$9.99': '$10.00',
|
||||
'$19.99': '$20.00',
|
||||
'$49.99': '$50.00'
|
||||
'$49.99': '$50.00',
|
||||
}
|
||||
return psubs.get(price_string, price_string)
|
||||
|
||||
@ -418,19 +340,23 @@ def get_available_purchase_count(tab: str | None = None) -> int:
|
||||
return count
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error calcing available purchases')
|
||||
return 0
|
||||
|
||||
|
||||
def _calc_count_for_tab(tabval: list[dict[str, Any]], our_tickets: int,
|
||||
count: int) -> int:
|
||||
def _calc_count_for_tab(
|
||||
tabval: list[dict[str, Any]], our_tickets: int, count: int
|
||||
) -> int:
|
||||
for section in tabval:
|
||||
for item in section['items']:
|
||||
ticket_cost = _internal.get_v1_account_misc_read_val(
|
||||
'price.' + item, None)
|
||||
'price.' + item, None
|
||||
)
|
||||
if ticket_cost is not None:
|
||||
if (our_tickets >= ticket_cost
|
||||
and not _internal.get_purchased(item)):
|
||||
if our_tickets >= ticket_cost and not _internal.get_purchased(
|
||||
item
|
||||
):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@ -443,6 +369,7 @@ def get_available_sale_time(tab: str) -> int | None:
|
||||
try:
|
||||
import datetime
|
||||
from ba._generated.enums import TimeType, TimeFormat
|
||||
|
||||
app = _ba.app
|
||||
sale_times: list[int | None] = []
|
||||
|
||||
@ -458,18 +385,21 @@ def get_available_sale_time(tab: str) -> int | None:
|
||||
# If we've got a time-remaining in our config, start there.
|
||||
if 'PSTR' in config:
|
||||
app.pro_sale_start_time = int(
|
||||
_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS))
|
||||
_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)
|
||||
)
|
||||
app.pro_sale_start_val = config['PSTR']
|
||||
else:
|
||||
|
||||
# We start the timer once we get the duration from
|
||||
# the server.
|
||||
start_duration = _internal.get_v1_account_misc_read_val(
|
||||
'proSaleDurationMinutes', None)
|
||||
'proSaleDurationMinutes', None
|
||||
)
|
||||
if start_duration is not None:
|
||||
app.pro_sale_start_time = int(
|
||||
_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS))
|
||||
app.pro_sale_start_val = (60000 * start_duration)
|
||||
_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)
|
||||
)
|
||||
app.pro_sale_start_val = 60000 * start_duration
|
||||
|
||||
# If we haven't heard from the server yet, no sale..
|
||||
else:
|
||||
@ -477,9 +407,13 @@ def get_available_sale_time(tab: str) -> int | None:
|
||||
|
||||
assert app.pro_sale_start_val is not None
|
||||
val: int | None = max(
|
||||
0, app.pro_sale_start_val -
|
||||
(_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) -
|
||||
app.pro_sale_start_time))
|
||||
0,
|
||||
app.pro_sale_start_val
|
||||
- (
|
||||
_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)
|
||||
- app.pro_sale_start_time
|
||||
),
|
||||
)
|
||||
|
||||
# Keep the value in the config up to date. I suppose we should
|
||||
# write the config occasionally but it should happen often enough
|
||||
@ -496,9 +430,12 @@ def get_available_sale_time(tab: str) -> int | None:
|
||||
for item in section['items']:
|
||||
if item in sales_raw:
|
||||
if not _internal.get_purchased(item):
|
||||
to_end = ((datetime.datetime.utcfromtimestamp(
|
||||
sales_raw[item]['e']) -
|
||||
datetime.datetime.utcnow()).total_seconds())
|
||||
to_end = (
|
||||
datetime.datetime.utcfromtimestamp(
|
||||
sales_raw[item]['e']
|
||||
)
|
||||
- datetime.datetime.utcnow()
|
||||
).total_seconds()
|
||||
if to_end > 0:
|
||||
sale_times.append(int(to_end * 1000))
|
||||
|
||||
@ -508,6 +445,7 @@ def get_available_sale_time(tab: str) -> int | None:
|
||||
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error calcing sale time')
|
||||
return None
|
||||
|
||||
@ -540,5 +478,6 @@ def get_unowned_game_types() -> set[type[ba.GameActivity]]:
|
||||
return unowned_games
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error calcing un-owned games')
|
||||
return set()
|
||||
|
||||
@ -44,10 +44,12 @@ class SessionTeam:
|
||||
id: int
|
||||
"""The unique numeric id of the team."""
|
||||
|
||||
def __init__(self,
|
||||
team_id: int = 0,
|
||||
name: ba.Lstr | str = '',
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0)):
|
||||
def __init__(
|
||||
self,
|
||||
team_id: int = 0,
|
||||
name: ba.Lstr | str = '',
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
):
|
||||
"""Instantiate a ba.SessionTeam.
|
||||
|
||||
In most cases, all teams are provided to you by the ba.Session,
|
||||
@ -109,7 +111,8 @@ class Team(Generic[PlayerType]):
|
||||
f' operator (__eq__) which will break internal'
|
||||
f' logic. Please remove it.\n'
|
||||
f'For dataclasses you can do "dataclass(eq=False)"'
|
||||
f' in the class decorator.')
|
||||
f' in the class decorator.'
|
||||
)
|
||||
|
||||
self.players = []
|
||||
self._sessionteam = weakref.ref(sessionteam)
|
||||
@ -120,8 +123,9 @@ class Team(Generic[PlayerType]):
|
||||
self._expired = False
|
||||
self._postinited = True
|
||||
|
||||
def manual_init(self, team_id: int, name: ba.Lstr | str,
|
||||
color: tuple[float, ...]) -> None:
|
||||
def manual_init(
|
||||
self, team_id: int, name: ba.Lstr | str, color: tuple[float, ...]
|
||||
) -> None:
|
||||
"""Manually init a team for uses such as bots."""
|
||||
self.id = team_id
|
||||
self.name = name
|
||||
@ -186,6 +190,7 @@ class Team(Generic[PlayerType]):
|
||||
if sessionteam is not None:
|
||||
return sessionteam
|
||||
from ba import _error
|
||||
|
||||
raise _error.SessionTeamNotFoundError()
|
||||
|
||||
|
||||
|
||||
@ -39,8 +39,9 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
returns True for ba.DualTeamSessions and ba.FreeForAllSessions;
|
||||
False otherwise.
|
||||
"""
|
||||
return (issubclass(sessiontype, DualTeamSession)
|
||||
or issubclass(sessiontype, FreeForAllSession))
|
||||
return issubclass(sessiontype, DualTeamSession) or issubclass(
|
||||
sessiontype, FreeForAllSession
|
||||
)
|
||||
|
||||
def __init__(self, settings: dict):
|
||||
super().__init__(settings)
|
||||
@ -55,6 +56,7 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
# pylint: disable=cyclic-import
|
||||
from ba._coopsession import CoopSession
|
||||
from bastd.actor.controlsguide import ControlsGuide
|
||||
|
||||
super().on_transition_in()
|
||||
|
||||
# On the first game, show the controls UI momentarily.
|
||||
@ -67,11 +69,13 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
lifespan = 10.0
|
||||
if self.slow_motion:
|
||||
lifespan *= 0.3
|
||||
ControlsGuide(delay=delay,
|
||||
lifespan=lifespan,
|
||||
scale=0.8,
|
||||
position=(380, 200),
|
||||
bright=True).autoretain()
|
||||
ControlsGuide(
|
||||
delay=delay,
|
||||
lifespan=lifespan,
|
||||
scale=0.8,
|
||||
position=(380, 200),
|
||||
bright=True,
|
||||
).autoretain()
|
||||
setattr(self.session, attrname, True)
|
||||
|
||||
def on_begin(self) -> None:
|
||||
@ -84,15 +88,19 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
elif isinstance(self.session, DualTeamSession):
|
||||
if len(self.players) >= 4:
|
||||
from ba import _achievement
|
||||
|
||||
_ba.app.ach.award_local_achievement('Team Player')
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception()
|
||||
|
||||
def spawn_player_spaz(self,
|
||||
player: PlayerType,
|
||||
position: Sequence[float] | None = None,
|
||||
angle: float | None = None) -> PlayerSpaz:
|
||||
def spawn_player_spaz(
|
||||
self,
|
||||
player: PlayerType,
|
||||
position: Sequence[float] | None = None,
|
||||
angle: float | None = None,
|
||||
) -> PlayerSpaz:
|
||||
"""
|
||||
Method override; spawns and wires up a standard ba.PlayerSpaz for
|
||||
a ba.Player.
|
||||
@ -103,7 +111,7 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
if position is None:
|
||||
# In teams-mode get our team-start-location.
|
||||
if isinstance(self.session, DualTeamSession):
|
||||
position = (self.map.get_start_position(player.team.id))
|
||||
position = self.map.get_start_position(player.team.id)
|
||||
else:
|
||||
# Otherwise do free-for-all spawn locations.
|
||||
position = self.map.get_ffa_start_position(self.players)
|
||||
@ -112,11 +120,12 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
|
||||
# FIXME: need to unify these arguments with GameActivity.end()
|
||||
def end( # type: ignore
|
||||
self,
|
||||
results: Any = None,
|
||||
announce_winning_team: bool = True,
|
||||
announce_delay: float = 0.1,
|
||||
force: bool = False) -> None:
|
||||
self,
|
||||
results: Any = None,
|
||||
announce_winning_team: bool = True,
|
||||
announce_delay: float = 0.1,
|
||||
force: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
End the game and announce the single winning team
|
||||
unless 'announce_winning_team' is False.
|
||||
@ -141,15 +150,19 @@ class TeamGameActivity(GameActivity[PlayerType, TeamType]):
|
||||
self,
|
||||
results,
|
||||
delay=announce_delay,
|
||||
announce_winning_team=announce_winning_team)
|
||||
announce_winning_team=announce_winning_team,
|
||||
)
|
||||
|
||||
# For co-op we just pass this up the chain with a delay added
|
||||
# (in most cases). Team games expect a delay for the announce
|
||||
# portion in teams/ffa mode so this keeps it consistent.
|
||||
else:
|
||||
# don't want delay on restarts..
|
||||
if (isinstance(results, dict) and 'outcome' in results
|
||||
and results['outcome'] == 'restart'):
|
||||
if (
|
||||
isinstance(results, dict)
|
||||
and 'outcome' in results
|
||||
and results['outcome'] == 'restart'
|
||||
):
|
||||
delay = 0.0
|
||||
else:
|
||||
delay = 2.0
|
||||
|
||||
@ -27,53 +27,94 @@ def get_next_tip() -> str:
|
||||
def get_all_tips() -> list[str]:
|
||||
"""Return the complete list of tips."""
|
||||
tips = [
|
||||
('If you are short on controllers, install the \'${REMOTE_APP_NAME}\' '
|
||||
'app\non your mobile devices to use them as controllers.'),
|
||||
('Create player profiles for yourself and your friends with\nyour '
|
||||
'preferred names and appearances instead of using random ones.'),
|
||||
('You can \'aim\' your punches by spinning left or right.\nThis is '
|
||||
'useful for knocking bad guys off edges or scoring in hockey.'),
|
||||
('If you pick up a curse, your only hope for survival is to\nfind a '
|
||||
'health powerup in the next few seconds.'),
|
||||
('A perfectly timed running-jumping-spin-punch can kill in a single '
|
||||
'hit\nand earn you lifelong respect from your friends.'),
|
||||
(
|
||||
'If you are short on controllers,'
|
||||
' install the \'${REMOTE_APP_NAME}\' app\n'
|
||||
'on your mobile devices to use them as controllers.'
|
||||
),
|
||||
(
|
||||
'Create player profiles for yourself and your friends with\nyour '
|
||||
'preferred names and appearances instead of using random ones.'
|
||||
),
|
||||
(
|
||||
'You can \'aim\' your punches by spinning left or right.\nThis is '
|
||||
'useful for knocking bad guys off edges or scoring in hockey.'
|
||||
),
|
||||
(
|
||||
'If you pick up a curse, your only hope for survival is to\nfind a '
|
||||
'health powerup in the next few seconds.'
|
||||
),
|
||||
(
|
||||
'A perfectly timed running-jumping-spin-punch can kill in a single '
|
||||
'hit\nand earn you lifelong respect from your friends.'
|
||||
),
|
||||
'Always remember to floss.',
|
||||
'Don\'t run all the time. Really. You will fall off cliffs.',
|
||||
('In Capture-the-Flag, your own flag must be at your base to score, '
|
||||
'If the other\nteam is about to score, stealing their flag can be '
|
||||
'a good way to stop them.'),
|
||||
('If you get a sticky-bomb stuck to you, jump around and spin in '
|
||||
'circles. You might\nshake the bomb off, or if nothing else your '
|
||||
'last moments will be entertaining.'),
|
||||
('You take damage when you whack your head on things,\nso try to not '
|
||||
'whack your head on things.'),
|
||||
(
|
||||
'In Capture-the-Flag, your own flag must be at your base to score, '
|
||||
'If the other\nteam is about to score, stealing their flag can be '
|
||||
'a good way to stop them.'
|
||||
),
|
||||
(
|
||||
'If you get a sticky-bomb stuck to you, jump around and spin in '
|
||||
'circles. You might\nshake the bomb off, or if nothing else your '
|
||||
'last moments will be entertaining.'
|
||||
),
|
||||
(
|
||||
'You take damage when you whack your head on things,\n'
|
||||
'so try to not whack your head on things.'
|
||||
),
|
||||
'If you kill an enemy in one hit you get double points for it.',
|
||||
('Despite their looks, all characters\' abilities are identical,\nso '
|
||||
'just pick whichever one you most closely resemble.'),
|
||||
(
|
||||
'Despite their looks, all characters\' abilities are identical,\n'
|
||||
'so just pick whichever one you most closely resemble.'
|
||||
),
|
||||
'You can throw bombs higher if you jump just before throwing.',
|
||||
('Throw strength is based on the direction you are holding.\nTo toss '
|
||||
'something gently in front of you, don\'t hold any direction.'),
|
||||
('If someone picks you up, punch them and they\'ll let go.\nThis '
|
||||
'works in real life too.'),
|
||||
('Don\'t get too cocky with that energy shield; you can still get '
|
||||
'yourself thrown off a cliff.'),
|
||||
('Many things can be picked up and thrown, including other players. '
|
||||
'Tossing\nyour enemies off cliffs can be an effective and '
|
||||
'emotionally fulfilling strategy.'),
|
||||
('Ice bombs are not very powerful, but they freeze\nwhoever they '
|
||||
'hit, leaving them vulnerable to shattering.'),
|
||||
(
|
||||
'Throw strength is based on the direction you are holding.\n'
|
||||
'To toss something gently in front of you, don\'t'
|
||||
' hold any direction.'
|
||||
),
|
||||
(
|
||||
'If someone picks you up, punch them and they\'ll let go.\nThis '
|
||||
'works in real life too.'
|
||||
),
|
||||
(
|
||||
'Don\'t get too cocky with that energy shield; you can still get '
|
||||
'yourself thrown off a cliff.'
|
||||
),
|
||||
(
|
||||
'Many things can be picked up and thrown,'
|
||||
' including other players. '
|
||||
'Tossing\nyour enemies off cliffs can be an effective and '
|
||||
'emotionally fulfilling strategy.'
|
||||
),
|
||||
(
|
||||
'Ice bombs are not very powerful, but they freeze\nwhoever they '
|
||||
'hit, leaving them vulnerable to shattering.'
|
||||
),
|
||||
'Don\'t spin for too long; you\'ll become dizzy and fall.',
|
||||
('Run back and forth before throwing a bomb\nto \'whiplash\' it '
|
||||
'and throw it farther.'),
|
||||
('Punches do more damage the faster your fists are moving,\nso '
|
||||
'try running, jumping, and spinning like crazy.'),
|
||||
(
|
||||
'Run back and forth before throwing a bomb\nto \'whiplash\' it '
|
||||
'and throw it farther.'
|
||||
),
|
||||
(
|
||||
'Punches do more damage the faster your fists are moving,\nso '
|
||||
'try running, jumping, and spinning like crazy.'
|
||||
),
|
||||
'In hockey, you\'ll maintain more speed if you turn gradually.',
|
||||
('The head is the most vulnerable area, so a sticky-bomb\nto the '
|
||||
'noggin usually means game-over.'),
|
||||
('Hold down any button to run. You\'ll get places faster\nbut '
|
||||
'won\'t turn very well, so watch out for cliffs.'),
|
||||
('You can judge when a bomb is going to explode based on the\n'
|
||||
'color of sparks from its fuse: yellow..orange..red..BOOM.'),
|
||||
(
|
||||
'The head is the most vulnerable area, so a sticky-bomb\nto the '
|
||||
'noggin usually means game-over.'
|
||||
),
|
||||
(
|
||||
'Hold down any button to run. You\'ll get places faster\nbut '
|
||||
'won\'t turn very well, so watch out for cliffs.'
|
||||
),
|
||||
(
|
||||
'You can judge when a bomb is going to explode based on the\n'
|
||||
'color of sparks from its fuse: yellow..orange..red..BOOM.'
|
||||
),
|
||||
]
|
||||
app = _ba.app
|
||||
if not app.iircade_mode:
|
||||
@ -81,12 +122,17 @@ def get_all_tips() -> list[str]:
|
||||
'If your framerate is choppy, try turning down resolution\nor '
|
||||
'visuals in the game\'s graphics settings.'
|
||||
]
|
||||
if (app.platform in ('android', 'ios') and not app.on_tv
|
||||
and not app.iircade_mode):
|
||||
if (
|
||||
app.platform in ('android', 'ios')
|
||||
and not app.on_tv
|
||||
and not app.iircade_mode
|
||||
):
|
||||
tips += [
|
||||
('If your device gets too warm or you\'d like to conserve '
|
||||
'battery power,\nturn down "Visuals" or "Resolution" '
|
||||
'in Settings->Graphics'),
|
||||
(
|
||||
'If your device gets too warm or you\'d like to conserve '
|
||||
'battery power,\nturn down "Visuals" or "Resolution" '
|
||||
'in Settings->Graphics'
|
||||
),
|
||||
]
|
||||
if app.platform in ['mac', 'android'] and not app.iircade_mode:
|
||||
tips += [
|
||||
|
||||
@ -17,6 +17,7 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
|
||||
# pylint: disable=too-many-locals
|
||||
from ba._generated.enums import SpecialChar
|
||||
from ba._gameutils import get_trophy_string
|
||||
|
||||
range1 = entry.get('prizeRange1')
|
||||
range2 = entry.get('prizeRange2')
|
||||
range3 = entry.get('prizeRange3')
|
||||
@ -27,12 +28,18 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
|
||||
trophy_type_2 = entry.get('prizeTrophy2')
|
||||
trophy_type_3 = entry.get('prizeTrophy3')
|
||||
out_vals = []
|
||||
for rng, prize, trophy_type in ((range1, prize1, trophy_type_1),
|
||||
(range2, prize2, trophy_type_2),
|
||||
(range3, prize3, trophy_type_3)):
|
||||
prval = ('' if rng is None else ('#' + str(rng[0])) if
|
||||
(rng[0] == rng[1]) else
|
||||
('#' + str(rng[0]) + '-' + str(rng[1])))
|
||||
for rng, prize, trophy_type in (
|
||||
(range1, prize1, trophy_type_1),
|
||||
(range2, prize2, trophy_type_2),
|
||||
(range3, prize3, trophy_type_3),
|
||||
):
|
||||
prval = (
|
||||
''
|
||||
if rng is None
|
||||
else ('#' + str(rng[0]))
|
||||
if (rng[0] == rng[1])
|
||||
else ('#' + str(rng[0]) + '-' + str(rng[1]))
|
||||
)
|
||||
pvval = ''
|
||||
if trophy_type is not None:
|
||||
pvval += get_trophy_string(trophy_type)
|
||||
@ -40,8 +47,7 @@ def get_tournament_prize_strings(entry: dict[str, Any]) -> list[str]:
|
||||
# If we've got trophies but not for this entry, throw some space
|
||||
# in to compensate so the ticket counts line up.
|
||||
if prize is not None:
|
||||
pvval = _ba.charstr(
|
||||
SpecialChar.TICKET_BACKING) + str(prize) + pvval
|
||||
pvval = _ba.charstr(SpecialChar.TICKET_BACKING) + str(prize) + pvval
|
||||
out_vals.append(prval)
|
||||
out_vals.append(pvval)
|
||||
return out_vals
|
||||
|
||||
@ -89,19 +89,21 @@ class UISubsystem:
|
||||
if bool(False): # force-test ui scale
|
||||
self._uiscale = UIScale.SMALL
|
||||
with _ba.Context('ui'):
|
||||
_ba.pushcall(lambda: _ba.screenmessage(
|
||||
f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
|
||||
color=(1, 0, 1),
|
||||
log=True))
|
||||
_ba.pushcall(
|
||||
lambda: _ba.screenmessage(
|
||||
f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
|
||||
color=(1, 0, 1),
|
||||
log=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.controller = UIController()
|
||||
|
||||
# Kick off our periodic UI upkeep.
|
||||
# FIXME: Can probably kill this if we do immediate UI death checks.
|
||||
self.upkeeptimer = _ba.Timer(2.6543,
|
||||
ui_upkeep,
|
||||
timetype=TimeType.REAL,
|
||||
repeat=True)
|
||||
self.upkeeptimer = _ba.Timer(
|
||||
2.6543, ui_upkeep, timetype=TimeType.REAL, repeat=True
|
||||
)
|
||||
|
||||
def set_main_menu_window(self, window: ba.Widget) -> None:
|
||||
"""Set the current 'main' window, replacing any existing."""
|
||||
@ -122,6 +124,7 @@ class UISubsystem:
|
||||
frameline = f'{frameinfo.filename} {frameinfo.lineno}'
|
||||
except Exception:
|
||||
from ba._error import print_exception
|
||||
|
||||
print_exception('Error calcing line for set_main_menu_window')
|
||||
|
||||
# With our legacy main-menu system, the caller is responsible for
|
||||
@ -136,9 +139,12 @@ class UISubsystem:
|
||||
# things.
|
||||
def _delay_kill() -> None:
|
||||
import time
|
||||
|
||||
if existing:
|
||||
print(f'Killing old main_menu_window'
|
||||
f' when called at: {frameline} t={time.time():.3f}')
|
||||
print(
|
||||
f'Killing old main_menu_window'
|
||||
f' when called at: {frameline} t={time.time():.3f}'
|
||||
)
|
||||
existing.delete()
|
||||
|
||||
_ba.timer(1.0, _delay_kill, timetype=TimeType.REAL)
|
||||
@ -148,8 +154,9 @@ class UISubsystem:
|
||||
"""Clear any existing 'main' window with the provided transition."""
|
||||
if self._main_menu_window:
|
||||
if transition is not None:
|
||||
_ba.containerwidget(edit=self._main_menu_window,
|
||||
transition=transition)
|
||||
_ba.containerwidget(
|
||||
edit=self._main_menu_window, transition=transition
|
||||
)
|
||||
else:
|
||||
self._main_menu_window.delete()
|
||||
|
||||
|
||||
@ -48,7 +48,8 @@ class WorkspaceSubsystem:
|
||||
target=lambda: self._set_active_workspace_bg(
|
||||
workspaceid=workspaceid,
|
||||
workspacename=workspacename,
|
||||
on_completed=on_completed),
|
||||
on_completed=on_completed,
|
||||
),
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
@ -60,16 +61,21 @@ class WorkspaceSubsystem:
|
||||
_ba.screenmessage(msg, color=(0, 1, 0))
|
||||
_ba.playsound(_ba.getsound('gunCocking'))
|
||||
|
||||
def _set_active_workspace_bg(self, workspaceid: str, workspacename: str,
|
||||
on_completed: Callable[[], None]) -> None:
|
||||
def _set_active_workspace_bg(
|
||||
self,
|
||||
workspaceid: str,
|
||||
workspacename: str,
|
||||
on_completed: Callable[[], None],
|
||||
) -> None:
|
||||
from ba._language import Lstr
|
||||
|
||||
class _SkipSyncError(RuntimeError):
|
||||
pass
|
||||
|
||||
set_path = True
|
||||
wspath = Path(_ba.get_volatile_data_directory(), 'workspaces',
|
||||
workspaceid)
|
||||
wspath = Path(
|
||||
_ba.get_volatile_data_directory(), 'workspaces', workspaceid
|
||||
)
|
||||
try:
|
||||
|
||||
# If it seems we're offline, don't even attempt a sync,
|
||||
@ -87,13 +93,17 @@ class WorkspaceSubsystem:
|
||||
while True:
|
||||
response = _ba.app.cloud.send_message(
|
||||
bacommon.cloud.WorkspaceFetchMessage(
|
||||
workspaceid=workspaceid, state=state))
|
||||
workspaceid=workspaceid, state=state
|
||||
)
|
||||
)
|
||||
state = response.state
|
||||
self._handle_deletes(workspace_dir=wspath,
|
||||
deletes=response.deletes)
|
||||
self._handle_deletes(
|
||||
workspace_dir=wspath, deletes=response.deletes
|
||||
)
|
||||
self._handle_downloads_inline(
|
||||
workspace_dir=wspath,
|
||||
downloads_inline=response.downloads_inline)
|
||||
downloads_inline=response.downloads_inline,
|
||||
)
|
||||
if response.done:
|
||||
# Server only deals in files; let's clean up any
|
||||
# leftover empty dirs after the dust has cleared.
|
||||
@ -104,8 +114,10 @@ class WorkspaceSubsystem:
|
||||
_ba.pushcall(
|
||||
tpartial(
|
||||
self._successmsg,
|
||||
Lstr(resource='activatedText',
|
||||
subs=[('${THING}', workspacename)]),
|
||||
Lstr(
|
||||
resource='activatedText',
|
||||
subs=[('${THING}', workspacename)],
|
||||
),
|
||||
),
|
||||
from_other_thread=True,
|
||||
)
|
||||
@ -114,8 +126,11 @@ class WorkspaceSubsystem:
|
||||
_ba.pushcall(
|
||||
tpartial(
|
||||
self._errmsg,
|
||||
Lstr(resource='workspaceSyncReuseText',
|
||||
subs=[('${WORKSPACE}', workspacename)])),
|
||||
Lstr(
|
||||
resource='workspaceSyncReuseText',
|
||||
subs=[('${WORKSPACE}', workspacename)],
|
||||
),
|
||||
),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
||||
@ -123,8 +138,10 @@ class WorkspaceSubsystem:
|
||||
# Avoid reusing existing if we fail in the middle; could
|
||||
# be in wonky state.
|
||||
set_path = False
|
||||
_ba.pushcall(tpartial(self._errmsg, Lstr(value=str(exc))),
|
||||
from_other_thread=True)
|
||||
_ba.pushcall(
|
||||
tpartial(self._errmsg, Lstr(value=str(exc))),
|
||||
from_other_thread=True,
|
||||
)
|
||||
except Exception:
|
||||
# Ditto.
|
||||
set_path = False
|
||||
@ -132,8 +149,10 @@ class WorkspaceSubsystem:
|
||||
_ba.pushcall(
|
||||
tpartial(
|
||||
self._errmsg,
|
||||
Lstr(resource='workspaceSyncErrorText',
|
||||
subs=[('${WORKSPACE}', workspacename)]),
|
||||
Lstr(
|
||||
resource='workspaceSyncErrorText',
|
||||
subs=[('${WORKSPACE}', workspacename)],
|
||||
),
|
||||
),
|
||||
from_other_thread=True,
|
||||
)
|
||||
@ -186,8 +205,7 @@ class WorkspaceSubsystem:
|
||||
# listed when the parent dir is visited, so lets make sure
|
||||
# to only acknowledge still-existing ones.
|
||||
dirnames = [
|
||||
d for d in dirnames
|
||||
if os.path.exists(os.path.join(basename, d))
|
||||
d for d in dirnames if os.path.exists(os.path.join(basename, d))
|
||||
]
|
||||
if not dirnames and not filenames and basename != prunedir:
|
||||
os.rmdir(basename)
|
||||
|
||||
@ -9,73 +9,173 @@ defensively) in mods.
|
||||
from __future__ import annotations
|
||||
|
||||
from _ba import (
|
||||
show_online_score_ui, set_ui_input_device, is_party_icon_visible,
|
||||
getinputdevice, add_clean_frame_callback, unlock_all_input,
|
||||
increment_analytics_count, set_debug_speed_exponent, get_special_widget,
|
||||
get_qrcode_texture, get_string_height, get_string_width, show_app_invite,
|
||||
appnameupper, lock_all_input, open_file_externally, fade_screen, appname,
|
||||
have_incentivized_ad, has_video_ads, workspaces_in_use,
|
||||
set_party_icon_always_visible, connect_to_party, get_game_port,
|
||||
end_host_scanning, host_scan_cycle, charstr, get_public_party_enabled,
|
||||
get_public_party_max_size, set_public_party_name,
|
||||
set_public_party_max_size, set_authenticate_clients,
|
||||
set_public_party_enabled, reset_random_player_names, new_host_session,
|
||||
get_foreground_host_session, get_local_active_input_devices_count,
|
||||
get_ui_input_device, is_in_replay, set_replay_speed_exponent,
|
||||
get_replay_speed_exponent, disconnect_from_host, set_party_window_open,
|
||||
get_connection_to_host_info, get_chat_messages, get_game_roster,
|
||||
disconnect_client, chatmessage, get_random_names, have_permission,
|
||||
request_permission, have_touchscreen_input, is_xcode_build,
|
||||
set_low_level_config_value, get_low_level_config_value,
|
||||
capture_gamepad_input, release_gamepad_input, has_gamma_control,
|
||||
get_max_graphics_quality, get_display_resolution, capture_keyboard_input,
|
||||
release_keyboard_input, value_test, set_touchscreen_editing,
|
||||
is_running_on_fire_tv, android_get_external_files_dir,
|
||||
set_telnet_access_enabled, new_replay_session, get_replays_dir)
|
||||
show_online_score_ui,
|
||||
set_ui_input_device,
|
||||
is_party_icon_visible,
|
||||
getinputdevice,
|
||||
add_clean_frame_callback,
|
||||
unlock_all_input,
|
||||
increment_analytics_count,
|
||||
set_debug_speed_exponent,
|
||||
get_special_widget,
|
||||
get_qrcode_texture,
|
||||
get_string_height,
|
||||
get_string_width,
|
||||
show_app_invite,
|
||||
appnameupper,
|
||||
lock_all_input,
|
||||
open_file_externally,
|
||||
fade_screen,
|
||||
appname,
|
||||
have_incentivized_ad,
|
||||
has_video_ads,
|
||||
workspaces_in_use,
|
||||
set_party_icon_always_visible,
|
||||
connect_to_party,
|
||||
get_game_port,
|
||||
end_host_scanning,
|
||||
host_scan_cycle,
|
||||
charstr,
|
||||
get_public_party_enabled,
|
||||
get_public_party_max_size,
|
||||
set_public_party_name,
|
||||
set_public_party_max_size,
|
||||
set_authenticate_clients,
|
||||
set_public_party_enabled,
|
||||
reset_random_player_names,
|
||||
new_host_session,
|
||||
get_foreground_host_session,
|
||||
get_local_active_input_devices_count,
|
||||
get_ui_input_device,
|
||||
is_in_replay,
|
||||
set_replay_speed_exponent,
|
||||
get_replay_speed_exponent,
|
||||
disconnect_from_host,
|
||||
set_party_window_open,
|
||||
get_connection_to_host_info,
|
||||
get_chat_messages,
|
||||
get_game_roster,
|
||||
disconnect_client,
|
||||
chatmessage,
|
||||
get_random_names,
|
||||
have_permission,
|
||||
request_permission,
|
||||
have_touchscreen_input,
|
||||
is_xcode_build,
|
||||
set_low_level_config_value,
|
||||
get_low_level_config_value,
|
||||
capture_gamepad_input,
|
||||
release_gamepad_input,
|
||||
has_gamma_control,
|
||||
get_max_graphics_quality,
|
||||
get_display_resolution,
|
||||
capture_keyboard_input,
|
||||
release_keyboard_input,
|
||||
value_test,
|
||||
set_touchscreen_editing,
|
||||
is_running_on_fire_tv,
|
||||
android_get_external_files_dir,
|
||||
set_telnet_access_enabled,
|
||||
new_replay_session,
|
||||
get_replays_dir,
|
||||
)
|
||||
|
||||
from ba._map import (get_map_class, register_map, preload_map_preview_media,
|
||||
get_map_display_string, get_filtered_map_name)
|
||||
from ba._map import (
|
||||
get_map_class,
|
||||
register_map,
|
||||
preload_map_preview_media,
|
||||
get_map_display_string,
|
||||
get_filtered_map_name,
|
||||
)
|
||||
from ba._appconfig import commit_app_config
|
||||
from ba._input import (get_device_value, get_input_map_hash,
|
||||
get_input_device_config)
|
||||
from ba._input import (
|
||||
get_device_value,
|
||||
get_input_map_hash,
|
||||
get_input_device_config,
|
||||
)
|
||||
from ba._general import getclass, json_prep, get_type_name
|
||||
from ba._activitytypes import JoinActivity, ScoreScreenActivity
|
||||
from ba._apputils import (is_browser_likely_available, get_remote_app_name,
|
||||
should_submit_debug_info)
|
||||
from ba._benchmark import (run_gpu_benchmark, run_cpu_benchmark,
|
||||
run_media_reload_benchmark, run_stress_test)
|
||||
from ba._apputils import (
|
||||
is_browser_likely_available,
|
||||
get_remote_app_name,
|
||||
should_submit_debug_info,
|
||||
)
|
||||
from ba._benchmark import (
|
||||
run_gpu_benchmark,
|
||||
run_cpu_benchmark,
|
||||
run_media_reload_benchmark,
|
||||
run_stress_test,
|
||||
)
|
||||
from ba._campaign import getcampaign
|
||||
from ba._messages import PlayerProfilesChangedMessage
|
||||
from ba._multiteamsession import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES
|
||||
from ba._music import do_play_music
|
||||
from ba._net import (master_server_get, master_server_post,
|
||||
get_ip_address_type, DEFAULT_REQUEST_TIMEOUT_SECONDS)
|
||||
from ba._net import (
|
||||
master_server_get,
|
||||
master_server_post,
|
||||
get_ip_address_type,
|
||||
DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
from ba._powerup import get_default_powerup_distribution
|
||||
from ba._profile import (get_player_profile_colors, get_player_profile_icon,
|
||||
get_player_colors)
|
||||
from ba._profile import (
|
||||
get_player_profile_colors,
|
||||
get_player_profile_icon,
|
||||
get_player_colors,
|
||||
)
|
||||
from ba._tips import get_next_tip
|
||||
from ba._playlist import (get_default_free_for_all_playlist,
|
||||
get_default_teams_playlist, filter_playlist)
|
||||
from ba._store import (get_available_sale_time, get_available_purchase_count,
|
||||
get_store_item_name_translated,
|
||||
get_store_item_display_size, get_store_layout,
|
||||
get_store_item, get_clean_price, get_unowned_maps,
|
||||
get_unowned_game_types)
|
||||
from ba._playlist import (
|
||||
get_default_free_for_all_playlist,
|
||||
get_default_teams_playlist,
|
||||
filter_playlist,
|
||||
)
|
||||
from ba._store import (
|
||||
get_available_sale_time,
|
||||
get_available_purchase_count,
|
||||
get_store_item_name_translated,
|
||||
get_store_item_display_size,
|
||||
get_store_layout,
|
||||
get_store_item,
|
||||
get_clean_price,
|
||||
get_unowned_maps,
|
||||
get_unowned_game_types,
|
||||
)
|
||||
from ba._tournament import get_tournament_prize_strings
|
||||
from ba._gameutils import get_trophy_string
|
||||
|
||||
from ba._internal import (
|
||||
get_v2_fleet, get_master_server_address, is_blessed, get_news_show,
|
||||
game_service_has_leaderboard, report_achievement, submit_score,
|
||||
tournament_query, power_ranking_query, restore_purchases, purchase,
|
||||
get_purchases_state, get_purchased, get_price, in_game_purchase,
|
||||
add_transaction, reset_achievements, get_public_login_id,
|
||||
have_outstanding_transactions, run_transactions,
|
||||
get_v1_account_misc_read_val, get_v1_account_misc_read_val_2,
|
||||
get_v1_account_misc_val, get_v1_account_ticket_count,
|
||||
get_v1_account_state_num, get_v1_account_state,
|
||||
get_v1_account_display_string, get_v1_account_type, get_v1_account_name,
|
||||
sign_out_v1, sign_in_v1, mark_config_dirty)
|
||||
get_v2_fleet,
|
||||
get_master_server_address,
|
||||
is_blessed,
|
||||
get_news_show,
|
||||
game_service_has_leaderboard,
|
||||
report_achievement,
|
||||
submit_score,
|
||||
tournament_query,
|
||||
power_ranking_query,
|
||||
restore_purchases,
|
||||
purchase,
|
||||
get_purchases_state,
|
||||
get_purchased,
|
||||
get_price,
|
||||
in_game_purchase,
|
||||
add_transaction,
|
||||
reset_achievements,
|
||||
get_public_login_id,
|
||||
have_outstanding_transactions,
|
||||
run_transactions,
|
||||
get_v1_account_misc_read_val,
|
||||
get_v1_account_misc_read_val_2,
|
||||
get_v1_account_misc_val,
|
||||
get_v1_account_ticket_count,
|
||||
get_v1_account_state_num,
|
||||
get_v1_account_state,
|
||||
get_v1_account_display_string,
|
||||
get_v1_account_type,
|
||||
get_v1_account_name,
|
||||
sign_out_v1,
|
||||
sign_in_v1,
|
||||
mark_config_dirty,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
'show_online_score_ui',
|
||||
|
||||
@ -24,12 +24,18 @@ class MacMusicAppMusicPlayer(MusicPlayer):
|
||||
self._thread = _MacMusicAppThread()
|
||||
self._thread.start()
|
||||
|
||||
def on_select_entry(self, callback: Callable[[Any], None],
|
||||
current_entry: Any, selection_target_name: str) -> Any:
|
||||
def on_select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.soundtrack import entrytypeselect as etsel
|
||||
return etsel.SoundtrackEntryTypeSelectWindow(callback, current_entry,
|
||||
selection_target_name)
|
||||
|
||||
return etsel.SoundtrackEntryTypeSelectWindow(
|
||||
callback, current_entry, selection_target_name
|
||||
)
|
||||
|
||||
def on_set_volume(self, volume: float) -> None:
|
||||
self._thread.set_volume(volume)
|
||||
@ -44,8 +50,10 @@ class MacMusicAppMusicPlayer(MusicPlayer):
|
||||
if entry_type == 'iTunesPlaylist':
|
||||
self._thread.play_playlist(music.get_soundtrack_entry_name(entry))
|
||||
else:
|
||||
print('MacMusicAppMusicPlayer passed unrecognized entry type:',
|
||||
entry_type)
|
||||
print(
|
||||
'MacMusicAppMusicPlayer passed unrecognized entry type:',
|
||||
entry_type,
|
||||
)
|
||||
|
||||
def on_stop(self) -> None:
|
||||
self._thread.play_playlist(None)
|
||||
@ -70,6 +78,7 @@ class _MacMusicAppThread(threading.Thread):
|
||||
from ba._general import Call
|
||||
from ba._language import Lstr
|
||||
from ba._generated.enums import TimeType
|
||||
|
||||
_ba.set_thread_name('BA_MacMusicAppThread')
|
||||
_ba.mac_music_app_init()
|
||||
|
||||
@ -77,10 +86,15 @@ class _MacMusicAppThread(threading.Thread):
|
||||
# it causes any funny business (this used to background the app
|
||||
# sometimes, though I think that is fixed now)
|
||||
def do_print() -> None:
|
||||
_ba.timer(1.0,
|
||||
Call(_ba.screenmessage, Lstr(resource='usingItunesText'),
|
||||
(0, 1, 0)),
|
||||
timetype=TimeType.REAL)
|
||||
_ba.timer(
|
||||
1.0,
|
||||
Call(
|
||||
_ba.screenmessage,
|
||||
Lstr(resource='usingItunesText'),
|
||||
(0, 1, 0),
|
||||
),
|
||||
timetype=TimeType.REAL,
|
||||
)
|
||||
|
||||
_ba.pushcall(do_print, from_other_thread=True)
|
||||
|
||||
@ -153,15 +167,29 @@ class _MacMusicAppThread(threading.Thread):
|
||||
self._commands_available.set()
|
||||
|
||||
def _handle_get_playlists_command(
|
||||
self, target: Callable[[list[str]], None]) -> None:
|
||||
self, target: Callable[[list[str]], None]
|
||||
) -> None:
|
||||
from ba._general import Call
|
||||
|
||||
try:
|
||||
playlists = _ba.mac_music_app_get_playlists()
|
||||
playlists = [
|
||||
p for p in playlists if p not in [
|
||||
'Music', 'Movies', 'TV Shows', 'Podcasts', 'iTunes\xa0U',
|
||||
'Books', 'Genius', 'iTunes DJ', 'Music Videos',
|
||||
'Home Videos', 'Voice Memos', 'Audiobooks'
|
||||
p
|
||||
for p in playlists
|
||||
if p
|
||||
not in [
|
||||
'Music',
|
||||
'Movies',
|
||||
'TV Shows',
|
||||
'Podcasts',
|
||||
'iTunes\xa0U',
|
||||
'Books',
|
||||
'Genius',
|
||||
'iTunes DJ',
|
||||
'Music Videos',
|
||||
'Home Videos',
|
||||
'Voice Memos',
|
||||
'Audiobooks',
|
||||
]
|
||||
]
|
||||
playlists.sort(key=lambda x: x.lower())
|
||||
@ -194,7 +222,7 @@ class _MacMusicAppThread(threading.Thread):
|
||||
# Set our playlist and play it if our volume is up.
|
||||
self._current_playlist = target
|
||||
if self._volume > 0:
|
||||
self._orig_volume = (_ba.mac_music_app_get_volume())
|
||||
self._orig_volume = _ba.mac_music_app_get_volume()
|
||||
self._update_mac_music_app_volume()
|
||||
self._play_current_playlist()
|
||||
|
||||
@ -213,20 +241,30 @@ class _MacMusicAppThread(threading.Thread):
|
||||
def _play_current_playlist(self) -> None:
|
||||
try:
|
||||
from ba._general import Call
|
||||
|
||||
assert self._current_playlist is not None
|
||||
if _ba.mac_music_app_play_playlist(self._current_playlist):
|
||||
pass
|
||||
else:
|
||||
_ba.pushcall(Call(
|
||||
_ba.screenmessage,
|
||||
_ba.app.lang.get_resource('playlistNotFoundText') +
|
||||
': \'' + self._current_playlist + '\'', (1, 0, 0)),
|
||||
from_other_thread=True)
|
||||
_ba.pushcall(
|
||||
Call(
|
||||
_ba.screenmessage,
|
||||
_ba.app.lang.get_resource('playlistNotFoundText')
|
||||
+ ': \''
|
||||
+ self._current_playlist
|
||||
+ '\'',
|
||||
(1, 0, 0),
|
||||
),
|
||||
from_other_thread=True,
|
||||
)
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception(
|
||||
f'error playing playlist {self._current_playlist}')
|
||||
f'error playing playlist {self._current_playlist}'
|
||||
)
|
||||
|
||||
def _update_mac_music_app_volume(self) -> None:
|
||||
_ba.mac_music_app_set_volume(
|
||||
max(0, min(100, int(100.0 * self._volume))))
|
||||
max(0, min(100, int(100.0 * self._volume)))
|
||||
)
|
||||
|
||||
@ -47,10 +47,12 @@ def _request_storage_permission() -> bool:
|
||||
"""If needed, requests storage permission from the user (& return true)."""
|
||||
from ba._language import Lstr
|
||||
from ba._generated.enums import Permission
|
||||
|
||||
if not _ba.have_permission(Permission.STORAGE):
|
||||
_ba.playsound(_ba.getsound('error'))
|
||||
_ba.screenmessage(Lstr(resource='storagePermissionAccessText'),
|
||||
color=(1, 0, 0))
|
||||
_ba.screenmessage(
|
||||
Lstr(resource='storagePermissionAccessText'), color=(1, 0, 0)
|
||||
)
|
||||
_ba.timer(1.0, lambda: _ba.request_permission(Permission.STORAGE))
|
||||
return True
|
||||
return False
|
||||
@ -80,12 +82,15 @@ def show_user_scripts() -> None:
|
||||
if usd is not None and os.path.isdir(usd):
|
||||
file_name = usd + '/about_this_folder.txt'
|
||||
with open(file_name, 'w', encoding='utf-8') as outfile:
|
||||
outfile.write('You can drop files in here to mod the game.'
|
||||
' See settings/advanced'
|
||||
' in the game for more info.')
|
||||
outfile.write(
|
||||
'You can drop files in here to mod the game.'
|
||||
' See settings/advanced'
|
||||
' in the game for more info.'
|
||||
)
|
||||
_ba.android_media_scan_file(file_name)
|
||||
except Exception:
|
||||
from ba import _error
|
||||
|
||||
_error.print_exception('error writing about_this_folder stuff')
|
||||
|
||||
# On a few platforms we try to open the dir in the UI.
|
||||
@ -103,13 +108,14 @@ def create_user_system_scripts() -> None:
|
||||
(for editing and experiment with)
|
||||
"""
|
||||
import shutil
|
||||
|
||||
app = _ba.app
|
||||
|
||||
# First off, if we need permission for this, ask for it.
|
||||
if _request_storage_permission():
|
||||
return
|
||||
|
||||
path = (app.python_directory_user + '/sys/' + app.version)
|
||||
path = app.python_directory_user + '/sys/' + app.version
|
||||
pathtmp = path + '_tmp'
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
@ -123,31 +129,38 @@ def create_user_system_scripts() -> None:
|
||||
# to blow them away anyway to make changes;
|
||||
# See https://github.com/efroemling/ballistica/wiki
|
||||
# /Knowledge-Nuggets#python-cache-files-gotcha
|
||||
return ('__pycache__', )
|
||||
return ('__pycache__',)
|
||||
|
||||
print(f'COPYING "{app.python_directory_app}" -> "{pathtmp}".')
|
||||
shutil.copytree(app.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||
|
||||
print(f'MOVING "{pathtmp}" -> "{path}".')
|
||||
shutil.move(pathtmp, path)
|
||||
print(f"Created system scripts at :'{path}"
|
||||
f"'\nRestart {_ba.appname()} to use them."
|
||||
f' (use ba.quit() to exit the game)')
|
||||
print(
|
||||
f"Created system scripts at :'{path}"
|
||||
f"'\nRestart {_ba.appname()} to use them."
|
||||
f' (use ba.quit() to exit the game)'
|
||||
)
|
||||
if app.platform == 'android':
|
||||
print('Note: the new files may not be visible via '
|
||||
'android-file-transfer until you restart your device.')
|
||||
print(
|
||||
'Note: the new files may not be visible via '
|
||||
'android-file-transfer until you restart your device.'
|
||||
)
|
||||
|
||||
|
||||
def delete_user_system_scripts() -> None:
|
||||
"""Clean out the scripts created by create_user_system_scripts()."""
|
||||
import shutil
|
||||
|
||||
app = _ba.app
|
||||
path = (app.python_directory_user + '/sys/' + app.version)
|
||||
path = app.python_directory_user + '/sys/' + app.version
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
print(f'User system scripts deleted.\n'
|
||||
f'Restart {_ba.appname()} to use internal'
|
||||
f' scripts. (use ba.quit() to exit the game)')
|
||||
print(
|
||||
f'User system scripts deleted.\n'
|
||||
f'Restart {_ba.appname()} to use internal'
|
||||
f' scripts. (use ba.quit() to exit the game)'
|
||||
)
|
||||
else:
|
||||
print('User system scripts not found.')
|
||||
|
||||
|
||||
@ -31,13 +31,20 @@ class OSMusicPlayer(MusicPlayer):
|
||||
# FIXME: should ask the C++ layer for these; just hard-coding for now.
|
||||
return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid']
|
||||
|
||||
def on_select_entry(self, callback: Callable[[Any], None],
|
||||
current_entry: Any, selection_target_name: str) -> Any:
|
||||
def on_select_entry(
|
||||
self,
|
||||
callback: Callable[[Any], None],
|
||||
current_entry: Any,
|
||||
selection_target_name: str,
|
||||
) -> Any:
|
||||
# pylint: disable=cyclic-import
|
||||
from bastd.ui.soundtrack.entrytypeselect import (
|
||||
SoundtrackEntryTypeSelectWindow)
|
||||
return SoundtrackEntryTypeSelectWindow(callback, current_entry,
|
||||
selection_target_name)
|
||||
SoundtrackEntryTypeSelectWindow,
|
||||
)
|
||||
|
||||
return SoundtrackEntryTypeSelectWindow(
|
||||
callback, current_entry, selection_target_name
|
||||
)
|
||||
|
||||
def on_set_volume(self, volume: float) -> None:
|
||||
_ba.music_player_set_volume(volume)
|
||||
@ -56,22 +63,31 @@ class OSMusicPlayer(MusicPlayer):
|
||||
# valid file within it.
|
||||
self._want_to_play = True
|
||||
self._actually_playing = False
|
||||
_PickFolderSongThread(name, self.get_valid_music_file_extensions(),
|
||||
self._on_play_folder_cb).start()
|
||||
_PickFolderSongThread(
|
||||
name,
|
||||
self.get_valid_music_file_extensions(),
|
||||
self._on_play_folder_cb,
|
||||
).start()
|
||||
|
||||
def _on_play_folder_cb(self,
|
||||
result: str | list[str],
|
||||
error: str | None = None) -> None:
|
||||
def _on_play_folder_cb(
|
||||
self, result: str | list[str], error: str | None = None
|
||||
) -> None:
|
||||
from ba import _language
|
||||
|
||||
if error is not None:
|
||||
rstr = (_language.Lstr(
|
||||
resource='internal.errorPlayingMusicText').evaluate())
|
||||
rstr = _language.Lstr(
|
||||
resource='internal.errorPlayingMusicText'
|
||||
).evaluate()
|
||||
if isinstance(result, str):
|
||||
err_str = (rstr.replace('${MUSIC}', os.path.basename(result)) +
|
||||
'; ' + str(error))
|
||||
err_str = (
|
||||
rstr.replace('${MUSIC}', os.path.basename(result))
|
||||
+ '; '
|
||||
+ str(error)
|
||||
)
|
||||
else:
|
||||
err_str = (rstr.replace('${MUSIC}', '<multiple>') + '; ' +
|
||||
str(error))
|
||||
err_str = (
|
||||
rstr.replace('${MUSIC}', '<multiple>') + '; ' + str(error)
|
||||
)
|
||||
_ba.screenmessage(err_str, color=(1, 0, 0))
|
||||
return
|
||||
|
||||
@ -93,9 +109,12 @@ class OSMusicPlayer(MusicPlayer):
|
||||
|
||||
|
||||
class _PickFolderSongThread(threading.Thread):
|
||||
|
||||
def __init__(self, path: str, valid_extensions: list[str],
|
||||
callback: Callable[[str | list[str], str | None], None]):
|
||||
def __init__(
|
||||
self,
|
||||
path: str,
|
||||
valid_extensions: list[str],
|
||||
callback: Callable[[str | list[str], str | None], None],
|
||||
):
|
||||
super().__init__()
|
||||
self._valid_extensions = valid_extensions
|
||||
self._callback = callback
|
||||
@ -104,6 +123,7 @@ class _PickFolderSongThread(threading.Thread):
|
||||
def run(self) -> None:
|
||||
from ba import _language
|
||||
from ba._general import Call
|
||||
|
||||
do_print_error = True
|
||||
try:
|
||||
_ba.set_thread_name('BA_PickFolderSongThread')
|
||||
@ -111,24 +131,33 @@ class _PickFolderSongThread(threading.Thread):
|
||||
valid_extensions = ['.' + x for x in self._valid_extensions]
|
||||
for root, _subdirs, filenames in os.walk(self._path):
|
||||
for fname in filenames:
|
||||
if any(fname.lower().endswith(ext)
|
||||
for ext in valid_extensions):
|
||||
all_files.insert(random.randrange(len(all_files) + 1),
|
||||
root + '/' + fname)
|
||||
if any(
|
||||
fname.lower().endswith(ext) for ext in valid_extensions
|
||||
):
|
||||
all_files.insert(
|
||||
random.randrange(len(all_files) + 1),
|
||||
root + '/' + fname,
|
||||
)
|
||||
if not all_files:
|
||||
do_print_error = False
|
||||
raise RuntimeError(
|
||||
_language.Lstr(resource='internal.noMusicFilesInFolderText'
|
||||
).evaluate())
|
||||
_ba.pushcall(Call(self._callback, all_files, None),
|
||||
from_other_thread=True)
|
||||
_language.Lstr(
|
||||
resource='internal.noMusicFilesInFolderText'
|
||||
).evaluate()
|
||||
)
|
||||
_ba.pushcall(
|
||||
Call(self._callback, all_files, None), from_other_thread=True
|
||||
)
|
||||
except Exception as exc:
|
||||
from ba import _error
|
||||
|
||||
if do_print_error:
|
||||
_error.print_exception()
|
||||
try:
|
||||
err_str = str(exc)
|
||||
except Exception:
|
||||
err_str = '<ENCERR4523>'
|
||||
_ba.pushcall(Call(self._callback, self._path, err_str),
|
||||
from_other_thread=True)
|
||||
_ba.pushcall(
|
||||
Call(self._callback, self._path, err_str),
|
||||
from_other_thread=True,
|
||||
)
|
||||
|
||||
@ -44,6 +44,7 @@ class Window:
|
||||
@dataclass
|
||||
class UICleanupCheck:
|
||||
"""Holds info about a uicleanupcheck target."""
|
||||
|
||||
obj: weakref.ref
|
||||
widget: ba.Widget
|
||||
widget_death_time: float | None
|
||||
@ -112,6 +113,7 @@ class UIEntry:
|
||||
# TEMP HARD CODED - WILL REPLACE THIS WITH BA_META LOOKUPS.
|
||||
if self._name == 'mainmenu':
|
||||
from bastd.ui import mainmenu
|
||||
|
||||
return cast(Type[UILocation], mainmenu.MainMenuWindow)
|
||||
raise ValueError('unknown ui class ' + str(self._name))
|
||||
|
||||
@ -139,8 +141,9 @@ class UIController:
|
||||
"""Show the main menu, clearing other UIs from location stacks."""
|
||||
self._main_stack = []
|
||||
self._dialog_stack = []
|
||||
self._main_stack = (self._main_stack_game
|
||||
if in_game else self._main_stack_menu)
|
||||
self._main_stack = (
|
||||
self._main_stack_game if in_game else self._main_stack_menu
|
||||
)
|
||||
self._main_stack.append(UIEntry('mainmenu', self))
|
||||
self._update_ui()
|
||||
|
||||
@ -154,8 +157,13 @@ class UIController:
|
||||
entry.destroy()
|
||||
|
||||
# Now create the topmost one if there is one.
|
||||
entrynew = (self._dialog_stack[-1] if self._dialog_stack else
|
||||
self._main_stack[-1] if self._main_stack else None)
|
||||
entrynew = (
|
||||
self._dialog_stack[-1]
|
||||
if self._dialog_stack
|
||||
else self._main_stack[-1]
|
||||
if self._main_stack
|
||||
else None
|
||||
)
|
||||
if entrynew is not None:
|
||||
entrynew.create()
|
||||
|
||||
@ -190,9 +198,10 @@ def uicleanupcheck(obj: Any, widget: ba.Widget) -> None:
|
||||
widget.add_delete_callback(foobar)
|
||||
|
||||
_ba.app.ui.cleanupchecks.append(
|
||||
UICleanupCheck(obj=weakref.ref(obj),
|
||||
widget=widget,
|
||||
widget_death_time=None))
|
||||
UICleanupCheck(
|
||||
obj=weakref.ref(obj), widget=widget, widget_death_time=None
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def ui_upkeep() -> None:
|
||||
@ -218,9 +227,11 @@ def ui_upkeep() -> None:
|
||||
# Widget was already dead; complain if its been too long.
|
||||
if now - check.widget_death_time > 5.0:
|
||||
print(
|
||||
'WARNING:', obj,
|
||||
'WARNING:',
|
||||
obj,
|
||||
'is still alive 5 second after its widget died;'
|
||||
' you might have a memory leak.')
|
||||
' you might have a memory leak.',
|
||||
)
|
||||
print_active_refs(obj)
|
||||
|
||||
else:
|
||||
|
||||
@ -27,19 +27,23 @@ class CoopJoinActivity(JoinActivity):
|
||||
def on_transition_in(self) -> None:
|
||||
from bastd.actor.controlsguide import ControlsGuide
|
||||
from bastd.actor.text import Text
|
||||
|
||||
super().on_transition_in()
|
||||
assert isinstance(self.session, ba.CoopSession)
|
||||
assert self.session.campaign
|
||||
Text(self.session.campaign.getlevel(
|
||||
self.session.campaign_level_name).displayname,
|
||||
scale=1.3,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=4.0,
|
||||
color=(1, 1, 1, 0.6),
|
||||
position=(0, -95)).autoretain()
|
||||
Text(
|
||||
self.session.campaign.getlevel(
|
||||
self.session.campaign_level_name
|
||||
).displayname,
|
||||
scale=1.3,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=4.0,
|
||||
color=(1, 1, 1, 0.6),
|
||||
position=(0, -95),
|
||||
).autoretain()
|
||||
ControlsGuide(delay=1.0).autoretain()
|
||||
|
||||
ba.pushcall(self._show_remaining_achievements)
|
||||
@ -60,30 +64,34 @@ class CoopJoinActivity(JoinActivity):
|
||||
# Now list our remaining achievements for this level.
|
||||
assert self.session.campaign is not None
|
||||
assert isinstance(self.session, ba.CoopSession)
|
||||
levelname = (self.session.campaign.name + ':' +
|
||||
self.session.campaign_level_name)
|
||||
levelname = (
|
||||
self.session.campaign.name + ':' + self.session.campaign_level_name
|
||||
)
|
||||
ts_h_offs = 60
|
||||
|
||||
if not (ba.app.demo_mode or ba.app.arcade_mode):
|
||||
achievements = [
|
||||
a for a in ba.app.ach.achievements_for_coop_level(levelname)
|
||||
a
|
||||
for a in ba.app.ach.achievements_for_coop_level(levelname)
|
||||
if not a.complete
|
||||
]
|
||||
have_achievements = bool(achievements)
|
||||
achievements = [a for a in achievements if not a.complete]
|
||||
vrmode = ba.app.vr_mode
|
||||
if have_achievements:
|
||||
Text(ba.Lstr(resource='achievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs - 10, vpos),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=1.1 * 0.76,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1),
|
||||
shadow=1.0,
|
||||
flatness=1.0 if vrmode else 0.6,
|
||||
transition_delay=delay).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='achievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs - 10, vpos),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=1.1 * 0.76,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1.2, 1) if vrmode else (0.8, 0.8, 1, 1),
|
||||
shadow=1.0,
|
||||
flatness=1.0 if vrmode else 0.6,
|
||||
transition_delay=delay,
|
||||
).autoretain()
|
||||
hval = ts_h_offs + 50
|
||||
vpos -= 35
|
||||
for ach in achievements:
|
||||
@ -91,12 +99,14 @@ class CoopJoinActivity(JoinActivity):
|
||||
ach.create_display(hval, vpos, delay, style='in_game')
|
||||
vpos -= 55
|
||||
if not achievements:
|
||||
Text(ba.Lstr(resource='noAchievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs + 15, vpos + 10),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=0.7,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1, 0.5),
|
||||
transition_delay=delay + 0.5).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='noAchievementsRemainingText'),
|
||||
host_only=True,
|
||||
position=(ts_h_offs + 15, vpos + 10),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
scale=0.7,
|
||||
h_attach=Text.HAttach.LEFT,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
color=(1, 1, 1, 0.5),
|
||||
transition_delay=delay + 0.5,
|
||||
).autoretain()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -22,13 +22,15 @@ class DrawScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
def on_begin(self) -> None:
|
||||
ba.set_analytics_screen('Draw Score Screen')
|
||||
super().on_begin()
|
||||
ZoomText(ba.Lstr(resource='drawText'),
|
||||
position=(0, 0),
|
||||
maxwidth=400,
|
||||
shiftposition=(-220, 0),
|
||||
shiftdelay=2.0,
|
||||
flash=False,
|
||||
trail=False,
|
||||
jitter=1.0).autoretain()
|
||||
ZoomText(
|
||||
ba.Lstr(resource='drawText'),
|
||||
position=(0, 0),
|
||||
maxwidth=400,
|
||||
shiftposition=(-220, 0),
|
||||
shiftdelay=2.0,
|
||||
flash=False,
|
||||
trail=False,
|
||||
jitter=1.0,
|
||||
).autoretain()
|
||||
ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound))
|
||||
self.show_player_scores(results=self.settings_raw.get('results', None))
|
||||
|
||||
@ -37,91 +37,132 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
session = self.session
|
||||
assert isinstance(session, ba.MultiTeamSession)
|
||||
if ba.app.lang.get_resource('bestOfUseFirstToInstead'):
|
||||
best_txt = ba.Lstr(resource='firstToSeriesText',
|
||||
subs=[('${COUNT}',
|
||||
str(session.get_series_length() / 2 + 1))
|
||||
])
|
||||
best_txt = ba.Lstr(
|
||||
resource='firstToSeriesText',
|
||||
subs=[('${COUNT}', str(session.get_series_length() / 2 + 1))],
|
||||
)
|
||||
else:
|
||||
best_txt = ba.Lstr(resource='bestOfSeriesText',
|
||||
subs=[('${COUNT}',
|
||||
str(session.get_series_length()))])
|
||||
best_txt = ba.Lstr(
|
||||
resource='bestOfSeriesText',
|
||||
subs=[('${COUNT}', str(session.get_series_length()))],
|
||||
)
|
||||
|
||||
ZoomText(best_txt,
|
||||
position=(0, 175),
|
||||
shiftposition=(-250, 175),
|
||||
shiftdelay=2.5,
|
||||
flash=False,
|
||||
trail=False,
|
||||
h_align='center',
|
||||
scale=0.25,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
jitter=3.0).autoretain()
|
||||
ZoomText(
|
||||
best_txt,
|
||||
position=(0, 175),
|
||||
shiftposition=(-250, 175),
|
||||
shiftdelay=2.5,
|
||||
flash=False,
|
||||
trail=False,
|
||||
h_align='center',
|
||||
scale=0.25,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
jitter=3.0,
|
||||
).autoretain()
|
||||
for team in self.session.sessionteams:
|
||||
ba.timer(
|
||||
i * 0.15 + 0.15,
|
||||
ba.WeakCall(self._show_team_name, vval - i * height, team,
|
||||
i * 0.2, shift_time - (i * 0.150 + 0.150)))
|
||||
ba.timer(i * 0.150 + 0.5,
|
||||
ba.Call(ba.playsound, self._score_display_sound_small))
|
||||
scored = (team is self._winner)
|
||||
ba.WeakCall(
|
||||
self._show_team_name,
|
||||
vval - i * height,
|
||||
team,
|
||||
i * 0.2,
|
||||
shift_time - (i * 0.150 + 0.150),
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
i * 0.150 + 0.5,
|
||||
ba.Call(ba.playsound, self._score_display_sound_small),
|
||||
)
|
||||
scored = team is self._winner
|
||||
delay = 0.2
|
||||
if scored:
|
||||
delay = 1.2
|
||||
ba.timer(
|
||||
i * 0.150 + 0.2,
|
||||
ba.WeakCall(self._show_team_old_score, vval - i * height,
|
||||
team, shift_time - (i * 0.15 + 0.2)))
|
||||
ba.timer(i * 0.15 + 1.5,
|
||||
ba.Call(ba.playsound, self._score_display_sound))
|
||||
ba.WeakCall(
|
||||
self._show_team_old_score,
|
||||
vval - i * height,
|
||||
team,
|
||||
shift_time - (i * 0.15 + 0.2),
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
i * 0.15 + 1.5,
|
||||
ba.Call(ba.playsound, self._score_display_sound),
|
||||
)
|
||||
|
||||
ba.timer(
|
||||
i * 0.150 + delay,
|
||||
ba.WeakCall(self._show_team_score, vval - i * height, team,
|
||||
scored, i * 0.2 + 0.1,
|
||||
shift_time - (i * 0.15 + delay)))
|
||||
ba.WeakCall(
|
||||
self._show_team_score,
|
||||
vval - i * height,
|
||||
team,
|
||||
scored,
|
||||
i * 0.2 + 0.1,
|
||||
shift_time - (i * 0.15 + delay),
|
||||
),
|
||||
)
|
||||
i += 1
|
||||
self.show_player_scores()
|
||||
|
||||
def _show_team_name(self, pos_v: float, team: ba.SessionTeam,
|
||||
kill_delay: float, shiftdelay: float) -> None:
|
||||
def _show_team_name(
|
||||
self,
|
||||
pos_v: float,
|
||||
team: ba.SessionTeam,
|
||||
kill_delay: float,
|
||||
shiftdelay: float,
|
||||
) -> None:
|
||||
del kill_delay # Unused arg.
|
||||
ZoomText(ba.Lstr(value='${A}:', subs=[('${A}', team.name)]),
|
||||
position=(100, pos_v),
|
||||
shiftposition=(-150, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=False,
|
||||
trail=False,
|
||||
h_align='right',
|
||||
maxwidth=300,
|
||||
color=team.color,
|
||||
jitter=1.0).autoretain()
|
||||
ZoomText(
|
||||
ba.Lstr(value='${A}:', subs=[('${A}', team.name)]),
|
||||
position=(100, pos_v),
|
||||
shiftposition=(-150, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=False,
|
||||
trail=False,
|
||||
h_align='right',
|
||||
maxwidth=300,
|
||||
color=team.color,
|
||||
jitter=1.0,
|
||||
).autoretain()
|
||||
|
||||
def _show_team_old_score(self, pos_v: float, sessionteam: ba.SessionTeam,
|
||||
shiftdelay: float) -> None:
|
||||
ZoomText(str(sessionteam.customdata['score'] - 1),
|
||||
position=(150, pos_v),
|
||||
maxwidth=100,
|
||||
color=(0.6, 0.6, 0.7),
|
||||
shiftposition=(-100, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=False,
|
||||
trail=False,
|
||||
lifespan=1.0,
|
||||
h_align='left',
|
||||
jitter=1.0).autoretain()
|
||||
def _show_team_old_score(
|
||||
self, pos_v: float, sessionteam: ba.SessionTeam, shiftdelay: float
|
||||
) -> None:
|
||||
ZoomText(
|
||||
str(sessionteam.customdata['score'] - 1),
|
||||
position=(150, pos_v),
|
||||
maxwidth=100,
|
||||
color=(0.6, 0.6, 0.7),
|
||||
shiftposition=(-100, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=False,
|
||||
trail=False,
|
||||
lifespan=1.0,
|
||||
h_align='left',
|
||||
jitter=1.0,
|
||||
).autoretain()
|
||||
|
||||
def _show_team_score(self, pos_v: float, sessionteam: ba.SessionTeam,
|
||||
scored: bool, kill_delay: float,
|
||||
shiftdelay: float) -> None:
|
||||
def _show_team_score(
|
||||
self,
|
||||
pos_v: float,
|
||||
sessionteam: ba.SessionTeam,
|
||||
scored: bool,
|
||||
kill_delay: float,
|
||||
shiftdelay: float,
|
||||
) -> None:
|
||||
del kill_delay # Unused arg.
|
||||
ZoomText(str(sessionteam.customdata['score']),
|
||||
position=(150, pos_v),
|
||||
maxwidth=100,
|
||||
color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7),
|
||||
shiftposition=(-100, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=scored,
|
||||
trail=scored,
|
||||
h_align='left',
|
||||
jitter=1.0,
|
||||
trailcolor=(1, 0.8, 0.0, 0)).autoretain()
|
||||
ZoomText(
|
||||
str(sessionteam.customdata['score']),
|
||||
position=(150, pos_v),
|
||||
maxwidth=100,
|
||||
color=(1.0, 0.9, 0.5) if scored else (0.6, 0.6, 0.7),
|
||||
shiftposition=(-100, pos_v),
|
||||
shiftdelay=shiftdelay,
|
||||
flash=scored,
|
||||
trail=scored,
|
||||
h_align='left',
|
||||
jitter=1.0,
|
||||
trailcolor=(1, 0.8, 0.0, 0),
|
||||
).autoretain()
|
||||
|
||||
@ -28,6 +28,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
# pylint: disable=too-many-statements
|
||||
from bastd.actor.text import Text
|
||||
from bastd.actor.image import Image
|
||||
|
||||
ba.set_analytics_screen('FreeForAll Score Screen')
|
||||
super().on_begin()
|
||||
|
||||
@ -45,14 +46,17 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
key=lambda p: (
|
||||
p.team.sessionteam.customdata['previous_score'],
|
||||
p.getname(full=True),
|
||||
))
|
||||
),
|
||||
)
|
||||
player_order = list(self.players)
|
||||
player_order.sort(reverse=True,
|
||||
key=lambda p: (
|
||||
p.team.sessionteam.customdata['score'],
|
||||
p.team.sessionteam.customdata['score'],
|
||||
p.getname(full=True),
|
||||
))
|
||||
player_order.sort(
|
||||
reverse=True,
|
||||
key=lambda p: (
|
||||
p.team.sessionteam.customdata['score'],
|
||||
p.team.sessionteam.customdata['score'],
|
||||
p.getname(full=True),
|
||||
),
|
||||
)
|
||||
|
||||
v_offs = -74.0 + spacing * len(player_order_prev) * 0.5
|
||||
delay1 = 1.3 + 0.1
|
||||
@ -66,30 +70,36 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
ba.timer(0.3, ba.Call(ba.playsound, self._score_display_sound))
|
||||
results = self.settings_raw['results']
|
||||
assert isinstance(results, ba.GameResults)
|
||||
self.show_player_scores(delay=0.001,
|
||||
results=results,
|
||||
scale=1.2,
|
||||
x_offset=-110.0)
|
||||
self.show_player_scores(
|
||||
delay=0.001, results=results, scale=1.2, x_offset=-110.0
|
||||
)
|
||||
|
||||
sound_times: set[float] = set()
|
||||
|
||||
def _scoretxt(text: str,
|
||||
x_offs: float,
|
||||
y_offs: float,
|
||||
highlight: bool,
|
||||
delay: float,
|
||||
extrascale: float,
|
||||
flash: bool = False) -> Text:
|
||||
return Text(text,
|
||||
position=(ts_h_offs + x_offs * scale,
|
||||
y_base + (y_offs + v_offs + 2.0) * scale),
|
||||
scale=scale * extrascale,
|
||||
color=((1.0, 0.7, 0.3, 1.0) if highlight else
|
||||
(0.7, 0.7, 0.7, 0.7)),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay + delay,
|
||||
flash=flash).autoretain()
|
||||
def _scoretxt(
|
||||
text: str,
|
||||
x_offs: float,
|
||||
y_offs: float,
|
||||
highlight: bool,
|
||||
delay: float,
|
||||
extrascale: float,
|
||||
flash: bool = False,
|
||||
) -> Text:
|
||||
return Text(
|
||||
text,
|
||||
position=(
|
||||
ts_h_offs + x_offs * scale,
|
||||
y_base + (y_offs + v_offs + 2.0) * scale,
|
||||
),
|
||||
scale=scale * extrascale,
|
||||
color=(
|
||||
(1.0, 0.7, 0.3, 1.0) if highlight else (0.7, 0.7, 0.7, 0.7)
|
||||
),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay + delay,
|
||||
flash=flash,
|
||||
).autoretain()
|
||||
|
||||
v_offs -= spacing
|
||||
slide_amt = 0.0
|
||||
@ -98,16 +108,21 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
|
||||
session = self.session
|
||||
assert isinstance(session, ba.FreeForAllSession)
|
||||
title = Text(ba.Lstr(resource='firstToSeriesText',
|
||||
subs=[('${COUNT}',
|
||||
str(session.get_ffa_series_length()))]),
|
||||
scale=1.05 * scale,
|
||||
position=(ts_h_offs - 0.0 * scale,
|
||||
y_base + (v_offs + 50.0) * scale),
|
||||
h_align=Text.HAlign.CENTER,
|
||||
color=(0.5, 0.5, 0.5, 0.5),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
title = Text(
|
||||
ba.Lstr(
|
||||
resource='firstToSeriesText',
|
||||
subs=[('${COUNT}', str(session.get_ffa_series_length()))],
|
||||
),
|
||||
scale=1.05 * scale,
|
||||
position=(
|
||||
ts_h_offs - 0.0 * scale,
|
||||
y_base + (v_offs + 50.0) * scale,
|
||||
),
|
||||
h_align=Text.HAlign.CENTER,
|
||||
color=(0.5, 0.5, 0.5, 0.5),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
|
||||
v_offs -= 25
|
||||
v_offs_start = v_offs
|
||||
@ -115,152 +130,239 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, title.position_combine, 'input0', {
|
||||
self._safe_animate,
|
||||
title.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0.0: ts_h_offs - 0.0 * scale,
|
||||
transtime2: ts_h_offs - (0.0 + slide_amt) * scale
|
||||
}))
|
||||
transtime2: ts_h_offs - (0.0 + slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
for i, player in enumerate(player_order_prev):
|
||||
v_offs_2 = v_offs_start - spacing * (player_order.index(player))
|
||||
ba.timer(tdelay + 0.3,
|
||||
ba.Call(ba.playsound, self._score_display_sound_small))
|
||||
ba.timer(
|
||||
tdelay + 0.3,
|
||||
ba.Call(ba.playsound, self._score_display_sound_small),
|
||||
)
|
||||
if order_change:
|
||||
ba.timer(tdelay + delay2 + 0.1,
|
||||
ba.Call(ba.playsound, self._cymbal_sound))
|
||||
img = Image(player.get_icon(),
|
||||
position=(ts_h_offs - 72.0 * scale,
|
||||
y_base + (v_offs + 15.0) * scale),
|
||||
scale=(30.0 * scale, 30.0 * scale),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
ba.timer(
|
||||
tdelay + delay2 + 0.1,
|
||||
ba.Call(ba.playsound, self._cymbal_sound),
|
||||
)
|
||||
img = Image(
|
||||
player.get_icon(),
|
||||
position=(
|
||||
ts_h_offs - 72.0 * scale,
|
||||
y_base + (v_offs + 15.0) * scale,
|
||||
),
|
||||
scale=(30.0 * scale, 30.0 * scale),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
ba.timer(
|
||||
tdelay + delay2,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, img.position_combine, 'input1', {
|
||||
self._safe_animate,
|
||||
img.position_combine,
|
||||
'input1',
|
||||
{
|
||||
0: y_base + (v_offs + 15.0) * scale,
|
||||
transtime: y_base + (v_offs_2 + 15.0) * scale
|
||||
}))
|
||||
transtime: y_base + (v_offs_2 + 15.0) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, img.position_combine, 'input0', {
|
||||
self._safe_animate,
|
||||
img.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0: ts_h_offs - 72.0 * scale,
|
||||
transtime2: ts_h_offs - (72.0 + slide_amt) * scale
|
||||
}))
|
||||
txt = Text(ba.Lstr(value=player.getname(full=True)),
|
||||
maxwidth=130.0,
|
||||
scale=0.75 * scale,
|
||||
position=(ts_h_offs - 50.0 * scale,
|
||||
y_base + (v_offs + 15.0) * scale),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(player.team.color + (1, )),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
transtime2: ts_h_offs - (72.0 + slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
txt = Text(
|
||||
ba.Lstr(value=player.getname(full=True)),
|
||||
maxwidth=130.0,
|
||||
scale=0.75 * scale,
|
||||
position=(
|
||||
ts_h_offs - 50.0 * scale,
|
||||
y_base + (v_offs + 15.0) * scale,
|
||||
),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(player.team.color + (1,)),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
ba.timer(
|
||||
tdelay + delay2,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, txt.position_combine, 'input1', {
|
||||
self._safe_animate,
|
||||
txt.position_combine,
|
||||
'input1',
|
||||
{
|
||||
0: y_base + (v_offs + 15.0) * scale,
|
||||
transtime: y_base + (v_offs_2 + 15.0) * scale
|
||||
}))
|
||||
transtime: y_base + (v_offs_2 + 15.0) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, txt.position_combine, 'input0', {
|
||||
self._safe_animate,
|
||||
txt.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0: ts_h_offs - 50.0 * scale,
|
||||
transtime2: ts_h_offs - (50.0 + slide_amt) * scale
|
||||
}))
|
||||
transtime2: ts_h_offs - (50.0 + slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
txt_num = Text('#' + str(i + 1),
|
||||
scale=0.55 * scale,
|
||||
position=(ts_h_offs - 95.0 * scale,
|
||||
y_base + (v_offs + 8.0) * scale),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
color=(0.6, 0.6, 0.6, 0.6),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
txt_num = Text(
|
||||
'#' + str(i + 1),
|
||||
scale=0.55 * scale,
|
||||
position=(
|
||||
ts_h_offs - 95.0 * scale,
|
||||
y_base + (v_offs + 8.0) * scale,
|
||||
),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
color=(0.6, 0.6, 0.6, 0.6),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, txt_num.position_combine, 'input0', {
|
||||
self._safe_animate,
|
||||
txt_num.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0: ts_h_offs - 95.0 * scale,
|
||||
transtime2: ts_h_offs - (95.0 + slide_amt) * scale
|
||||
}))
|
||||
transtime2: ts_h_offs - (95.0 + slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
s_txt = _scoretxt(
|
||||
str(player.team.sessionteam.customdata['previous_score']), 80,
|
||||
0, False, 0, 1.0)
|
||||
str(player.team.sessionteam.customdata['previous_score']),
|
||||
80,
|
||||
0,
|
||||
False,
|
||||
0,
|
||||
1.0,
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay2,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, s_txt.position_combine, 'input1', {
|
||||
self._safe_animate,
|
||||
s_txt.position_combine,
|
||||
'input1',
|
||||
{
|
||||
0: y_base + (v_offs + 2.0) * scale,
|
||||
transtime: y_base + (v_offs_2 + 2.0) * scale
|
||||
}))
|
||||
transtime: y_base + (v_offs_2 + 2.0) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, s_txt.position_combine, 'input0', {
|
||||
self._safe_animate,
|
||||
s_txt.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0: ts_h_offs + 80.0 * scale,
|
||||
transtime2: ts_h_offs + (80.0 - slide_amt) * scale
|
||||
}))
|
||||
transtime2: ts_h_offs + (80.0 - slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
score_change = (
|
||||
player.team.sessionteam.customdata['score'] -
|
||||
player.team.sessionteam.customdata['previous_score'])
|
||||
player.team.sessionteam.customdata['score']
|
||||
- player.team.sessionteam.customdata['previous_score']
|
||||
)
|
||||
if score_change > 0:
|
||||
xval = 113
|
||||
yval = 3.0
|
||||
s_txt_2 = _scoretxt('+' + str(score_change),
|
||||
xval,
|
||||
yval,
|
||||
True,
|
||||
0,
|
||||
0.7,
|
||||
flash=True)
|
||||
s_txt_2 = _scoretxt(
|
||||
'+' + str(score_change),
|
||||
xval,
|
||||
yval,
|
||||
True,
|
||||
0,
|
||||
0.7,
|
||||
flash=True,
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay2,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, s_txt_2.position_combine, 'input1',
|
||||
self._safe_animate,
|
||||
s_txt_2.position_combine,
|
||||
'input1',
|
||||
{
|
||||
0: y_base + (v_offs + yval + 2.0) * scale,
|
||||
transtime: y_base + (v_offs_2 + yval + 2.0) * scale
|
||||
}))
|
||||
transtime: y_base + (v_offs_2 + yval + 2.0) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
ba.timer(
|
||||
tdelay + delay3,
|
||||
ba.WeakCall(
|
||||
self._safe_animate, s_txt_2.position_combine, 'input0',
|
||||
self._safe_animate,
|
||||
s_txt_2.position_combine,
|
||||
'input0',
|
||||
{
|
||||
0: ts_h_offs + xval * scale,
|
||||
transtime2: ts_h_offs + (xval - slide_amt) * scale
|
||||
}))
|
||||
transtime2: ts_h_offs + (xval - slide_amt) * scale,
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
def _safesetattr(node: ba.Node | None, attr: str,
|
||||
value: Any) -> None:
|
||||
def _safesetattr(
|
||||
node: ba.Node | None, attr: str, value: Any
|
||||
) -> None:
|
||||
if node:
|
||||
setattr(node, attr, value)
|
||||
|
||||
ba.timer(
|
||||
tdelay + delay1,
|
||||
ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)))
|
||||
ba.Call(_safesetattr, s_txt.node, 'color', (1, 1, 1, 1)),
|
||||
)
|
||||
for j in range(score_change):
|
||||
ba.timer((tdelay + delay1 + 0.15 * j),
|
||||
ba.Call(
|
||||
_safesetattr, s_txt.node, 'text',
|
||||
str(player.team.sessionteam.
|
||||
customdata['previous_score'] + j + 1)))
|
||||
ba.timer(
|
||||
(tdelay + delay1 + 0.15 * j),
|
||||
ba.Call(
|
||||
_safesetattr,
|
||||
s_txt.node,
|
||||
'text',
|
||||
str(
|
||||
player.team.sessionteam.customdata[
|
||||
'previous_score'
|
||||
]
|
||||
+ j
|
||||
+ 1
|
||||
),
|
||||
),
|
||||
)
|
||||
tfin = tdelay + delay1 + 0.15 * j
|
||||
if tfin not in sound_times:
|
||||
sound_times.add(tfin)
|
||||
ba.timer(
|
||||
tfin,
|
||||
ba.Call(ba.playsound,
|
||||
self._score_display_sound_small))
|
||||
ba.Call(
|
||||
ba.playsound, self._score_display_sound_small
|
||||
),
|
||||
)
|
||||
v_offs -= spacing
|
||||
|
||||
def _safe_animate(self, node: ba.Node | None, attr: str,
|
||||
keys: dict[float, float]) -> None:
|
||||
def _safe_animate(
|
||||
self, node: ba.Node | None, attr: str, keys: dict[float, float]
|
||||
) -> None:
|
||||
"""Run an animation on a node if the node still exists."""
|
||||
if node:
|
||||
ba.animate(node, attr, keys)
|
||||
|
||||
@ -24,6 +24,7 @@ class MultiTeamJoinActivity(JoinActivity):
|
||||
def on_transition_in(self) -> None:
|
||||
from bastd.actor.controlsguide import ControlsGuide
|
||||
from ba import DualTeamSession
|
||||
|
||||
super().on_transition_in()
|
||||
ControlsGuide(delay=1.0).autoretain()
|
||||
|
||||
@ -31,50 +32,62 @@ class MultiTeamJoinActivity(JoinActivity):
|
||||
assert isinstance(session, ba.MultiTeamSession)
|
||||
|
||||
# Show info about the next up game.
|
||||
self._next_up_text = Text(ba.Lstr(
|
||||
value='${1} ${2}',
|
||||
subs=[('${1}', ba.Lstr(resource='upFirstText')),
|
||||
('${2}', session.get_next_game_description())]),
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
scale=0.7,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(0, -70),
|
||||
flash=False,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=5.0)
|
||||
self._next_up_text = Text(
|
||||
ba.Lstr(
|
||||
value='${1} ${2}',
|
||||
subs=[
|
||||
('${1}', ba.Lstr(resource='upFirstText')),
|
||||
('${2}', session.get_next_game_description()),
|
||||
],
|
||||
),
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
scale=0.7,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(0, -70),
|
||||
flash=False,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=5.0,
|
||||
)
|
||||
|
||||
# In teams mode, show our two team names.
|
||||
# FIXME: Lobby should handle this.
|
||||
if isinstance(ba.getsession(), DualTeamSession):
|
||||
team_names = [team.name for team in ba.getsession().sessionteams]
|
||||
team_colors = [
|
||||
tuple(team.color) + (0.5, )
|
||||
tuple(team.color) + (0.5,)
|
||||
for team in ba.getsession().sessionteams
|
||||
]
|
||||
if len(team_names) == 2:
|
||||
for i in range(2):
|
||||
Text(team_names[i],
|
||||
scale=0.7,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(-200 + 350 * i, -100),
|
||||
color=team_colors[i],
|
||||
transition=Text.Transition.FADE_IN).autoretain()
|
||||
Text(
|
||||
team_names[i],
|
||||
scale=0.7,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
v_attach=Text.VAttach.TOP,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(-200 + 350 * i, -100),
|
||||
color=team_colors[i],
|
||||
transition=Text.Transition.FADE_IN,
|
||||
).autoretain()
|
||||
|
||||
Text(ba.Lstr(resource='mustInviteFriendsText',
|
||||
subs=[('${GATHER}',
|
||||
ba.Lstr(resource='gatherWindow.titleText'))]),
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
scale=0.8,
|
||||
host_only=True,
|
||||
v_attach=Text.VAttach.CENTER,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(0, 0),
|
||||
flash=False,
|
||||
color=(0, 1, 0, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=2.0,
|
||||
transition_out_delay=7.0).autoretain()
|
||||
Text(
|
||||
ba.Lstr(
|
||||
resource='mustInviteFriendsText',
|
||||
subs=[
|
||||
('${GATHER}', ba.Lstr(resource='gatherWindow.titleText'))
|
||||
],
|
||||
),
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
scale=0.8,
|
||||
host_only=True,
|
||||
v_attach=Text.VAttach.CENTER,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
position=(0, 0),
|
||||
flash=False,
|
||||
color=(0, 1, 0, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=2.0,
|
||||
transition_out_delay=7.0,
|
||||
).autoretain()
|
||||
|
||||
@ -28,34 +28,43 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
||||
super().on_begin()
|
||||
session = self.session
|
||||
if self._show_up_next and isinstance(session, ba.MultiTeamSession):
|
||||
txt = ba.Lstr(value='${A} ${B}',
|
||||
subs=[
|
||||
('${A}',
|
||||
ba.Lstr(resource='upNextText',
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(session.get_game_number() + 1))
|
||||
])),
|
||||
('${B}', session.get_next_game_description())
|
||||
])
|
||||
Text(txt,
|
||||
maxwidth=900,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
v_attach=Text.VAttach.BOTTOM,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
position=(0, 53),
|
||||
flash=False,
|
||||
color=(0.3, 0.3, 0.35, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=2.0).autoretain()
|
||||
txt = ba.Lstr(
|
||||
value='${A} ${B}',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='upNextText',
|
||||
subs=[
|
||||
('${COUNT}', str(session.get_game_number() + 1))
|
||||
],
|
||||
),
|
||||
),
|
||||
('${B}', session.get_next_game_description()),
|
||||
],
|
||||
)
|
||||
Text(
|
||||
txt,
|
||||
maxwidth=900,
|
||||
h_attach=Text.HAttach.CENTER,
|
||||
v_attach=Text.VAttach.BOTTOM,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
position=(0, 53),
|
||||
flash=False,
|
||||
color=(0.3, 0.3, 0.35, 1.0),
|
||||
transition=Text.Transition.FADE_IN,
|
||||
transition_delay=2.0,
|
||||
).autoretain()
|
||||
|
||||
def show_player_scores(self,
|
||||
delay: float = 2.5,
|
||||
results: ba.GameResults | None = None,
|
||||
scale: float = 1.0,
|
||||
x_offset: float = 0.0,
|
||||
y_offset: float = 0.0) -> None:
|
||||
def show_player_scores(
|
||||
self,
|
||||
delay: float = 2.5,
|
||||
results: ba.GameResults | None = None,
|
||||
scale: float = 1.0,
|
||||
x_offset: float = 0.0,
|
||||
y_offset: float = 0.0,
|
||||
) -> None:
|
||||
"""Show scores for individual players."""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
@ -96,7 +105,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def _get_player_score_set_entry(
|
||||
player: ba.SessionPlayer) -> ba.PlayerRecord | None:
|
||||
player: ba.SessionPlayer,
|
||||
) -> ba.PlayerRecord | None:
|
||||
for p_rec in valid_players:
|
||||
if p_rec[1].player is player:
|
||||
return p_rec[1]
|
||||
@ -108,7 +118,8 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
||||
for team in winnergroup.teams:
|
||||
if len(team.players) == 1:
|
||||
player_entry = _get_player_score_set_entry(
|
||||
team.players[0])
|
||||
team.players[0]
|
||||
)
|
||||
if player_entry is not None:
|
||||
player_records.append(player_entry)
|
||||
else:
|
||||
@ -124,33 +135,43 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
||||
|
||||
voffs = -140.0 + spacing * len(player_records) * 0.5
|
||||
|
||||
def _txt(xoffs: float,
|
||||
yoffs: float,
|
||||
text: ba.Lstr,
|
||||
h_align: Text.HAlign = Text.HAlign.RIGHT,
|
||||
extrascale: float = 1.0,
|
||||
maxwidth: float | None = 120.0) -> None:
|
||||
Text(text,
|
||||
color=(0.5, 0.5, 0.6, 0.5),
|
||||
position=(ts_h_offs + xoffs * scale,
|
||||
ts_v_offset + (voffs + yoffs + 4.0) * scale),
|
||||
h_align=h_align,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
scale=0.8 * scale * extrascale,
|
||||
maxwidth=maxwidth,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
def _txt(
|
||||
xoffs: float,
|
||||
yoffs: float,
|
||||
text: ba.Lstr,
|
||||
h_align: Text.HAlign = Text.HAlign.RIGHT,
|
||||
extrascale: float = 1.0,
|
||||
maxwidth: float | None = 120.0,
|
||||
) -> None:
|
||||
Text(
|
||||
text,
|
||||
color=(0.5, 0.5, 0.6, 0.5),
|
||||
position=(
|
||||
ts_h_offs + xoffs * scale,
|
||||
ts_v_offset + (voffs + yoffs + 4.0) * scale,
|
||||
),
|
||||
h_align=h_align,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
scale=0.8 * scale * extrascale,
|
||||
maxwidth=maxwidth,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
|
||||
session = self.session
|
||||
assert isinstance(session, ba.MultiTeamSession)
|
||||
tval = ba.Lstr(resource='gameLeadersText',
|
||||
subs=[('${COUNT}', str(session.get_game_number()))])
|
||||
_txt(180,
|
||||
43,
|
||||
tval,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
extrascale=1.4,
|
||||
maxwidth=None)
|
||||
tval = ba.Lstr(
|
||||
resource='gameLeadersText',
|
||||
subs=[('${COUNT}', str(session.get_game_number()))],
|
||||
)
|
||||
_txt(
|
||||
180,
|
||||
43,
|
||||
tval,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
extrascale=1.4,
|
||||
maxwidth=None,
|
||||
)
|
||||
_txt(-15, 4, ba.Lstr(resource='playerText'), h_align=Text.HAlign.LEFT)
|
||||
_txt(180, 4, ba.Lstr(resource='killsText'))
|
||||
_txt(280, 4, ba.Lstr(resource='deathsText'), maxwidth=100)
|
||||
@ -162,52 +183,80 @@ class MultiTeamScoreScreenActivity(ScoreScreenActivity):
|
||||
|
||||
topkillcount = 0
|
||||
topkilledcount = 99999
|
||||
top_score = 0 if not player_records else _get_prec_score(
|
||||
player_records[0])
|
||||
top_score = (
|
||||
0 if not player_records else _get_prec_score(player_records[0])
|
||||
)
|
||||
|
||||
for prec in player_records:
|
||||
topkillcount = max(topkillcount, prec.accum_kill_count)
|
||||
topkilledcount = min(topkilledcount, prec.accum_killed_count)
|
||||
|
||||
def _scoretxt(text: str | ba.Lstr,
|
||||
x_offs: float,
|
||||
highlight: bool,
|
||||
delay2: float,
|
||||
maxwidth: float = 70.0) -> None:
|
||||
Text(text,
|
||||
position=(ts_h_offs + x_offs * scale,
|
||||
ts_v_offset + (voffs + 15) * scale),
|
||||
scale=scale,
|
||||
color=(1.0, 0.9, 0.5, 1.0) if highlight else
|
||||
(0.5, 0.5, 0.6, 0.5),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=maxwidth,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay + delay2).autoretain()
|
||||
def _scoretxt(
|
||||
text: str | ba.Lstr,
|
||||
x_offs: float,
|
||||
highlight: bool,
|
||||
delay2: float,
|
||||
maxwidth: float = 70.0,
|
||||
) -> None:
|
||||
Text(
|
||||
text,
|
||||
position=(
|
||||
ts_h_offs + x_offs * scale,
|
||||
ts_v_offset + (voffs + 15) * scale,
|
||||
),
|
||||
scale=scale,
|
||||
color=(1.0, 0.9, 0.5, 1.0)
|
||||
if highlight
|
||||
else (0.5, 0.5, 0.6, 0.5),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=maxwidth,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay + delay2,
|
||||
).autoretain()
|
||||
|
||||
for playerrec in player_records:
|
||||
tdelay += 0.05
|
||||
voffs -= spacing
|
||||
Image(playerrec.get_icon(),
|
||||
position=(ts_h_offs - 12 * scale,
|
||||
ts_v_offset + (voffs + 15.0) * scale),
|
||||
scale=(30.0 * scale, 30.0 * scale),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
Text(ba.Lstr(value=playerrec.getname(full=True)),
|
||||
maxwidth=160,
|
||||
scale=0.75 * scale,
|
||||
position=(ts_h_offs + 10.0 * scale,
|
||||
ts_v_offset + (voffs + 15) * scale),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(playerrec.team.color + (1, )),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
_scoretxt(str(playerrec.accum_kill_count), 180,
|
||||
playerrec.accum_kill_count == topkillcount, 0.1)
|
||||
_scoretxt(str(playerrec.accum_killed_count), 280,
|
||||
playerrec.accum_killed_count == topkilledcount, 0.1)
|
||||
_scoretxt(_get_prec_score_str(playerrec), 390,
|
||||
_get_prec_score(playerrec) == top_score, 0.2)
|
||||
Image(
|
||||
playerrec.get_icon(),
|
||||
position=(
|
||||
ts_h_offs - 12 * scale,
|
||||
ts_v_offset + (voffs + 15.0) * scale,
|
||||
),
|
||||
scale=(30.0 * scale, 30.0 * scale),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
Text(
|
||||
ba.Lstr(value=playerrec.getname(full=True)),
|
||||
maxwidth=160,
|
||||
scale=0.75 * scale,
|
||||
position=(
|
||||
ts_h_offs + 10.0 * scale,
|
||||
ts_v_offset + (voffs + 15) * scale,
|
||||
),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(playerrec.team.color + (1,)),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
_scoretxt(
|
||||
str(playerrec.accum_kill_count),
|
||||
180,
|
||||
playerrec.accum_kill_count == topkillcount,
|
||||
0.1,
|
||||
)
|
||||
_scoretxt(
|
||||
str(playerrec.accum_killed_count),
|
||||
280,
|
||||
playerrec.accum_killed_count == topkilledcount,
|
||||
0.1,
|
||||
)
|
||||
_scoretxt(
|
||||
_get_prec_score_str(playerrec),
|
||||
390,
|
||||
_get_prec_score(playerrec) == top_score,
|
||||
0.2,
|
||||
)
|
||||
|
||||
@ -33,8 +33,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
# pylint: disable=too-many-statements
|
||||
from bastd.actor.text import Text
|
||||
from bastd.actor.image import Image
|
||||
ba.set_analytics_screen('FreeForAll Series Victory Screen' if self.
|
||||
_is_ffa else 'Teams Series Victory Screen')
|
||||
|
||||
ba.set_analytics_screen(
|
||||
'FreeForAll Series Victory Screen'
|
||||
if self._is_ffa
|
||||
else 'Teams Series Victory Screen'
|
||||
)
|
||||
if ba.app.ui.uiscale is ba.UIScale.LARGE:
|
||||
sval = ba.Lstr(resource='pressAnyKeyButtonPlayAgainText')
|
||||
else:
|
||||
@ -46,8 +50,9 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
|
||||
# Pause a moment before playing victory music.
|
||||
ba.timer(0.6, ba.WeakCall(self._play_victory_music))
|
||||
ba.timer(4.4,
|
||||
ba.WeakCall(self._show_winner, self.settings_raw['winner']))
|
||||
ba.timer(
|
||||
4.4, ba.WeakCall(self._show_winner, self.settings_raw['winner'])
|
||||
)
|
||||
ba.timer(4.6, ba.Call(ba.playsound, self._score_display_sound))
|
||||
|
||||
# Score / Name / Player-record.
|
||||
@ -58,8 +63,12 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
for _pkey, prec in self.stats.get_records().items():
|
||||
if prec.player.in_game:
|
||||
player_entries.append(
|
||||
(prec.player.sessionteam.customdata['score'],
|
||||
prec.getname(full=True), prec))
|
||||
(
|
||||
prec.player.sessionteam.customdata['score'],
|
||||
prec.getname(full=True),
|
||||
prec,
|
||||
)
|
||||
)
|
||||
player_entries.sort(reverse=True, key=lambda x: x[0])
|
||||
else:
|
||||
for _pkey, prec in self.stats.get_records().items():
|
||||
@ -72,18 +81,29 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
t_incr = 0.12
|
||||
|
||||
always_use_first_to = ba.app.lang.get_resource(
|
||||
'bestOfUseFirstToInstead')
|
||||
'bestOfUseFirstToInstead'
|
||||
)
|
||||
|
||||
session = self.session
|
||||
if self._is_ffa:
|
||||
assert isinstance(session, ba.FreeForAllSession)
|
||||
txt = ba.Lstr(
|
||||
value='${A}:',
|
||||
subs=[('${A}',
|
||||
ba.Lstr(resource='firstToFinalText',
|
||||
subs=[('${COUNT}',
|
||||
str(session.get_ffa_series_length()))]))
|
||||
])
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='firstToFinalText',
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(session.get_ffa_series_length()),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
else:
|
||||
assert isinstance(session, ba.MultiTeamSession)
|
||||
|
||||
@ -96,31 +116,52 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
txt = ba.Lstr(
|
||||
value='${A}:',
|
||||
subs=[
|
||||
('${A}',
|
||||
ba.Lstr(resource='firstToFinalText',
|
||||
subs=[
|
||||
('${COUNT}',
|
||||
str(session.get_series_length() / 2 + 1))
|
||||
]))
|
||||
])
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='firstToFinalText',
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(
|
||||
session.get_series_length() / 2 + 1
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
else:
|
||||
txt = ba.Lstr(
|
||||
value='${A}:',
|
||||
subs=[('${A}',
|
||||
ba.Lstr(resource='bestOfFinalText',
|
||||
subs=[('${COUNT}',
|
||||
str(session.get_series_length()))]))
|
||||
])
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='bestOfFinalText',
|
||||
subs=[
|
||||
(
|
||||
'${COUNT}',
|
||||
str(session.get_series_length()),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
Text(txt,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(0, 220),
|
||||
scale=1.2,
|
||||
transition=Text.Transition.IN_TOP_SLOW,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
transition_delay=t_incr * 4).autoretain()
|
||||
Text(
|
||||
txt,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(0, 220),
|
||||
scale=1.2,
|
||||
transition=Text.Transition.IN_TOP_SLOW,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
transition_delay=t_incr * 4,
|
||||
).autoretain()
|
||||
|
||||
win_score = (session.get_series_length() - 1) // 2 + 1
|
||||
lose_score = 0
|
||||
@ -129,17 +170,23 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
lose_score = team.sessionteam.customdata['score']
|
||||
|
||||
if not self._is_ffa:
|
||||
Text(ba.Lstr(resource='gamesToText',
|
||||
subs=[('${WINCOUNT}', str(win_score)),
|
||||
('${LOSECOUNT}', str(lose_score))]),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
maxwidth=160,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
position=(0, -215),
|
||||
scale=1.8,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
transition_delay=4.8 + t_incr * 4).autoretain()
|
||||
Text(
|
||||
ba.Lstr(
|
||||
resource='gamesToText',
|
||||
subs=[
|
||||
('${WINCOUNT}', str(win_score)),
|
||||
('${LOSECOUNT}', str(lose_score)),
|
||||
],
|
||||
),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
maxwidth=160,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
position=(0, -215),
|
||||
scale=1.8,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.CENTER,
|
||||
transition_delay=4.8 + t_incr * 4,
|
||||
).autoretain()
|
||||
|
||||
if self._is_ffa:
|
||||
v_extra = 120
|
||||
@ -158,31 +205,37 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
mvp_name = entry[1]
|
||||
break
|
||||
if mvp is not None:
|
||||
Text(ba.Lstr(resource='mostValuablePlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='mostValuablePlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
|
||||
Image(mvp.get_icon(),
|
||||
position=(230, ts_height / 2 - 55 + 14 - 5),
|
||||
scale=(70, 70),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Image(
|
||||
mvp.get_icon(),
|
||||
position=(230, ts_height / 2 - 55 + 14 - 5),
|
||||
scale=(70, 70),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
assert mvp_name is not None
|
||||
Text(ba.Lstr(value=mvp_name),
|
||||
position=(280, ts_height / 2 - 55 + 15 - 5),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=170,
|
||||
scale=1.3,
|
||||
color=ba.safecolor(mvp.team.color + (1, )),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(value=mvp_name),
|
||||
position=(280, ts_height / 2 - 55 + 15 - 5),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=170,
|
||||
scale=1.3,
|
||||
color=ba.safecolor(mvp.team.color + (1,)),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
|
||||
# Most violent.
|
||||
@ -193,41 +246,56 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
mvp_name = entry[1]
|
||||
most_kills = entry[2].kill_count
|
||||
if mvp is not None:
|
||||
Text(ba.Lstr(resource='mostViolentPlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 - 150 + v_extra + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(ba.Lstr(value='(${A})',
|
||||
subs=[('${A}',
|
||||
ba.Lstr(resource='killsTallyText',
|
||||
subs=[('${COUNT}', str(most_kills))]))
|
||||
]),
|
||||
position=(260, ts_height / 2 - 150 - 15 + v_extra),
|
||||
color=(0.3, 0.3, 0.3, 1.0),
|
||||
scale=0.6,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='mostViolentPlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 - 150 + v_extra + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
Text(
|
||||
ba.Lstr(
|
||||
value='(${A})',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='killsTallyText',
|
||||
subs=[('${COUNT}', str(most_kills))],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
position=(260, ts_height / 2 - 150 - 15 + v_extra),
|
||||
color=(0.3, 0.3, 0.3, 1.0),
|
||||
scale=0.6,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
|
||||
Image(mvp.get_icon(),
|
||||
position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
|
||||
scale=(50, 50),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Image(
|
||||
mvp.get_icon(),
|
||||
position=(233, ts_height / 2 - 150 - 30 - 46 + 25 + v_extra),
|
||||
scale=(50, 50),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
assert mvp_name is not None
|
||||
Text(ba.Lstr(value=mvp_name),
|
||||
position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=180,
|
||||
color=ba.safecolor(mvp.team.color + (1, )),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(value=mvp_name),
|
||||
position=(270, ts_height / 2 - 150 - 30 - 36 + v_extra + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=180,
|
||||
color=ba.safecolor(mvp.team.color + (1,)),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
|
||||
# Most killed.
|
||||
@ -239,49 +307,66 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
mkp_name = entry[1]
|
||||
most_killed = entry[2].killed_count
|
||||
if mkp is not None:
|
||||
Text(ba.Lstr(resource='mostViolatedPlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 - 300 + v_extra + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(ba.Lstr(value='(${A})',
|
||||
subs=[('${A}',
|
||||
ba.Lstr(resource='deathsTallyText',
|
||||
subs=[('${COUNT}', str(most_killed))]))
|
||||
]),
|
||||
position=(260, ts_height / 2 - 300 - 15 + v_extra),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
scale=0.6,
|
||||
color=(0.3, 0.3, 0.3, 1.0),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='mostViolatedPlayerText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=300,
|
||||
position=(180, ts_height / 2 - 300 + v_extra + 15),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
h_align=Text.HAlign.LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
Text(
|
||||
ba.Lstr(
|
||||
value='(${A})',
|
||||
subs=[
|
||||
(
|
||||
'${A}',
|
||||
ba.Lstr(
|
||||
resource='deathsTallyText',
|
||||
subs=[('${COUNT}', str(most_killed))],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
position=(260, ts_height / 2 - 300 - 15 + v_extra),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
scale=0.6,
|
||||
color=(0.3, 0.3, 0.3, 1.0),
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
Image(mkp.get_icon(),
|
||||
position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
|
||||
scale=(50, 50),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Image(
|
||||
mkp.get_icon(),
|
||||
position=(233, ts_height / 2 - 300 - 30 - 46 + 25 + v_extra),
|
||||
scale=(50, 50),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
assert mkp_name is not None
|
||||
Text(ba.Lstr(value=mkp_name),
|
||||
position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(mkp.team.color + (1, )),
|
||||
maxwidth=180,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval).autoretain()
|
||||
Text(
|
||||
ba.Lstr(value=mkp_name),
|
||||
position=(270, ts_height / 2 - 300 - 30 - 36 + v_extra + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
color=ba.safecolor(mkp.team.color + (1,)),
|
||||
maxwidth=180,
|
||||
transition=Text.Transition.IN_LEFT,
|
||||
transition_delay=tval,
|
||||
).autoretain()
|
||||
tval += 4 * t_incr
|
||||
|
||||
# Now show individual scores.
|
||||
tdelay = tval
|
||||
Text(ba.Lstr(resource='finalScoresText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(ts_h_offs, ts_height / 2),
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
Text(
|
||||
ba.Lstr(resource='finalScoresText'),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(ts_h_offs, ts_height / 2),
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
tdelay += 4 * t_incr
|
||||
|
||||
v_offs = 0.0
|
||||
@ -289,33 +374,41 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
for _score, name, prec in player_entries:
|
||||
tdelay -= 4 * t_incr
|
||||
v_offs -= 40
|
||||
Text(str(prec.team.customdata['score'])
|
||||
if self._is_ffa else str(prec.score),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(ts_h_offs + 230, ts_height / 2 + v_offs),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
Text(
|
||||
str(prec.team.customdata['score'])
|
||||
if self._is_ffa
|
||||
else str(prec.score),
|
||||
color=(0.5, 0.5, 0.5, 1.0),
|
||||
position=(ts_h_offs + 230, ts_height / 2 + v_offs),
|
||||
h_align=Text.HAlign.RIGHT,
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
tdelay -= 4 * t_incr
|
||||
|
||||
Image(prec.get_icon(),
|
||||
position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
|
||||
scale=(30, 30),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
Text(ba.Lstr(value=name),
|
||||
position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=180,
|
||||
color=ba.safecolor(prec.team.color + (1, )),
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay).autoretain()
|
||||
Image(
|
||||
prec.get_icon(),
|
||||
position=(ts_h_offs - 72, ts_height / 2 + v_offs + 15),
|
||||
scale=(30, 30),
|
||||
transition=Image.Transition.IN_LEFT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
Text(
|
||||
ba.Lstr(value=name),
|
||||
position=(ts_h_offs - 50, ts_height / 2 + v_offs + 15),
|
||||
h_align=Text.HAlign.LEFT,
|
||||
v_align=Text.VAlign.CENTER,
|
||||
maxwidth=180,
|
||||
color=ba.safecolor(prec.team.color + (1,)),
|
||||
transition=Text.Transition.IN_RIGHT,
|
||||
transition_delay=tdelay,
|
||||
).autoretain()
|
||||
|
||||
ba.timer(15.0, ba.WeakCall(self._show_tips))
|
||||
|
||||
def _show_tips(self) -> None:
|
||||
from bastd.actor.tipstext import TipsText
|
||||
|
||||
self._tips_text = TipsText(offs_y=70)
|
||||
|
||||
def _play_victory_music(self) -> None:
|
||||
@ -327,29 +420,37 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
def _show_winner(self, team: ba.SessionTeam) -> None:
|
||||
from bastd.actor.image import Image
|
||||
from bastd.actor.zoomtext import ZoomText
|
||||
|
||||
if not self._is_ffa:
|
||||
offs_v = 0.0
|
||||
ZoomText(team.name,
|
||||
position=(0, 97),
|
||||
color=team.color,
|
||||
scale=1.15,
|
||||
jitter=1.0,
|
||||
maxwidth=250).autoretain()
|
||||
ZoomText(
|
||||
team.name,
|
||||
position=(0, 97),
|
||||
color=team.color,
|
||||
scale=1.15,
|
||||
jitter=1.0,
|
||||
maxwidth=250,
|
||||
).autoretain()
|
||||
else:
|
||||
offs_v = -80.0
|
||||
if len(team.players) == 1:
|
||||
i = Image(team.players[0].get_icon(),
|
||||
position=(0, 143),
|
||||
scale=(100, 100)).autoretain()
|
||||
i = Image(
|
||||
team.players[0].get_icon(),
|
||||
position=(0, 143),
|
||||
scale=(100, 100),
|
||||
).autoretain()
|
||||
assert i.node
|
||||
ba.animate(i.node, 'opacity', {0.0: 0.0, 0.25: 1.0})
|
||||
ZoomText(ba.Lstr(
|
||||
value=team.players[0].getname(full=True, icon=False)),
|
||||
position=(0, 97 + offs_v),
|
||||
color=team.color,
|
||||
scale=1.15,
|
||||
jitter=1.0,
|
||||
maxwidth=250).autoretain()
|
||||
ZoomText(
|
||||
ba.Lstr(
|
||||
value=team.players[0].getname(full=True, icon=False)
|
||||
),
|
||||
position=(0, 97 + offs_v),
|
||||
color=team.color,
|
||||
scale=1.15,
|
||||
jitter=1.0,
|
||||
maxwidth=250,
|
||||
).autoretain()
|
||||
|
||||
s_extra = 1.0 if self._is_ffa else 1.0
|
||||
|
||||
@ -362,15 +463,19 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
|
||||
|
||||
# Temp - if these come up as the english default, fall-back to the
|
||||
# unified old form which is more likely to be translated.
|
||||
ZoomText(wins_text,
|
||||
position=(0, -10 + offs_v),
|
||||
color=team.color,
|
||||
scale=0.65 * s_extra,
|
||||
jitter=1.0,
|
||||
maxwidth=250).autoretain()
|
||||
ZoomText(ba.Lstr(resource='seriesWinLine2Text'),
|
||||
position=(0, -110 + offs_v),
|
||||
scale=1.0 * s_extra,
|
||||
color=team.color,
|
||||
jitter=1.0,
|
||||
maxwidth=250).autoretain()
|
||||
ZoomText(
|
||||
wins_text,
|
||||
position=(0, -10 + offs_v),
|
||||
color=team.color,
|
||||
scale=0.65 * s_extra,
|
||||
jitter=1.0,
|
||||
maxwidth=250,
|
||||
).autoretain()
|
||||
ZoomText(
|
||||
ba.Lstr(resource='seriesWinLine2Text'),
|
||||
position=(0, -110 + offs_v),
|
||||
scale=1.0 * s_extra,
|
||||
color=team.color,
|
||||
jitter=1.0,
|
||||
maxwidth=250,
|
||||
).autoretain()
|
||||
|
||||
@ -17,10 +17,12 @@ if TYPE_CHECKING:
|
||||
class Background(ba.Actor):
|
||||
"""Simple Fading Background Actor."""
|
||||
|
||||
def __init__(self,
|
||||
fade_time: float = 0.5,
|
||||
start_faded: bool = False,
|
||||
show_logo: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
fade_time: float = 0.5,
|
||||
start_faded: bool = False,
|
||||
show_logo: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self._dying = False
|
||||
self.fade_time = fade_time
|
||||
@ -31,22 +33,24 @@ class Background(ba.Actor):
|
||||
session = ba.getsession()
|
||||
self._session = weakref.ref(session)
|
||||
with ba.Context(session):
|
||||
self.node = ba.newnode('image',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'fill_screen': True,
|
||||
'texture': ba.gettexture('bg'),
|
||||
'tilt_translate': -0.3,
|
||||
'has_alpha_channel': False,
|
||||
'color': (1, 1, 1)
|
||||
})
|
||||
self.node = ba.newnode(
|
||||
'image',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'fill_screen': True,
|
||||
'texture': ba.gettexture('bg'),
|
||||
'tilt_translate': -0.3,
|
||||
'has_alpha_channel': False,
|
||||
'color': (1, 1, 1),
|
||||
},
|
||||
)
|
||||
if not start_faded:
|
||||
ba.animate(self.node,
|
||||
'opacity', {
|
||||
0.0: 0.0,
|
||||
self.fade_time: 1.0
|
||||
},
|
||||
loop=False)
|
||||
ba.animate(
|
||||
self.node,
|
||||
'opacity',
|
||||
{0.0: 0.0, self.fade_time: 1.0},
|
||||
loop=False,
|
||||
)
|
||||
if show_logo:
|
||||
logo_texture = ba.gettexture('logo')
|
||||
logo_model = ba.getmodel('logo')
|
||||
@ -63,27 +67,27 @@ class Background(ba.Actor):
|
||||
'color': (0.15, 0.15, 0.15),
|
||||
'position': (0, 0),
|
||||
'tilt_translate': -0.05,
|
||||
'absolute_scale': False
|
||||
})
|
||||
'absolute_scale': False,
|
||||
},
|
||||
)
|
||||
self.node.connectattr('opacity', self.logo, 'opacity')
|
||||
# add jitter/pulse for a stop-motion-y look unless we're in VR
|
||||
# in which case stillness is better
|
||||
if not ba.app.vr_mode:
|
||||
self.cmb = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={'size': 2})
|
||||
self.cmb = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
for attr in ['input0', 'input1']:
|
||||
ba.animate(self.cmb,
|
||||
attr, {
|
||||
0.0: 0.693,
|
||||
0.05: 0.7,
|
||||
0.5: 0.693
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(
|
||||
self.cmb,
|
||||
attr,
|
||||
{0.0: 0.693, 0.05: 0.7, 0.5: 0.693},
|
||||
loop=True,
|
||||
)
|
||||
self.cmb.connectattr('output', self.logo, 'scale')
|
||||
cmb = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={'size': 2})
|
||||
cmb = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
cmb.connectattr('output', self.logo, 'position')
|
||||
# Gen some random keys for that stop-motion-y look.
|
||||
keys = {}
|
||||
@ -114,8 +118,10 @@ class Background(ba.Actor):
|
||||
# since it was part of the session's scene.
|
||||
# Let's make sure that's the case.
|
||||
# (since otherwise we have no way to kill it)
|
||||
ba.print_error('got None session on Background _die'
|
||||
' (and node still exists!)')
|
||||
ba.print_error(
|
||||
'got None session on Background _die'
|
||||
' (and node still exists!)'
|
||||
)
|
||||
elif session is not None:
|
||||
with ba.Context(session):
|
||||
if not self._dying and self.node:
|
||||
@ -123,12 +129,12 @@ class Background(ba.Actor):
|
||||
if immediate:
|
||||
self.node.delete()
|
||||
else:
|
||||
ba.animate(self.node,
|
||||
'opacity', {
|
||||
0.0: 1.0,
|
||||
self.fade_time: 0.0
|
||||
},
|
||||
loop=False)
|
||||
ba.animate(
|
||||
self.node,
|
||||
'opacity',
|
||||
{0.0: 1.0, self.fade_time: 0.0},
|
||||
loop=False,
|
||||
)
|
||||
ba.timer(self.fade_time + 0.1, self.node.delete)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -23,12 +23,14 @@ class ControlsGuide(ba.Actor):
|
||||
be newbies watching.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
position: tuple[float, float] = (390.0, 120.0),
|
||||
scale: float = 1.0,
|
||||
delay: float = 0.0,
|
||||
lifespan: float | None = None,
|
||||
bright: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
position: tuple[float, float] = (390.0, 120.0),
|
||||
scale: float = 1.0,
|
||||
delay: float = 0.0,
|
||||
lifespan: float | None = None,
|
||||
bright: bool = False,
|
||||
):
|
||||
"""Instantiate an overlay.
|
||||
|
||||
delay: is the time in seconds before the overlay fades in.
|
||||
@ -62,19 +64,31 @@ class ControlsGuide(ba.Actor):
|
||||
if ba.app.iircade_mode:
|
||||
xtweak = 0.2
|
||||
ytweak = 0.2
|
||||
jump_pos = (position[0] + offs * (-1.2 + xtweak),
|
||||
position[1] + offs * (0.1 + ytweak))
|
||||
bomb_pos = (position[0] + offs * (0.0 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak))
|
||||
punch_pos = (position[0] + offs * (1.2 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak))
|
||||
jump_pos = (
|
||||
position[0] + offs * (-1.2 + xtweak),
|
||||
position[1] + offs * (0.1 + ytweak),
|
||||
)
|
||||
bomb_pos = (
|
||||
position[0] + offs * (0.0 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak),
|
||||
)
|
||||
punch_pos = (
|
||||
position[0] + offs * (1.2 + xtweak),
|
||||
position[1] + offs * (0.5 + ytweak),
|
||||
)
|
||||
|
||||
pickup_pos = (position[0] + offs * (-1.4 + xtweak),
|
||||
position[1] + offs * (-1.2 + ytweak))
|
||||
extra_pos_1 = (position[0] + offs * (-0.2 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak))
|
||||
extra_pos_2 = (position[0] + offs * (1.0 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak))
|
||||
pickup_pos = (
|
||||
position[0] + offs * (-1.4 + xtweak),
|
||||
position[1] + offs * (-1.2 + ytweak),
|
||||
)
|
||||
extra_pos_1 = (
|
||||
position[0] + offs * (-0.2 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak),
|
||||
)
|
||||
extra_pos_2 = (
|
||||
position[0] + offs * (1.0 + xtweak),
|
||||
position[1] + offs * (-0.8 + ytweak),
|
||||
)
|
||||
self._force_hide_button_names = True
|
||||
else:
|
||||
punch_pos = (position[0] - offs * 1.1, position[1])
|
||||
@ -86,25 +100,32 @@ class ControlsGuide(ba.Actor):
|
||||
self._force_hide_button_names = False
|
||||
|
||||
if show_title:
|
||||
self._title_text_pos_top = (position[0],
|
||||
position[1] + 139.0 * scale)
|
||||
self._title_text_pos_bottom = (position[0],
|
||||
position[1] + 139.0 * scale)
|
||||
self._title_text_pos_top = (
|
||||
position[0],
|
||||
position[1] + 139.0 * scale,
|
||||
)
|
||||
self._title_text_pos_bottom = (
|
||||
position[0],
|
||||
position[1] + 139.0 * scale,
|
||||
)
|
||||
clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7)
|
||||
tval = ba.Lstr(value='${A}:',
|
||||
subs=[('${A}', ba.Lstr(resource='controlsText'))])
|
||||
self._title_text = ba.newnode('text',
|
||||
attrs={
|
||||
'text': tval,
|
||||
'host_only': True,
|
||||
'scale': 1.1 * scale,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 480,
|
||||
'v_align': 'center',
|
||||
'h_align': 'center',
|
||||
'color': clr
|
||||
})
|
||||
tval = ba.Lstr(
|
||||
value='${A}:', subs=[('${A}', ba.Lstr(resource='controlsText'))]
|
||||
)
|
||||
self._title_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': tval,
|
||||
'host_only': True,
|
||||
'scale': 1.1 * scale,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 480,
|
||||
'v_align': 'center',
|
||||
'h_align': 'center',
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._title_text = None
|
||||
pos = jump_pos
|
||||
@ -118,20 +139,23 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr
|
||||
})
|
||||
self._jump_text = ba.newnode('text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr
|
||||
})
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._jump_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (0.2, 0.6, 1) if ouya else (1, 0.7, 0.3)
|
||||
pos = punch_pos
|
||||
self._punch_image = ba.newnode(
|
||||
@ -143,20 +167,23 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr
|
||||
})
|
||||
self._punch_text = ba.newnode('text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr
|
||||
})
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._punch_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
pos = bomb_pos
|
||||
clr = (1, 0.3, 0.3)
|
||||
self._bomb_image = ba.newnode(
|
||||
@ -168,20 +195,23 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr
|
||||
})
|
||||
self._bomb_text = ba.newnode('text',
|
||||
attrs={
|
||||
'h_align': 'center',
|
||||
'v_align': 'top',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr
|
||||
})
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._bomb_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_align': 'center',
|
||||
'v_align': 'top',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
pos = pickup_pos
|
||||
clr = (1, 0.8, 0.3) if ouya else (0.8, 0.5, 1)
|
||||
self._pickup_image = ba.newnode(
|
||||
@ -193,25 +223,27 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': pos,
|
||||
'scale': (image_size, image_size),
|
||||
'color': clr
|
||||
})
|
||||
self._pick_up_text = ba.newnode('text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position':
|
||||
(pos[0], pos[1] - offs5),
|
||||
'color': clr
|
||||
})
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
self._pick_up_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'scale': 1.5 * scale,
|
||||
'flatness': 1.0,
|
||||
'host_only': True,
|
||||
'shadow': 1.0,
|
||||
'maxwidth': maxw,
|
||||
'position': (pos[0], pos[1] - offs5),
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (0.9, 0.9, 2.0, 1.0) if bright else (0.8, 0.8, 2.0, 1.0)
|
||||
self._run_text_pos_top = (position[0], position[1] - 135.0 * scale)
|
||||
self._run_text_pos_bottom = (position[0], position[1] - 172.0 * scale)
|
||||
sval = (1.0 * scale if ba.app.vr_mode else 0.8 * scale)
|
||||
sval = 1.0 * scale if ba.app.vr_mode else 0.8 * scale
|
||||
self._run_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
@ -222,20 +254,23 @@ class ControlsGuide(ba.Actor):
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'color': clr
|
||||
})
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
clr = (1, 1, 1) if bright else (0.7, 0.7, 0.7)
|
||||
self._extra_text = ba.newnode('text',
|
||||
attrs={
|
||||
'scale': 0.8 * scale,
|
||||
'host_only': True,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'color': clr
|
||||
})
|
||||
self._extra_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'scale': 0.8 * scale,
|
||||
'host_only': True,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
'h_align': 'center',
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
|
||||
if extra_pos_1 is not None:
|
||||
self._extra_image_1: ba.Node | None = ba.newnode(
|
||||
@ -247,8 +282,9 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': extra_pos_1,
|
||||
'scale': (image_size, image_size),
|
||||
'color': (0.5, 0.5, 0.5)
|
||||
})
|
||||
'color': (0.5, 0.5, 0.5),
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._extra_image_1 = None
|
||||
if extra_pos_2 is not None:
|
||||
@ -261,16 +297,23 @@ class ControlsGuide(ba.Actor):
|
||||
'vr_depth': 10,
|
||||
'position': extra_pos_2,
|
||||
'scale': (image_size, image_size),
|
||||
'color': (0.5, 0.5, 0.5)
|
||||
})
|
||||
'color': (0.5, 0.5, 0.5),
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._extra_image_2 = None
|
||||
|
||||
self._nodes = [
|
||||
self._bomb_image, self._bomb_text, self._punch_image,
|
||||
self._punch_text, self._jump_image, self._jump_text,
|
||||
self._pickup_image, self._pick_up_text, self._run_text,
|
||||
self._extra_text
|
||||
self._bomb_image,
|
||||
self._bomb_text,
|
||||
self._punch_image,
|
||||
self._punch_text,
|
||||
self._jump_image,
|
||||
self._jump_text,
|
||||
self._pickup_image,
|
||||
self._pick_up_text,
|
||||
self._run_text,
|
||||
self._extra_text,
|
||||
]
|
||||
if show_title:
|
||||
assert self._title_text
|
||||
@ -304,10 +347,11 @@ class ControlsGuide(ba.Actor):
|
||||
if self._lifespan is not None:
|
||||
self._cancel_timer = ba.Timer(
|
||||
self._lifespan,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage(immediate=True)))
|
||||
self._fade_in_timer = ba.Timer(1.0,
|
||||
ba.WeakCall(self._check_fade_in),
|
||||
repeat=True)
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage(immediate=True)),
|
||||
)
|
||||
self._fade_in_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._check_fade_in), repeat=True
|
||||
)
|
||||
self._check_fade_in() # Do one check immediately.
|
||||
|
||||
def _check_fade_in(self) -> None:
|
||||
@ -318,7 +362,8 @@ class ControlsGuide(ba.Actor):
|
||||
# (otherwise it is confusing to see the touchscreen buttons right
|
||||
# next to our display buttons)
|
||||
touchscreen: ba.InputDevice | None = ba.internal.getinputdevice(
|
||||
'TouchScreen', '#1', doraise=False)
|
||||
'TouchScreen', '#1', doraise=False
|
||||
)
|
||||
|
||||
if touchscreen is not None:
|
||||
# We look at the session's players; not the activity's.
|
||||
@ -335,10 +380,18 @@ class ControlsGuide(ba.Actor):
|
||||
# Only count this one if it has non-empty button names
|
||||
# (filters out wiimotes, the remote-app, etc).
|
||||
for device in input_devices:
|
||||
for name in ('buttonPunch', 'buttonJump', 'buttonBomb',
|
||||
'buttonPickUp'):
|
||||
if self._meaningful_button_name(
|
||||
device, get_device_value(device, name)) != '':
|
||||
for name in (
|
||||
'buttonPunch',
|
||||
'buttonJump',
|
||||
'buttonBomb',
|
||||
'buttonPickUp',
|
||||
):
|
||||
if (
|
||||
self._meaningful_button_name(
|
||||
device, get_device_value(device, name)
|
||||
)
|
||||
!= ''
|
||||
):
|
||||
fade_in = True
|
||||
break
|
||||
if fade_in:
|
||||
@ -357,18 +410,20 @@ class ControlsGuide(ba.Actor):
|
||||
|
||||
# If we were given a lifespan, transition out after it.
|
||||
if self._lifespan is not None:
|
||||
ba.timer(self._lifespan,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
ba.timer(
|
||||
self._lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())
|
||||
)
|
||||
self._update()
|
||||
self._update_timer = ba.Timer(1.0,
|
||||
ba.WeakCall(self._update),
|
||||
repeat=True)
|
||||
self._update_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
def _update(self) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
from ba.internal import get_device_value, get_remote_app_name
|
||||
|
||||
if self._dead:
|
||||
return
|
||||
punch_button_names = set()
|
||||
@ -389,11 +444,12 @@ class ControlsGuide(ba.Actor):
|
||||
input_devices.append(kbd)
|
||||
|
||||
# We word things specially if we have nothing but keyboards.
|
||||
all_keyboards = (input_devices
|
||||
and all(i.name == 'Keyboard' for i in input_devices))
|
||||
only_remote = (len(input_devices) == 1
|
||||
and all(i.name == 'Amazon Fire TV Remote'
|
||||
for i in input_devices))
|
||||
all_keyboards = input_devices and all(
|
||||
i.name == 'Keyboard' for i in input_devices
|
||||
)
|
||||
only_remote = len(input_devices) == 1 and all(
|
||||
i.name == 'Amazon Fire TV Remote' for i in input_devices
|
||||
)
|
||||
|
||||
right_button_names = set()
|
||||
left_button_names = set()
|
||||
@ -408,40 +464,57 @@ class ControlsGuide(ba.Actor):
|
||||
if all_keyboards:
|
||||
right_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonRight')))
|
||||
get_device_value(device, 'buttonRight')
|
||||
)
|
||||
)
|
||||
left_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonLeft')))
|
||||
get_device_value(device, 'buttonLeft')
|
||||
)
|
||||
)
|
||||
down_button_names.add(
|
||||
device.get_button_name(
|
||||
get_device_value(device, 'buttonDown')))
|
||||
get_device_value(device, 'buttonDown')
|
||||
)
|
||||
)
|
||||
up_button_names.add(
|
||||
device.get_button_name(get_device_value(
|
||||
device, 'buttonUp')))
|
||||
device.get_button_name(get_device_value(device, 'buttonUp'))
|
||||
)
|
||||
|
||||
# Ignore empty values; things like the remote app or
|
||||
# wiimotes can return these.
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonPunch'))
|
||||
device, get_device_value(device, 'buttonPunch')
|
||||
)
|
||||
if bname != '':
|
||||
punch_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonJump'))
|
||||
device, get_device_value(device, 'buttonJump')
|
||||
)
|
||||
if bname != '':
|
||||
jump_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonBomb'))
|
||||
device, get_device_value(device, 'buttonBomb')
|
||||
)
|
||||
if bname != '':
|
||||
bomb_button_names.add(bname)
|
||||
bname = self._meaningful_button_name(
|
||||
device, get_device_value(device, 'buttonPickUp'))
|
||||
device, get_device_value(device, 'buttonPickUp')
|
||||
)
|
||||
if bname != '':
|
||||
pickup_button_names.add(bname)
|
||||
|
||||
# If we have no values yet, we may want to throw out some sane
|
||||
# defaults.
|
||||
if all(not lst for lst in (punch_button_names, jump_button_names,
|
||||
bomb_button_names, pickup_button_names)):
|
||||
if all(
|
||||
not lst
|
||||
for lst in (
|
||||
punch_button_names,
|
||||
jump_button_names,
|
||||
bomb_button_names,
|
||||
pickup_button_names,
|
||||
)
|
||||
):
|
||||
# Otherwise on android show standard buttons.
|
||||
if ba.app.platform == 'android':
|
||||
punch_button_names.add('X')
|
||||
@ -451,24 +524,42 @@ class ControlsGuide(ba.Actor):
|
||||
|
||||
run_text = ba.Lstr(
|
||||
value='${R}: ${B}',
|
||||
subs=[('${R}', ba.Lstr(resource='runText')),
|
||||
('${B}',
|
||||
ba.Lstr(resource='holdAnyKeyText'
|
||||
if all_keyboards else 'holdAnyButtonText'))])
|
||||
subs=[
|
||||
('${R}', ba.Lstr(resource='runText')),
|
||||
(
|
||||
'${B}',
|
||||
ba.Lstr(
|
||||
resource='holdAnyKeyText'
|
||||
if all_keyboards
|
||||
else 'holdAnyButtonText'
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
# If we're all keyboards, lets show move keys too.
|
||||
if (all_keyboards and len(up_button_names) == 1
|
||||
and len(down_button_names) == 1 and len(left_button_names) == 1
|
||||
and len(right_button_names) == 1):
|
||||
if (
|
||||
all_keyboards
|
||||
and len(up_button_names) == 1
|
||||
and len(down_button_names) == 1
|
||||
and len(left_button_names) == 1
|
||||
and len(right_button_names) == 1
|
||||
):
|
||||
up_text = list(up_button_names)[0]
|
||||
down_text = list(down_button_names)[0]
|
||||
left_text = list(left_button_names)[0]
|
||||
right_text = list(right_button_names)[0]
|
||||
run_text = ba.Lstr(value='${M}: ${U}, ${L}, ${D}, ${R}\n${RUN}',
|
||||
subs=[('${M}', ba.Lstr(resource='moveText')),
|
||||
('${U}', up_text), ('${L}', left_text),
|
||||
('${D}', down_text), ('${R}', right_text),
|
||||
('${RUN}', run_text)])
|
||||
run_text = ba.Lstr(
|
||||
value='${M}: ${U}, ${L}, ${D}, ${R}\n${RUN}',
|
||||
subs=[
|
||||
('${M}', ba.Lstr(resource='moveText')),
|
||||
('${U}', up_text),
|
||||
('${L}', left_text),
|
||||
('${D}', down_text),
|
||||
('${R}', right_text),
|
||||
('${RUN}', run_text),
|
||||
],
|
||||
)
|
||||
|
||||
if self._force_hide_button_names:
|
||||
jump_button_names.clear()
|
||||
@ -479,9 +570,10 @@ class ControlsGuide(ba.Actor):
|
||||
self._run_text.text = run_text
|
||||
w_text: ba.Lstr | str
|
||||
if only_remote and self._lifespan is None:
|
||||
w_text = ba.Lstr(resource='fireTVRemoteWarningText',
|
||||
subs=[('${REMOTE_APP_NAME}',
|
||||
get_remote_app_name())])
|
||||
w_text = ba.Lstr(
|
||||
resource='fireTVRemoteWarningText',
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
|
||||
)
|
||||
else:
|
||||
w_text = ''
|
||||
self._extra_text.text = w_text
|
||||
@ -497,12 +589,16 @@ class ControlsGuide(ba.Actor):
|
||||
self._jump_text.text = tval
|
||||
if tval == '':
|
||||
self._run_text.position = self._run_text_pos_top
|
||||
self._extra_text.position = (self._run_text_pos_top[0],
|
||||
self._run_text_pos_top[1] - 50)
|
||||
self._extra_text.position = (
|
||||
self._run_text_pos_top[0],
|
||||
self._run_text_pos_top[1] - 50,
|
||||
)
|
||||
else:
|
||||
self._run_text.position = self._run_text_pos_bottom
|
||||
self._extra_text.position = (self._run_text_pos_bottom[0],
|
||||
self._run_text_pos_bottom[1] - 50)
|
||||
self._extra_text.position = (
|
||||
self._run_text_pos_bottom[0],
|
||||
self._run_text_pos_bottom[1] - 50,
|
||||
)
|
||||
if len(bomb_button_names) == 1:
|
||||
self._bomb_text.text = list(bomb_button_names)[0]
|
||||
else:
|
||||
|
||||
@ -99,8 +99,10 @@ class FlagFactory:
|
||||
'or',
|
||||
('they_dont_have_material', shared.footing_material),
|
||||
),
|
||||
actions=(('modify_part_collision', 'collide', False),
|
||||
('modify_part_collision', 'physical', False)),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', False),
|
||||
('modify_part_collision', 'physical', False),
|
||||
),
|
||||
)
|
||||
|
||||
self.flag_texture = ba.gettexture('flagColor')
|
||||
@ -164,12 +166,14 @@ class Flag(ba.Actor):
|
||||
Can be stationary or carry-able by players.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
materials: Sequence[ba.Material] | None = None,
|
||||
touchable: bool = True,
|
||||
dropped_timeout: int | None = None):
|
||||
def __init__(
|
||||
self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
materials: Sequence[ba.Material] | None = None,
|
||||
touchable: bool = True,
|
||||
dropped_timeout: int | None = None,
|
||||
):
|
||||
"""Instantiate a flag.
|
||||
|
||||
If 'touchable' is False, the flag will only touch terrain;
|
||||
@ -198,18 +202,20 @@ class Flag(ba.Actor):
|
||||
if not touchable:
|
||||
materials = [factory.no_hit_material] + materials
|
||||
|
||||
finalmaterials = ([shared.object_material, factory.flagmaterial] +
|
||||
materials)
|
||||
self.node = ba.newnode('flag',
|
||||
attrs={
|
||||
'position':
|
||||
(position[0], position[1] + 0.75,
|
||||
position[2]),
|
||||
'color_texture': factory.flag_texture,
|
||||
'color': color,
|
||||
'materials': finalmaterials
|
||||
},
|
||||
delegate=self)
|
||||
finalmaterials = [
|
||||
shared.object_material,
|
||||
factory.flagmaterial,
|
||||
] + materials
|
||||
self.node = ba.newnode(
|
||||
'flag',
|
||||
attrs={
|
||||
'position': (position[0], position[1] + 0.75, position[2]),
|
||||
'color_texture': factory.flag_texture,
|
||||
'color': color,
|
||||
'materials': finalmaterials,
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
if dropped_timeout is not None:
|
||||
dropped_timeout = int(dropped_timeout)
|
||||
@ -217,19 +223,21 @@ class Flag(ba.Actor):
|
||||
self._counter: ba.Node | None
|
||||
if self._dropped_timeout is not None:
|
||||
self._count = self._dropped_timeout
|
||||
self._tick_timer = ba.Timer(1.0,
|
||||
call=ba.WeakCall(self._tick),
|
||||
repeat=True)
|
||||
self._counter = ba.newnode('text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'in_world': True,
|
||||
'color': (1, 1, 1, 0.7),
|
||||
'scale': 0.015,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center'
|
||||
})
|
||||
self._tick_timer = ba.Timer(
|
||||
1.0, call=ba.WeakCall(self._tick), repeat=True
|
||||
)
|
||||
self._counter = ba.newnode(
|
||||
'text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'in_world': True,
|
||||
'color': (1, 1, 1, 0.7),
|
||||
'scale': 0.015,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._counter = None
|
||||
|
||||
@ -248,9 +256,13 @@ class Flag(ba.Actor):
|
||||
# until then.
|
||||
if not self._has_moved:
|
||||
nodepos = self.node.position
|
||||
if (max(
|
||||
if (
|
||||
max(
|
||||
abs(nodepos[i] - self._initial_position[i])
|
||||
for i in list(range(3))) > 1.0):
|
||||
for i in list(range(3))
|
||||
)
|
||||
> 1.0
|
||||
):
|
||||
self._has_moved = True
|
||||
|
||||
if self._held_count > 0 or not self._has_moved:
|
||||
@ -263,8 +275,11 @@ class Flag(ba.Actor):
|
||||
if self._count <= 10:
|
||||
nodepos = self.node.position
|
||||
assert self._counter
|
||||
self._counter.position = (nodepos[0], nodepos[1] + 1.3,
|
||||
nodepos[2])
|
||||
self._counter.position = (
|
||||
nodepos[0],
|
||||
nodepos[1] + 1.3,
|
||||
nodepos[2],
|
||||
)
|
||||
self._counter.text = str(self._count)
|
||||
if self._count < 1:
|
||||
self.handlemessage(ba.DieMessage())
|
||||
@ -275,10 +290,9 @@ class Flag(ba.Actor):
|
||||
def _hide_score_text(self) -> None:
|
||||
assert self._score_text is not None
|
||||
assert isinstance(self._score_text.scale, float)
|
||||
ba.animate(self._score_text, 'scale', {
|
||||
0: self._score_text.scale,
|
||||
0.2: 0
|
||||
})
|
||||
ba.animate(
|
||||
self._score_text, 'scale', {0: self._score_text.scale, 0.2: 0}
|
||||
)
|
||||
|
||||
def set_score_text(self, text: str) -> None:
|
||||
"""Show a message over the flag; handy for scores."""
|
||||
@ -286,23 +300,24 @@ class Flag(ba.Actor):
|
||||
return
|
||||
if not self._score_text:
|
||||
start_scale = 0.0
|
||||
math = ba.newnode('math',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input1': (0, 1.4, 0),
|
||||
'operation': 'add'
|
||||
})
|
||||
math = ba.newnode(
|
||||
'math',
|
||||
owner=self.node,
|
||||
attrs={'input1': (0, 1.4, 0), 'operation': 'add'},
|
||||
)
|
||||
self.node.connectattr('position', math, 'input2')
|
||||
self._score_text = ba.newnode('text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'scale': 0.02,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center'
|
||||
})
|
||||
self._score_text = ba.newnode(
|
||||
'text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'scale': 0.02,
|
||||
'shadow': 0.5,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
)
|
||||
math.connectattr('output', self._score_text, 'position')
|
||||
else:
|
||||
assert isinstance(self._score_text.scale, float)
|
||||
@ -311,7 +326,8 @@ class Flag(ba.Actor):
|
||||
self._score_text.color = ba.safecolor(self.node.color)
|
||||
ba.animate(self._score_text, 'scale', {0: start_scale, 0.2: 0.02})
|
||||
self._score_text_hide_timer = ba.Timer(
|
||||
1.0, ba.WeakCall(self._hide_score_text))
|
||||
1.0, ba.WeakCall(self._hide_score_text)
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
@ -324,10 +340,21 @@ class Flag(ba.Actor):
|
||||
assert self.node
|
||||
assert msg.force_direction is not None
|
||||
self.node.handlemessage(
|
||||
'impulse', msg.pos[0], msg.pos[1], msg.pos[2], msg.velocity[0],
|
||||
msg.velocity[1], msg.velocity[2], msg.magnitude,
|
||||
msg.velocity_magnitude, msg.radius, 0, msg.force_direction[0],
|
||||
msg.force_direction[1], msg.force_direction[2])
|
||||
'impulse',
|
||||
msg.pos[0],
|
||||
msg.pos[1],
|
||||
msg.pos[2],
|
||||
msg.velocity[0],
|
||||
msg.velocity[1],
|
||||
msg.velocity[2],
|
||||
msg.magnitude,
|
||||
msg.velocity_magnitude,
|
||||
msg.radius,
|
||||
0,
|
||||
msg.force_direction[0],
|
||||
msg.force_direction[1],
|
||||
msg.force_direction[2],
|
||||
)
|
||||
elif isinstance(msg, ba.PickedUpMessage):
|
||||
self._held_count += 1
|
||||
if self._held_count == 1 and self._counter is not None:
|
||||
|
||||
@ -18,6 +18,7 @@ class Image(ba.Actor):
|
||||
|
||||
class Transition(Enum):
|
||||
"""Transition types we support."""
|
||||
|
||||
FADE_IN = 'fade_in'
|
||||
IN_RIGHT = 'in_right'
|
||||
IN_LEFT = 'in_left'
|
||||
@ -27,25 +28,28 @@ class Image(ba.Actor):
|
||||
|
||||
class Attach(Enum):
|
||||
"""Attach types we support."""
|
||||
|
||||
CENTER = 'center'
|
||||
TOP_CENTER = 'topCenter'
|
||||
TOP_LEFT = 'topLeft'
|
||||
BOTTOM_CENTER = 'bottomCenter'
|
||||
|
||||
def __init__(self,
|
||||
texture: ba.Texture | dict[str, Any],
|
||||
position: tuple[float, float] = (0, 0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
attach: Attach = Attach.CENTER,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
scale: tuple[float, float] = (100.0, 100.0),
|
||||
transition_out_delay: float | None = None,
|
||||
model_opaque: ba.Model | None = None,
|
||||
model_transparent: ba.Model | None = None,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
texture: ba.Texture | dict[str, Any],
|
||||
position: tuple[float, float] = (0, 0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
attach: Attach = Attach.CENTER,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
scale: tuple[float, float] = (100.0, 100.0),
|
||||
transition_out_delay: float | None = None,
|
||||
model_opaque: ba.Model | None = None,
|
||||
model_transparent: ba.Model | None = None,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
@ -66,22 +70,24 @@ class Image(ba.Actor):
|
||||
tint_texture = None
|
||||
mask_texture = None
|
||||
|
||||
self.node = ba.newnode('image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_color': tint_color,
|
||||
'tint_texture': tint_texture,
|
||||
'position': position,
|
||||
'vr_depth': vr_depth,
|
||||
'scale': scale,
|
||||
'mask_texture': mask_texture,
|
||||
'color': color,
|
||||
'absolute_scale': True,
|
||||
'host_only': host_only,
|
||||
'front': front,
|
||||
'attach': attach.value
|
||||
},
|
||||
delegate=self)
|
||||
self.node = ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_color': tint_color,
|
||||
'tint_texture': tint_texture,
|
||||
'position': position,
|
||||
'vr_depth': vr_depth,
|
||||
'scale': scale,
|
||||
'mask_texture': mask_texture,
|
||||
'color': color,
|
||||
'absolute_scale': True,
|
||||
'host_only': host_only,
|
||||
'front': front,
|
||||
'attach': attach.value,
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
if model_opaque is not None:
|
||||
self.node.model_opaque = model_opaque
|
||||
@ -95,13 +101,13 @@ class Image(ba.Actor):
|
||||
keys[transition_delay + transition_out_delay] = color[3]
|
||||
keys[transition_delay + transition_out_delay + 0.5] = 0
|
||||
ba.animate(self.node, 'opacity', keys)
|
||||
cmb = self.position_combine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={'size': 2})
|
||||
cmb = self.position_combine = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
if transition is self.Transition.IN_RIGHT:
|
||||
keys = {
|
||||
transition_delay: position[0] + 1200,
|
||||
transition_delay + 0.2: position[0]
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
@ -110,32 +116,27 @@ class Image(ba.Actor):
|
||||
elif transition is self.Transition.IN_LEFT:
|
||||
keys = {
|
||||
transition_delay: position[0] - 1200,
|
||||
transition_delay + 0.2: position[0]
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[0]
|
||||
keys[transition_delay + transition_out_delay +
|
||||
200] = -position[0] - 1200
|
||||
keys[transition_delay + transition_out_delay + 200] = (
|
||||
-position[0] - 1200
|
||||
)
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
cmb.input1 = position[1]
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM_SLOW:
|
||||
keys = {
|
||||
transition_delay: -400,
|
||||
transition_delay + 3.5: position[1]
|
||||
}
|
||||
keys = {transition_delay: -400, transition_delay + 3.5: position[1]}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 2.0: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
ba.animate(cmb, 'input1', keys)
|
||||
ba.animate(self.node, 'opacity', o_keys)
|
||||
elif transition is self.Transition.IN_BOTTOM:
|
||||
keys = {
|
||||
transition_delay: -400,
|
||||
transition_delay + 0.2: position[1]
|
||||
}
|
||||
keys = {transition_delay: -400, transition_delay + 0.2: position[1]}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[1]
|
||||
@ -159,8 +160,10 @@ class Image(ba.Actor):
|
||||
|
||||
# If we're transitioning out, die at the end of it.
|
||||
if transition_out_delay is not None:
|
||||
ba.timer(transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
ba.timer(
|
||||
transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
|
||||
@ -20,32 +20,34 @@ class OnScreenCountdown(ba.Actor):
|
||||
Useful for time-based games that count down to zero.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
duration: int,
|
||||
endcall: Callable[[], Any] | None = None):
|
||||
def __init__(self, duration: int, endcall: Callable[[], Any] | None = None):
|
||||
"""Duration is provided in seconds."""
|
||||
super().__init__()
|
||||
self._timeremaining = duration
|
||||
self._ended = False
|
||||
self._endcall = endcall
|
||||
self.node = ba.newnode('text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': ''
|
||||
})
|
||||
self.inputnode = ba.newnode('timedisplay',
|
||||
attrs={
|
||||
'time2': duration * 1000,
|
||||
'timemax': duration * 1000,
|
||||
'timemin': 0
|
||||
})
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
self.inputnode = ba.newnode(
|
||||
'timedisplay',
|
||||
attrs={
|
||||
'time2': duration * 1000,
|
||||
'timemax': duration * 1000,
|
||||
'timemin': 0,
|
||||
},
|
||||
)
|
||||
self.inputnode.connectattr('output', self.node, 'text')
|
||||
self._countdownsounds = {
|
||||
10: ba.getsound('announceTen'),
|
||||
@ -57,7 +59,7 @@ class OnScreenCountdown(ba.Actor):
|
||||
4: ba.getsound('announceFour'),
|
||||
3: ba.getsound('announceThree'),
|
||||
2: ba.getsound('announceTwo'),
|
||||
1: ba.getsound('announceOne')
|
||||
1: ba.getsound('announceOne'),
|
||||
}
|
||||
self._timer: ba.Timer | None = None
|
||||
|
||||
@ -65,8 +67,9 @@ class OnScreenCountdown(ba.Actor):
|
||||
"""Start the timer."""
|
||||
globalsnode = ba.getactivity().globalsnode
|
||||
globalsnode.connectattr('time', self.inputnode, 'time1')
|
||||
self.inputnode.time2 = (globalsnode.time +
|
||||
(self._timeremaining + 1) * 1000)
|
||||
self.inputnode.time2 = (
|
||||
globalsnode.time + (self._timeremaining + 1) * 1000
|
||||
)
|
||||
self._timer = ba.Timer(1.0, self._update, repeat=True)
|
||||
|
||||
def on_expire(self) -> None:
|
||||
|
||||
@ -22,23 +22,23 @@ class OnScreenTimer(ba.Actor):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._starttime_ms: int | None = None
|
||||
self.node = ba.newnode('text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': ''
|
||||
})
|
||||
self.inputnode = ba.newnode('timedisplay',
|
||||
attrs={
|
||||
'timemin': 0,
|
||||
'showsubseconds': True
|
||||
})
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'center',
|
||||
'h_align': 'center',
|
||||
'color': (1, 1, 0.5, 1),
|
||||
'flatness': 0.5,
|
||||
'shadow': 0.5,
|
||||
'position': (0, -70),
|
||||
'scale': 1.4,
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
self.inputnode = ba.newnode(
|
||||
'timedisplay', attrs={'timemin': 0, 'showsubseconds': True}
|
||||
)
|
||||
self.inputnode.connectattr('output', self.node, 'text')
|
||||
|
||||
def start(self) -> None:
|
||||
@ -47,16 +47,19 @@ class OnScreenTimer(ba.Actor):
|
||||
assert isinstance(tval, int)
|
||||
self._starttime_ms = tval
|
||||
self.inputnode.time1 = self._starttime_ms
|
||||
ba.getactivity().globalsnode.connectattr('time', self.inputnode,
|
||||
'time2')
|
||||
ba.getactivity().globalsnode.connectattr(
|
||||
'time', self.inputnode, 'time2'
|
||||
)
|
||||
|
||||
def has_started(self) -> bool:
|
||||
"""Return whether this timer has started yet."""
|
||||
return self._starttime_ms is not None
|
||||
|
||||
def stop(self,
|
||||
endtime: int | float | None = None,
|
||||
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> None:
|
||||
def stop(
|
||||
self,
|
||||
endtime: int | float | None = None,
|
||||
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS,
|
||||
) -> None:
|
||||
"""End the timer.
|
||||
|
||||
If 'endtime' is not None, it is used when calculating
|
||||
@ -85,19 +88,19 @@ class OnScreenTimer(ba.Actor):
|
||||
# Overloads so type checker knows our exact return type based in args.
|
||||
@overload
|
||||
def getstarttime(
|
||||
self,
|
||||
timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS
|
||||
self, timeformat: Literal[ba.TimeFormat.SECONDS] = ba.TimeFormat.SECONDS
|
||||
) -> float:
|
||||
...
|
||||
|
||||
@overload
|
||||
def getstarttime(self,
|
||||
timeformat: Literal[ba.TimeFormat.MILLISECONDS]) -> int:
|
||||
def getstarttime(
|
||||
self, timeformat: Literal[ba.TimeFormat.MILLISECONDS]
|
||||
) -> int:
|
||||
...
|
||||
|
||||
def getstarttime(
|
||||
self,
|
||||
timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS) -> int | float:
|
||||
self, timeformat: ba.TimeFormat = ba.TimeFormat.SECONDS
|
||||
) -> int | float:
|
||||
"""Return the sim-time when start() was called.
|
||||
|
||||
Time will be returned in seconds if timeformat is SECONDS or
|
||||
|
||||
@ -45,24 +45,28 @@ class PlayerSpaz(Spaz):
|
||||
to the current ba.Activity.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
player: ba.Player,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
highlight: Sequence[float] = (0.5, 0.5, 0.5),
|
||||
character: str = 'Spaz',
|
||||
powerups_expire: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
player: ba.Player,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0),
|
||||
highlight: Sequence[float] = (0.5, 0.5, 0.5),
|
||||
character: str = 'Spaz',
|
||||
powerups_expire: bool = True,
|
||||
):
|
||||
"""Create a spaz for the provided ba.Player.
|
||||
|
||||
Note: this does not wire up any controls;
|
||||
you must call connect_controls_to_player() to do so.
|
||||
"""
|
||||
|
||||
super().__init__(color=color,
|
||||
highlight=highlight,
|
||||
character=character,
|
||||
source_player=player,
|
||||
start_invincible=True,
|
||||
powerups_expire=powerups_expire)
|
||||
super().__init__(
|
||||
color=color,
|
||||
highlight=highlight,
|
||||
character=character,
|
||||
source_player=player,
|
||||
start_invincible=True,
|
||||
powerups_expire=powerups_expire,
|
||||
)
|
||||
self.last_player_attacked_by: ba.Player | None = None
|
||||
self.last_attacked_time = 0.0
|
||||
self.last_attacked_type: tuple[str, str] | None = None
|
||||
@ -74,19 +78,20 @@ class PlayerSpaz(Spaz):
|
||||
# Overloads to tell the type system our return type based on doraise val.
|
||||
|
||||
@overload
|
||||
def getplayer(self,
|
||||
playertype: type[PlayerType],
|
||||
doraise: Literal[False] = False) -> PlayerType | None:
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: Literal[False] = False
|
||||
) -> PlayerType | None:
|
||||
...
|
||||
|
||||
@overload
|
||||
def getplayer(self, playertype: type[PlayerType],
|
||||
doraise: Literal[True]) -> PlayerType:
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: Literal[True]
|
||||
) -> PlayerType:
|
||||
...
|
||||
|
||||
def getplayer(self,
|
||||
playertype: type[PlayerType],
|
||||
doraise: bool = False) -> PlayerType | None:
|
||||
def getplayer(
|
||||
self, playertype: type[PlayerType], doraise: bool = False
|
||||
) -> PlayerType | None:
|
||||
"""Get the ba.Player associated with this Spaz.
|
||||
|
||||
By default this will return None if the Player no longer exists.
|
||||
@ -99,13 +104,15 @@ class PlayerSpaz(Spaz):
|
||||
raise ba.PlayerNotFoundError()
|
||||
return player if player.exists() else None
|
||||
|
||||
def connect_controls_to_player(self,
|
||||
enable_jump: bool = True,
|
||||
enable_punch: bool = True,
|
||||
enable_pickup: bool = True,
|
||||
enable_bomb: bool = True,
|
||||
enable_run: bool = True,
|
||||
enable_fly: bool = True) -> None:
|
||||
def connect_controls_to_player(
|
||||
self,
|
||||
enable_jump: bool = True,
|
||||
enable_punch: bool = True,
|
||||
enable_pickup: bool = True,
|
||||
enable_bomb: bool = True,
|
||||
enable_run: bool = True,
|
||||
enable_fly: bool = True,
|
||||
) -> None:
|
||||
"""Wire this spaz up to the provided ba.Player.
|
||||
|
||||
Full control of the character is given by default
|
||||
@ -126,10 +133,12 @@ class PlayerSpaz(Spaz):
|
||||
|
||||
player.assigninput(ba.InputType.UP_DOWN, self.on_move_up_down)
|
||||
player.assigninput(ba.InputType.LEFT_RIGHT, self.on_move_left_right)
|
||||
player.assigninput(ba.InputType.HOLD_POSITION_PRESS,
|
||||
self.on_hold_position_press)
|
||||
player.assigninput(ba.InputType.HOLD_POSITION_RELEASE,
|
||||
self.on_hold_position_release)
|
||||
player.assigninput(
|
||||
ba.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press
|
||||
)
|
||||
player.assigninput(
|
||||
ba.InputType.HOLD_POSITION_RELEASE, self.on_hold_position_release
|
||||
)
|
||||
intp = ba.InputType
|
||||
if enable_jump:
|
||||
player.assigninput(intp.JUMP_PRESS, self.on_jump_press)
|
||||
@ -171,8 +180,10 @@ class PlayerSpaz(Spaz):
|
||||
self.on_run(0.0)
|
||||
self.on_fly_release()
|
||||
else:
|
||||
print('WARNING: disconnect_controls_from_player() called for'
|
||||
' non-connected player')
|
||||
print(
|
||||
'WARNING: disconnect_controls_from_player() called for'
|
||||
' non-connected player'
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
# FIXME: Tidy this up.
|
||||
@ -216,8 +227,9 @@ class PlayerSpaz(Spaz):
|
||||
if not self._dead:
|
||||
|
||||
# Immediate-mode or left-game deaths don't count as 'kills'.
|
||||
killed = (not msg.immediate
|
||||
and msg.how is not ba.DeathType.LEFT_GAME)
|
||||
killed = (
|
||||
not msg.immediate and msg.how is not ba.DeathType.LEFT_GAME
|
||||
)
|
||||
|
||||
activity = self._activity()
|
||||
|
||||
@ -237,13 +249,16 @@ class PlayerSpaz(Spaz):
|
||||
# all bot kills would register as suicides; need to
|
||||
# change this from last_player_attacked_by to
|
||||
# something like last_actor_attacked_by to fix that.
|
||||
if (self.last_player_attacked_by
|
||||
and ba.time() - self.last_attacked_time < 4.0):
|
||||
if (
|
||||
self.last_player_attacked_by
|
||||
and ba.time() - self.last_attacked_time < 4.0
|
||||
):
|
||||
killerplayer = self.last_player_attacked_by
|
||||
else:
|
||||
# ok, call it a suicide unless we're in co-op
|
||||
if (activity is not None and not isinstance(
|
||||
activity.session, ba.CoopSession)):
|
||||
if activity is not None and not isinstance(
|
||||
activity.session, ba.CoopSession
|
||||
):
|
||||
killerplayer = player
|
||||
else:
|
||||
killerplayer = None
|
||||
@ -255,8 +270,10 @@ class PlayerSpaz(Spaz):
|
||||
# Only report if both the player and the activity still exist.
|
||||
if killed and activity is not None and player:
|
||||
activity.handlemessage(
|
||||
ba.PlayerDiedMessage(player, killed, killerplayer,
|
||||
msg.how))
|
||||
ba.PlayerDiedMessage(
|
||||
player, killed, killerplayer, msg.how
|
||||
)
|
||||
)
|
||||
|
||||
super().handlemessage(msg) # Augment standard behavior.
|
||||
|
||||
|
||||
@ -19,13 +19,15 @@ class PopupText(ba.Actor):
|
||||
category: Gameplay Classes
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
text: str | ba.Lstr,
|
||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
random_offset: float = 0.5,
|
||||
offset: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
scale: float = 1.0):
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
random_offset: float = 0.5,
|
||||
offset: Sequence[float] = (0.0, 0.0, 0.0),
|
||||
scale: float = 1.0,
|
||||
):
|
||||
"""Instantiate with given values.
|
||||
|
||||
random_offset is the amount of random offset from the provided position
|
||||
@ -35,73 +37,86 @@ class PopupText(ba.Actor):
|
||||
super().__init__()
|
||||
if len(color) == 3:
|
||||
color = (color[0], color[1], color[2], 1.0)
|
||||
pos = (position[0] + offset[0] + random_offset *
|
||||
(0.5 - random.random()), position[1] + offset[1] +
|
||||
random_offset * (0.5 - random.random()), position[2] +
|
||||
offset[2] + random_offset * (0.5 - random.random()))
|
||||
pos = (
|
||||
position[0] + offset[0] + random_offset * (0.5 - random.random()),
|
||||
position[1] + offset[1] + random_offset * (0.5 - random.random()),
|
||||
position[2] + offset[2] + random_offset * (0.5 - random.random()),
|
||||
)
|
||||
|
||||
self.node = ba.newnode('text',
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center'
|
||||
},
|
||||
delegate=self)
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': text,
|
||||
'in_world': True,
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'h_align': 'center',
|
||||
},
|
||||
delegate=self,
|
||||
)
|
||||
|
||||
lifespan = 1.5
|
||||
|
||||
# scale up
|
||||
ba.animate(
|
||||
self.node, 'scale', {
|
||||
self.node,
|
||||
'scale',
|
||||
{
|
||||
0: 0.0,
|
||||
lifespan * 0.11: 0.020 * 0.7 * scale,
|
||||
lifespan * 0.16: 0.013 * 0.7 * scale,
|
||||
lifespan * 0.25: 0.014 * 0.7 * scale
|
||||
})
|
||||
lifespan * 0.25: 0.014 * 0.7 * scale,
|
||||
},
|
||||
)
|
||||
|
||||
# translate upward
|
||||
self._tcombine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': pos[0],
|
||||
'input2': pos[2],
|
||||
'size': 3
|
||||
})
|
||||
ba.animate(self._tcombine, 'input1', {
|
||||
0: pos[1] + 1.5,
|
||||
lifespan: pos[1] + 2.0
|
||||
})
|
||||
self._tcombine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input0': pos[0], 'input2': pos[2], 'size': 3},
|
||||
)
|
||||
ba.animate(
|
||||
self._tcombine, 'input1', {0: pos[1] + 1.5, lifespan: pos[1] + 2.0}
|
||||
)
|
||||
self._tcombine.connectattr('output', self.node, 'position')
|
||||
|
||||
# fade our opacity in/out
|
||||
self._combine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4
|
||||
})
|
||||
self._combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4,
|
||||
},
|
||||
)
|
||||
for i in range(4):
|
||||
ba.animate(
|
||||
self._combine, 'input' + str(i), {
|
||||
self._combine,
|
||||
'input' + str(i),
|
||||
{
|
||||
0.13 * lifespan: color[i],
|
||||
0.18 * lifespan: 4.0 * color[i],
|
||||
0.22 * lifespan: color[i]
|
||||
})
|
||||
ba.animate(self._combine, 'input3', {
|
||||
0: 0,
|
||||
0.1 * lifespan: color[3],
|
||||
0.7 * lifespan: color[3],
|
||||
lifespan: 0
|
||||
})
|
||||
0.22 * lifespan: color[i],
|
||||
},
|
||||
)
|
||||
ba.animate(
|
||||
self._combine,
|
||||
'input3',
|
||||
{
|
||||
0: 0,
|
||||
0.1 * lifespan: color[3],
|
||||
0.7 * lifespan: color[3],
|
||||
lifespan: 0,
|
||||
},
|
||||
)
|
||||
self._combine.connectattr('output', self.node, 'color')
|
||||
|
||||
# kill ourself
|
||||
self._die_timer = ba.Timer(
|
||||
lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage())
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
|
||||
@ -87,6 +87,7 @@ class PowerupBoxFactory:
|
||||
to get a shared instance.
|
||||
"""
|
||||
from ba.internal import get_default_powerup_distribution
|
||||
|
||||
shared = SharedObjects.get()
|
||||
self._lastpoweruptype: str | None = None
|
||||
self.model = ba.getmodel('powerup')
|
||||
@ -118,7 +119,8 @@ class PowerupBoxFactory:
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', _TouchedMessage()),
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
# We don't wanna be picked up.
|
||||
self.powerup_material.add_actions(
|
||||
@ -136,9 +138,11 @@ class PowerupBoxFactory:
|
||||
for _i in range(int(freq)):
|
||||
self._powerupdist.append(powerup)
|
||||
|
||||
def get_random_powerup_type(self,
|
||||
forcetype: str | None = None,
|
||||
excludetypes: list[str] | None = None) -> str:
|
||||
def get_random_powerup_type(
|
||||
self,
|
||||
forcetype: str | None = None,
|
||||
excludetypes: list[str] | None = None,
|
||||
) -> str:
|
||||
"""Returns a random powerup type (string).
|
||||
|
||||
See ba.Powerup.poweruptype for available type values.
|
||||
@ -161,9 +165,9 @@ class PowerupBoxFactory:
|
||||
ptype = 'health'
|
||||
else:
|
||||
while True:
|
||||
ptype = self._powerupdist[random.randint(
|
||||
0,
|
||||
len(self._powerupdist) - 1)]
|
||||
ptype = self._powerupdist[
|
||||
random.randint(0, len(self._powerupdist) - 1)
|
||||
]
|
||||
if ptype not in excludetypes:
|
||||
break
|
||||
self._lastpoweruptype = ptype
|
||||
@ -199,10 +203,12 @@ class PowerupBox(ba.Actor):
|
||||
node: ba.Node
|
||||
"""The 'prop' ba.Node representing this box."""
|
||||
|
||||
def __init__(self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
poweruptype: str = 'triple_bombs',
|
||||
expire: bool = True):
|
||||
def __init__(
|
||||
self,
|
||||
position: Sequence[float] = (0.0, 1.0, 0.0),
|
||||
poweruptype: str = 'triple_bombs',
|
||||
expire: bool = True,
|
||||
):
|
||||
"""Create a powerup-box of the requested type at the given position.
|
||||
|
||||
see ba.Powerup.poweruptype for valid type strings.
|
||||
@ -250,19 +256,23 @@ class PowerupBox(ba.Actor):
|
||||
'color_texture': tex,
|
||||
'reflection': 'powerup',
|
||||
'reflection_scale': [1.0],
|
||||
'materials': (factory.powerup_material,
|
||||
shared.object_material)
|
||||
}) # yapf: disable
|
||||
'materials': (factory.powerup_material, shared.object_material),
|
||||
},
|
||||
) # yapf: disable
|
||||
|
||||
# Animate in.
|
||||
curve = ba.animate(self.node, 'model_scale', {0: 0, 0.14: 1.6, 0.2: 1})
|
||||
ba.timer(0.2, curve.delete)
|
||||
|
||||
if expire:
|
||||
ba.timer(DEFAULT_POWERUP_INTERVAL - 2.5,
|
||||
ba.WeakCall(self._start_flashing))
|
||||
ba.timer(DEFAULT_POWERUP_INTERVAL - 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
ba.timer(
|
||||
DEFAULT_POWERUP_INTERVAL - 2.5,
|
||||
ba.WeakCall(self._start_flashing),
|
||||
)
|
||||
ba.timer(
|
||||
DEFAULT_POWERUP_INTERVAL - 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def _start_flashing(self) -> None:
|
||||
if self.node:
|
||||
@ -275,9 +285,9 @@ class PowerupBox(ba.Actor):
|
||||
factory = PowerupBoxFactory.get()
|
||||
assert self.node
|
||||
if self.poweruptype == 'health':
|
||||
ba.playsound(factory.health_powerup_sound,
|
||||
3,
|
||||
position=self.node.position)
|
||||
ba.playsound(
|
||||
factory.health_powerup_sound, 3, position=self.node.position
|
||||
)
|
||||
ba.playsound(factory.powerup_sound, 3, position=self.node.position)
|
||||
self._powersgiven = True
|
||||
self.handlemessage(ba.DieMessage())
|
||||
@ -286,7 +296,8 @@ class PowerupBox(ba.Actor):
|
||||
if not self._powersgiven:
|
||||
node = ba.getcollision().opposingnode
|
||||
node.handlemessage(
|
||||
ba.PowerupMessage(self.poweruptype, sourcenode=self.node))
|
||||
ba.PowerupMessage(self.poweruptype, sourcenode=self.node)
|
||||
)
|
||||
|
||||
elif isinstance(msg, ba.DieMessage):
|
||||
if self.node:
|
||||
|
||||
@ -39,8 +39,11 @@ class RespawnIcon:
|
||||
|
||||
# Now find the first unused slot and use that.
|
||||
index = 0
|
||||
while (index in respawn_icons and respawn_icons[index]() is not None
|
||||
and respawn_icons[index]().visible):
|
||||
while (
|
||||
index in respawn_icons
|
||||
and respawn_icons[index]() is not None
|
||||
and respawn_icons[index]().visible
|
||||
):
|
||||
index += 1
|
||||
respawn_icons[index] = weakref.ref(self)
|
||||
|
||||
@ -50,66 +53,75 @@ class RespawnIcon:
|
||||
h_offs = -10
|
||||
ipos = (-40 - h_offs if on_right else 40 + h_offs, -180 + offs)
|
||||
self._image: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode('image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_texture': icon['tint_texture'],
|
||||
'tint_color': icon['tint_color'],
|
||||
'tint2_color': icon['tint2_color'],
|
||||
'mask_texture': mask_tex,
|
||||
'position': ipos,
|
||||
'scale': (32, 32),
|
||||
'opacity': 1.0,
|
||||
'absolute_scale': True,
|
||||
'attach': 'topRight' if on_right else 'topLeft'
|
||||
}))
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'texture': texture,
|
||||
'tint_texture': icon['tint_texture'],
|
||||
'tint_color': icon['tint_color'],
|
||||
'tint2_color': icon['tint2_color'],
|
||||
'mask_texture': mask_tex,
|
||||
'position': ipos,
|
||||
'scale': (32, 32),
|
||||
'opacity': 1.0,
|
||||
'absolute_scale': True,
|
||||
'attach': 'topRight' if on_right else 'topLeft',
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._image.node
|
||||
ba.animate(self._image.node, 'opacity', {0.0: 0, 0.2: 0.7})
|
||||
|
||||
npos = (-40 - h_offs if on_right else 40 + h_offs, -205 + 49 + offs)
|
||||
self._name: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode('text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'text': ba.Lstr(value=player.getname()),
|
||||
'maxwidth': 100,
|
||||
'h_align': 'center',
|
||||
'v_align': 'center',
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'scale': 0.5,
|
||||
'position': npos
|
||||
}))
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'v_attach': 'top',
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'text': ba.Lstr(value=player.getname()),
|
||||
'maxwidth': 100,
|
||||
'h_align': 'center',
|
||||
'v_align': 'center',
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'scale': 0.5,
|
||||
'position': npos,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._name.node
|
||||
ba.animate(self._name.node, 'scale', {0: 0, 0.1: 0.5})
|
||||
|
||||
tpos = (-60 - h_offs if on_right else 60 + h_offs, -192 + offs)
|
||||
self._text: ba.NodeActor | None = ba.NodeActor(
|
||||
ba.newnode('text',
|
||||
attrs={
|
||||
'position': tpos,
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'h_align': 'right' if on_right else 'left',
|
||||
'scale': 0.9,
|
||||
'shadow': 0.5,
|
||||
'flatness': 0.5,
|
||||
'v_attach': 'top',
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'text': ''
|
||||
}))
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'position': tpos,
|
||||
'h_attach': 'right' if on_right else 'left',
|
||||
'h_align': 'right' if on_right else 'left',
|
||||
'scale': 0.9,
|
||||
'shadow': 0.5,
|
||||
'flatness': 0.5,
|
||||
'v_attach': 'top',
|
||||
'color': ba.safecolor(icon['tint_color']),
|
||||
'text': '',
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
assert self._text.node
|
||||
ba.animate(self._text.node, 'scale', {0: 0, 0.1: 0.9})
|
||||
|
||||
self._respawn_time = ba.time() + respawn_time
|
||||
self._update()
|
||||
self._timer: ba.Timer | None = ba.Timer(1.0,
|
||||
ba.WeakCall(self._update),
|
||||
repeat=True)
|
||||
self._timer: ba.Timer | None = ba.Timer(
|
||||
1.0, ba.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
|
||||
@ -14,9 +14,15 @@ if TYPE_CHECKING:
|
||||
|
||||
|
||||
class _Entry:
|
||||
|
||||
def __init__(self, scoreboard: Scoreboard, team: ba.Team, do_cover: bool,
|
||||
scale: float, label: ba.Lstr | None, flash_length: float):
|
||||
def __init__(
|
||||
self,
|
||||
scoreboard: Scoreboard,
|
||||
team: ba.Team,
|
||||
do_cover: bool,
|
||||
scale: float,
|
||||
label: ba.Lstr | None,
|
||||
flash_length: float,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
self._scoreboard = weakref.ref(scoreboard)
|
||||
self._do_cover = do_cover
|
||||
@ -45,84 +51,93 @@ class _Entry:
|
||||
if vrmode:
|
||||
self._backing_color = [0.1 + c * 0.1 for c in safe_team_color]
|
||||
else:
|
||||
self._backing_color = [
|
||||
0.05 + c * 0.17 for c in safe_team_color
|
||||
]
|
||||
self._backing_color = [0.05 + c * 0.17 for c in safe_team_color]
|
||||
else:
|
||||
self._backing_color = [0.05 + c * 0.1 for c in safe_team_color]
|
||||
|
||||
opacity = (0.8 if vrmode else 0.8) if self._do_cover else 0.5
|
||||
self._backing = ba.NodeActor(
|
||||
ba.newnode('image',
|
||||
attrs={
|
||||
'scale': (self._width, self._height),
|
||||
'opacity': opacity,
|
||||
'color': self._backing_color,
|
||||
'vr_depth': -3,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._backing_tex
|
||||
}))
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'scale': (self._width, self._height),
|
||||
'opacity': opacity,
|
||||
'color': self._backing_color,
|
||||
'vr_depth': -3,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._backing_tex,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self._barcolor = safe_team_color
|
||||
self._bar = ba.NodeActor(
|
||||
ba.newnode('image',
|
||||
attrs={
|
||||
'opacity': 0.7,
|
||||
'color': self._barcolor,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._bar_tex
|
||||
}))
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'opacity': 0.7,
|
||||
'color': self._barcolor,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._bar_tex,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
self._bar_scale = ba.newnode('combine',
|
||||
owner=self._bar.node,
|
||||
attrs={
|
||||
'size': 2,
|
||||
'input0': self._bar_width,
|
||||
'input1': self._bar_height
|
||||
})
|
||||
self._bar_scale = ba.newnode(
|
||||
'combine',
|
||||
owner=self._bar.node,
|
||||
attrs={
|
||||
'size': 2,
|
||||
'input0': self._bar_width,
|
||||
'input1': self._bar_height,
|
||||
},
|
||||
)
|
||||
assert self._bar.node
|
||||
self._bar_scale.connectattr('output', self._bar.node, 'scale')
|
||||
self._bar_position = ba.newnode('combine',
|
||||
owner=self._bar.node,
|
||||
attrs={
|
||||
'size': 2,
|
||||
'input0': 0,
|
||||
'input1': 0
|
||||
})
|
||||
self._bar_position = ba.newnode(
|
||||
'combine',
|
||||
owner=self._bar.node,
|
||||
attrs={'size': 2, 'input0': 0, 'input1': 0},
|
||||
)
|
||||
self._bar_position.connectattr('output', self._bar.node, 'position')
|
||||
self._cover_color = safe_team_color
|
||||
if self._do_cover:
|
||||
self._cover = ba.NodeActor(
|
||||
ba.newnode('image',
|
||||
attrs={
|
||||
'scale':
|
||||
(self._width * 1.15, self._height * 1.6),
|
||||
'opacity': 1.0,
|
||||
'color': self._cover_color,
|
||||
'vr_depth': 2,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._cover_tex,
|
||||
'model_transparent': self._model
|
||||
}))
|
||||
ba.newnode(
|
||||
'image',
|
||||
attrs={
|
||||
'scale': (self._width * 1.15, self._height * 1.6),
|
||||
'opacity': 1.0,
|
||||
'color': self._cover_color,
|
||||
'vr_depth': 2,
|
||||
'attach': 'topLeft',
|
||||
'texture': self._cover_tex,
|
||||
'model_transparent': self._model,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
clr = safe_team_color
|
||||
maxwidth = 130.0 * (1.0 - scoreboard.score_split)
|
||||
flatness = ((1.0 if vrmode else 0.5) if self._do_cover else 1.0)
|
||||
flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0
|
||||
self._score_text = ba.NodeActor(
|
||||
ba.newnode('text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'right',
|
||||
'v_align': 'center',
|
||||
'maxwidth': maxwidth,
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'text': '',
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'color': clr
|
||||
}))
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'right',
|
||||
'v_align': 'center',
|
||||
'maxwidth': maxwidth,
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'text': '',
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'color': clr,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
clr = safe_team_color
|
||||
|
||||
@ -148,28 +163,31 @@ class _Entry:
|
||||
team_name_label = team_name_label[:10] + '...'
|
||||
team_name_label = ba.Lstr(value=team_name_label)
|
||||
|
||||
flatness = ((1.0 if vrmode else 0.5) if self._do_cover else 1.0)
|
||||
flatness = (1.0 if vrmode else 0.5) if self._do_cover else 1.0
|
||||
self._name_text = ba.NodeActor(
|
||||
ba.newnode('text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'left',
|
||||
'v_align': 'center',
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'maxwidth': 130 * scoreboard.score_split,
|
||||
'text': team_name_label,
|
||||
'color': clr + (1.0, )
|
||||
}))
|
||||
ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'h_attach': 'left',
|
||||
'v_attach': 'top',
|
||||
'h_align': 'left',
|
||||
'v_align': 'center',
|
||||
'vr_depth': 2,
|
||||
'scale': self._scale * 0.9,
|
||||
'shadow': 1.0 if vrmode else 0.5,
|
||||
'flatness': flatness,
|
||||
'maxwidth': 130 * scoreboard.score_split,
|
||||
'text': team_name_label,
|
||||
'color': clr + (1.0,),
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
def flash(self, countdown: bool, extra_flash: bool) -> None:
|
||||
"""Flash momentarily."""
|
||||
self._flash_timer = ba.Timer(0.1,
|
||||
ba.WeakCall(self._do_flash),
|
||||
repeat=True)
|
||||
self._flash_timer = ba.Timer(
|
||||
0.1, ba.WeakCall(self._do_flash), repeat=True
|
||||
)
|
||||
if countdown:
|
||||
self._flash_counter = 10
|
||||
else:
|
||||
@ -186,23 +204,28 @@ class _Entry:
|
||||
return
|
||||
|
||||
self._pos = tuple(position)
|
||||
self._backing.node.position = (position[0] + self._width / 2,
|
||||
position[1] - self._height / 2)
|
||||
self._backing.node.position = (
|
||||
position[0] + self._width / 2,
|
||||
position[1] - self._height / 2,
|
||||
)
|
||||
if self._do_cover:
|
||||
assert self._cover.node
|
||||
self._cover.node.position = (position[0] + self._width / 2,
|
||||
position[1] - self._height / 2)
|
||||
self._cover.node.position = (
|
||||
position[0] + self._width / 2,
|
||||
position[1] - self._height / 2,
|
||||
)
|
||||
self._bar_position.input0 = self._pos[0] + self._bar_width / 2
|
||||
self._bar_position.input1 = self._pos[1] - self._bar_height / 2
|
||||
assert self._score_text.node
|
||||
self._score_text.node.position = (self._pos[0] + self._width -
|
||||
7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height +
|
||||
16.0 * self._scale)
|
||||
self._score_text.node.position = (
|
||||
self._pos[0] + self._width - 7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height + 16.0 * self._scale,
|
||||
)
|
||||
assert self._name_text.node
|
||||
self._name_text.node.position = (self._pos[0] + 7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height +
|
||||
16.0 * self._scale)
|
||||
self._name_text.node.position = (
|
||||
self._pos[0] + 7.0 * self._scale,
|
||||
self._pos[1] - self._bar_height + 16.0 * self._scale,
|
||||
)
|
||||
|
||||
def _set_flash_colors(self, flash: bool) -> None:
|
||||
self._flash_colors = flash
|
||||
@ -215,16 +238,29 @@ class _Entry:
|
||||
scale = 2.0
|
||||
_safesetcolor(
|
||||
self._backing.node,
|
||||
(self._backing_color[0] * scale, self._backing_color[1] *
|
||||
scale, self._backing_color[2] * scale))
|
||||
_safesetcolor(self._bar.node,
|
||||
(self._barcolor[0] * scale, self._barcolor[1] *
|
||||
scale, self._barcolor[2] * scale))
|
||||
(
|
||||
self._backing_color[0] * scale,
|
||||
self._backing_color[1] * scale,
|
||||
self._backing_color[2] * scale,
|
||||
),
|
||||
)
|
||||
_safesetcolor(
|
||||
self._bar.node,
|
||||
(
|
||||
self._barcolor[0] * scale,
|
||||
self._barcolor[1] * scale,
|
||||
self._barcolor[2] * scale,
|
||||
),
|
||||
)
|
||||
if self._do_cover:
|
||||
_safesetcolor(
|
||||
self._cover.node,
|
||||
(self._cover_color[0] * scale, self._cover_color[1] *
|
||||
scale, self._cover_color[2] * scale))
|
||||
(
|
||||
self._cover_color[0] * scale,
|
||||
self._cover_color[1] * scale,
|
||||
self._cover_color[2] * scale,
|
||||
),
|
||||
)
|
||||
else:
|
||||
_safesetcolor(self._backing.node, self._backing_color)
|
||||
_safesetcolor(self._bar.node, self._barcolor)
|
||||
@ -239,12 +275,14 @@ class _Entry:
|
||||
self._flash_counter -= 1
|
||||
self._set_flash_colors(not self._flash_colors)
|
||||
|
||||
def set_value(self,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True) -> None:
|
||||
def set_value(
|
||||
self,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True,
|
||||
) -> None:
|
||||
"""Set the value for the scoreboard entry."""
|
||||
|
||||
# If we have no score yet, just set it.. otherwise compare
|
||||
@ -253,8 +291,11 @@ class _Entry:
|
||||
self._score = score
|
||||
else:
|
||||
if score > self._score or (countdown and score < self._score):
|
||||
extra_flash = (max_score is not None and score >= max_score
|
||||
and not countdown) or (countdown and score == 0)
|
||||
extra_flash = (
|
||||
max_score is not None
|
||||
and score >= max_score
|
||||
and not countdown
|
||||
) or (countdown and score == 0)
|
||||
if flash:
|
||||
self.flash(countdown, extra_flash)
|
||||
self._score = score
|
||||
@ -265,25 +306,26 @@ class _Entry:
|
||||
if countdown:
|
||||
self._bar_width = max(
|
||||
2.0 * self._scale,
|
||||
self._width * (1.0 - (float(score) / max_score)))
|
||||
self._width * (1.0 - (float(score) / max_score)),
|
||||
)
|
||||
else:
|
||||
self._bar_width = max(
|
||||
2.0 * self._scale,
|
||||
self._width * (min(1.0,
|
||||
float(score) / max_score)))
|
||||
self._width * (min(1.0, float(score) / max_score)),
|
||||
)
|
||||
|
||||
cur_width = self._bar_scale.input0
|
||||
ba.animate(self._bar_scale, 'input0', {
|
||||
0.0: cur_width,
|
||||
0.25: self._bar_width
|
||||
})
|
||||
ba.animate(
|
||||
self._bar_scale, 'input0', {0.0: cur_width, 0.25: self._bar_width}
|
||||
)
|
||||
self._bar_scale.input1 = self._bar_height
|
||||
cur_x = self._bar_position.input0
|
||||
assert self._pos is not None
|
||||
ba.animate(self._bar_position, 'input0', {
|
||||
0.0: cur_x,
|
||||
0.25: self._pos[0] + self._bar_width / 2
|
||||
})
|
||||
ba.animate(
|
||||
self._bar_position,
|
||||
'input0',
|
||||
{0.0: cur_x, 0.25: self._pos[0] + self._bar_width / 2},
|
||||
)
|
||||
self._bar_position.input1 = self._pos[1] - self._bar_height / 2
|
||||
assert self._score_text.node
|
||||
if show_value:
|
||||
@ -353,13 +395,15 @@ class Scoreboard:
|
||||
self._scale = 1.0
|
||||
self._flash_length = 1.0
|
||||
|
||||
def set_team_value(self,
|
||||
team: ba.Team,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True) -> None:
|
||||
def set_team_value(
|
||||
self,
|
||||
team: ba.Team,
|
||||
score: float,
|
||||
max_score: float | None = None,
|
||||
countdown: bool = False,
|
||||
flash: bool = True,
|
||||
show_value: bool = True,
|
||||
) -> None:
|
||||
"""Update the score-board display for the given ba.Team."""
|
||||
if team.id not in self._entries:
|
||||
self._add_team(team)
|
||||
@ -370,21 +414,25 @@ class Scoreboard:
|
||||
team.customdata[self._ENTRYSTORENAME] = _EntryProxy(self, team)
|
||||
|
||||
# Now set the entry.
|
||||
self._entries[team.id].set_value(score=score,
|
||||
max_score=max_score,
|
||||
countdown=countdown,
|
||||
flash=flash,
|
||||
show_value=show_value)
|
||||
self._entries[team.id].set_value(
|
||||
score=score,
|
||||
max_score=max_score,
|
||||
countdown=countdown,
|
||||
flash=flash,
|
||||
show_value=show_value,
|
||||
)
|
||||
|
||||
def _add_team(self, team: ba.Team) -> None:
|
||||
if team.id in self._entries:
|
||||
raise RuntimeError('Duplicate team add')
|
||||
self._entries[team.id] = _Entry(self,
|
||||
team,
|
||||
do_cover=self._do_cover,
|
||||
scale=self._scale,
|
||||
label=self._label,
|
||||
flash_length=self._flash_length)
|
||||
self._entries[team.id] = _Entry(
|
||||
self,
|
||||
team,
|
||||
do_cover=self._do_cover,
|
||||
scale=self._scale,
|
||||
label=self._label,
|
||||
flash_length=self._flash_length,
|
||||
)
|
||||
self._update_teams()
|
||||
|
||||
def remove_team(self, team_id: int) -> None:
|
||||
|
||||
@ -38,10 +38,10 @@ class Spawner:
|
||||
"""The spawn position."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
spawner: Spawner,
|
||||
data: Any,
|
||||
pt: Sequence[float], # pylint: disable=invalid-name
|
||||
self,
|
||||
spawner: Spawner,
|
||||
data: Any,
|
||||
pt: Sequence[float], # pylint: disable=invalid-name
|
||||
):
|
||||
"""Instantiate with the given values."""
|
||||
self.spawner = spawner
|
||||
@ -49,12 +49,13 @@ class Spawner:
|
||||
self.pt = pt # pylint: disable=invalid-name
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: Any = None,
|
||||
pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name
|
||||
spawn_time: float = 1.0,
|
||||
send_spawn_message: bool = True,
|
||||
spawn_callback: Callable[[], Any] | None = None):
|
||||
self,
|
||||
data: Any = None,
|
||||
pt: Sequence[float] = (0, 0, 0), # pylint: disable=invalid-name
|
||||
spawn_time: float = 1.0,
|
||||
send_spawn_message: bool = True,
|
||||
spawn_callback: Callable[[], Any] | None = None,
|
||||
):
|
||||
"""Instantiate a Spawner.
|
||||
|
||||
Requires some custom data, a position,
|
||||
@ -66,19 +67,23 @@ class Spawner:
|
||||
self._data = data
|
||||
self._pt = pt
|
||||
# create a light where the spawn will happen
|
||||
self._light = ba.newnode('light',
|
||||
attrs={
|
||||
'position': tuple(pt),
|
||||
'radius': 0.1,
|
||||
'color': (1.0, 0.1, 0.1),
|
||||
'lights_volumes': False
|
||||
})
|
||||
self._light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': tuple(pt),
|
||||
'radius': 0.1,
|
||||
'color': (1.0, 0.1, 0.1),
|
||||
'lights_volumes': False,
|
||||
},
|
||||
)
|
||||
scl = float(spawn_time) / 3.75
|
||||
min_val = 0.4
|
||||
max_val = 0.7
|
||||
ba.playsound(self._spawner_sound, position=self._light.position)
|
||||
ba.animate(
|
||||
self._light, 'intensity', {
|
||||
self._light,
|
||||
'intensity',
|
||||
{
|
||||
0.0: 0.0,
|
||||
0.25 * scl: max_val,
|
||||
0.500 * scl: min_val,
|
||||
@ -95,8 +100,9 @@ class Spawner:
|
||||
3.250 * scl: 1.5 * max_val,
|
||||
3.500 * scl: min_val,
|
||||
3.750 * scl: 2.0,
|
||||
4.000 * scl: 0.0
|
||||
})
|
||||
4.000 * scl: 0.0,
|
||||
},
|
||||
)
|
||||
ba.timer(spawn_time, self._spawn)
|
||||
|
||||
def _spawn(self) -> None:
|
||||
@ -108,4 +114,5 @@ class Spawner:
|
||||
activity = ba.getactivity()
|
||||
if activity is not None:
|
||||
activity.handlemessage(
|
||||
self.SpawnMessage(self, self._data, self._pt))
|
||||
self.SpawnMessage(self, self._data, self._pt)
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -89,8 +89,9 @@ class Appearance:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
if self.name in ba.app.spaz_appearances:
|
||||
raise Exception('spaz appearance name "' + self.name +
|
||||
'" already exists.')
|
||||
raise Exception(
|
||||
'spaz appearance name "' + self.name + '" already exists.'
|
||||
)
|
||||
ba.app.spaz_appearances[self.name] = self
|
||||
self.color_texture = ''
|
||||
self.color_mask_texture = ''
|
||||
@ -141,10 +142,16 @@ def register_appearances() -> None:
|
||||
t.toes_model = 'neoSpazToes'
|
||||
t.jump_sounds = ['spazJump01', 'spazJump02', 'spazJump03', 'spazJump04']
|
||||
t.attack_sounds = [
|
||||
'spazAttack01', 'spazAttack02', 'spazAttack03', 'spazAttack04'
|
||||
'spazAttack01',
|
||||
'spazAttack02',
|
||||
'spazAttack03',
|
||||
'spazAttack04',
|
||||
]
|
||||
t.impact_sounds = [
|
||||
'spazImpact01', 'spazImpact02', 'spazImpact03', 'spazImpact04'
|
||||
'spazImpact01',
|
||||
'spazImpact02',
|
||||
'spazImpact03',
|
||||
'spazImpact04',
|
||||
]
|
||||
t.death_sounds = ['spazDeath01']
|
||||
t.pickup_sounds = ['spazPickup01']
|
||||
@ -170,10 +177,16 @@ def register_appearances() -> None:
|
||||
t.toes_model = 'zoeToes'
|
||||
t.jump_sounds = ['zoeJump01', 'zoeJump02', 'zoeJump03']
|
||||
t.attack_sounds = [
|
||||
'zoeAttack01', 'zoeAttack02', 'zoeAttack03', 'zoeAttack04'
|
||||
'zoeAttack01',
|
||||
'zoeAttack02',
|
||||
'zoeAttack03',
|
||||
'zoeAttack04',
|
||||
]
|
||||
t.impact_sounds = [
|
||||
'zoeImpact01', 'zoeImpact02', 'zoeImpact03', 'zoeImpact04'
|
||||
'zoeImpact01',
|
||||
'zoeImpact02',
|
||||
'zoeImpact03',
|
||||
'zoeImpact04',
|
||||
]
|
||||
t.death_sounds = ['zoeDeath01']
|
||||
t.pickup_sounds = ['zoePickup01']
|
||||
@ -226,8 +239,16 @@ def register_appearances() -> None:
|
||||
t.lower_leg_model = 'kronkLowerLeg'
|
||||
t.toes_model = 'kronkToes'
|
||||
kronk_sounds = [
|
||||
'kronk1', 'kronk2', 'kronk3', 'kronk4', 'kronk5', 'kronk6', 'kronk7',
|
||||
'kronk8', 'kronk9', 'kronk10'
|
||||
'kronk1',
|
||||
'kronk2',
|
||||
'kronk3',
|
||||
'kronk4',
|
||||
'kronk5',
|
||||
'kronk6',
|
||||
'kronk7',
|
||||
'kronk8',
|
||||
'kronk9',
|
||||
'kronk10',
|
||||
]
|
||||
t.jump_sounds = kronk_sounds
|
||||
t.attack_sounds = kronk_sounds
|
||||
@ -255,8 +276,16 @@ def register_appearances() -> None:
|
||||
t.lower_leg_model = 'melLowerLeg'
|
||||
t.toes_model = 'melToes'
|
||||
mel_sounds = [
|
||||
'mel01', 'mel02', 'mel03', 'mel04', 'mel05', 'mel06', 'mel07', 'mel08',
|
||||
'mel09', 'mel10'
|
||||
'mel01',
|
||||
'mel02',
|
||||
'mel03',
|
||||
'mel04',
|
||||
'mel05',
|
||||
'mel06',
|
||||
'mel07',
|
||||
'mel08',
|
||||
'mel09',
|
||||
'mel10',
|
||||
]
|
||||
t.attack_sounds = mel_sounds
|
||||
t.jump_sounds = mel_sounds
|
||||
@ -284,8 +313,13 @@ def register_appearances() -> None:
|
||||
t.lower_leg_model = 'jackLowerLeg'
|
||||
t.toes_model = 'jackToes'
|
||||
hit_sounds = [
|
||||
'jackHit01', 'jackHit02', 'jackHit03', 'jackHit04', 'jackHit05',
|
||||
'jackHit06', 'jackHit07'
|
||||
'jackHit01',
|
||||
'jackHit02',
|
||||
'jackHit03',
|
||||
'jackHit04',
|
||||
'jackHit05',
|
||||
'jackHit06',
|
||||
'jackHit07',
|
||||
]
|
||||
sounds = ['jack01', 'jack02', 'jack03', 'jack04', 'jack05', 'jack06']
|
||||
t.attack_sounds = sounds
|
||||
@ -340,9 +374,7 @@ def register_appearances() -> None:
|
||||
t.upper_leg_model = 'frostyUpperLeg'
|
||||
t.lower_leg_model = 'frostyLowerLeg'
|
||||
t.toes_model = 'frostyToes'
|
||||
frosty_sounds = [
|
||||
'frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05'
|
||||
]
|
||||
frosty_sounds = ['frosty01', 'frosty02', 'frosty03', 'frosty04', 'frosty05']
|
||||
frosty_hit_sounds = ['frostyHit01', 'frostyHit02', 'frostyHit03']
|
||||
t.attack_sounds = frosty_sounds
|
||||
t.jump_sounds = frosty_sounds
|
||||
@ -558,7 +590,10 @@ def register_appearances() -> None:
|
||||
t.lower_leg_model = 'actionHeroLowerLeg'
|
||||
t.toes_model = 'actionHeroToes'
|
||||
action_hero_sounds = [
|
||||
'actionHero1', 'actionHero2', 'actionHero3', 'actionHero4'
|
||||
'actionHero1',
|
||||
'actionHero2',
|
||||
'actionHero3',
|
||||
'actionHero4',
|
||||
]
|
||||
action_hero_hit_sounds = ['actionHeroHit1', 'actionHeroHit2']
|
||||
t.attack_sounds = action_hero_sounds
|
||||
@ -857,7 +892,10 @@ def register_appearances() -> None:
|
||||
t.lower_leg_model = 'operaSingerLowerLeg'
|
||||
t.toes_model = 'operaSingerToes'
|
||||
opera_singer_sounds = [
|
||||
'operaSinger1', 'operaSinger2', 'operaSinger3', 'operaSinger4'
|
||||
'operaSinger1',
|
||||
'operaSinger2',
|
||||
'operaSinger3',
|
||||
'operaSinger4',
|
||||
]
|
||||
opera_singer_hit_sounds = ['operaSingerHit1', 'operaSingerHit2']
|
||||
t.attack_sounds = opera_singer_sounds
|
||||
|
||||
@ -57,8 +57,12 @@ class SpazBotDiedMessage:
|
||||
how: ba.DeathType
|
||||
"""The particular type of death."""
|
||||
|
||||
def __init__(self, spazbot: SpazBot, killerplayer: ba.Player | None,
|
||||
how: ba.DeathType):
|
||||
def __init__(
|
||||
self,
|
||||
spazbot: SpazBot,
|
||||
killerplayer: ba.Player | None,
|
||||
how: ba.DeathType,
|
||||
):
|
||||
"""Instantiate with given values."""
|
||||
self.spazbot = spazbot
|
||||
self.killerplayer = killerplayer
|
||||
@ -105,12 +109,14 @@ class SpazBot(Spaz):
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Instantiate a spaz-bot."""
|
||||
super().__init__(color=self.color,
|
||||
highlight=self.highlight,
|
||||
character=self.character,
|
||||
source_player=None,
|
||||
start_invincible=False,
|
||||
can_accept_powerups=False)
|
||||
super().__init__(
|
||||
color=self.color,
|
||||
highlight=self.highlight,
|
||||
character=self.character,
|
||||
source_player=None,
|
||||
start_invincible=False,
|
||||
can_accept_powerups=False,
|
||||
)
|
||||
|
||||
# If you need to add custom behavior to a bot, set this to a callable
|
||||
# which takes one arg (the bot) and returns False if the bot's normal
|
||||
@ -126,8 +132,9 @@ class SpazBot(Spaz):
|
||||
self.held_count = 0
|
||||
self.last_player_held_by: ba.Player | None = None
|
||||
self.target_flag: Flag | None = None
|
||||
self._charge_speed = 0.5 * (self.charge_speed_min +
|
||||
self.charge_speed_max)
|
||||
self._charge_speed = 0.5 * (
|
||||
self.charge_speed_min + self.charge_speed_max
|
||||
)
|
||||
self._lead_amount = 0.5
|
||||
self._mode = 'wait'
|
||||
self._charge_closing_in = False
|
||||
@ -172,16 +179,19 @@ class SpazBot(Spaz):
|
||||
|
||||
# Ignore player-points that are significantly below the bot
|
||||
# (keeps bots from following players off cliffs).
|
||||
if (closest_dist is None
|
||||
or dist < closest_dist) and (plpt[1] > botpt[1] - 5.0):
|
||||
if (closest_dist is None or dist < closest_dist) and (
|
||||
plpt[1] > botpt[1] - 5.0
|
||||
):
|
||||
closest_dist = dist
|
||||
closest_vel = plvel
|
||||
closest = plpt
|
||||
if closest_dist is not None:
|
||||
assert closest_vel is not None
|
||||
assert closest is not None
|
||||
return (ba.Vec3(closest[0], closest[1], closest[2]),
|
||||
ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2]))
|
||||
return (
|
||||
ba.Vec3(closest[0], closest[1], closest[2]),
|
||||
ba.Vec3(closest_vel[0], closest_vel[1], closest_vel[2]),
|
||||
)
|
||||
return None, None
|
||||
|
||||
def set_player_points(self, pts: list[tuple[ba.Vec3, ba.Vec3]]) -> None:
|
||||
@ -212,7 +222,7 @@ class SpazBot(Spaz):
|
||||
# towards the flag and try to pick it up.
|
||||
if self.target_flag:
|
||||
if self.node.hold_node:
|
||||
holding_flag = (self.node.hold_node.getnodetype() == 'flag')
|
||||
holding_flag = self.node.hold_node.getnodetype() == 'flag'
|
||||
else:
|
||||
holding_flag = False
|
||||
|
||||
@ -225,7 +235,7 @@ class SpazBot(Spaz):
|
||||
# Otherwise try to go pick it up.
|
||||
elif self.target_flag.node:
|
||||
target_pt_raw = ba.Vec3(*self.target_flag.node.position)
|
||||
diff = (target_pt_raw - our_pos)
|
||||
diff = target_pt_raw - our_pos
|
||||
diff = ba.Vec3(diff[0], 0, diff[2]) # Don't care about y.
|
||||
dist = diff.length()
|
||||
to_target = diff.normalized()
|
||||
@ -253,8 +263,7 @@ class SpazBot(Spaz):
|
||||
|
||||
# Not a flag-bearer. If we're holding anything but a bomb, drop it.
|
||||
if self.node.hold_node:
|
||||
holding_bomb = (self.node.hold_node.getnodetype()
|
||||
in ['bomb', 'prop'])
|
||||
holding_bomb = self.node.hold_node.getnodetype() in ['bomb', 'prop']
|
||||
if not holding_bomb:
|
||||
self.node.pickup_pressed = True
|
||||
self.node.pickup_pressed = False
|
||||
@ -287,10 +296,11 @@ class SpazBot(Spaz):
|
||||
|
||||
# Use a point out in front of them as real target.
|
||||
# (more out in front the farther from us they are)
|
||||
target_pt = (target_pt_raw +
|
||||
target_vel * dist_raw * 0.3 * self._lead_amount)
|
||||
target_pt = (
|
||||
target_pt_raw + target_vel * dist_raw * 0.3 * self._lead_amount
|
||||
)
|
||||
|
||||
diff = (target_pt - our_pos)
|
||||
diff = target_pt - our_pos
|
||||
dist = diff.length()
|
||||
to_target = diff.normalized()
|
||||
|
||||
@ -349,8 +359,9 @@ class SpazBot(Spaz):
|
||||
|
||||
elif self._mode == 'charge':
|
||||
if random.random() < 0.3:
|
||||
self._charge_speed = random.uniform(self.charge_speed_min,
|
||||
self.charge_speed_max)
|
||||
self._charge_speed = random.uniform(
|
||||
self.charge_speed_min, self.charge_speed_max
|
||||
)
|
||||
|
||||
# If we're a runner we run during charges *except when near
|
||||
# an edge (otherwise we tend to fly off easily).
|
||||
@ -396,22 +407,29 @@ class SpazBot(Spaz):
|
||||
# from our target. When this value increases it means our charge
|
||||
# is over (ran by them or something).
|
||||
if self._mode == 'charge':
|
||||
if (self._charge_closing_in
|
||||
and self._last_charge_dist < dist < 3.0):
|
||||
if (
|
||||
self._charge_closing_in
|
||||
and self._last_charge_dist < dist < 3.0
|
||||
):
|
||||
self._charge_closing_in = False
|
||||
self._last_charge_dist = dist
|
||||
|
||||
# If we have a clean shot, throw!
|
||||
if (self.throw_dist_min <= dist < self.throw_dist_max
|
||||
and random.random() < self.throwiness and can_attack):
|
||||
if (
|
||||
self.throw_dist_min <= dist < self.throw_dist_max
|
||||
and random.random() < self.throwiness
|
||||
and can_attack
|
||||
):
|
||||
self._mode = 'throw'
|
||||
self._lead_amount = ((0.4 + random.random() * 0.6)
|
||||
if dist_raw > 4.0 else
|
||||
(0.1 + random.random() * 0.4))
|
||||
self._lead_amount = (
|
||||
(0.4 + random.random() * 0.6)
|
||||
if dist_raw > 4.0
|
||||
else (0.1 + random.random() * 0.4)
|
||||
)
|
||||
self._have_dropped_throw_bomb = False
|
||||
self._throw_release_time = (ba.time() +
|
||||
(1.0 / self.throw_rate) *
|
||||
(0.8 + 1.3 * random.random()))
|
||||
self._throw_release_time = ba.time() + (
|
||||
1.0 / self.throw_rate
|
||||
) * (0.8 + 1.3 * random.random())
|
||||
|
||||
# If we're static, always charge (which for us means barely move).
|
||||
elif self.static:
|
||||
@ -433,8 +451,11 @@ class SpazBot(Spaz):
|
||||
|
||||
# We're within charging distance, backed against an edge,
|
||||
# or farther than our max throw distance.. chaaarge!
|
||||
elif (dist < self.charge_dist_max or dist > self.throw_dist_max
|
||||
or self.map.is_point_near_edge(our_pos, self._running)):
|
||||
elif (
|
||||
dist < self.charge_dist_max
|
||||
or dist > self.throw_dist_max
|
||||
or self.map.is_point_near_edge(our_pos, self._running)
|
||||
):
|
||||
if self._mode != 'charge':
|
||||
self._mode = 'charge'
|
||||
self._lead_amount = 0.01
|
||||
@ -450,10 +471,15 @@ class SpazBot(Spaz):
|
||||
|
||||
# Do some awesome jumps if we're running.
|
||||
# FIXME: pylint: disable=too-many-boolean-expressions
|
||||
if ((self._running and 1.2 < dist < 2.2
|
||||
and ba.time() - self._last_jump_time > 1.0)
|
||||
or (self.bouncy and ba.time() - self._last_jump_time > 0.4
|
||||
and random.random() < 0.5)):
|
||||
if (
|
||||
self._running
|
||||
and 1.2 < dist < 2.2
|
||||
and ba.time() - self._last_jump_time > 1.0
|
||||
) or (
|
||||
self.bouncy
|
||||
and ba.time() - self._last_jump_time > 0.4
|
||||
and random.random() < 0.5
|
||||
):
|
||||
self._last_jump_time = ba.time()
|
||||
self.node.jump_pressed = True
|
||||
self.node.jump_pressed = False
|
||||
@ -526,8 +552,10 @@ class SpazBot(Spaz):
|
||||
# If they were attacked by someone in the last few
|
||||
# seconds that person's the killer.
|
||||
# Otherwise it was a suicide.
|
||||
if (self.last_player_attacked_by
|
||||
and ba.time() - self.last_attacked_time < 4.0):
|
||||
if (
|
||||
self.last_player_attacked_by
|
||||
and ba.time() - self.last_attacked_time < 4.0
|
||||
):
|
||||
killerplayer = self.last_player_attacked_by
|
||||
else:
|
||||
killerplayer = None
|
||||
@ -538,7 +566,8 @@ class SpazBot(Spaz):
|
||||
killerplayer = None
|
||||
if activity is not None:
|
||||
activity.handlemessage(
|
||||
SpazBotDiedMessage(self, killerplayer, msg.how))
|
||||
SpazBotDiedMessage(self, killerplayer, msg.how)
|
||||
)
|
||||
super().handlemessage(msg) # Augment standard behavior.
|
||||
|
||||
# Keep track of the player who last hit us for point rewarding.
|
||||
@ -558,6 +587,7 @@ class BomberBot(SpazBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
character = 'Spaz'
|
||||
punchiness = 0.3
|
||||
|
||||
@ -567,6 +597,7 @@ class BomberBotLite(BomberBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
color = LITE_BOT_COLOR
|
||||
highlight = LITE_BOT_HIGHLIGHT
|
||||
punchiness = 0.2
|
||||
@ -581,6 +612,7 @@ class BomberBotStaticLite(BomberBotLite):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
throw_dist_min = 0.0
|
||||
|
||||
@ -590,6 +622,7 @@ class BomberBotStatic(BomberBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
throw_dist_min = 0.0
|
||||
|
||||
@ -599,6 +632,7 @@ class BomberBotPro(BomberBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
points_mult = 2
|
||||
color = PRO_BOT_COLOR
|
||||
highlight = PRO_BOT_HIGHLIGHT
|
||||
@ -615,6 +649,7 @@ class BomberBotProShielded(BomberBotPro):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
points_mult = 3
|
||||
default_shields = True
|
||||
|
||||
@ -624,6 +659,7 @@ class BomberBotProStatic(BomberBotPro):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
throw_dist_min = 0.0
|
||||
|
||||
@ -633,6 +669,7 @@ class BomberBotProStaticShielded(BomberBotProShielded):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
throw_dist_min = 0.0
|
||||
|
||||
@ -642,6 +679,7 @@ class BrawlerBot(SpazBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
character = 'Kronk'
|
||||
punchiness = 0.9
|
||||
charge_dist_max = 9999.0
|
||||
@ -656,6 +694,7 @@ class BrawlerBotLite(BrawlerBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
color = LITE_BOT_COLOR
|
||||
highlight = LITE_BOT_HIGHLIGHT
|
||||
punchiness = 0.3
|
||||
@ -668,6 +707,7 @@ class BrawlerBotPro(BrawlerBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
color = PRO_BOT_COLOR
|
||||
highlight = PRO_BOT_HIGHLIGHT
|
||||
run = True
|
||||
@ -682,6 +722,7 @@ class BrawlerBotProShielded(BrawlerBotPro):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
default_shields = True
|
||||
points_mult = 3
|
||||
|
||||
@ -731,6 +772,7 @@ class ChargerBotPro(ChargerBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
color = PRO_BOT_COLOR
|
||||
highlight = PRO_BOT_HIGHLIGHT
|
||||
default_shields = True
|
||||
@ -743,6 +785,7 @@ class ChargerBotProShielded(ChargerBotPro):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
default_shields = True
|
||||
points_mult = 4
|
||||
|
||||
@ -752,6 +795,7 @@ class TriggerBot(SpazBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
character = 'Zoe'
|
||||
punchiness = 0.75
|
||||
throwiness = 0.7
|
||||
@ -769,6 +813,7 @@ class TriggerBotStatic(TriggerBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
throw_dist_min = 0.0
|
||||
|
||||
@ -778,6 +823,7 @@ class TriggerBotPro(TriggerBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
color = PRO_BOT_COLOR
|
||||
highlight = PRO_BOT_HIGHLIGHT
|
||||
default_bomb_count = 3
|
||||
@ -796,6 +842,7 @@ class TriggerBotProShielded(TriggerBotPro):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
default_shields = True
|
||||
points_mult = 4
|
||||
|
||||
@ -805,6 +852,7 @@ class StickyBot(SpazBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
character = 'Mel'
|
||||
punchiness = 0.9
|
||||
throwiness = 1.0
|
||||
@ -826,6 +874,7 @@ class StickyBotStatic(StickyBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
static = True
|
||||
|
||||
|
||||
@ -834,6 +883,7 @@ class ExplodeyBot(SpazBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
character = 'Jack Morgan'
|
||||
run = True
|
||||
charge_dist_min = 0.0
|
||||
@ -851,6 +901,7 @@ class ExplodeyBotNoTimeLimit(ExplodeyBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
curse_time = None
|
||||
|
||||
|
||||
@ -859,6 +910,7 @@ class ExplodeyBotShielded(ExplodeyBot):
|
||||
|
||||
category: Bot Classes
|
||||
"""
|
||||
|
||||
default_shields = True
|
||||
points_mult = 5
|
||||
|
||||
@ -889,22 +941,31 @@ class SpazBotSet:
|
||||
self.clear()
|
||||
|
||||
def spawn_bot(
|
||||
self,
|
||||
bot_type: type[SpazBot],
|
||||
pos: Sequence[float],
|
||||
spawn_time: float = 3.0,
|
||||
on_spawn_call: Callable[[SpazBot], Any] | None = None) -> None:
|
||||
self,
|
||||
bot_type: type[SpazBot],
|
||||
pos: Sequence[float],
|
||||
spawn_time: float = 3.0,
|
||||
on_spawn_call: Callable[[SpazBot], Any] | None = None,
|
||||
) -> None:
|
||||
"""Spawn a bot from this set."""
|
||||
from bastd.actor import spawner
|
||||
spawner.Spawner(pt=pos,
|
||||
spawn_time=spawn_time,
|
||||
send_spawn_message=False,
|
||||
spawn_callback=ba.Call(self._spawn_bot, bot_type, pos,
|
||||
on_spawn_call))
|
||||
|
||||
spawner.Spawner(
|
||||
pt=pos,
|
||||
spawn_time=spawn_time,
|
||||
send_spawn_message=False,
|
||||
spawn_callback=ba.Call(
|
||||
self._spawn_bot, bot_type, pos, on_spawn_call
|
||||
),
|
||||
)
|
||||
self._spawning_count += 1
|
||||
|
||||
def _spawn_bot(self, bot_type: type[SpazBot], pos: Sequence[float],
|
||||
on_spawn_call: Callable[[SpazBot], Any] | None) -> None:
|
||||
def _spawn_bot(
|
||||
self,
|
||||
bot_type: type[SpazBot],
|
||||
pos: Sequence[float],
|
||||
on_spawn_call: Callable[[SpazBot], Any] | None,
|
||||
) -> None:
|
||||
spaz = bot_type()
|
||||
ba.playsound(self._spawn_sound, position=pos)
|
||||
assert spaz.node
|
||||
@ -918,8 +979,9 @@ class SpazBotSet:
|
||||
|
||||
def have_living_bots(self) -> bool:
|
||||
"""Return whether any bots in the set are alive or spawning."""
|
||||
return (self._spawning_count > 0
|
||||
or any(any(b.is_alive() for b in l) for l in self._bot_lists))
|
||||
return self._spawning_count > 0 or any(
|
||||
any(b.is_alive() for b in l) for l in self._bot_lists
|
||||
)
|
||||
|
||||
def get_living_bots(self) -> list[SpazBot]:
|
||||
"""Get the living bots in the set."""
|
||||
@ -935,15 +997,18 @@ class SpazBotSet:
|
||||
# Update one of our bot lists each time through.
|
||||
# First off, remove no-longer-existing bots from the list.
|
||||
try:
|
||||
bot_list = self._bot_lists[self._bot_update_list] = ([
|
||||
bot_list = self._bot_lists[self._bot_update_list] = [
|
||||
b for b in self._bot_lists[self._bot_update_list] if b
|
||||
])
|
||||
]
|
||||
except Exception:
|
||||
bot_list = []
|
||||
ba.print_exception('Error updating bot list: ' +
|
||||
str(self._bot_lists[self._bot_update_list]))
|
||||
self._bot_update_list = (self._bot_update_list +
|
||||
1) % self._bot_list_count
|
||||
ba.print_exception(
|
||||
'Error updating bot list: '
|
||||
+ str(self._bot_lists[self._bot_update_list])
|
||||
)
|
||||
self._bot_update_list = (
|
||||
self._bot_update_list + 1
|
||||
) % self._bot_list_count
|
||||
|
||||
# Update our list of player points for the bots to use.
|
||||
player_pts = []
|
||||
@ -956,8 +1021,12 @@ class SpazBotSet:
|
||||
if player.is_alive():
|
||||
assert isinstance(player.actor, Spaz)
|
||||
assert player.actor.node
|
||||
player_pts.append((ba.Vec3(player.actor.node.position),
|
||||
ba.Vec3(player.actor.node.velocity)))
|
||||
player_pts.append(
|
||||
(
|
||||
ba.Vec3(player.actor.node.position),
|
||||
ba.Vec3(player.actor.node.velocity),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
ba.print_exception('Error on bot-set _update.')
|
||||
|
||||
@ -980,9 +1049,9 @@ class SpazBotSet:
|
||||
|
||||
def start_moving(self) -> None:
|
||||
"""Start processing bot AI updates so they start doing their thing."""
|
||||
self._bot_update_timer = ba.Timer(0.05,
|
||||
ba.WeakCall(self._update),
|
||||
repeat=True)
|
||||
self._bot_update_timer = ba.Timer(
|
||||
0.05, ba.WeakCall(self._update), repeat=True
|
||||
)
|
||||
|
||||
def stop_moving(self) -> None:
|
||||
"""Tell all bots to stop moving and stops updating their AI.
|
||||
@ -1022,20 +1091,28 @@ class SpazBotSet:
|
||||
assert bot.node # (should exist if 'if bot' was True)
|
||||
bot.node.move_left_right = 0
|
||||
bot.node.move_up_down = 0
|
||||
ba.timer(0.5 * random.random(),
|
||||
ba.Call(bot.handlemessage, ba.CelebrateMessage()))
|
||||
ba.timer(
|
||||
0.5 * random.random(),
|
||||
ba.Call(bot.handlemessage, ba.CelebrateMessage()),
|
||||
)
|
||||
jump_duration = random.randrange(400, 500)
|
||||
j = random.randrange(0, 200)
|
||||
for _i in range(10):
|
||||
bot.node.jump_pressed = True
|
||||
bot.node.jump_pressed = False
|
||||
j += jump_duration
|
||||
ba.timer(random.uniform(0.0, 1.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'))
|
||||
ba.timer(random.uniform(1.0, 2.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'))
|
||||
ba.timer(random.uniform(2.0, 3.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'))
|
||||
ba.timer(
|
||||
random.uniform(0.0, 1.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'),
|
||||
)
|
||||
ba.timer(
|
||||
random.uniform(1.0, 2.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'),
|
||||
)
|
||||
ba.timer(
|
||||
random.uniform(2.0, 3.0),
|
||||
ba.Call(bot.node.handlemessage, 'attack_sound'),
|
||||
)
|
||||
|
||||
def add_bot(self, bot: SpazBot) -> None:
|
||||
"""Add a ba.SpazBot instance to the set."""
|
||||
|
||||
@ -89,22 +89,33 @@ class SpazFactory:
|
||||
# pylint: disable=cyclic-import
|
||||
# FIXME: should probably put these somewhere common so we don't
|
||||
# have to import them from a module that imports us.
|
||||
from bastd.actor.spaz import (PickupMessage, PunchHitMessage,
|
||||
CurseExplodeMessage)
|
||||
from bastd.actor.spaz import (
|
||||
PickupMessage,
|
||||
PunchHitMessage,
|
||||
CurseExplodeMessage,
|
||||
)
|
||||
|
||||
shared = SharedObjects.get()
|
||||
self.impact_sounds_medium = (ba.getsound('impactMedium'),
|
||||
ba.getsound('impactMedium2'))
|
||||
self.impact_sounds_hard = (ba.getsound('impactHard'),
|
||||
ba.getsound('impactHard2'),
|
||||
ba.getsound('impactHard3'))
|
||||
self.impact_sounds_harder = (ba.getsound('bigImpact'),
|
||||
ba.getsound('bigImpact2'))
|
||||
self.impact_sounds_medium = (
|
||||
ba.getsound('impactMedium'),
|
||||
ba.getsound('impactMedium2'),
|
||||
)
|
||||
self.impact_sounds_hard = (
|
||||
ba.getsound('impactHard'),
|
||||
ba.getsound('impactHard2'),
|
||||
ba.getsound('impactHard3'),
|
||||
)
|
||||
self.impact_sounds_harder = (
|
||||
ba.getsound('bigImpact'),
|
||||
ba.getsound('bigImpact2'),
|
||||
)
|
||||
self.single_player_death_sound = ba.getsound('playerDeath')
|
||||
self.punch_sound_weak = ba.getsound('punchWeak01')
|
||||
self.punch_sound = ba.getsound('punch01')
|
||||
self.punch_sound_strong = (ba.getsound('punchStrong01'),
|
||||
ba.getsound('punchStrong02'))
|
||||
self.punch_sound_strong = (
|
||||
ba.getsound('punchStrong01'),
|
||||
ba.getsound('punchStrong02'),
|
||||
)
|
||||
self.punch_sound_stronger = ba.getsound('superPunch')
|
||||
self.swish_sound = ba.getsound('punchSwish')
|
||||
self.block_sound = ba.getsound('block')
|
||||
@ -126,47 +137,64 @@ class SpazFactory:
|
||||
# Eww; this probably should just be built into the spaz node.
|
||||
self.roller_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1)))
|
||||
actions=(
|
||||
('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1),
|
||||
),
|
||||
)
|
||||
|
||||
self.spaz_material.add_actions(
|
||||
conditions=('they_have_material', footing_material),
|
||||
actions=(('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1)))
|
||||
actions=(
|
||||
('message', 'our_node', 'at_connect', 'footing', 1),
|
||||
('message', 'our_node', 'at_disconnect', 'footing', -1),
|
||||
),
|
||||
)
|
||||
|
||||
# Punches.
|
||||
self.punch_material.add_actions(
|
||||
conditions=('they_are_different_node_than_us', ),
|
||||
conditions=('they_are_different_node_than_us',),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', PunchHitMessage()),
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
# Pickups.
|
||||
self.pickup_material.add_actions(
|
||||
conditions=(('they_are_different_node_than_us', ), 'and',
|
||||
('they_have_material', object_material)),
|
||||
conditions=(
|
||||
('they_are_different_node_than_us',),
|
||||
'and',
|
||||
('they_have_material', object_material),
|
||||
),
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('message', 'our_node', 'at_connect', PickupMessage()),
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
# Curse.
|
||||
self.curse_material.add_actions(
|
||||
conditions=(
|
||||
('they_are_different_node_than_us', ),
|
||||
('they_are_different_node_than_us',),
|
||||
'and',
|
||||
('they_have_material', player_material),
|
||||
),
|
||||
actions=('message', 'our_node', 'at_connect',
|
||||
CurseExplodeMessage()),
|
||||
actions=(
|
||||
'message',
|
||||
'our_node',
|
||||
'at_connect',
|
||||
CurseExplodeMessage(),
|
||||
),
|
||||
)
|
||||
|
||||
self.foot_impact_sounds = (ba.getsound('footImpact01'),
|
||||
ba.getsound('footImpact02'),
|
||||
ba.getsound('footImpact03'))
|
||||
self.foot_impact_sounds = (
|
||||
ba.getsound('footImpact01'),
|
||||
ba.getsound('footImpact02'),
|
||||
ba.getsound('footImpact03'),
|
||||
)
|
||||
|
||||
self.foot_skid_sound = ba.getsound('skid01')
|
||||
self.foot_roll_sound = ba.getsound('scamper01')
|
||||
@ -177,7 +205,8 @@ class SpazFactory:
|
||||
('impact_sound', self.foot_impact_sounds, 1, 0.2),
|
||||
('skid_sound', self.foot_skid_sound, 20, 0.3),
|
||||
('roll_sound', self.foot_roll_sound, 20, 3.0),
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
self.skid_sound = ba.getsound('gravelSkid')
|
||||
|
||||
@ -187,7 +216,8 @@ class SpazFactory:
|
||||
('impact_sound', self.foot_impact_sounds, 20, 6),
|
||||
('skid_sound', self.skid_sound, 2.0, 1),
|
||||
('roll_sound', self.skid_sound, 2.0, 1),
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
self.shield_up_sound = ba.getsound('shieldUp')
|
||||
self.shield_down_sound = ba.getsound('shieldDown')
|
||||
@ -200,7 +230,7 @@ class SpazFactory:
|
||||
(
|
||||
('we_are_younger_than', 51),
|
||||
'and',
|
||||
('they_are_different_node_than_us', ),
|
||||
('they_are_different_node_than_us',),
|
||||
),
|
||||
'and',
|
||||
('they_dont_have_material', region_material),
|
||||
@ -213,17 +243,23 @@ class SpazFactory:
|
||||
# Lets load some basic rules.
|
||||
# (allows them to be tweaked from the master server)
|
||||
self.shield_decay_rate = ba.internal.get_v1_account_misc_read_val(
|
||||
'rsdr', 10.0)
|
||||
'rsdr', 10.0
|
||||
)
|
||||
self.punch_cooldown = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpc', 400)
|
||||
self.punch_cooldown_gloves = (ba.internal.get_v1_account_misc_read_val(
|
||||
'rpcg', 300))
|
||||
'rpc', 400
|
||||
)
|
||||
self.punch_cooldown_gloves = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpcg', 300
|
||||
)
|
||||
self.punch_power_scale = ba.internal.get_v1_account_misc_read_val(
|
||||
'rpp', 1.2)
|
||||
'rpp', 1.2
|
||||
)
|
||||
self.punch_power_scale_gloves = (
|
||||
ba.internal.get_v1_account_misc_read_val('rppg', 1.4))
|
||||
ba.internal.get_v1_account_misc_read_val('rppg', 1.4)
|
||||
)
|
||||
self.max_shield_spillover_damage = (
|
||||
ba.internal.get_v1_account_misc_read_val('rsms', 500))
|
||||
ba.internal.get_v1_account_misc_read_val('rsms', 500)
|
||||
)
|
||||
|
||||
def get_style(self, character: str) -> str:
|
||||
"""Return the named style for this character.
|
||||
@ -253,7 +289,7 @@ class SpazFactory:
|
||||
'hand_model': ba.getmodel(char.hand_model),
|
||||
'upper_leg_model': ba.getmodel(char.upper_leg_model),
|
||||
'lower_leg_model': ba.getmodel(char.lower_leg_model),
|
||||
'toes_model': ba.getmodel(char.toes_model)
|
||||
'toes_model': ba.getmodel(char.toes_model),
|
||||
}
|
||||
else:
|
||||
media = self.spaz_media[character]
|
||||
|
||||
@ -18,6 +18,7 @@ class Text(ba.Actor):
|
||||
|
||||
class Transition(Enum):
|
||||
"""Transition types for text."""
|
||||
|
||||
FADE_IN = 'fade_in'
|
||||
IN_RIGHT = 'in_right'
|
||||
IN_LEFT = 'in_left'
|
||||
@ -27,46 +28,52 @@ class Text(ba.Actor):
|
||||
|
||||
class HAlign(Enum):
|
||||
"""Horizontal alignment type."""
|
||||
|
||||
LEFT = 'left'
|
||||
CENTER = 'center'
|
||||
RIGHT = 'right'
|
||||
|
||||
class VAlign(Enum):
|
||||
"""Vertical alignment type."""
|
||||
|
||||
NONE = 'none'
|
||||
CENTER = 'center'
|
||||
|
||||
class HAttach(Enum):
|
||||
"""Horizontal attach type."""
|
||||
|
||||
LEFT = 'left'
|
||||
CENTER = 'center'
|
||||
RIGHT = 'right'
|
||||
|
||||
class VAttach(Enum):
|
||||
"""Vertical attach type."""
|
||||
|
||||
BOTTOM = 'bottom'
|
||||
CENTER = 'center'
|
||||
TOP = 'top'
|
||||
|
||||
def __init__(self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
h_align: HAlign = HAlign.LEFT,
|
||||
v_align: VAlign = VAlign.NONE,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
flash: bool = False,
|
||||
v_attach: VAttach = VAttach.CENTER,
|
||||
h_attach: HAttach = HAttach.CENTER,
|
||||
scale: float = 1.0,
|
||||
transition_out_delay: float | None = None,
|
||||
maxwidth: float | None = None,
|
||||
shadow: float = 0.5,
|
||||
flatness: float = 0.0,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False):
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
h_align: HAlign = HAlign.LEFT,
|
||||
v_align: VAlign = VAlign.NONE,
|
||||
color: Sequence[float] = (1.0, 1.0, 1.0, 1.0),
|
||||
transition: Transition | None = None,
|
||||
transition_delay: float = 0.0,
|
||||
flash: bool = False,
|
||||
v_attach: VAttach = VAttach.CENTER,
|
||||
h_attach: HAttach = HAttach.CENTER,
|
||||
scale: float = 1.0,
|
||||
transition_out_delay: float | None = None,
|
||||
maxwidth: float | None = None,
|
||||
shadow: float = 0.5,
|
||||
flatness: float = 0.0,
|
||||
vr_depth: float = 0.0,
|
||||
host_only: bool = False,
|
||||
front: bool = False,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-locals
|
||||
@ -88,21 +95,25 @@ class Text(ba.Actor):
|
||||
'maxwidth': 0.0 if maxwidth is None else maxwidth,
|
||||
'host_only': host_only,
|
||||
'front': front,
|
||||
'scale': scale
|
||||
})
|
||||
'scale': scale,
|
||||
},
|
||||
)
|
||||
|
||||
if transition is self.Transition.FADE_IN:
|
||||
if flash:
|
||||
raise Exception('fixme: flash and fade-in'
|
||||
' currently cant both be on')
|
||||
cmb = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4
|
||||
})
|
||||
raise RuntimeError(
|
||||
'fixme: flash and fade-in currently cant both be on'
|
||||
)
|
||||
cmb = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': color[0],
|
||||
'input1': color[1],
|
||||
'input2': color[2],
|
||||
'size': 4,
|
||||
},
|
||||
)
|
||||
keys = {transition_delay: 0.0, transition_delay + 0.5: color[3]}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = color[3]
|
||||
@ -115,38 +126,35 @@ class Text(ba.Actor):
|
||||
tm1 = 0.15
|
||||
tm2 = 0.3
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 4})
|
||||
ba.animate(cmb,
|
||||
'input0', {
|
||||
0.0: color[0] * mult,
|
||||
tm1: color[0],
|
||||
tm2: color[0] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(cmb,
|
||||
'input1', {
|
||||
0.0: color[1] * mult,
|
||||
tm1: color[1],
|
||||
tm2: color[1] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(cmb,
|
||||
'input2', {
|
||||
0.0: color[2] * mult,
|
||||
tm1: color[2],
|
||||
tm2: color[2] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input0',
|
||||
{0.0: color[0] * mult, tm1: color[0], tm2: color[0] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input1',
|
||||
{0.0: color[1] * mult, tm1: color[1], tm2: color[1] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
cmb,
|
||||
'input2',
|
||||
{0.0: color[2] * mult, tm1: color[2], tm2: color[2] * mult},
|
||||
loop=True,
|
||||
)
|
||||
cmb.input3 = color[3]
|
||||
cmb.connectattr('output', self.node, 'color')
|
||||
|
||||
cmb = self.position_combine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={'size': 2})
|
||||
cmb = self.position_combine = ba.newnode(
|
||||
'combine', owner=self.node, attrs={'size': 2}
|
||||
)
|
||||
|
||||
if transition is self.Transition.IN_RIGHT:
|
||||
keys = {
|
||||
transition_delay: position[0] + 1300,
|
||||
transition_delay + 0.2: position[0]
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
@ -155,13 +163,14 @@ class Text(ba.Actor):
|
||||
elif transition is self.Transition.IN_LEFT:
|
||||
keys = {
|
||||
transition_delay: position[0] - 1300,
|
||||
transition_delay + 0.2: position[0]
|
||||
transition_delay + 0.2: position[0],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
keys[transition_delay + transition_out_delay] = position[0]
|
||||
keys[transition_delay + transition_out_delay +
|
||||
0.2] = position[0] - 1300.0
|
||||
keys[transition_delay + transition_out_delay + 0.2] = (
|
||||
position[0] - 1300.0
|
||||
)
|
||||
o_keys[transition_delay + transition_out_delay + 0.15] = 1.0
|
||||
o_keys[transition_delay + transition_out_delay + 0.2] = 0.0
|
||||
ba.animate(cmb, 'input0', keys)
|
||||
@ -170,7 +179,7 @@ class Text(ba.Actor):
|
||||
elif transition is self.Transition.IN_BOTTOM_SLOW:
|
||||
keys = {
|
||||
transition_delay: -100.0,
|
||||
transition_delay + 1.0: position[1]
|
||||
transition_delay + 1.0: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.2: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
@ -179,7 +188,7 @@ class Text(ba.Actor):
|
||||
elif transition is self.Transition.IN_BOTTOM:
|
||||
keys = {
|
||||
transition_delay: -100.0,
|
||||
transition_delay + 0.2: position[1]
|
||||
transition_delay + 0.2: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0.0, transition_delay + 0.05: 1.0}
|
||||
if transition_out_delay is not None:
|
||||
@ -193,7 +202,7 @@ class Text(ba.Actor):
|
||||
elif transition is self.Transition.IN_TOP_SLOW:
|
||||
keys = {
|
||||
transition_delay: 400.0,
|
||||
transition_delay + 3.5: position[1]
|
||||
transition_delay + 3.5: position[1],
|
||||
}
|
||||
o_keys = {transition_delay: 0, transition_delay + 1.0: 1.0}
|
||||
cmb.input0 = position[0]
|
||||
@ -207,8 +216,10 @@ class Text(ba.Actor):
|
||||
|
||||
# If we're transitioning out, die at the end of it.
|
||||
if transition_out_delay is not None:
|
||||
ba.timer(transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
ba.timer(
|
||||
transition_delay + transition_out_delay + 1.0,
|
||||
ba.WeakCall(self.handlemessage, ba.DieMessage()),
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
|
||||
@ -20,44 +20,47 @@ class TipsText(ba.Actor):
|
||||
self._tip_scale = 0.8
|
||||
self._tip_title_scale = 1.1
|
||||
self._offs_y = offs_y
|
||||
self.node = ba.newnode('text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': '',
|
||||
'scale': self._tip_scale,
|
||||
'h_align': 'left',
|
||||
'maxwidth': 800,
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom'
|
||||
})
|
||||
tval = ba.Lstr(value='${A}:',
|
||||
subs=[('${A}', ba.Lstr(resource='tipText'))])
|
||||
self.title_node = ba.newnode('text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': tval,
|
||||
'scale': self._tip_title_scale,
|
||||
'maxwidth': 122,
|
||||
'h_align': 'right',
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom'
|
||||
})
|
||||
self.node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': '',
|
||||
'scale': self._tip_scale,
|
||||
'h_align': 'left',
|
||||
'maxwidth': 800,
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom',
|
||||
},
|
||||
)
|
||||
tval = ba.Lstr(
|
||||
value='${A}:', subs=[('${A}', ba.Lstr(resource='tipText'))]
|
||||
)
|
||||
self.title_node = ba.newnode(
|
||||
'text',
|
||||
delegate=self,
|
||||
attrs={
|
||||
'text': tval,
|
||||
'scale': self._tip_title_scale,
|
||||
'maxwidth': 122,
|
||||
'h_align': 'right',
|
||||
'vr_depth': -20,
|
||||
'v_align': 'center',
|
||||
'v_attach': 'bottom',
|
||||
},
|
||||
)
|
||||
self._message_duration = 10000
|
||||
self._message_spacing = 3000
|
||||
self._change_timer = ba.Timer(
|
||||
0.001 * (self._message_duration + self._message_spacing),
|
||||
ba.WeakCall(self.change_phrase),
|
||||
repeat=True)
|
||||
self._combine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input0': 1.0,
|
||||
'input1': 0.8,
|
||||
'input2': 1.0,
|
||||
'size': 4
|
||||
})
|
||||
repeat=True,
|
||||
)
|
||||
self._combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4},
|
||||
)
|
||||
self._combine.connectattr('output', self.node, 'color')
|
||||
self._combine.connectattr('output', self.title_node, 'color')
|
||||
self.change_phrase()
|
||||
@ -65,9 +68,11 @@ class TipsText(ba.Actor):
|
||||
def change_phrase(self) -> None:
|
||||
"""Switch the visible tip phrase."""
|
||||
from ba.internal import get_remote_app_name, get_next_tip
|
||||
next_tip = ba.Lstr(translate=('tips', get_next_tip()),
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())
|
||||
])
|
||||
|
||||
next_tip = ba.Lstr(
|
||||
translate=('tips', get_next_tip()),
|
||||
subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
|
||||
)
|
||||
spc = self._message_spacing
|
||||
assert self.node
|
||||
self.node.position = (-200, self._offs_y)
|
||||
@ -76,12 +81,14 @@ class TipsText(ba.Actor):
|
||||
spc: 0,
|
||||
spc + 1000: 1.0,
|
||||
spc + self._message_duration - 1000: 1.0,
|
||||
spc + self._message_duration: 0.0
|
||||
spc + self._message_duration: 0.0,
|
||||
}
|
||||
ba.animate(self._combine,
|
||||
'input3', {k: v * 0.5
|
||||
for k, v in list(keys.items())},
|
||||
timeformat=ba.TimeFormat.MILLISECONDS)
|
||||
ba.animate(
|
||||
self._combine,
|
||||
'input3',
|
||||
{k: v * 0.5 for k, v in list(keys.items())},
|
||||
timeformat=ba.TimeFormat.MILLISECONDS,
|
||||
)
|
||||
self.node.text = next_tip
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
|
||||
@ -21,22 +21,24 @@ class ZoomText(ba.Actor):
|
||||
Used for things such as the 'BOB WINS' victory messages.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
shiftposition: tuple[float, float] | None = None,
|
||||
shiftdelay: float | None = None,
|
||||
lifespan: float | None = None,
|
||||
flash: bool = True,
|
||||
trail: bool = True,
|
||||
h_align: str = 'center',
|
||||
color: Sequence[float] = (0.9, 0.4, 0.0),
|
||||
jitter: float = 0.0,
|
||||
trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0),
|
||||
scale: float = 1.0,
|
||||
project_scale: float = 1.0,
|
||||
tilt_translate: float = 0.0,
|
||||
maxwidth: float | None = None):
|
||||
def __init__(
|
||||
self,
|
||||
text: str | ba.Lstr,
|
||||
position: tuple[float, float] = (0.0, 0.0),
|
||||
shiftposition: tuple[float, float] | None = None,
|
||||
shiftdelay: float | None = None,
|
||||
lifespan: float | None = None,
|
||||
flash: bool = True,
|
||||
trail: bool = True,
|
||||
h_align: str = 'center',
|
||||
color: Sequence[float] = (0.9, 0.4, 0.0),
|
||||
jitter: float = 0.0,
|
||||
trailcolor: Sequence[float] = (1.0, 0.35, 0.1, 0.0),
|
||||
scale: float = 1.0,
|
||||
project_scale: float = 1.0,
|
||||
tilt_translate: float = 0.0,
|
||||
maxwidth: float | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
super().__init__()
|
||||
self._dying = False
|
||||
@ -61,8 +63,9 @@ class ZoomText(ba.Actor):
|
||||
'maxwidth': maxwidth if maxwidth is not None else 0.0,
|
||||
'tilt_translate': tilt_translate,
|
||||
'h_align': h_align,
|
||||
'v_align': 'center'
|
||||
})
|
||||
'v_align': 'center',
|
||||
},
|
||||
)
|
||||
|
||||
# we never jitter in vr mode..
|
||||
if ba.app.vr_mode:
|
||||
@ -78,79 +81,81 @@ class ZoomText(ba.Actor):
|
||||
positionadjusted2 = (shiftposition[0], shiftposition[1] - 100)
|
||||
ba.timer(
|
||||
shiftdelay,
|
||||
ba.WeakCall(self._shift, positionadjusted, positionadjusted2))
|
||||
ba.WeakCall(self._shift, positionadjusted, positionadjusted2),
|
||||
)
|
||||
if jitter > 0.0:
|
||||
ba.timer(
|
||||
shiftdelay + 0.25,
|
||||
ba.WeakCall(self._jitter, positionadjusted2,
|
||||
jitter * scale))
|
||||
color_combine = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'input2': color[2],
|
||||
'input3': 1.0,
|
||||
'size': 4
|
||||
})
|
||||
ba.WeakCall(
|
||||
self._jitter, positionadjusted2, jitter * scale
|
||||
),
|
||||
)
|
||||
color_combine = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={'input2': color[2], 'input3': 1.0, 'size': 4},
|
||||
)
|
||||
if trail:
|
||||
trailcolor_n = ba.newnode('combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'size': 3,
|
||||
'input0': trailcolor[0],
|
||||
'input1': trailcolor[1],
|
||||
'input2': trailcolor[2]
|
||||
})
|
||||
trailcolor_n = ba.newnode(
|
||||
'combine',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'size': 3,
|
||||
'input0': trailcolor[0],
|
||||
'input1': trailcolor[1],
|
||||
'input2': trailcolor[2],
|
||||
},
|
||||
)
|
||||
trailcolor_n.connectattr('output', self.node, 'trailcolor')
|
||||
basemult = 0.85
|
||||
ba.animate(
|
||||
self.node, 'trail_project_scale', {
|
||||
self.node,
|
||||
'trail_project_scale',
|
||||
{
|
||||
0: 0 * project_scale,
|
||||
basemult * 0.201: 0.6 * project_scale,
|
||||
basemult * 0.347: 0.8 * project_scale,
|
||||
basemult * 0.478: 0.9 * project_scale,
|
||||
basemult * 0.595: 0.93 * project_scale,
|
||||
basemult * 0.748: 0.95 * project_scale,
|
||||
basemult * 0.941: 0.95 * project_scale
|
||||
})
|
||||
basemult * 0.941: 0.95 * project_scale,
|
||||
},
|
||||
)
|
||||
if flash:
|
||||
mult = 2.0
|
||||
tm1 = 0.15
|
||||
tm2 = 0.3
|
||||
ba.animate(color_combine,
|
||||
'input0', {
|
||||
0: color[0] * mult,
|
||||
tm1: color[0],
|
||||
tm2: color[0] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(color_combine,
|
||||
'input1', {
|
||||
0: color[1] * mult,
|
||||
tm1: color[1],
|
||||
tm2: color[1] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(color_combine,
|
||||
'input2', {
|
||||
0: color[2] * mult,
|
||||
tm1: color[2],
|
||||
tm2: color[2] * mult
|
||||
},
|
||||
loop=True)
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input0',
|
||||
{0: color[0] * mult, tm1: color[0], tm2: color[0] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input1',
|
||||
{0: color[1] * mult, tm1: color[1], tm2: color[1] * mult},
|
||||
loop=True,
|
||||
)
|
||||
ba.animate(
|
||||
color_combine,
|
||||
'input2',
|
||||
{0: color[2] * mult, tm1: color[2], tm2: color[2] * mult},
|
||||
loop=True,
|
||||
)
|
||||
else:
|
||||
color_combine.input0 = color[0]
|
||||
color_combine.input1 = color[1]
|
||||
color_combine.connectattr('output', self.node, 'color')
|
||||
ba.animate(self.node, 'project_scale', {
|
||||
0: 0,
|
||||
0.27: 1.05 * project_scale,
|
||||
0.3: 1 * project_scale
|
||||
})
|
||||
ba.animate(
|
||||
self.node,
|
||||
'project_scale',
|
||||
{0: 0, 0.27: 1.05 * project_scale, 0.3: 1 * project_scale},
|
||||
)
|
||||
|
||||
# if they give us a lifespan, kill ourself down the line
|
||||
if lifespan is not None:
|
||||
ba.timer(lifespan, ba.WeakCall(self.handlemessage,
|
||||
ba.DieMessage()))
|
||||
ba.timer(lifespan, ba.WeakCall(self.handlemessage, ba.DieMessage()))
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
assert not self.expired
|
||||
@ -161,18 +166,22 @@ class ZoomText(ba.Actor):
|
||||
self.node.delete()
|
||||
else:
|
||||
ba.animate(
|
||||
self.node, 'project_scale', {
|
||||
self.node,
|
||||
'project_scale',
|
||||
{
|
||||
0.0: 1 * self._project_scale,
|
||||
0.6: 1.2 * self._project_scale
|
||||
})
|
||||
0.6: 1.2 * self._project_scale,
|
||||
},
|
||||
)
|
||||
ba.animate(self.node, 'opacity', {0.0: 1, 0.3: 0})
|
||||
ba.animate(self.node, 'trail_opacity', {0.0: 1, 0.6: 0})
|
||||
ba.timer(0.7, self.node.delete)
|
||||
return None
|
||||
return super().handlemessage(msg)
|
||||
|
||||
def _jitter(self, position: tuple[float, float],
|
||||
jitter_amount: float) -> None:
|
||||
def _jitter(
|
||||
self, position: tuple[float, float], jitter_amount: float
|
||||
) -> None:
|
||||
if not self.node:
|
||||
return
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2})
|
||||
@ -181,14 +190,17 @@ class ZoomText(ba.Actor):
|
||||
timeval = 0.0
|
||||
# gen some random keys for that stop-motion-y look
|
||||
for _i in range(10):
|
||||
keys[timeval] = (position[index] +
|
||||
(random.random() - 0.5) * jitter_amount * 1.6)
|
||||
keys[timeval] = (
|
||||
position[index]
|
||||
+ (random.random() - 0.5) * jitter_amount * 1.6
|
||||
)
|
||||
timeval += random.random() * 0.1
|
||||
ba.animate(cmb, attr, keys, loop=True)
|
||||
cmb.connectattr('output', self.node, 'position')
|
||||
|
||||
def _shift(self, position1: tuple[float, float],
|
||||
position2: tuple[float, float]) -> None:
|
||||
def _shift(
|
||||
self, position1: tuple[float, float], position2: tuple[float, float]
|
||||
) -> None:
|
||||
if not self.node:
|
||||
return
|
||||
cmb = ba.newnode('combine', owner=self.node, attrs={'size': 2})
|
||||
|
||||
@ -15,17 +15,23 @@ class AppDelegate(ba.AppDelegate):
|
||||
"""Defines handlers for high level app functionality."""
|
||||
|
||||
def create_default_game_settings_ui(
|
||||
self, gameclass: type[ba.GameActivity],
|
||||
sessiontype: type[ba.Session], settings: dict | None,
|
||||
completion_call: Callable[[dict | None], Any]) -> None:
|
||||
self,
|
||||
gameclass: type[ba.GameActivity],
|
||||
sessiontype: type[ba.Session],
|
||||
settings: dict | None,
|
||||
completion_call: Callable[[dict | None], Any],
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
|
||||
# Replace the main window once we come up successfully.
|
||||
from bastd.ui.playlist.editgame import PlaylistEditGameWindow
|
||||
|
||||
ba.app.ui.clear_main_menu_window(transition='out_left')
|
||||
ba.app.ui.set_main_menu_window(
|
||||
PlaylistEditGameWindow(
|
||||
gameclass,
|
||||
sessiontype,
|
||||
settings,
|
||||
completion_call=completion_call).get_root_widget())
|
||||
completion_call=completion_call,
|
||||
).get_root_widget()
|
||||
)
|
||||
|
||||
@ -91,8 +91,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
|
||||
# Base class overrides
|
||||
self.slow_motion = self._epic_mode
|
||||
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
|
||||
ba.MusicType.FORWARD_MARCH)
|
||||
self.default_music = (
|
||||
ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FORWARD_MARCH
|
||||
)
|
||||
|
||||
def get_instance_description(self) -> str | Sequence:
|
||||
if self._score_to_win == 1:
|
||||
@ -107,19 +108,19 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
def create_team(self, sessionteam: ba.SessionTeam) -> Team:
|
||||
shared = SharedObjects.get()
|
||||
base_pos = self.map.get_flag_position(sessionteam.id)
|
||||
ba.newnode('light',
|
||||
attrs={
|
||||
'position': base_pos,
|
||||
'intensity': 0.6,
|
||||
'height_attenuated': False,
|
||||
'volume_intensity_scale': 0.1,
|
||||
'radius': 0.1,
|
||||
'color': sessionteam.color
|
||||
})
|
||||
ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': base_pos,
|
||||
'intensity': 0.6,
|
||||
'height_attenuated': False,
|
||||
'volume_intensity_scale': 0.1,
|
||||
'radius': 0.1,
|
||||
'color': sessionteam.color,
|
||||
},
|
||||
)
|
||||
Flag.project_stand(base_pos)
|
||||
flag = Flag(touchable=False,
|
||||
position=base_pos,
|
||||
color=sessionteam.color)
|
||||
flag = Flag(touchable=False, position=base_pos, color=sessionteam.color)
|
||||
team = Team(base_pos=base_pos, flag=flag)
|
||||
|
||||
mat = self._base_region_materials[sessionteam.id] = ba.Material()
|
||||
@ -128,8 +129,11 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('call', 'at_connect', ba.Call(self._handle_base_collide,
|
||||
team)),
|
||||
(
|
||||
'call',
|
||||
'at_connect',
|
||||
ba.Call(self._handle_base_collide, team),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@ -140,8 +144,9 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
'position': (base_pos[0], base_pos[1] + 0.75, base_pos[2]),
|
||||
'scale': (0.5, 0.5, 0.5),
|
||||
'type': 'sphere',
|
||||
'materials': [self._base_region_materials[sessionteam.id]]
|
||||
})
|
||||
'materials': [self._base_region_materials[sessionteam.id]],
|
||||
},
|
||||
)
|
||||
|
||||
return team
|
||||
|
||||
@ -163,13 +168,15 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
super().handlemessage(msg)
|
||||
|
||||
def _flash_base(self, team: Team, length: float = 2.0) -> None:
|
||||
light = ba.newnode('light',
|
||||
attrs={
|
||||
'position': team.base_pos,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.3,
|
||||
'color': team.color
|
||||
})
|
||||
light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': team.base_pos,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.3,
|
||||
'color': team.color,
|
||||
},
|
||||
)
|
||||
ba.animate(light, 'intensity', {0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
|
||||
ba.timer(length, light.delete)
|
||||
|
||||
@ -203,38 +210,34 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
for player in player_team.players:
|
||||
if player.is_alive():
|
||||
pos = player.node.position
|
||||
light = ba.newnode('light',
|
||||
attrs={
|
||||
'position': pos,
|
||||
'color': player_team.color,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.4
|
||||
})
|
||||
light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': pos,
|
||||
'color': player_team.color,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.4,
|
||||
},
|
||||
)
|
||||
ba.timer(0.5, light.delete)
|
||||
ba.animate(light, 'intensity', {
|
||||
0: 0,
|
||||
0.1: 1.0,
|
||||
0.5: 0
|
||||
})
|
||||
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.5: 0})
|
||||
|
||||
new_pos = (self.map.get_start_position(player_team.id))
|
||||
light = ba.newnode('light',
|
||||
attrs={
|
||||
'position': new_pos,
|
||||
'color': player_team.color,
|
||||
'radius': 0.4,
|
||||
'height_attenuated': False
|
||||
})
|
||||
new_pos = self.map.get_start_position(player_team.id)
|
||||
light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': new_pos,
|
||||
'color': player_team.color,
|
||||
'radius': 0.4,
|
||||
'height_attenuated': False,
|
||||
},
|
||||
)
|
||||
ba.timer(0.5, light.delete)
|
||||
ba.animate(light, 'intensity', {
|
||||
0: 0,
|
||||
0.1: 1.0,
|
||||
0.5: 0
|
||||
})
|
||||
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.5: 0})
|
||||
if player.actor:
|
||||
player.actor.handlemessage(
|
||||
ba.StandMessage(new_pos,
|
||||
random.uniform(0, 360)))
|
||||
ba.StandMessage(new_pos, random.uniform(0, 360))
|
||||
)
|
||||
|
||||
# Have teammates celebrate.
|
||||
for player in player_team.players:
|
||||
@ -254,5 +257,6 @@ class AssaultGame(ba.TeamGameActivity[Player, Team]):
|
||||
|
||||
def _update_scoreboard(self) -> None:
|
||||
for team in self.teams:
|
||||
self._scoreboard.set_team_value(team, team.score,
|
||||
self._score_to_win)
|
||||
self._scoreboard.set_team_value(
|
||||
team, team.score, self._score_to_win
|
||||
)
|
||||
|
||||
@ -12,8 +12,13 @@ from typing import TYPE_CHECKING
|
||||
import ba
|
||||
from bastd.actor.playerspaz import PlayerSpaz
|
||||
from bastd.actor.scoreboard import Scoreboard
|
||||
from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage,
|
||||
FlagDroppedMessage, FlagDiedMessage)
|
||||
from bastd.actor.flag import (
|
||||
FlagFactory,
|
||||
Flag,
|
||||
FlagPickedUpMessage,
|
||||
FlagDroppedMessage,
|
||||
FlagDiedMessage,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
@ -26,18 +31,18 @@ class CTFFlag(Flag):
|
||||
|
||||
def __init__(self, team: Team):
|
||||
assert team.flagmaterial is not None
|
||||
super().__init__(materials=[team.flagmaterial],
|
||||
position=team.base_pos,
|
||||
color=team.color)
|
||||
super().__init__(
|
||||
materials=[team.flagmaterial],
|
||||
position=team.base_pos,
|
||||
color=team.color,
|
||||
)
|
||||
self._team = team
|
||||
self.held_count = 0
|
||||
self.counter = ba.newnode('text',
|
||||
owner=self.node,
|
||||
attrs={
|
||||
'in_world': True,
|
||||
'scale': 0.02,
|
||||
'h_align': 'center'
|
||||
})
|
||||
self.counter = ba.newnode(
|
||||
'text',
|
||||
owner=self.node,
|
||||
attrs={'in_world': True, 'scale': 0.02, 'h_align': 'center'},
|
||||
)
|
||||
self.reset_return_times()
|
||||
self.last_player_to_hold: Player | None = None
|
||||
self.time_out_respawn_time: int | None = None
|
||||
@ -64,11 +69,15 @@ class Player(ba.Player['Team']):
|
||||
class Team(ba.Team[Player]):
|
||||
"""Our team type for this game."""
|
||||
|
||||
def __init__(self, base_pos: Sequence[float],
|
||||
base_region_material: ba.Material, base_region: ba.Node,
|
||||
spaz_material_no_flag_physical: ba.Material,
|
||||
spaz_material_no_flag_collide: ba.Material,
|
||||
flagmaterial: ba.Material):
|
||||
def __init__(
|
||||
self,
|
||||
base_pos: Sequence[float],
|
||||
base_region_material: ba.Material,
|
||||
base_region: ba.Node,
|
||||
spaz_material_no_flag_physical: ba.Material,
|
||||
spaz_material_no_flag_collide: ba.Material,
|
||||
flagmaterial: ba.Material,
|
||||
):
|
||||
self.base_pos = base_pos
|
||||
self.base_region_material = base_region_material
|
||||
self.base_region = base_region
|
||||
@ -158,8 +167,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
|
||||
# Base class overrides.
|
||||
self.slow_motion = self._epic_mode
|
||||
self.default_music = (ba.MusicType.EPIC if self._epic_mode else
|
||||
ba.MusicType.FLAG_CATCHER)
|
||||
self.default_music = (
|
||||
ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FLAG_CATCHER
|
||||
)
|
||||
|
||||
def get_instance_description(self) -> str | Sequence:
|
||||
if self._score_to_win == 1:
|
||||
@ -178,15 +188,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
base_pos = self.map.get_flag_position(sessionteam.id)
|
||||
Flag.project_stand(base_pos)
|
||||
|
||||
ba.newnode('light',
|
||||
attrs={
|
||||
'position': base_pos,
|
||||
'intensity': 0.6,
|
||||
'height_attenuated': False,
|
||||
'volume_intensity_scale': 0.1,
|
||||
'radius': 0.1,
|
||||
'color': sessionteam.color
|
||||
})
|
||||
ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': base_pos,
|
||||
'intensity': 0.6,
|
||||
'height_attenuated': False,
|
||||
'volume_intensity_scale': 0.1,
|
||||
'radius': 0.1,
|
||||
'color': sessionteam.color,
|
||||
},
|
||||
)
|
||||
|
||||
base_region_mat = ba.Material()
|
||||
pos = base_pos
|
||||
@ -196,19 +208,22 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
'position': (pos[0], pos[1] + 0.75, pos[2]),
|
||||
'scale': (0.5, 0.5, 0.5),
|
||||
'type': 'sphere',
|
||||
'materials': [base_region_mat, self._all_bases_material]
|
||||
})
|
||||
'materials': [base_region_mat, self._all_bases_material],
|
||||
},
|
||||
)
|
||||
|
||||
spaz_mat_no_flag_physical = ba.Material()
|
||||
spaz_mat_no_flag_collide = ba.Material()
|
||||
flagmat = ba.Material()
|
||||
|
||||
team = Team(base_pos=base_pos,
|
||||
base_region_material=base_region_mat,
|
||||
base_region=base_region,
|
||||
spaz_material_no_flag_physical=spaz_mat_no_flag_physical,
|
||||
spaz_material_no_flag_collide=spaz_mat_no_flag_collide,
|
||||
flagmaterial=flagmat)
|
||||
team = Team(
|
||||
base_pos=base_pos,
|
||||
base_region_material=base_region_mat,
|
||||
base_region=base_region,
|
||||
spaz_material_no_flag_physical=spaz_mat_no_flag_physical,
|
||||
spaz_material_no_flag_collide=spaz_mat_no_flag_collide,
|
||||
flagmaterial=flagmat,
|
||||
)
|
||||
|
||||
# Some parts of our spazzes don't collide physically with our
|
||||
# flags but generate callbacks.
|
||||
@ -216,11 +231,18 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
conditions=('they_have_material', flagmat),
|
||||
actions=(
|
||||
('modify_part_collision', 'physical', False),
|
||||
('call', 'at_connect',
|
||||
lambda: self._handle_touching_own_flag(team, True)),
|
||||
('call', 'at_disconnect',
|
||||
lambda: self._handle_touching_own_flag(team, False)),
|
||||
))
|
||||
(
|
||||
'call',
|
||||
'at_connect',
|
||||
lambda: self._handle_touching_own_flag(team, True),
|
||||
),
|
||||
(
|
||||
'call',
|
||||
'at_disconnect',
|
||||
lambda: self._handle_touching_own_flag(team, False),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
# Other parts of our spazzes don't collide with our flags at all.
|
||||
spaz_mat_no_flag_collide.add_actions(
|
||||
@ -234,11 +256,18 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
actions=(
|
||||
('modify_part_collision', 'collide', True),
|
||||
('modify_part_collision', 'physical', False),
|
||||
('call', 'at_connect',
|
||||
lambda: self._handle_flag_entered_base(team)),
|
||||
('call', 'at_disconnect',
|
||||
lambda: self._handle_flag_left_base(team)),
|
||||
))
|
||||
(
|
||||
'call',
|
||||
'at_connect',
|
||||
lambda: self._handle_flag_entered_base(team),
|
||||
),
|
||||
(
|
||||
'call',
|
||||
'at_disconnect',
|
||||
lambda: self._handle_flag_left_base(team),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
return team
|
||||
|
||||
@ -276,9 +305,12 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
if team.enemy_flag_at_base:
|
||||
# And show team name which scored (but actually we could
|
||||
# show here player who returned enemy flag).
|
||||
self.show_zoom_message(ba.Lstr(resource='nameScoresText',
|
||||
subs=[('${NAME}', team.name)]),
|
||||
color=team.color)
|
||||
self.show_zoom_message(
|
||||
ba.Lstr(
|
||||
resource='nameScoresText', subs=[('${NAME}', team.name)]
|
||||
),
|
||||
color=team.color,
|
||||
)
|
||||
self._score(team)
|
||||
else:
|
||||
team.enemy_flag_at_base = True
|
||||
@ -308,15 +340,13 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
'scale': 0.013,
|
||||
'color': (1, 1, 0, 1),
|
||||
'h_align': 'center',
|
||||
'position': (bpos[0], bpos[1] + 3.2, bpos[2])
|
||||
})
|
||||
'position': (bpos[0], bpos[1] + 3.2, bpos[2]),
|
||||
},
|
||||
)
|
||||
ba.timer(5.1, tnode.delete)
|
||||
ba.animate(tnode, 'scale', {
|
||||
0.0: 0,
|
||||
0.2: 0.013,
|
||||
4.8: 0.013,
|
||||
5.0: 0
|
||||
})
|
||||
ba.animate(
|
||||
tnode, 'scale', {0.0: 0, 0.2: 0.013, 4.8: 0.013, 5.0: 0}
|
||||
)
|
||||
|
||||
def _tick(self) -> None:
|
||||
# If either flag is away from base and not being held, tick down its
|
||||
@ -344,10 +374,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
# to show its auto-return counter. (if there's self-touches
|
||||
# its showing that time).
|
||||
if team.flag_return_touches == 0:
|
||||
flag.counter.text = (str(flag.time_out_respawn_time) if (
|
||||
time_out_counting_down
|
||||
and flag.time_out_respawn_time is not None
|
||||
and flag.time_out_respawn_time <= 10) else '')
|
||||
flag.counter.text = (
|
||||
str(flag.time_out_respawn_time)
|
||||
if (
|
||||
time_out_counting_down
|
||||
and flag.time_out_respawn_time is not None
|
||||
and flag.time_out_respawn_time <= 10
|
||||
)
|
||||
else ''
|
||||
)
|
||||
flag.counter.color = (1, 1, 1, 0.5)
|
||||
flag.counter.scale = 0.014
|
||||
|
||||
@ -389,8 +424,10 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
if flag.team is team:
|
||||
|
||||
# Check times here to prevent too much flashing.
|
||||
if (team.last_flag_leave_time is None
|
||||
or cur_time - team.last_flag_leave_time > 3.0):
|
||||
if (
|
||||
team.last_flag_leave_time is None
|
||||
or cur_time - team.last_flag_leave_time > 3.0
|
||||
):
|
||||
ba.playsound(self._alarmsound, position=team.base_pos)
|
||||
self._flash_base(team)
|
||||
team.last_flag_leave_time = cur_time
|
||||
@ -406,12 +443,15 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
return # No need to return when its at home.
|
||||
if team.touch_return_timer_ticking is None:
|
||||
team.touch_return_timer_ticking = ba.NodeActor(
|
||||
ba.newnode('sound',
|
||||
attrs={
|
||||
'sound': self._ticking_sound,
|
||||
'positional': False,
|
||||
'loop': True
|
||||
}))
|
||||
ba.newnode(
|
||||
'sound',
|
||||
attrs={
|
||||
'sound': self._ticking_sound,
|
||||
'positional': False,
|
||||
'loop': True,
|
||||
},
|
||||
)
|
||||
)
|
||||
flag = team.flag
|
||||
if flag.touch_return_time is not None:
|
||||
flag.touch_return_time -= 0.1
|
||||
@ -428,9 +468,9 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
for player in team.players:
|
||||
if player.touching_own_flag > 0:
|
||||
return_score = 10 + 5 * int(self.flag_touch_return_time)
|
||||
self.stats.player_scored(player,
|
||||
return_score,
|
||||
screenmessage=False)
|
||||
self.stats.player_scored(
|
||||
player, return_score, screenmessage=False
|
||||
)
|
||||
|
||||
def _handle_touching_own_flag(self, team: Team, connecting: bool) -> None:
|
||||
"""Called when a player touches or stops touching their own team flag.
|
||||
@ -450,14 +490,17 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
player = spaz.getplayer(Player, True)
|
||||
|
||||
if player:
|
||||
player.touching_own_flag += (1 if connecting else -1)
|
||||
player.touching_own_flag += 1 if connecting else -1
|
||||
|
||||
# If return-time is zero, just kill it immediately.. otherwise keep
|
||||
# track of touches and count down.
|
||||
if float(self.flag_touch_return_time) <= 0.0:
|
||||
assert team.flag is not None
|
||||
if (connecting and not team.home_flag_at_base
|
||||
and team.flag.held_count == 0):
|
||||
if (
|
||||
connecting
|
||||
and not team.home_flag_at_base
|
||||
and team.flag.held_count == 0
|
||||
):
|
||||
self._award_players_touching_own_flag(team)
|
||||
ba.getcollision().opposingnode.handlemessage(ba.DieMessage())
|
||||
|
||||
@ -469,7 +512,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
team.touch_return_timer = ba.Timer(
|
||||
0.1,
|
||||
call=ba.Call(self._touch_return_update, team),
|
||||
repeat=True)
|
||||
repeat=True,
|
||||
)
|
||||
team.touch_return_timer_ticking = None
|
||||
else:
|
||||
team.flag_return_touches -= 1
|
||||
@ -480,20 +524,24 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
ba.print_error('CTF flag_return_touches < 0')
|
||||
|
||||
def _flash_base(self, team: Team, length: float = 2.0) -> None:
|
||||
light = ba.newnode('light',
|
||||
attrs={
|
||||
'position': team.base_pos,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.3,
|
||||
'color': team.color
|
||||
})
|
||||
light = ba.newnode(
|
||||
'light',
|
||||
attrs={
|
||||
'position': team.base_pos,
|
||||
'height_attenuated': False,
|
||||
'radius': 0.3,
|
||||
'color': team.color,
|
||||
},
|
||||
)
|
||||
ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
|
||||
ba.timer(length, light.delete)
|
||||
|
||||
def spawn_player_spaz(self,
|
||||
player: Player,
|
||||
position: Sequence[float] | None = None,
|
||||
angle: float | None = None) -> PlayerSpaz:
|
||||
def spawn_player_spaz(
|
||||
self,
|
||||
player: Player,
|
||||
position: Sequence[float] | None = None,
|
||||
angle: float | None = None,
|
||||
) -> PlayerSpaz:
|
||||
"""Intercept new spazzes and add our team material for them."""
|
||||
spaz = super().spawn_player_spaz(player, position, angle)
|
||||
player = spaz.getplayer(Player, True)
|
||||
@ -510,22 +558,27 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
# (so we can calc restores).
|
||||
assert spaz.node
|
||||
spaz.node.materials = list(spaz.node.materials) + no_physical_mats
|
||||
spaz.node.roller_materials = list(
|
||||
spaz.node.roller_materials) + no_physical_mats
|
||||
spaz.node.roller_materials = (
|
||||
list(spaz.node.roller_materials) + no_physical_mats
|
||||
)
|
||||
|
||||
# Pickups and punches shouldn't hit at all though.
|
||||
spaz.node.punch_materials = list(
|
||||
spaz.node.punch_materials) + no_collide_mats
|
||||
spaz.node.pickup_materials = list(
|
||||
spaz.node.pickup_materials) + no_collide_mats
|
||||
spaz.node.extras_material = list(
|
||||
spaz.node.extras_material) + no_collide_mats
|
||||
spaz.node.punch_materials = (
|
||||
list(spaz.node.punch_materials) + no_collide_mats
|
||||
)
|
||||
spaz.node.pickup_materials = (
|
||||
list(spaz.node.pickup_materials) + no_collide_mats
|
||||
)
|
||||
spaz.node.extras_material = (
|
||||
list(spaz.node.extras_material) + no_collide_mats
|
||||
)
|
||||
return spaz
|
||||
|
||||
def _update_scoreboard(self) -> None:
|
||||
for team in self.teams:
|
||||
self._scoreboard.set_team_value(team, team.score,
|
||||
self._score_to_win)
|
||||
self._scoreboard.set_team_value(
|
||||
team, team.score, self._score_to_win
|
||||
)
|
||||
|
||||
def handlemessage(self, msg: Any) -> Any:
|
||||
|
||||
@ -543,7 +596,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
|
||||
assert isinstance(msg.flag, CTFFlag)
|
||||
try:
|
||||
msg.flag.last_player_to_hold = msg.node.getdelegate(
|
||||
PlayerSpaz, True).getplayer(Player, True)
|
||||
PlayerSpaz, True
|
||||
).getplayer(Player, True)
|
||||
except ba.NotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user