diff --git a/.efrocachemap b/.efrocachemap
index b9f20cc3..34cba2d6 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -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"
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 02b785bb..78ae599b 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -57,6 +57,7 @@
addr
addrstr
adisp
+ advb
advertizing
aidl
aint
@@ -1935,6 +1936,7 @@
ppre
pproxy
pptabcom
+ prab
pragmas
prch
prec
@@ -1994,6 +1996,7 @@
projs
promocode
proxykey
+ prtb
prunedir
prval
pstats
@@ -2041,6 +2044,7 @@
pvrtcbest
pvrtcfast
pvval
+ pwin
pybee
pybuild
pybuildapple
@@ -2932,6 +2936,7 @@
yscl
ytweak
yval
+ zabcdefghijklmnopqrstuvwxyz
zaggy
zimbot
zipapp
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0374c658..8a0c9b15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/assets/src/ba_data/python/._ba_sources_hash b/assets/src/ba_data/python/._ba_sources_hash
index 861b23df..8067bbe6 100644
--- a/assets/src/ba_data/python/._ba_sources_hash
+++ b/assets/src/ba_data/python/._ba_sources_hash
@@ -1 +1 @@
-194057364831757023796080999188881665880
\ No newline at end of file
+180157672676216986210895241045962795292
\ No newline at end of file
diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py
index 2f751fc3..7b3bdcb5 100644
--- a/assets/src/ba_data/python/_ba.py
+++ b/assets/src/ba_data/python/_ba.py
@@ -39,6 +39,7 @@ if TYPE_CHECKING:
from ba._app import App
import ba
+
_T = TypeVar('_T')
app: App
@@ -51,38 +52,46 @@ def _uninferrable() -> Any:
class ActivityData:
+
"""(internal)"""
def exists(self) -> bool:
+
"""Returns whether the ActivityData still exists.
Most functionality will fail on a nonexistent instance.
"""
return bool()
def expire(self) -> None:
+
"""Expires the internal data for the activity"""
return None
def make_foreground(self) -> None:
+
"""Sets this activity as the foreground one in its session."""
return None
def start(self) -> None:
+
"""Begins the activity running"""
return None
class CollideModel:
+
"""A reference to a collide-model.
Category: **Asset Classes**
Use ba.getcollidemodel() to instantiate one.
"""
+
pass
class Context:
+
"""A game context state.
Category: **General Utility Classes**
@@ -148,6 +157,7 @@ class Context:
class ContextCall:
+
"""A context-preserving callable.
Category: **General Utility Classes**
@@ -193,6 +203,7 @@ class ContextCall:
class Data:
+
"""A reference to a data object.
Category: **Asset Classes**
@@ -201,6 +212,7 @@ class Data:
"""
def getvalue(self) -> Any:
+
"""Return the data object's value.
This can consist of anything representable by json (dicts, lists,
@@ -213,55 +225,69 @@ class Data:
class InputDevice:
+
"""An input-device such as a gamepad, touchscreen, or keyboard.
Category: **Gameplay Classes**
"""
+
allows_configuring: bool
+
"""Whether the input-device can be configured."""
has_meaningful_button_names: bool
+
"""Whether button names returned by this instance match labels
on the actual device. (Can be used to determine whether to show
them in controls-overlays, etc.)."""
player: ba.SessionPlayer | None
+
"""The player associated with this input device."""
client_id: int
+
"""The numeric client-id this device is associated with.
This is only meaningful for remote client inputs; for
all local devices this will be -1."""
name: str
+
"""The name of the device."""
unique_identifier: str
+
"""A string that can be used to persistently identify the device,
even among other devices of the same type. Used for saving
prefs, etc."""
id: int
+
"""The unique numeric id of this device."""
instance_number: int
+
"""The number of this device among devices of the same type."""
is_controller_app: bool
+
"""Whether this input-device represents a locally-connected
controller-app."""
is_remote_client: bool
+
"""Whether this input-device represents a remotely-connected
client."""
def exists(self) -> bool:
+
"""Return whether the underlying device for this object is
still present.
"""
return bool()
def get_axis_name(self, axis_id: int) -> str:
+
"""Given an axis ID, return the name of the axis on this device.
Can return an empty string if the value is not meaningful to humans.
@@ -269,14 +295,17 @@ class InputDevice:
return str()
def get_button_name(self, button_id: int) -> ba.Lstr:
+
"""Given a button ID, return a human-readable name for that key/button.
Can return an empty string if the value is not meaningful to humans.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Lstr(value='')
def get_default_player_name(self) -> str:
+
"""(internal)
Returns the default player name for this device. (used for the 'random'
@@ -285,10 +314,12 @@ class InputDevice:
return str()
def get_player_profiles(self) -> dict:
+
"""(internal)"""
return dict()
def get_v1_account_name(self, full: bool) -> str:
+
"""Returns the account name associated with this device.
(can be used to get account names for remote players)
@@ -296,15 +327,18 @@ class InputDevice:
return str()
def is_connected_to_remote_player(self) -> bool:
+
"""(internal)"""
return bool()
def remove_remote_player_from_game(self) -> None:
+
"""(internal)"""
return None
class Material:
+
"""An entity applied to game objects to modify collision behavior.
Category: **Gameplay Classes**
@@ -328,11 +362,13 @@ class Material:
pass
label: str
+
"""A label for the material; only used for debugging."""
- def add_actions(self,
- actions: tuple,
- conditions: tuple | None = None) -> None:
+ def add_actions(
+ self, actions: tuple, conditions: tuple | None = None
+ ) -> None:
+
"""Add one or more actions to the material, optionally with conditions.
##### Conditions
@@ -483,6 +519,7 @@ class Material:
class Model:
+
"""A reference to a model.
Category: **Asset Classes**
@@ -490,10 +527,12 @@ class Model:
Models are used for drawing.
Use ba.getmodel() to instantiate one.
"""
+
pass
class Node:
+
"""Reference to a Node; the low level building block of the game.
Category: **Gameplay Classes**
@@ -633,12 +672,14 @@ class Node:
tint: Sequence[float] = (1.0, 1.0, 1.0)
def add_death_action(self, action: Callable[[], None]) -> None:
+
"""Add a callable object to be called upon this node's death.
Note that these actions are run just after the node dies, not before.
"""
return None
def connectattr(self, srcattr: str, dstnode: Node, dstattr: str) -> None:
+
"""Connect one of this node's attributes to an attribute on another
node. This will immediately set the target attribute's value to that
of the source attribute, and will continue to do so once per step
@@ -655,12 +696,14 @@ class Node:
return None
def delete(self, ignore_missing: bool = True) -> None:
+
"""Delete the node. Ignores already-deleted nodes if `ignore_missing`
is True; otherwise a ba.NodeNotFoundError is thrown.
"""
return None
def exists(self) -> bool:
+
"""Returns whether the Node still exists.
Most functionality will fail on a nonexistent Node, so it's never a bad
idea to check this.
@@ -673,9 +716,9 @@ class Node:
# Show that ur return type varies based on "doraise" value:
@overload
- def getdelegate(self,
- type: type[_T],
- doraise: Literal[False] = False) -> _T | None:
+ def getdelegate(
+ self, type: type[_T], doraise: Literal[False] = False
+ ) -> _T | None:
...
@overload
@@ -683,6 +726,7 @@ class Node:
...
def getdelegate(self, type: Any, doraise: bool = False) -> Any:
+
"""Return the node's current delegate object if it matches
a certain type.
@@ -693,16 +737,19 @@ class Node:
return None
def getname(self) -> str:
+
"""Return the name assigned to a Node; used mainly for debugging"""
return str()
def getnodetype(self) -> str:
+
"""Return the type of Node referenced by this object as a string.
(Note this is different from the Python type which is always ba.Node)
"""
return str()
def handlemessage(self, *args: Any) -> None:
+
"""General message handling; can be passed any message object.
All standard message objects are forwarded along to the ba.Node's
@@ -720,9 +767,11 @@ class Node:
class SessionData:
+
"""(internal)"""
def exists(self) -> bool:
+
"""Returns whether the SessionData still exists.
Most functionality will fail on a nonexistent instance.
"""
@@ -730,6 +779,7 @@ class SessionData:
class SessionPlayer:
+
"""A reference to a player in the ba.Session.
Category: **Gameplay Classes**
@@ -743,7 +793,9 @@ class SessionPlayer:
that a SessionPlayer is still present if retaining references to one
for any length of time.
"""
+
id: int
+
"""The unique numeric ID of the Player.
Note that you can also use the boolean operator for this same
@@ -751,53 +803,66 @@ class SessionPlayer:
the right thing both for Player objects and values of None."""
in_game: bool
+
"""This bool value will be True once the Player has completed
any lobby character/team selection."""
sessionteam: ba.SessionTeam
+
"""The ba.SessionTeam this Player is on. If the SessionPlayer
is still in its lobby selecting a team/etc. then a
ba.SessionTeamNotFoundError will be raised."""
inputdevice: ba.InputDevice
+
"""The input device associated with the player."""
color: Sequence[float]
+
"""The base color for this Player.
In team games this will match the ba.SessionTeam's color."""
highlight: Sequence[float]
+
"""A secondary color for this player.
This is used for minor highlights and accents
to allow a player to stand apart from his teammates
who may all share the same team (primary) color."""
character: str
+
"""The character this player has selected in their profile."""
activityplayer: ba.Player | None
+
"""The current game-specific instance for this player."""
- def assigninput(self, type: ba.InputType | tuple[ba.InputType, ...],
- call: Callable) -> None:
+ def assigninput(
+ self, type: ba.InputType | tuple[ba.InputType, ...], call: Callable
+ ) -> None:
+
"""Set the python callable to be run for one or more types of input."""
return None
def exists(self) -> bool:
+
"""Return whether the underlying player is still in the game."""
return bool()
def get_icon(self) -> dict[str, Any]:
+
"""Returns the character's icon (images, colors, etc contained
in a dict.
"""
return {'foo': 'bar'}
def get_icon_info(self) -> dict[str, Any]:
+
"""(internal)"""
return {'foo': 'bar'}
def get_v1_account_id(self) -> str:
+
"""Return the V1 Account ID this player is signed in under, if
there is one and it can be determined with relative certainty.
Returns None otherwise. Note that this may require an active
@@ -808,38 +873,53 @@ class SessionPlayer:
return str()
def getname(self, full: bool = False, icon: bool = True) -> str:
+
"""Returns the player's name. If icon is True, the long version of the
name may include an icon.
"""
return str()
def remove_from_game(self) -> None:
+
"""Removes the player from the game."""
return None
def resetinput(self) -> None:
+
"""Clears out the player's assigned input actions."""
return None
- def set_icon_info(self, texture: str, tint_texture: str,
- tint_color: Sequence[float],
- tint2_color: Sequence[float]) -> None:
+ def set_icon_info(
+ self,
+ texture: str,
+ tint_texture: str,
+ tint_color: Sequence[float],
+ tint2_color: Sequence[float],
+ ) -> None:
+
"""(internal)"""
return None
def setactivity(self, activity: ba.Activity | None) -> None:
+
"""(internal)"""
return None
- def setdata(self, team: ba.SessionTeam, character: str,
- color: Sequence[float], highlight: Sequence[float]) -> None:
+ def setdata(
+ self,
+ team: ba.SessionTeam,
+ character: str,
+ color: Sequence[float],
+ highlight: Sequence[float],
+ ) -> None:
+
"""(internal)"""
return None
- def setname(self,
- name: str,
- full_name: str | None = None,
- real: bool = True) -> None:
+ def setname(
+ self, name: str, full_name: str | None = None, real: bool = True
+ ) -> None:
+
"""Set the player's name to the provided string.
A number will automatically be appended if the name is not unique from
other players.
@@ -847,31 +927,37 @@ class SessionPlayer:
return None
def setnode(self, node: Node | None) -> None:
+
"""(internal)"""
return None
class Sound:
+
"""A reference to a sound.
Category: **Asset Classes**
Use ba.getsound() to instantiate one.
"""
+
pass
class Texture:
+
"""A reference to a texture.
Category: **Asset Classes**
Use ba.gettexture() to instantiate one.
"""
+
pass
class Timer:
+
"""Timers are used to run code at later points in time.
Category: **General Utility Classes**
@@ -919,17 +1005,20 @@ class Timer:
... ba.timer(3.89, stop_saying_it)
"""
- def __init__(self,
- time: float,
- call: Callable[[], Any],
- repeat: bool = False,
- timetype: ba.TimeType = TimeType.SIM,
- timeformat: ba.TimeFormat = TimeFormat.SECONDS,
- suppress_format_warning: bool = False):
+ def __init__(
+ self,
+ time: float,
+ call: Callable[[], Any],
+ repeat: bool = False,
+ timetype: ba.TimeType = TimeType.SIM,
+ timeformat: ba.TimeFormat = TimeFormat.SECONDS,
+ suppress_format_warning: bool = False,
+ ):
pass
class Vec3(Sequence[float]):
+
"""A vector of 3 floats.
Category: **General Utility Classes**
@@ -940,13 +1029,17 @@ class Vec3(Sequence[float]):
- with a single three-member sequence arg, sequence values are copied
- otherwise assumes individual x/y/z args (positional or keywords)
"""
+
x: float
+
"""The vector's X component."""
y: float
+
"""The vector's Y component."""
z: float
+
"""The vector's Z component."""
# pylint: disable=function-redefined
@@ -1019,23 +1112,28 @@ class Vec3(Sequence[float]):
pass
def cross(self, other: Vec3) -> Vec3:
+
"""Returns the cross product of this vector and another."""
return Vec3()
def dot(self, other: Vec3) -> float:
+
"""Returns the dot product of this vector and another."""
return float()
def length(self) -> float:
+
"""Returns the length of the vector."""
return float()
def normalized(self) -> Vec3:
+
"""Returns a normalized version of the vector."""
return Vec3()
class Widget:
+
"""Internal type for low level UI elements; buttons, windows, etc.
Category: **User Interface Classes**
@@ -1046,20 +1144,24 @@ class Widget:
"""
def activate(self) -> None:
+
"""Activates a widget; the same as if it had been clicked."""
return None
def add_delete_callback(self, call: Callable) -> None:
+
"""Add a call to be run immediately after this widget is destroyed."""
return None
def delete(self, ignore_missing: bool = True) -> None:
+
"""Delete the Widget. Ignores already-deleted Widgets if ignore_missing
is True; otherwise an Exception is thrown.
"""
return None
def exists(self) -> bool:
+
"""Returns whether the Widget still exists.
Most functionality will fail on a nonexistent widget.
@@ -1070,10 +1172,12 @@ class Widget:
return bool()
def get_children(self) -> list[ba.Widget]:
+
"""Returns any child Widgets of this Widget."""
return [Widget()]
def get_screen_space_center(self) -> tuple[float, float]:
+
"""Returns the coords of the ba.Widget center relative to the center
of the screen. This can be useful for placing pop-up windows and other
special cases.
@@ -1081,10 +1185,12 @@ class Widget:
return (0.0, 0.0)
def get_selected_child(self) -> ba.Widget | None:
+
"""Returns the selected child Widget or None if nothing is selected."""
return Widget()
def get_widget_type(self) -> str:
+
"""Return the internal type of the Widget as a string. Note that this
is different from the Python ba.Widget type, which is the same for
all widgets.
@@ -1093,12 +1199,15 @@ class Widget:
def _app() -> ba.App:
+
"""(internal)"""
import ba # pylint: disable=cyclic-import
+
return ba.App()
def add_clean_frame_callback(call: Callable) -> None:
+
"""(internal)
Provide an object to be called once the next non-progress-bar-frame has
@@ -1109,6 +1218,7 @@ def add_clean_frame_callback(call: Callable) -> None:
def android_get_external_files_dir() -> str:
+
"""(internal)
Returns the android external storage path, or None if there is none on
@@ -1118,6 +1228,7 @@ def android_get_external_files_dir() -> str:
def android_media_scan_file(file_name: str) -> None:
+
"""(internal)
Refreshes Android MTP Index for a file; use this to get file
@@ -1127,26 +1238,31 @@ def android_media_scan_file(file_name: str) -> None:
def android_show_wifi_settings() -> None:
+
"""(internal)"""
return None
def app_instance_uuid() -> str:
+
"""(internal)"""
return str()
def apply_config() -> None:
+
"""(internal)"""
return None
def appname() -> str:
+
"""(internal)"""
return str()
def appnameupper() -> str:
+
"""(internal)
Return whether this build of the game can display full unicode such as
@@ -1156,52 +1272,57 @@ def appnameupper() -> str:
def back_press() -> None:
+
"""(internal)"""
return None
def bless() -> None:
+
"""(internal)"""
return None
-def buttonwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- on_activate_call: Callable | None = None,
- label: str | ba.Lstr | None = None,
- color: Sequence[float] | None = None,
- down_widget: ba.Widget | None = None,
- up_widget: ba.Widget | None = None,
- left_widget: ba.Widget | None = None,
- right_widget: ba.Widget | None = None,
- texture: ba.Texture | None = None,
- text_scale: float | None = None,
- textcolor: Sequence[float] | None = None,
- enable_sound: bool | None = None,
- model_transparent: ba.Model | None = None,
- model_opaque: ba.Model | None = None,
- repeat: bool | None = None,
- scale: float | None = None,
- transition_delay: float | None = None,
- on_select_call: Callable | None = None,
- button_type: str | None = None,
- extra_touch_border_scale: float | None = None,
- selectable: bool | None = None,
- show_buffer_top: float | None = None,
- icon: ba.Texture | None = None,
- iconscale: float | None = None,
- icon_tint: float | None = None,
- icon_color: Sequence[float] | None = None,
- autoselect: bool | None = None,
- mask_texture: ba.Texture | None = None,
- tint_texture: ba.Texture | None = None,
- tint_color: Sequence[float] | None = None,
- tint2_color: Sequence[float] | None = None,
- text_flatness: float | None = None,
- text_res_scale: float | None = None,
- enabled: bool | None = None) -> ba.Widget:
+def buttonwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ on_activate_call: Callable | None = None,
+ label: str | ba.Lstr | None = None,
+ color: Sequence[float] | None = None,
+ down_widget: ba.Widget | None = None,
+ up_widget: ba.Widget | None = None,
+ left_widget: ba.Widget | None = None,
+ right_widget: ba.Widget | None = None,
+ texture: ba.Texture | None = None,
+ text_scale: float | None = None,
+ textcolor: Sequence[float] | None = None,
+ enable_sound: bool | None = None,
+ model_transparent: ba.Model | None = None,
+ model_opaque: ba.Model | None = None,
+ repeat: bool | None = None,
+ scale: float | None = None,
+ transition_delay: float | None = None,
+ on_select_call: Callable | None = None,
+ button_type: str | None = None,
+ extra_touch_border_scale: float | None = None,
+ selectable: bool | None = None,
+ show_buffer_top: float | None = None,
+ icon: ba.Texture | None = None,
+ iconscale: float | None = None,
+ icon_tint: float | None = None,
+ icon_color: Sequence[float] | None = None,
+ autoselect: bool | None = None,
+ mask_texture: ba.Texture | None = None,
+ tint_texture: ba.Texture | None = None,
+ tint_color: Sequence[float] | None = None,
+ tint2_color: Sequence[float] | None = None,
+ text_flatness: float | None = None,
+ text_res_scale: float | None = None,
+ enabled: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a button widget.
Category: **User Interface Functions**
@@ -1211,10 +1332,12 @@ def buttonwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def camerashake(intensity: float = 1.0) -> None:
+
"""Shake the camera.
Category: **Gameplay Functions**
@@ -1227,16 +1350,19 @@ def camerashake(intensity: float = 1.0) -> None:
def can_display_full_unicode() -> bool:
+
"""(internal)"""
return bool()
def can_show_ad() -> bool:
+
"""(internal)"""
return bool()
def capture_gamepad_input(call: Callable[[dict], None]) -> None:
+
"""(internal)
Add a callable to be called for subsequent gamepad events.
@@ -1246,6 +1372,7 @@ def capture_gamepad_input(call: Callable[[dict], None]) -> None:
def capture_keyboard_input(call: Callable[[dict], None]) -> None:
+
"""(internal)
Add a callable to be called for subsequent keyboard-game-pad events.
@@ -1255,6 +1382,7 @@ def capture_keyboard_input(call: Callable[[dict], None]) -> None:
def charstr(char_id: ba.SpecialChar) -> str:
+
"""Get a unicode string representing a special character.
Category: **General Utility Functions**
@@ -1268,28 +1396,34 @@ def charstr(char_id: ba.SpecialChar) -> str:
return str()
-def chatmessage(message: str | ba.Lstr,
- clients: Sequence[int] | None = None,
- sender_override: str | None = None) -> None:
+def chatmessage(
+ message: str | ba.Lstr,
+ clients: Sequence[int] | None = None,
+ sender_override: str | None = None,
+) -> None:
+
"""(internal)"""
return None
-def checkboxwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- text: str | ba.Lstr | None = None,
- value: bool | None = None,
- on_value_change_call: Callable[[bool], None] | None = None,
- on_select_call: Callable[[], None] | None = None,
- text_scale: float | None = None,
- textcolor: Sequence[float] | None = None,
- scale: float | None = None,
- is_radio_button: bool | None = None,
- maxwidth: float | None = None,
- autoselect: bool | None = None,
- color: Sequence[float] | None = None) -> ba.Widget:
+def checkboxwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ text: str | ba.Lstr | None = None,
+ value: bool | None = None,
+ on_value_change_call: Callable[[bool], None] | None = None,
+ on_select_call: Callable[[], None] | None = None,
+ text_scale: float | None = None,
+ textcolor: Sequence[float] | None = None,
+ scale: float | None = None,
+ is_radio_button: bool | None = None,
+ maxwidth: float | None = None,
+ autoselect: bool | None = None,
+ color: Sequence[float] | None = None,
+) -> ba.Widget:
+
"""Create or edit a check-box widget.
Category: **User Interface Functions**
@@ -1299,15 +1433,18 @@ def checkboxwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def client_info_query_response(token: str, response: Any) -> None:
+
"""(internal)"""
return None
def clipboard_get_text() -> str:
+
"""Return text currently on the system clipboard.
Category: **General Utility Functions**
@@ -1319,6 +1456,7 @@ def clipboard_get_text() -> str:
def clipboard_has_text() -> bool:
+
"""Return whether there is currently text on the clipboard.
Category: **General Utility Functions**
@@ -1330,6 +1468,7 @@ def clipboard_has_text() -> bool:
def clipboard_is_supported() -> bool:
+
"""Return whether this platform supports clipboard operations at all.
Category: **General Utility Functions**
@@ -1341,6 +1480,7 @@ def clipboard_is_supported() -> bool:
def clipboard_set_text(value: str) -> None:
+
"""Copy a string to the system clipboard.
Category: **General Utility Functions**
@@ -1351,23 +1491,26 @@ def clipboard_set_text(value: str) -> None:
return None
-def columnwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- background: bool | None = None,
- selected_child: ba.Widget | None = None,
- visible_child: ba.Widget | None = None,
- single_depth: bool | None = None,
- print_list_exit_instructions: bool | None = None,
- left_border: float | None = None,
- top_border: float | None = None,
- bottom_border: float | None = None,
- selection_loops_to_parent: bool | None = None,
- border: float | None = None,
- margin: float | None = None,
- claims_left_right: bool | None = None,
- claims_tab: bool | None = None) -> ba.Widget:
+def columnwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ background: bool | None = None,
+ selected_child: ba.Widget | None = None,
+ visible_child: ba.Widget | None = None,
+ single_depth: bool | None = None,
+ print_list_exit_instructions: bool | None = None,
+ left_border: float | None = None,
+ top_border: float | None = None,
+ bottom_border: float | None = None,
+ selection_loops_to_parent: bool | None = None,
+ border: float | None = None,
+ margin: float | None = None,
+ claims_left_right: bool | None = None,
+ claims_tab: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a column widget.
Category: **User Interface Functions**
@@ -1377,22 +1520,26 @@ def columnwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def commit_config(config: str) -> None:
+
"""(internal)"""
return None
-def connect_to_party(address: str,
- port: int | None = None,
- print_progress: bool = True) -> None:
+def connect_to_party(
+ address: str, port: int | None = None, print_progress: bool = True
+) -> None:
+
"""(internal)"""
return None
def console_print(*args: Any) -> None:
+
"""(internal)
Print the provided args to the game console (using str()).
@@ -1402,37 +1549,40 @@ def console_print(*args: Any) -> None:
return None
-def containerwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- background: bool | None = None,
- selected_child: ba.Widget | None = None,
- transition: str | None = None,
- cancel_button: ba.Widget | None = None,
- start_button: ba.Widget | None = None,
- root_selectable: bool | None = None,
- on_activate_call: Callable[[], None] | None = None,
- claims_left_right: bool | None = None,
- claims_tab: bool | None = None,
- selection_loops: bool | None = None,
- selection_loops_to_parent: bool | None = None,
- scale: float | None = None,
- on_outside_click_call: Callable[[], None] | None = None,
- single_depth: bool | None = None,
- visible_child: ba.Widget | None = None,
- stack_offset: Sequence[float] | None = None,
- color: Sequence[float] | None = None,
- on_cancel_call: Callable[[], None] | None = None,
- print_list_exit_instructions: bool | None = None,
- click_activate: bool | None = None,
- always_highlight: bool | None = None,
- selectable: bool | None = None,
- scale_origin_stack_offset: Sequence[float] | None = None,
- toolbar_visibility: str | None = None,
- on_select_call: Callable[[], None] | None = None,
- claim_outside_clicks: bool | None = None,
- claims_up_down: bool | None = None) -> ba.Widget:
+def containerwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ background: bool | None = None,
+ selected_child: ba.Widget | None = None,
+ transition: str | None = None,
+ cancel_button: ba.Widget | None = None,
+ start_button: ba.Widget | None = None,
+ root_selectable: bool | None = None,
+ on_activate_call: Callable[[], None] | None = None,
+ claims_left_right: bool | None = None,
+ claims_tab: bool | None = None,
+ selection_loops: bool | None = None,
+ selection_loops_to_parent: bool | None = None,
+ scale: float | None = None,
+ on_outside_click_call: Callable[[], None] | None = None,
+ single_depth: bool | None = None,
+ visible_child: ba.Widget | None = None,
+ stack_offset: Sequence[float] | None = None,
+ color: Sequence[float] | None = None,
+ on_cancel_call: Callable[[], None] | None = None,
+ print_list_exit_instructions: bool | None = None,
+ click_activate: bool | None = None,
+ always_highlight: bool | None = None,
+ selectable: bool | None = None,
+ scale_origin_stack_offset: Sequence[float] | None = None,
+ toolbar_visibility: str | None = None,
+ on_select_call: Callable[[], None] | None = None,
+ claim_outside_clicks: bool | None = None,
+ claims_up_down: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a container widget.
Category: **User Interface Functions**
@@ -1442,15 +1592,18 @@ def containerwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def contains_python_dist() -> bool:
+
"""(internal)"""
return bool()
def debug_print_py_err() -> None:
+
"""(internal)
Debugging func for tracking leaked Python errors in the C++ layer..
@@ -1459,11 +1612,13 @@ def debug_print_py_err() -> None:
def disconnect_client(client_id: int, ban_time: int = 300) -> bool:
+
"""(internal)"""
return bool()
def disconnect_from_host() -> None:
+
"""(internal)
Category: General Utility Functions
@@ -1472,6 +1627,7 @@ def disconnect_from_host() -> None:
def display_log(name: str, level: str, message: str) -> None:
+
"""(internal)
Sends a log message to the in-game console and any per-platform
@@ -1482,6 +1638,7 @@ def display_log(name: str, level: str, message: str) -> None:
def do_once() -> bool:
+
"""Return whether this is the first time running a line of code.
Category: **General Utility Functions**
@@ -1501,18 +1658,22 @@ def do_once() -> bool:
def ehv() -> None:
+
"""(internal)"""
return None
-def emitfx(position: Sequence[float],
- velocity: Sequence[float] | None = None,
- count: int = 10,
- scale: float = 1.0,
- spread: float = 1.0,
- chunk_type: str = 'rock',
- emit_type: str = 'chunks',
- tendril_type: str = 'smoke') -> None:
+def emitfx(
+ position: Sequence[float],
+ velocity: Sequence[float] | None = None,
+ count: int = 10,
+ scale: float = 1.0,
+ spread: float = 1.0,
+ chunk_type: str = 'rock',
+ emit_type: str = 'chunks',
+ tendril_type: str = 'smoke',
+) -> None:
+
"""Emit particles, smoke, etc. into the fx sim layer.
Category: **Gameplay Functions**
@@ -1526,6 +1687,7 @@ def emitfx(position: Sequence[float],
def end_host_scanning() -> None:
+
"""(internal)
Category: General Utility Functions
@@ -1534,6 +1696,7 @@ def end_host_scanning() -> None:
def env() -> dict:
+
"""(internal)
Returns a dict containing general info about the operating environment
@@ -1545,13 +1708,15 @@ def env() -> dict:
def evaluate_lstr(value: str) -> str:
+
"""(internal)"""
return str()
-def fade_screen(to: int = 0,
- time: float = 0.25,
- endcall: Callable[[], None] | None = None) -> None:
+def fade_screen(
+ to: int = 0, time: float = 0.25, endcall: Callable[[], None] | None = None
+) -> None:
+
"""(internal)
Fade the local game screen in our out from black over a duration of
@@ -1563,6 +1728,7 @@ def fade_screen(to: int = 0,
def focus_window() -> None:
+
"""(internal)
A workaround for some unintentional backgrounding that occurs on mac
@@ -1571,16 +1737,19 @@ def focus_window() -> None:
def get_appconfig_builtin_keys() -> list[str]:
+
"""(internal)"""
return ['blah', 'blah2']
def get_appconfig_default_value(key: str) -> Any:
+
"""(internal)"""
return _uninferrable()
def get_camera_position() -> tuple[float, ...]:
+
"""(internal)
WARNING: these camera controls will not apply to network clients
@@ -1591,6 +1760,7 @@ def get_camera_position() -> tuple[float, ...]:
def get_camera_target() -> tuple[float, ...]:
+
"""(internal)
WARNING: these camera controls will not apply to network clients
@@ -1601,11 +1771,13 @@ def get_camera_target() -> tuple[float, ...]:
def get_chat_messages() -> list[str]:
+
"""(internal)"""
return ['blah', 'blah2']
def get_client_public_device_uuid(client_id: int) -> str | None:
+
"""(internal)
Category: General Utility Functions
@@ -1620,6 +1792,7 @@ def get_client_public_device_uuid(client_id: int) -> str | None:
def get_collision_info(*args: Any) -> Any:
+
"""Return collision related values
Category: **Gameplay Functions**
@@ -1632,6 +1805,7 @@ def get_collision_info(*args: Any) -> Any:
def get_configurable_game_pads() -> list:
+
"""(internal)
Returns a list of the currently connected gamepads that can be
@@ -1641,11 +1815,13 @@ def get_configurable_game_pads() -> list:
def get_connection_to_host_info() -> dict:
+
"""(internal)"""
return dict()
def get_display_resolution() -> tuple[int, int] | None:
+
"""(internal)
Return the currently selected display resolution for fullscreen
@@ -1655,26 +1831,31 @@ def get_display_resolution() -> tuple[int, int] | None:
def get_foreground_host_activity() -> ba.Activity | None:
+
"""(internal)
Returns the ba.Activity currently in the foreground, or None if there
is none.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Activity(settings={})
def get_foreground_host_session() -> ba.Session | None:
+
"""(internal)
Return the ba.Session currently being displayed, or None if there is
none.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Session([])
def get_game_port() -> int:
+
"""(internal)
Return the port ballistica is hosting on.
@@ -1683,11 +1864,13 @@ def get_game_port() -> int:
def get_game_roster() -> list[dict[str, Any]]:
+
"""(internal)"""
return [{'foo': 'bar'}]
def get_idle_time() -> int:
+
"""(internal)
Returns the amount of time since any game input has been received.
@@ -1696,16 +1879,19 @@ def get_idle_time() -> int:
def get_local_active_input_devices_count() -> int:
+
"""(internal)"""
return int()
def get_low_level_config_value(key: str, default_value: int) -> int:
+
"""(internal)"""
return int()
def get_max_graphics_quality() -> str:
+
"""(internal)
Return the max graphics-quality supported on the current hardware.
@@ -1713,54 +1899,70 @@ def get_max_graphics_quality() -> str:
return str()
-def get_package_collide_model(package: ba.AssetPackage,
- name: str) -> ba.CollideModel:
+def get_package_collide_model(
+ package: ba.AssetPackage, name: str
+) -> ba.CollideModel:
+
"""(internal)"""
import ba # pylint: disable=cyclic-import
+
return ba.CollideModel()
def get_package_data(package: ba.AssetPackage, name: str) -> ba.Data:
+
"""(internal)."""
import ba # pylint: disable=cyclic-import
+
return ba.Data()
def get_package_model(package: ba.AssetPackage, name: str) -> ba.Model:
+
"""(internal)"""
import ba # pylint: disable=cyclic-import
+
return ba.Model()
def get_package_sound(package: ba.AssetPackage, name: str) -> ba.Sound:
+
"""(internal)."""
import ba # pylint: disable=cyclic-import
+
return ba.Sound()
def get_package_texture(package: ba.AssetPackage, name: str) -> ba.Texture:
+
"""(internal)"""
import ba # pylint: disable=cyclic-import
+
return ba.Texture()
def get_public_party_enabled() -> bool:
+
"""(internal)"""
return bool()
def get_public_party_max_size() -> int:
+
"""(internal)"""
return int()
def get_qrcode_texture(url: str) -> ba.Texture:
+
"""(internal)"""
import ba # pylint: disable=cyclic-import
+
return ba.Texture()
def get_random_names() -> list:
+
"""(internal)
Returns the random names used by the game.
@@ -1769,6 +1971,7 @@ def get_random_names() -> list:
def get_replay_speed_exponent() -> int:
+
"""(internal)
Returns current replay speed value. Actual displayed speed is pow(2,speed).
@@ -1777,16 +1980,19 @@ def get_replay_speed_exponent() -> int:
def get_replays_dir() -> str:
+
"""(internal)"""
return str()
def get_special_widget(name: str) -> Widget:
+
"""(internal)"""
return Widget()
def get_string_height(string: str, suppress_warning: bool = False) -> float:
+
"""(internal)
Given a string, returns its height using the standard small app
@@ -1796,6 +2002,7 @@ def get_string_height(string: str, suppress_warning: bool = False) -> float:
def get_string_width(string: str, suppress_warning: bool = False) -> float:
+
"""(internal)
Given a string, returns its width using the standard small app
@@ -1805,6 +2012,7 @@ def get_string_width(string: str, suppress_warning: bool = False) -> float:
def get_thread_name() -> str:
+
"""(internal)
Returns the name of the current thread.
@@ -1815,21 +2023,25 @@ def get_thread_name() -> str:
def get_ui_input_device() -> ba.InputDevice:
+
"""(internal)
Returns the input-device that currently owns the user interface, or
None if there is none.
"""
import ba # pylint: disable=cyclic-import
+
return ba.InputDevice()
def get_v1_cloud_log() -> str:
+
"""(internal)"""
return str()
def get_v1_cloud_log_file_path() -> str:
+
"""(internal)
Return the path to the app log file.
@@ -1838,6 +2050,7 @@ def get_v1_cloud_log_file_path() -> str:
def get_volatile_data_directory() -> str:
+
"""(internal)
Return the path to the app volatile data directory.
@@ -1873,6 +2086,7 @@ def getactivity(doraise: bool = True) -> ba.Activity | None:
def getcollidemodel(name: str) -> ba.CollideModel:
+
"""Return a collide-model, loading it if necessary.
Category: **Asset Functions**
@@ -1886,10 +2100,12 @@ def getcollidemodel(name: str) -> ba.CollideModel:
in the background if necessary.
"""
import ba # pylint: disable=cyclic-import
+
return ba.CollideModel()
def getdata(name: str) -> ba.Data:
+
"""Return a data, loading it if necessary.
Category: **Asset Functions**
@@ -1900,20 +2116,22 @@ def getdata(name: str) -> ba.Data:
in the background if necessary.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Data()
# Show that our return type varies based on "doraise" value:
@overload
-def getinputdevice(name: str,
- unique_id: str,
- doraise: Literal[True] = True) -> ba.InputDevice:
+def getinputdevice(
+ name: str, unique_id: str, doraise: Literal[True] = True
+) -> ba.InputDevice:
...
@overload
-def getinputdevice(name: str, unique_id: str,
- doraise: Literal[False]) -> ba.InputDevice | None:
+def getinputdevice(
+ name: str, unique_id: str, doraise: Literal[False]
+) -> ba.InputDevice | None:
...
@@ -1928,6 +2146,7 @@ def getinputdevice(name: str, unique_id: str, doraise: bool = True) -> Any:
def getmodel(name: str) -> ba.Model:
+
"""Return a model, loading it if necessary.
Category: **Asset Functions**
@@ -1938,10 +2157,12 @@ def getmodel(name: str) -> ba.Model:
in the background if necessary.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Model()
def getnodes() -> list:
+
"""Return all nodes in the current ba.Context.
Category: **Gameplay Functions**
@@ -1973,6 +2194,7 @@ def getsession(doraise: bool = True) -> ba.Session | None:
def getsound(name: str) -> ba.Sound:
+
"""Return a sound, loading it if necessary.
Category: **Asset Functions**
@@ -1983,10 +2205,12 @@ def getsound(name: str) -> ba.Sound:
in the background if necessary.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Sound()
def gettexture(name: str) -> ba.Texture:
+
"""Return a texture, loading it if necessary.
Category: **Asset Functions**
@@ -1997,10 +2221,12 @@ def gettexture(name: str) -> ba.Texture:
in the background if necessary.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Texture()
def has_gamma_control() -> bool:
+
"""(internal)
Returns whether the system can adjust overall screen gamma)
@@ -2009,21 +2235,25 @@ def has_gamma_control() -> bool:
def has_user_run_commands() -> bool:
+
"""(internal)"""
return bool()
def has_video_ads() -> bool:
+
"""(internal)"""
return bool()
def have_chars(text: str) -> bool:
+
"""(internal)"""
return bool()
def have_connected_clients() -> bool:
+
"""(internal)
Category: General Utility Functions
@@ -2032,16 +2262,19 @@ def have_connected_clients() -> bool:
def have_incentivized_ad() -> bool:
+
"""(internal)"""
return bool()
def have_permission(permission: ba.Permission) -> bool:
+
"""(internal)"""
return bool()
def have_touchscreen_input() -> bool:
+
"""(internal)
Returns whether or not a touch-screen input is present
@@ -2050,26 +2283,30 @@ def have_touchscreen_input() -> bool:
def host_scan_cycle() -> list:
+
"""(internal)"""
return list()
-def hscrollwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- background: bool | None = None,
- selected_child: ba.Widget | None = None,
- capture_arrows: bool | None = None,
- on_select_call: Callable[[], None] | None = None,
- center_small_content: bool | None = None,
- color: Sequence[float] | None = None,
- highlight: bool | None = None,
- border_opacity: float | None = None,
- simple_culling_h: float | None = None,
- claims_left_right: bool | None = None,
- claims_up_down: bool | None = None,
- claims_tab: bool | None = None) -> ba.Widget:
+def hscrollwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ background: bool | None = None,
+ selected_child: ba.Widget | None = None,
+ capture_arrows: bool | None = None,
+ on_select_call: Callable[[], None] | None = None,
+ center_small_content: bool | None = None,
+ color: Sequence[float] | None = None,
+ highlight: bool | None = None,
+ border_opacity: float | None = None,
+ simple_culling_h: float | None = None,
+ claims_left_right: bool | None = None,
+ claims_up_down: bool | None = None,
+ claims_tab: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a horizontal scroll widget.
Category: **User Interface Functions**
@@ -2079,27 +2316,31 @@ def hscrollwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
-def imagewidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- color: Sequence[float] | None = None,
- texture: ba.Texture | None = None,
- opacity: float | None = None,
- model_transparent: ba.Model | None = None,
- model_opaque: ba.Model | None = None,
- has_alpha_channel: bool = True,
- tint_texture: ba.Texture | None = None,
- tint_color: Sequence[float] | None = None,
- transition_delay: float | None = None,
- draw_controller: ba.Widget | None = None,
- tint2_color: Sequence[float] | None = None,
- tilt_scale: float | None = None,
- mask_texture: ba.Texture | None = None,
- radial_amount: float | None = None) -> ba.Widget:
+def imagewidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ color: Sequence[float] | None = None,
+ texture: ba.Texture | None = None,
+ opacity: float | None = None,
+ model_transparent: ba.Model | None = None,
+ model_opaque: ba.Model | None = None,
+ has_alpha_channel: bool = True,
+ tint_texture: ba.Texture | None = None,
+ tint_color: Sequence[float] | None = None,
+ transition_delay: float | None = None,
+ draw_controller: ba.Widget | None = None,
+ tint2_color: Sequence[float] | None = None,
+ tilt_scale: float | None = None,
+ mask_texture: ba.Texture | None = None,
+ radial_amount: float | None = None,
+) -> ba.Widget:
+
"""Create or edit an image widget.
Category: **User Interface Functions**
@@ -2109,10 +2350,12 @@ def imagewidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def in_logic_thread() -> bool:
+
"""(internal)
Returns whether or not the current thread is the logic thread.
@@ -2121,33 +2364,39 @@ def in_logic_thread() -> bool:
def increment_analytics_count(name: str, increment: int = 1) -> None:
+
"""(internal)"""
return None
-def increment_analytics_count_raw_2(name: str,
- uses_increment: bool = True,
- increment: int = 1) -> None:
+def increment_analytics_count_raw_2(
+ name: str, uses_increment: bool = True, increment: int = 1
+) -> None:
+
"""(internal)"""
return None
def increment_analytics_counts_raw(name: str, increment: int = 1) -> None:
+
"""(internal)"""
return None
def is_in_replay() -> bool:
+
"""(internal)"""
return bool()
def is_log_full() -> bool:
+
"""(internal)"""
return bool()
def is_os_playing_music() -> bool:
+
"""(internal)
Tells whether the OS is currently playing music of some sort.
@@ -2158,6 +2407,7 @@ def is_os_playing_music() -> bool:
def is_ouya_build() -> bool:
+
"""(internal)
Returns whether we're running the ouya-specific version
@@ -2166,26 +2416,31 @@ def is_ouya_build() -> bool:
def is_party_icon_visible() -> bool:
+
"""(internal)"""
return bool()
def is_running_on_fire_tv() -> bool:
+
"""(internal)"""
return bool()
def is_running_on_ouya() -> bool:
+
"""(internal)"""
return bool()
def is_xcode_build() -> bool:
+
"""(internal)"""
return bool()
def lock_all_input() -> None:
+
"""(internal)
Prevents all keyboard, mouse, and gamepad events from being processed.
@@ -2194,46 +2449,55 @@ def lock_all_input() -> None:
def mac_music_app_get_library_source() -> None:
+
"""(internal)"""
return None
def mac_music_app_get_playlists() -> list[str]:
+
"""(internal)"""
return ['blah', 'blah2']
def mac_music_app_get_volume() -> int:
+
"""(internal)"""
return int()
def mac_music_app_init() -> None:
+
"""(internal)"""
return None
def mac_music_app_play_playlist(playlist: str) -> bool:
+
"""(internal)"""
return bool()
def mac_music_app_set_volume(volume: int) -> None:
+
"""(internal)"""
return None
def mac_music_app_stop() -> None:
+
"""(internal)"""
return None
def mark_log_sent() -> None:
+
"""(internal)"""
return None
def music_player_play(files: Any) -> None:
+
"""(internal)
Starts internal music file playback (for internal use)
@@ -2242,6 +2506,7 @@ def music_player_play(files: Any) -> None:
def music_player_set_volume(volume: float) -> None:
+
"""(internal)
Sets internal music player volume (for internal use)
@@ -2250,6 +2515,7 @@ def music_player_set_volume(volume: float) -> None:
def music_player_shutdown() -> None:
+
"""(internal)
Finalizes internal music file playback (for internal use)
@@ -2258,6 +2524,7 @@ def music_player_shutdown() -> None:
def music_player_stop() -> None:
+
"""(internal)
Stops internal music file playback (for internal use)
@@ -2265,19 +2532,24 @@ def music_player_stop() -> None:
return None
-def new_host_session(sessiontype: type[ba.Session],
- benchmark_type: str | None = None) -> None:
+def new_host_session(
+ sessiontype: type[ba.Session], benchmark_type: str | None = None
+) -> None:
+
"""(internal)"""
return None
def new_replay_session(file_name: str) -> None:
+
"""(internal)"""
return None
-def newactivity(activity_type: type[ba.Activity],
- settings: dict | None = None) -> ba.Activity:
+def newactivity(
+ activity_type: type[ba.Activity], settings: dict | None = None
+) -> ba.Activity:
+
"""Instantiates a ba.Activity given a type object.
Category: **General Utility Functions**
@@ -2286,14 +2558,18 @@ def newactivity(activity_type: type[ba.Activity],
instantiated; you must go through this function.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Activity(settings={})
-def newnode(type: str,
- owner: ba.Node | None = None,
- attrs: dict | None = None,
- name: str | None = None,
- delegate: Any = None) -> Node:
+def newnode(
+ type: str,
+ owner: ba.Node | None = None,
+ attrs: dict | None = None,
+ name: str | None = None,
+ delegate: Any = None,
+) -> Node:
+
"""Add a node of the given type to the game.
Category: **Gameplay Functions**
@@ -2316,6 +2592,7 @@ def newnode(type: str,
def open_dir_externally(path: str) -> None:
+
"""(internal)
Open the provided dir in the default external app.
@@ -2324,6 +2601,7 @@ def open_dir_externally(path: str) -> None:
def open_file_externally(path: str) -> None:
+
"""(internal)
Open the provided file in the default external app.
@@ -2332,6 +2610,7 @@ def open_file_externally(path: str) -> None:
def open_url(address: str) -> None:
+
"""Open a provided URL.
Category: **General Utility Functions**
@@ -2342,10 +2621,13 @@ def open_url(address: str) -> None:
return None
-def playsound(sound: Sound,
- volume: float = 1.0,
- position: Sequence[float] | None = None,
- host_only: bool = False) -> None:
+def playsound(
+ sound: Sound,
+ volume: float = 1.0,
+ position: Sequence[float] | None = None,
+ host_only: bool = False,
+) -> None:
+
"""Play a ba.Sound a single time.
Category: **Gameplay Functions**
@@ -2357,6 +2639,7 @@ def playsound(sound: Sound,
def print_context() -> None:
+
"""(internal)
Prints info about the current context state; for debugging.
@@ -2365,6 +2648,7 @@ def print_context() -> None:
def print_load_info() -> None:
+
"""(internal)
Category: **General Utility Functions**
@@ -2373,6 +2657,7 @@ def print_load_info() -> None:
def printnodes() -> None:
+
"""Print various info about existing nodes; useful for debugging.
Category: **Gameplay Functions**
@@ -2381,6 +2666,7 @@ def printnodes() -> None:
def printobjects() -> None:
+
"""Print debugging info about game objects.
Category: **General Utility Functions**
@@ -2391,10 +2677,13 @@ def printobjects() -> None:
return None
-def pushcall(call: Callable,
- from_other_thread: bool = False,
- suppress_other_thread_warning: bool = False,
- other_thread_use_fg_context: bool = False) -> None:
+def pushcall(
+ call: Callable,
+ from_other_thread: bool = False,
+ suppress_other_thread_warning: bool = False,
+ other_thread_use_fg_context: bool = False,
+) -> None:
+
"""Pushes a call onto the event loop to be run during the next cycle.
Category: **General Utility Functions**
@@ -2415,6 +2704,7 @@ def pushcall(call: Callable,
def quit(soft: bool = False, back: bool = False) -> None:
+
"""Quit the game.
Category: **General Utility Functions**
@@ -2426,16 +2716,19 @@ def quit(soft: bool = False, back: bool = False) -> None:
def register_activity(activity: ba.Activity) -> ActivityData:
+
"""(internal)"""
return ActivityData()
def register_session(session: ba.Session) -> SessionData:
+
"""(internal)"""
return SessionData()
def release_gamepad_input() -> None:
+
"""(internal)
Resumes normal gamepad event processing.
@@ -2444,6 +2737,7 @@ def release_gamepad_input() -> None:
def release_keyboard_input() -> None:
+
"""(internal)
Resumes normal keyboard event processing.
@@ -2452,6 +2746,7 @@ def release_keyboard_input() -> None:
def reload_media() -> None:
+
"""(internal)
Reload all currently loaded game media; useful for
@@ -2461,35 +2756,42 @@ def reload_media() -> None:
def request_permission(permission: ba.Permission) -> None:
+
"""(internal)"""
return None
def reset_game_activity_tracking() -> None:
+
"""(internal)"""
return None
def reset_random_player_names() -> None:
+
"""(internal)"""
return None
def resolve_appconfig_value(key: str) -> Any:
+
"""(internal)"""
return _uninferrable()
-def rowwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- background: bool | None = None,
- selected_child: ba.Widget | None = None,
- visible_child: ba.Widget | None = None,
- claims_left_right: bool | None = None,
- claims_tab: bool | None = None,
- selection_loops_to_parent: bool | None = None) -> ba.Widget:
+def rowwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ background: bool | None = None,
+ selected_child: ba.Widget | None = None,
+ visible_child: ba.Widget | None = None,
+ claims_left_right: bool | None = None,
+ claims_tab: bool | None = None,
+ selection_loops_to_parent: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a row widget.
Category: **User Interface Functions**
@@ -2499,11 +2801,14 @@ def rowwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
-def safecolor(color: Sequence[float],
- target_intensity: float = 0.6) -> tuple[float, ...]:
+def safecolor(
+ color: Sequence[float], target_intensity: float = 0.6
+) -> tuple[float, ...]:
+
"""Given a color tuple, return a color safe to display as text.
Category: **General Utility Functions**
@@ -2514,13 +2819,16 @@ def safecolor(color: Sequence[float],
return (0.0, 0.0, 0.0)
-def screenmessage(message: str | ba.Lstr,
- color: Sequence[float] | None = None,
- top: bool = False,
- image: dict[str, Any] | None = None,
- log: bool = False,
- clients: Sequence[int] | None = None,
- transient: bool = False) -> None:
+def screenmessage(
+ message: str | ba.Lstr,
+ color: Sequence[float] | None = None,
+ top: bool = False,
+ image: dict[str, Any] | None = None,
+ log: bool = False,
+ clients: Sequence[int] | None = None,
+ transient: bool = False,
+) -> None:
+
"""Print a message to the local client's screen, in a given color.
Category: **General Utility Functions**
@@ -2539,24 +2847,27 @@ def screenmessage(message: str | ba.Lstr,
return None
-def scrollwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- background: bool | None = None,
- selected_child: ba.Widget | None = None,
- capture_arrows: bool = False,
- on_select_call: Callable | None = None,
- center_small_content: bool | None = None,
- color: Sequence[float] | None = None,
- highlight: bool | None = None,
- border_opacity: float | None = None,
- simple_culling_v: float | None = None,
- selection_loops_to_parent: bool | None = None,
- claims_left_right: bool | None = None,
- claims_up_down: bool | None = None,
- claims_tab: bool | None = None,
- autoselect: bool | None = None) -> ba.Widget:
+def scrollwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ background: bool | None = None,
+ selected_child: ba.Widget | None = None,
+ capture_arrows: bool = False,
+ on_select_call: Callable | None = None,
+ center_small_content: bool | None = None,
+ color: Sequence[float] | None = None,
+ highlight: bool | None = None,
+ border_opacity: float | None = None,
+ simple_culling_v: float | None = None,
+ selection_loops_to_parent: bool | None = None,
+ claims_left_right: bool | None = None,
+ claims_up_down: bool | None = None,
+ claims_tab: bool | None = None,
+ autoselect: bool | None = None,
+) -> ba.Widget:
+
"""Create or edit a scroll widget.
Category: **User Interface Functions**
@@ -2566,15 +2877,18 @@ def scrollwidget(edit: ba.Widget | None = None,
are applied to the Widget.
"""
import ba # pylint: disable=cyclic-import
+
return ba.Widget()
def set_admins(admins: list[str]) -> None:
+
"""(internal)"""
return None
def set_analytics_screen(screen: str) -> None:
+
"""Used for analytics to see where in the app players spend their time.
Category: **General Utility Functions**
@@ -2587,11 +2901,13 @@ def set_analytics_screen(screen: str) -> None:
def set_authenticate_clients(enable: bool) -> None:
+
"""(internal)"""
return None
def set_camera_manual(value: bool) -> None:
+
"""(internal)
WARNING: these camera controls will not apply to network clients
@@ -2602,6 +2918,7 @@ def set_camera_manual(value: bool) -> None:
def set_camera_position(x: float, y: float, z: float) -> None:
+
"""(internal)
WARNING: these camera controls will not apply to network clients
@@ -2612,6 +2929,7 @@ def set_camera_position(x: float, y: float, z: float) -> None:
def set_camera_target(x: float, y: float, z: float) -> None:
+
"""(internal)
WARNING: these camera controls will not apply to network clients
@@ -2622,6 +2940,7 @@ def set_camera_target(x: float, y: float, z: float) -> None:
def set_debug_speed_exponent(speed: int) -> None:
+
"""(internal)
Sets the debug speed scale for the game. Actual speed is pow(2,speed).
@@ -2630,24 +2949,29 @@ def set_debug_speed_exponent(speed: int) -> None:
def set_enable_default_kick_voting(enable: bool) -> None:
+
"""(internal)"""
return None
def set_internal_language_keys(
- listobj: list[tuple[str, str]],
- random_names_list: list[tuple[str, str]]) -> None:
+ listobj: list[tuple[str, str]], random_names_list: list[tuple[str, str]]
+) -> None:
+
"""(internal)"""
return None
def set_low_level_config_value(key: str, value: int) -> None:
+
"""(internal)"""
return None
def set_map_bounds(
- bounds: tuple[float, float, float, float, float, float]) -> None:
+ bounds: tuple[float, float, float, float, float, float]
+) -> None:
+
"""(internal)
Set map bounds. Generally nodes that go outside of this box are killed.
@@ -2656,46 +2980,55 @@ def set_map_bounds(
def set_master_server_source(source: int) -> None:
+
"""(internal)"""
return None
def set_party_icon_always_visible(value: bool) -> None:
+
"""(internal)"""
return None
def set_party_window_open(value: bool) -> None:
+
"""(internal)"""
return None
def set_platform_misc_read_vals(mode: str) -> None:
+
"""(internal)"""
return None
def set_public_party_enabled(enabled: bool) -> None:
+
"""(internal)"""
return None
def set_public_party_max_size(max_size: int) -> None:
+
"""(internal)"""
return None
def set_public_party_name(name: str) -> None:
+
"""(internal)"""
return None
def set_public_party_stats_url(url: str | None) -> None:
+
"""(internal)"""
return None
def set_replay_speed_exponent(speed: int) -> None:
+
"""(internal)
Set replay speed. Actual displayed speed is pow(2, speed).
@@ -2704,16 +3037,19 @@ def set_replay_speed_exponent(speed: int) -> None:
def set_stress_testing(testing: bool, player_count: int) -> None:
+
"""(internal)"""
return None
def set_telnet_access_enabled(enable: bool) -> None:
+
"""(internal)"""
return None
def set_thread_name(name: str) -> None:
+
"""(internal)
Sets the name of the current thread (on platforms where this is
@@ -2724,11 +3060,13 @@ def set_thread_name(name: str) -> None:
def set_touchscreen_editing(editing: bool) -> None:
+
"""(internal)"""
return None
def set_ui_input_device(input_device: ba.InputDevice | None) -> None:
+
"""(internal)
Sets the input-device that currently owns the user interface.
@@ -2737,25 +3075,31 @@ def set_ui_input_device(input_device: ba.InputDevice | None) -> None:
def setup_sigint() -> None:
+
"""(internal)"""
return None
-def show_ad(purpose: str,
- on_completion_call: Callable[[], None] | None = None) -> None:
+def show_ad(
+ purpose: str, on_completion_call: Callable[[], None] | None = None
+) -> None:
+
"""(internal)"""
return None
def show_ad_2(
- purpose: str,
- on_completion_call: Callable[[bool], None] | None = None) -> None:
+ purpose: str, on_completion_call: Callable[[bool], None] | None = None
+) -> None:
+
"""(internal)"""
return None
-def show_app_invite(title: str | ba.Lstr, message: str | ba.Lstr,
- code: str) -> None:
+def show_app_invite(
+ title: str | ba.Lstr, message: str | ba.Lstr, code: str
+) -> None:
+
"""(internal)
Category: **General Utility Functions**
@@ -2763,14 +3107,18 @@ def show_app_invite(title: str | ba.Lstr, message: str | ba.Lstr,
return None
-def show_online_score_ui(show: str = 'general',
- game: str | None = None,
- game_version: str | None = None) -> None:
+def show_online_score_ui(
+ show: str = 'general',
+ game: str | None = None,
+ game_version: str | None = None,
+) -> None:
+
"""(internal)"""
return None
def show_progress_bar() -> None:
+
"""(internal)
Category: **General Utility Functions**
@@ -2779,45 +3127,49 @@ def show_progress_bar() -> None:
def submit_analytics_counts() -> None:
+
"""(internal)"""
return None
-def textwidget(edit: ba.Widget | None = None,
- parent: ba.Widget | None = None,
- size: Sequence[float] | None = None,
- position: Sequence[float] | None = None,
- text: str | ba.Lstr | None = None,
- v_align: str | None = None,
- h_align: str | None = None,
- editable: bool | None = None,
- padding: float | None = None,
- on_return_press_call: Callable[[], None] | None = None,
- on_activate_call: Callable[[], None] | None = None,
- selectable: bool | None = None,
- query: ba.Widget | None = None,
- max_chars: int | None = None,
- color: Sequence[float] | None = None,
- click_activate: bool | None = None,
- on_select_call: Callable[[], None] | None = None,
- always_highlight: bool | None = None,
- draw_controller: ba.Widget | None = None,
- scale: float | None = None,
- corner_scale: float | None = None,
- description: str | ba.Lstr | None = None,
- transition_delay: float | None = None,
- maxwidth: float | None = None,
- max_height: float | None = None,
- flatness: float | None = None,
- shadow: float | None = None,
- autoselect: bool | None = None,
- rotate: float | None = None,
- enabled: bool | None = None,
- force_internal_editing: bool | None = None,
- always_show_carat: bool | None = None,
- big: bool | None = None,
- extra_touch_border_scale: float | None = None,
- res_scale: float | None = None) -> Widget:
+def textwidget(
+ edit: ba.Widget | None = None,
+ parent: ba.Widget | None = None,
+ size: Sequence[float] | None = None,
+ position: Sequence[float] | None = None,
+ text: str | ba.Lstr | None = None,
+ v_align: str | None = None,
+ h_align: str | None = None,
+ editable: bool | None = None,
+ padding: float | None = None,
+ on_return_press_call: Callable[[], None] | None = None,
+ on_activate_call: Callable[[], None] | None = None,
+ selectable: bool | None = None,
+ query: ba.Widget | None = None,
+ max_chars: int | None = None,
+ color: Sequence[float] | None = None,
+ click_activate: bool | None = None,
+ on_select_call: Callable[[], None] | None = None,
+ always_highlight: bool | None = None,
+ draw_controller: ba.Widget | None = None,
+ scale: float | None = None,
+ corner_scale: float | None = None,
+ description: str | ba.Lstr | None = None,
+ transition_delay: float | None = None,
+ maxwidth: float | None = None,
+ max_height: float | None = None,
+ flatness: float | None = None,
+ shadow: float | None = None,
+ autoselect: bool | None = None,
+ rotate: float | None = None,
+ enabled: bool | None = None,
+ force_internal_editing: bool | None = None,
+ always_show_carat: bool | None = None,
+ big: bool | None = None,
+ extra_touch_border_scale: float | None = None,
+ res_scale: float | None = None,
+) -> Widget:
+
"""Create or edit a text widget.
Category: **User Interface Functions**
@@ -2834,27 +3186,34 @@ def textwidget(edit: ba.Widget | None = None,
@overload
def time(
- timetype: ba.TimeType = TimeType.SIM,
- timeformat: Literal[TimeFormat.SECONDS] = TimeFormat.SECONDS) -> float:
+ timetype: ba.TimeType = TimeType.SIM,
+ timeformat: Literal[TimeFormat.SECONDS] = TimeFormat.SECONDS,
+) -> float:
...
# This "*" keyword-only hack lets us accept 1 arg (timeformat=MILLISECS) forms.
@overload
-def time(timetype: ba.TimeType = TimeType.SIM,
- *,
- timeformat: Literal[TimeFormat.MILLISECONDS]) -> int:
+def time(
+ timetype: ba.TimeType = TimeType.SIM,
+ *,
+ timeformat: Literal[TimeFormat.MILLISECONDS],
+) -> int:
...
@overload
-def time(timetype: ba.TimeType,
- timeformat: Literal[TimeFormat.MILLISECONDS]) -> int:
+def time(
+ timetype: ba.TimeType, timeformat: Literal[TimeFormat.MILLISECONDS]
+) -> int:
...
-def time(timetype: ba.TimeType = TimeType.SIM,
- timeformat: ba.TimeFormat = TimeFormat.SECONDS) -> Any:
+def time(
+ timetype: ba.TimeType = TimeType.SIM,
+ timeformat: ba.TimeFormat = TimeFormat.SECONDS,
+) -> Any:
+
"""Return the current time.
Category: **General Utility Functions**
@@ -2889,6 +3248,7 @@ def time(timetype: ba.TimeType = TimeType.SIM,
def time_format_check(time_format: ba.TimeFormat, length: float | int) -> None:
+
"""(internal)
Logs suspicious time values for timers or animate calls.
@@ -2899,12 +3259,15 @@ def time_format_check(time_format: ba.TimeFormat, length: float | int) -> None:
return None
-def timer(time: float,
- call: Callable[[], Any],
- repeat: bool = False,
- timetype: ba.TimeType = TimeType.SIM,
- timeformat: ba.TimeFormat = TimeFormat.SECONDS,
- suppress_format_warning: bool = False) -> None:
+def timer(
+ time: float,
+ call: Callable[[], Any],
+ repeat: bool = False,
+ timetype: ba.TimeType = TimeType.SIM,
+ timeformat: ba.TimeFormat = TimeFormat.SECONDS,
+ suppress_format_warning: bool = False,
+) -> None:
+
"""Schedule a call to run at a later point in time.
Category: **General Utility Functions**
@@ -2962,6 +3325,7 @@ def timer(time: float,
def uibounds() -> tuple[float, float, float, float]:
+
"""(internal)
Returns a tuple of 4 values: (x-min, x-max, y-min, y-max) representing
@@ -2973,6 +3337,7 @@ def uibounds() -> tuple[float, float, float, float]:
def unlock_all_input() -> None:
+
"""(internal)
Resumes normal keyboard, mouse, and gamepad event processing.
@@ -2981,11 +3346,13 @@ def unlock_all_input() -> None:
def user_ran_commands() -> None:
+
"""(internal)"""
return None
def v1_cloud_log(message: str) -> None:
+
"""(internal)
Push messages to the old v1 cloud log.
@@ -2993,23 +3360,27 @@ def v1_cloud_log(message: str) -> None:
return None
-def value_test(arg: str,
- change: float | None = None,
- absolute: float | None = None) -> float:
+def value_test(
+ arg: str, change: float | None = None, absolute: float | None = None
+) -> float:
+
"""(internal)"""
return float()
-def widget(edit: ba.Widget | None = None,
- up_widget: ba.Widget | None = None,
- down_widget: ba.Widget | None = None,
- left_widget: ba.Widget | None = None,
- right_widget: ba.Widget | None = None,
- show_buffer_top: float | None = None,
- show_buffer_bottom: float | None = None,
- show_buffer_left: float | None = None,
- show_buffer_right: float | None = None,
- autoselect: bool | None = None) -> None:
+def widget(
+ edit: ba.Widget | None = None,
+ up_widget: ba.Widget | None = None,
+ down_widget: ba.Widget | None = None,
+ left_widget: ba.Widget | None = None,
+ right_widget: ba.Widget | None = None,
+ show_buffer_top: float | None = None,
+ show_buffer_bottom: float | None = None,
+ show_buffer_left: float | None = None,
+ show_buffer_right: float | None = None,
+ autoselect: bool | None = None,
+) -> None:
+
"""Edit common attributes of any widget.
Category: **User Interface Functions**
@@ -3020,6 +3391,7 @@ def widget(edit: ba.Widget | None = None,
def workspaces_in_use() -> bool:
+
"""(internal)
Returns whether workspaces functionality has been enabled at
diff --git a/assets/src/ba_data/python/_bainternal.py b/assets/src/ba_data/python/_bainternal.py
index cf89a390..d1bf8109 100644
--- a/assets/src/ba_data/python/_bainternal.py
+++ b/assets/src/ba_data/python/_bainternal.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 43d98979..34e33c98 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_accountv1.py b/assets/src/ba_data/python/ba/_accountv1.py
index d6cf47d0..3d8d196d 100644
--- a/assets/src/ba_data/python/ba/_accountv1.py
+++ b/assets/src/ba_data/python/ba/_accountv1.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_accountv2.py b/assets/src/ba_data/python/ba/_accountv2.py
index 4d34af45..e05a1e6f 100644
--- a/assets/src/ba_data/python/ba/_accountv2.py
+++ b/assets/src/ba_data/python/ba/_accountv2.py
@@ -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."""
diff --git a/assets/src/ba_data/python/ba/_achievement.py b/assets/src/ba_data/python/ba/_achievement.py
index 006e0c7e..c666e3f3 100644
--- a/assets/src/ba_data/python/ba/_achievement.py
+++ b/assets/src/ba_data/python/ba/_achievement.py
@@ -59,7 +59,7 @@ ACH_LEVEL_NAMES = {
'Uber Football Shutout': 'Uber Football',
'Uber Football Victory': 'Uber Football',
'Uber Onslaught Victory': 'Uber Onslaught',
- 'Uber Runaround Victory': 'Uber Runaround'
+ 'Uber Runaround Victory': 'Uber Runaround',
}
@@ -86,230 +86,425 @@ class AchievementSubsystem:
# 5
achs.append(
- Achievement('In Control', 'achievementInControl', (1, 1, 1), '',
- 5))
+ Achievement('In Control', 'achievementInControl', (1, 1, 1), '', 5)
+ )
# 15
achs.append(
- Achievement('Sharing is Caring', 'achievementSharingIsCaring',
- (1, 1, 1), '', 15))
+ Achievement(
+ 'Sharing is Caring',
+ 'achievementSharingIsCaring',
+ (1, 1, 1),
+ '',
+ 15,
+ )
+ )
# 10
achs.append(
- Achievement('Dual Wielding', 'achievementDualWielding', (1, 1, 1),
- '', 10))
+ Achievement(
+ 'Dual Wielding', 'achievementDualWielding', (1, 1, 1), '', 10
+ )
+ )
# 10
achs.append(
- Achievement('Free Loader', 'achievementFreeLoader', (1, 1, 1), '',
- 10))
+ Achievement(
+ 'Free Loader', 'achievementFreeLoader', (1, 1, 1), '', 10
+ )
+ )
# 20
achs.append(
- Achievement('Team Player', 'achievementTeamPlayer', (1, 1, 1), '',
- 20))
+ Achievement(
+ 'Team Player', 'achievementTeamPlayer', (1, 1, 1), '', 20
+ )
+ )
# 5
achs.append(
- Achievement('Onslaught Training Victory', 'achievementOnslaught',
- (1, 1, 1), 'Default:Onslaught Training', 5))
+ Achievement(
+ 'Onslaught Training Victory',
+ 'achievementOnslaught',
+ (1, 1, 1),
+ 'Default:Onslaught Training',
+ 5,
+ )
+ )
# 5
achs.append(
- Achievement('Off You Go Then', 'achievementOffYouGo',
- (1, 1.1, 1.3), 'Default:Onslaught Training', 5))
+ Achievement(
+ 'Off You Go Then',
+ 'achievementOffYouGo',
+ (1, 1.1, 1.3),
+ 'Default:Onslaught Training',
+ 5,
+ )
+ )
# 10
achs.append(
- Achievement('Boxer',
- 'achievementBoxer', (1, 0.6, 0.6),
- 'Default:Onslaught Training',
- 10,
- hard_mode_only=True))
+ Achievement(
+ 'Boxer',
+ 'achievementBoxer',
+ (1, 0.6, 0.6),
+ 'Default:Onslaught Training',
+ 10,
+ hard_mode_only=True,
+ )
+ )
# 10
achs.append(
- Achievement('Rookie Onslaught Victory', 'achievementOnslaught',
- (0.5, 1.4, 0.6), 'Default:Rookie Onslaught', 10))
+ Achievement(
+ 'Rookie Onslaught Victory',
+ 'achievementOnslaught',
+ (0.5, 1.4, 0.6),
+ 'Default:Rookie Onslaught',
+ 10,
+ )
+ )
# 10
achs.append(
- Achievement('Mine Games', 'achievementMine', (1, 1, 1.4),
- 'Default:Rookie Onslaught', 10))
+ Achievement(
+ 'Mine Games',
+ 'achievementMine',
+ (1, 1, 1.4),
+ 'Default:Rookie Onslaught',
+ 10,
+ )
+ )
# 15
achs.append(
- Achievement('Flawless Victory',
- 'achievementFlawlessVictory', (1, 1, 1),
- 'Default:Rookie Onslaught',
- 15,
- hard_mode_only=True))
+ Achievement(
+ 'Flawless Victory',
+ 'achievementFlawlessVictory',
+ (1, 1, 1),
+ 'Default:Rookie Onslaught',
+ 15,
+ hard_mode_only=True,
+ )
+ )
# 10
achs.append(
- Achievement('Rookie Football Victory',
- 'achievementFootballVictory', (1.0, 1, 0.6),
- 'Default:Rookie Football', 10))
+ Achievement(
+ 'Rookie Football Victory',
+ 'achievementFootballVictory',
+ (1.0, 1, 0.6),
+ 'Default:Rookie Football',
+ 10,
+ )
+ )
# 10
achs.append(
- Achievement('Super Punch', 'achievementSuperPunch', (1, 1, 1.8),
- 'Default:Rookie Football', 10))
+ Achievement(
+ 'Super Punch',
+ 'achievementSuperPunch',
+ (1, 1, 1.8),
+ 'Default:Rookie Football',
+ 10,
+ )
+ )
# 15
achs.append(
- Achievement('Rookie Football Shutout',
- 'achievementFootballShutout', (1, 1, 1),
- 'Default:Rookie Football',
- 15,
- hard_mode_only=True))
+ Achievement(
+ 'Rookie Football Shutout',
+ 'achievementFootballShutout',
+ (1, 1, 1),
+ 'Default:Rookie Football',
+ 15,
+ hard_mode_only=True,
+ )
+ )
# 15
achs.append(
- Achievement('Pro Onslaught Victory', 'achievementOnslaught',
- (0.3, 1, 2.0), 'Default:Pro Onslaught', 15))
+ Achievement(
+ 'Pro Onslaught Victory',
+ 'achievementOnslaught',
+ (0.3, 1, 2.0),
+ 'Default:Pro Onslaught',
+ 15,
+ )
+ )
# 15
achs.append(
- Achievement('Boom Goes the Dynamite', 'achievementTNT',
- (1.4, 1.2, 0.8), 'Default:Pro Onslaught', 15))
+ Achievement(
+ 'Boom Goes the Dynamite',
+ 'achievementTNT',
+ (1.4, 1.2, 0.8),
+ 'Default:Pro Onslaught',
+ 15,
+ )
+ )
# 20
achs.append(
- Achievement('Pro Boxer',
- 'achievementBoxer', (2, 2, 0),
- 'Default:Pro Onslaught',
- 20,
- hard_mode_only=True))
+ Achievement(
+ 'Pro Boxer',
+ 'achievementBoxer',
+ (2, 2, 0),
+ 'Default:Pro Onslaught',
+ 20,
+ hard_mode_only=True,
+ )
+ )
# 15
achs.append(
- Achievement('Pro Football Victory', 'achievementFootballVictory',
- (1.3, 1.3, 2.0), 'Default:Pro Football', 15))
+ Achievement(
+ 'Pro Football Victory',
+ 'achievementFootballVictory',
+ (1.3, 1.3, 2.0),
+ 'Default:Pro Football',
+ 15,
+ )
+ )
# 15
achs.append(
- Achievement('Super Mega Punch', 'achievementSuperPunch',
- (2, 1, 0.6), 'Default:Pro Football', 15))
+ Achievement(
+ 'Super Mega Punch',
+ 'achievementSuperPunch',
+ (2, 1, 0.6),
+ 'Default:Pro Football',
+ 15,
+ )
+ )
# 20
achs.append(
- Achievement('Pro Football Shutout',
- 'achievementFootballShutout', (0.7, 0.7, 2.0),
- 'Default:Pro Football',
- 20,
- hard_mode_only=True))
+ Achievement(
+ 'Pro Football Shutout',
+ 'achievementFootballShutout',
+ (0.7, 0.7, 2.0),
+ 'Default:Pro Football',
+ 20,
+ hard_mode_only=True,
+ )
+ )
# 15
achs.append(
- Achievement('Pro Runaround Victory', 'achievementRunaround',
- (1, 1, 1), 'Default:Pro Runaround', 15))
+ Achievement(
+ 'Pro Runaround Victory',
+ 'achievementRunaround',
+ (1, 1, 1),
+ 'Default:Pro Runaround',
+ 15,
+ )
+ )
# 20
achs.append(
- Achievement('Precision Bombing',
- 'achievementCrossHair', (1, 1, 1.3),
- 'Default:Pro Runaround',
- 20,
- hard_mode_only=True))
+ Achievement(
+ 'Precision Bombing',
+ 'achievementCrossHair',
+ (1, 1, 1.3),
+ 'Default:Pro Runaround',
+ 20,
+ hard_mode_only=True,
+ )
+ )
# 25
achs.append(
- Achievement('The Wall',
- 'achievementWall', (1, 0.7, 0.7),
- 'Default:Pro Runaround',
- 25,
- hard_mode_only=True))
+ Achievement(
+ 'The Wall',
+ 'achievementWall',
+ (1, 0.7, 0.7),
+ 'Default:Pro Runaround',
+ 25,
+ hard_mode_only=True,
+ )
+ )
# 30
achs.append(
- Achievement('Uber Onslaught Victory', 'achievementOnslaught',
- (2, 2, 1), 'Default:Uber Onslaught', 30))
+ Achievement(
+ 'Uber Onslaught Victory',
+ 'achievementOnslaught',
+ (2, 2, 1),
+ 'Default:Uber Onslaught',
+ 30,
+ )
+ )
# 30
achs.append(
- Achievement('Gold Miner',
- 'achievementMine', (2, 1.6, 0.2),
- 'Default:Uber Onslaught',
- 30,
- hard_mode_only=True))
+ Achievement(
+ 'Gold Miner',
+ 'achievementMine',
+ (2, 1.6, 0.2),
+ 'Default:Uber Onslaught',
+ 30,
+ hard_mode_only=True,
+ )
+ )
# 30
achs.append(
- Achievement('TNT Terror',
- 'achievementTNT', (2, 1.8, 0.3),
- 'Default:Uber Onslaught',
- 30,
- hard_mode_only=True))
+ Achievement(
+ 'TNT Terror',
+ 'achievementTNT',
+ (2, 1.8, 0.3),
+ 'Default:Uber Onslaught',
+ 30,
+ hard_mode_only=True,
+ )
+ )
# 30
achs.append(
- Achievement('Uber Football Victory', 'achievementFootballVictory',
- (1.8, 1.4, 0.3), 'Default:Uber Football', 30))
+ Achievement(
+ 'Uber Football Victory',
+ 'achievementFootballVictory',
+ (1.8, 1.4, 0.3),
+ 'Default:Uber Football',
+ 30,
+ )
+ )
# 30
achs.append(
- Achievement('Got the Moves',
- 'achievementGotTheMoves', (2, 1, 0),
- 'Default:Uber Football',
- 30,
- hard_mode_only=True))
+ Achievement(
+ 'Got the Moves',
+ 'achievementGotTheMoves',
+ (2, 1, 0),
+ 'Default:Uber Football',
+ 30,
+ hard_mode_only=True,
+ )
+ )
# 40
achs.append(
- Achievement('Uber Football Shutout',
- 'achievementFootballShutout', (2, 2, 0),
- 'Default:Uber Football',
- 40,
- hard_mode_only=True))
+ Achievement(
+ 'Uber Football Shutout',
+ 'achievementFootballShutout',
+ (2, 2, 0),
+ 'Default:Uber Football',
+ 40,
+ hard_mode_only=True,
+ )
+ )
# 30
achs.append(
- Achievement('Uber Runaround Victory', 'achievementRunaround',
- (1.5, 1.2, 0.2), 'Default:Uber Runaround', 30))
+ Achievement(
+ 'Uber Runaround Victory',
+ 'achievementRunaround',
+ (1.5, 1.2, 0.2),
+ 'Default:Uber Runaround',
+ 30,
+ )
+ )
# 40
achs.append(
- Achievement('The Great Wall',
- 'achievementWall', (2, 1.7, 0.4),
- 'Default:Uber Runaround',
- 40,
- hard_mode_only=True))
+ Achievement(
+ 'The Great Wall',
+ 'achievementWall',
+ (2, 1.7, 0.4),
+ 'Default:Uber Runaround',
+ 40,
+ hard_mode_only=True,
+ )
+ )
# 40
achs.append(
- Achievement('Stayin\' Alive',
- 'achievementStayinAlive', (2, 2, 1),
- 'Default:Uber Runaround',
- 40,
- hard_mode_only=True))
+ Achievement(
+ 'Stayin\' Alive',
+ 'achievementStayinAlive',
+ (2, 2, 1),
+ 'Default:Uber Runaround',
+ 40,
+ hard_mode_only=True,
+ )
+ )
# 20
achs.append(
- Achievement('Last Stand Master',
- 'achievementMedalSmall', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 20,
- hard_mode_only=True))
+ Achievement(
+ 'Last Stand Master',
+ 'achievementMedalSmall',
+ (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 20,
+ hard_mode_only=True,
+ )
+ )
# 40
achs.append(
- Achievement('Last Stand Wizard',
- 'achievementMedalMedium', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 40,
- hard_mode_only=True))
+ Achievement(
+ 'Last Stand Wizard',
+ 'achievementMedalMedium',
+ (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 40,
+ hard_mode_only=True,
+ )
+ )
# 60
achs.append(
- Achievement('Last Stand God',
- 'achievementMedalLarge', (2, 1.5, 0.3),
- 'Default:The Last Stand',
- 60,
- hard_mode_only=True))
+ Achievement(
+ 'Last Stand God',
+ 'achievementMedalLarge',
+ (2, 1.5, 0.3),
+ 'Default:The Last Stand',
+ 60,
+ hard_mode_only=True,
+ )
+ )
# 5
achs.append(
- Achievement('Onslaught Master', 'achievementMedalSmall',
- (0.7, 1, 0.7), 'Challenges:Infinite Onslaught', 5))
+ Achievement(
+ 'Onslaught Master',
+ 'achievementMedalSmall',
+ (0.7, 1, 0.7),
+ 'Challenges:Infinite Onslaught',
+ 5,
+ )
+ )
# 15
achs.append(
- Achievement('Onslaught Wizard', 'achievementMedalMedium',
- (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 15))
+ Achievement(
+ 'Onslaught Wizard',
+ 'achievementMedalMedium',
+ (0.7, 1.0, 0.7),
+ 'Challenges:Infinite Onslaught',
+ 15,
+ )
+ )
# 30
achs.append(
- Achievement('Onslaught God', 'achievementMedalLarge',
- (0.7, 1.0, 0.7), 'Challenges:Infinite Onslaught', 30))
+ Achievement(
+ 'Onslaught God',
+ 'achievementMedalLarge',
+ (0.7, 1.0, 0.7),
+ 'Challenges:Infinite Onslaught',
+ 30,
+ )
+ )
# 5
achs.append(
- Achievement('Runaround Master', 'achievementMedalSmall',
- (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 5))
+ Achievement(
+ 'Runaround Master',
+ 'achievementMedalSmall',
+ (1.0, 1.0, 1.2),
+ 'Challenges:Infinite Runaround',
+ 5,
+ )
+ )
# 15
achs.append(
- Achievement('Runaround Wizard', 'achievementMedalMedium',
- (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 15))
+ Achievement(
+ 'Runaround Wizard',
+ 'achievementMedalMedium',
+ (1.0, 1.0, 1.2),
+ 'Challenges:Infinite Runaround',
+ 15,
+ )
+ )
# 30
achs.append(
- Achievement('Runaround God', 'achievementMedalLarge',
- (1.0, 1.0, 1.2), 'Challenges:Infinite Runaround', 30))
+ Achievement(
+ 'Runaround God',
+ 'achievementMedalLarge',
+ (1.0, 1.0, 1.2),
+ 'Challenges:Infinite Runaround',
+ 30,
+ )
+ )
def award_local_achievement(self, achname: str) -> None:
"""For non-game-based achievements such as controller-connection."""
@@ -321,10 +516,9 @@ class AchievementSubsystem:
_internal.report_achievement(achname)
# And to our account.
- _internal.add_transaction({
- 'type': 'ACHIEVEMENT',
- 'name': achname
- })
+ _internal.add_transaction(
+ {'type': 'ACHIEVEMENT', 'name': achname}
+ )
# Now attempt to show a banner.
self.display_achievement_banner(achname)
@@ -377,17 +571,17 @@ class AchievementSubsystem:
raise ValueError("Invalid achievement name: '" + name + "'")
return achs[0]
- def achievements_for_coop_level(self,
- level_name: str) -> list[Achievement]:
+ def achievements_for_coop_level(self, level_name: str) -> list[Achievement]:
"""Given a level name, return achievements available for it."""
# For the Easy campaign we return achievements for the Default
# campaign too. (want the user to see what achievements are part of the
# level even if they can't unlock them all on easy mode).
return [
- a for a in self.achievements
- if a.level_name in (level_name,
- level_name.replace('Easy', 'Default'))
+ a
+ for a in self.achievements
+ if a.level_name
+ in (level_name, level_name.replace('Easy', 'Default'))
]
def _test(self) -> None:
@@ -443,13 +637,15 @@ class Achievement:
Category: **App Classes**
"""
- def __init__(self,
- name: str,
- icon_name: str,
- icon_color: Sequence[float],
- level_name: str,
- award: int,
- hard_mode_only: bool = False):
+ def __init__(
+ self,
+ name: str,
+ icon_name: str,
+ icon_color: Sequence[float],
+ level_name: str,
+ award: int,
+ hard_mode_only: bool = False,
+ ):
self._name = name
self._icon_name = icon_name
self._icon_color: Sequence[float] = list(icon_color) + [1]
@@ -471,7 +667,8 @@ class Achievement:
def get_icon_texture(self, complete: bool) -> ba.Texture:
"""Return the icon texture to display for this achievement"""
return _ba.gettexture(
- self._icon_name if complete else 'achievementEmpty')
+ self._icon_name if complete else 'achievementEmpty'
+ )
def get_icon_color(self, complete: bool) -> Sequence[float]:
"""Return the color tint for this Achievement's icon."""
@@ -494,6 +691,7 @@ class Achievement:
def announce_completion(self, sound: bool = True) -> None:
"""Kick off an announcement for this achievement's completion."""
from ba._generated.enums import TimeType
+
app = _ba.app
# Even though there are technically achievements when we're not
@@ -512,14 +710,17 @@ class Achievement:
# Need to check last time too; its possible our timer wasn't able to
# clear itself if an activity died and took it down with it.
- if ((app.ach.achievement_display_timer is None
- or _ba.time(TimeType.REAL) - app.ach.last_achievement_display_time
- > 2.0) and _ba.getactivity(doraise=False) is not None):
+ if (
+ app.ach.achievement_display_timer is None
+ or _ba.time(TimeType.REAL) - app.ach.last_achievement_display_time
+ > 2.0
+ ) and _ba.getactivity(doraise=False) is not None:
app.ach.achievement_display_timer = _ba.Timer(
1.0,
_display_next_achievement,
repeat=True,
- timetype=TimeType.BASE)
+ timetype=TimeType.BASE,
+ )
# Show the first immediately.
_display_next_achievement()
@@ -538,27 +739,37 @@ class Achievement:
def display_name(self) -> ba.Lstr:
"""Return a ba.Lstr for this Achievement's name."""
from ba._language import Lstr
+
name: ba.Lstr | str
try:
if self._level_name != '':
from ba._campaign import getcampaign
+
campaignname, campaign_level = self._level_name.split(':')
- name = getcampaign(campaignname).getlevel(
- campaign_level).displayname
+ name = (
+ getcampaign(campaignname)
+ .getlevel(campaign_level)
+ .displayname
+ )
else:
name = ''
except Exception:
name = ''
print_exception()
- return Lstr(resource='achievements.' + self._name + '.name',
- subs=[('${LEVEL}', name)])
+ return Lstr(
+ resource='achievements.' + self._name + '.name',
+ subs=[('${LEVEL}', name)],
+ )
@property
def description(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's brief description."""
from ba._language import Lstr
- if 'description' in _ba.app.lang.get_resource('achievements')[
- self._name]:
+
+ if (
+ 'description'
+ in _ba.app.lang.get_resource('achievements')[self._name]
+ ):
return Lstr(resource='achievements.' + self._name + '.description')
return Lstr(resource='achievements.' + self._name + '.descriptionFull')
@@ -566,12 +777,17 @@ class Achievement:
def description_complete(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's description when completed."""
from ba._language import Lstr
- if 'descriptionComplete' in _ba.app.lang.get_resource('achievements')[
- self._name]:
- return Lstr(resource='achievements.' + self._name +
- '.descriptionComplete')
- return Lstr(resource='achievements.' + self._name +
- '.descriptionFullComplete')
+
+ if (
+ 'descriptionComplete'
+ in _ba.app.lang.get_resource('achievements')[self._name]
+ ):
+ return Lstr(
+ resource='achievements.' + self._name + '.descriptionComplete'
+ )
+ return Lstr(
+ resource='achievements.' + self._name + '.descriptionFullComplete'
+ )
@property
def description_full(self) -> ba.Lstr:
@@ -580,25 +796,44 @@ class Achievement:
return Lstr(
resource='achievements.' + self._name + '.descriptionFull',
- subs=[('${LEVEL}',
- Lstr(translate=('coopLevelNames',
- ACH_LEVEL_NAMES.get(self._name, '?'))))])
+ subs=[
+ (
+ '${LEVEL}',
+ Lstr(
+ translate=(
+ 'coopLevelNames',
+ ACH_LEVEL_NAMES.get(self._name, '?'),
+ )
+ ),
+ )
+ ],
+ )
@property
def description_full_complete(self) -> ba.Lstr:
"""Get a ba.Lstr for the Achievement's full desc. when completed."""
from ba._language import Lstr
+
return Lstr(
resource='achievements.' + self._name + '.descriptionFullComplete',
- subs=[('${LEVEL}',
- Lstr(translate=('coopLevelNames',
- ACH_LEVEL_NAMES.get(self._name, '?'))))])
+ subs=[
+ (
+ '${LEVEL}',
+ Lstr(
+ translate=(
+ 'coopLevelNames',
+ ACH_LEVEL_NAMES.get(self._name, '?'),
+ )
+ ),
+ )
+ ],
+ )
def get_award_ticket_value(self, include_pro_bonus: bool = False) -> int:
"""Get the ticket award value for this achievement."""
- val: int = (_internal.get_v1_account_misc_read_val(
- 'achAward.' + self._name, self._award) *
- _get_ach_mult(include_pro_bonus))
+ val: int = _internal.get_v1_account_misc_read_val(
+ 'achAward.' + self._name, self._award
+ ) * _get_ach_mult(include_pro_bonus)
assert isinstance(val, int)
return val
@@ -606,17 +841,20 @@ class Achievement:
def power_ranking_value(self) -> int:
"""Get the power-ranking award value for this achievement."""
val: int = _internal.get_v1_account_misc_read_val(
- 'achLeaguePoints.' + self._name, self._award)
+ 'achLeaguePoints.' + self._name, self._award
+ )
assert isinstance(val, int)
return val
- def create_display(self,
- x: float,
- y: float,
- delay: float,
- outdelay: float | None = None,
- color: Sequence[float] | None = None,
- style: str = 'post_game') -> list[ba.Actor]:
+ def create_display(
+ self,
+ x: float,
+ y: float,
+ delay: float,
+ outdelay: float | None = None,
+ color: Sequence[float] | None = None,
+ style: str = 'post_game',
+ ) -> list[ba.Actor]:
"""Create a display for the Achievement.
Shows the Achievement icon, name, and description.
@@ -660,7 +898,7 @@ class Achievement:
if isinstance(session, CoopSession):
campaign = session.campaign
assert campaign is not None
- hmo = (self._hard_mode_only and campaign.name == 'Easy')
+ hmo = self._hard_mode_only and campaign.name == 'Easy'
else:
hmo = False
except Exception:
@@ -671,11 +909,9 @@ class Achievement:
if in_game_colors:
objs = []
- out_delay_fin = (delay +
- outdelay) if outdelay is not None else None
+ out_delay_fin = (delay + outdelay) if outdelay is not None else None
if color is not None:
- cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2],
- color[3])
+ cl1 = (2.0 * color[0], 2.0 * color[1], 2.0 * color[2], color[3])
cl2 = color
else:
cl1 = (1.5, 1.5, 2, 1.0)
@@ -686,51 +922,59 @@ class Achievement:
cl2 = (cl2[0], cl2[1], cl2[2], cl2[3] * 0.2)
objs.append(
- Image(self.get_icon_texture(False),
- host_only=True,
- color=cl1,
- position=(x - 25, y + 5),
- attach=attach,
- transition=Image.Transition.FADE_IN,
- transition_delay=delay,
- vr_depth=4,
- transition_out_delay=out_delay_fin,
- scale=(40, 40)).autoretain())
+ Image(
+ self.get_icon_texture(False),
+ host_only=True,
+ color=cl1,
+ position=(x - 25, y + 5),
+ attach=attach,
+ transition=Image.Transition.FADE_IN,
+ transition_delay=delay,
+ vr_depth=4,
+ transition_out_delay=out_delay_fin,
+ scale=(40, 40),
+ ).autoretain()
+ )
txt = self.display_name
txt_s = 0.85
txt_max_w = 300
objs.append(
- Text(txt,
- host_only=True,
- maxwidth=txt_max_w,
- position=(x, y + 2),
- transition=Text.Transition.FADE_IN,
- scale=txt_s,
- flatness=0.6,
- shadow=0.5,
- h_attach=h_attach,
- v_attach=v_attach,
- color=cl2,
- transition_delay=delay + 0.05,
- transition_out_delay=out_delay_fin).autoretain())
+ Text(
+ txt,
+ host_only=True,
+ maxwidth=txt_max_w,
+ position=(x, y + 2),
+ transition=Text.Transition.FADE_IN,
+ scale=txt_s,
+ flatness=0.6,
+ shadow=0.5,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ color=cl2,
+ transition_delay=delay + 0.05,
+ transition_out_delay=out_delay_fin,
+ ).autoretain()
+ )
txt2_s = 0.62
txt2_max_w = 400
objs.append(
- Text(self.description_full
- if in_main_menu else self.description,
- host_only=True,
- maxwidth=txt2_max_w,
- position=(x, y - 14),
- transition=Text.Transition.FADE_IN,
- vr_depth=-5,
- h_attach=h_attach,
- v_attach=v_attach,
- scale=txt2_s,
- flatness=1.0,
- shadow=0.5,
- color=cl2,
- transition_delay=delay + 0.1,
- transition_out_delay=out_delay_fin).autoretain())
+ Text(
+ self.description_full if in_main_menu else self.description,
+ host_only=True,
+ maxwidth=txt2_max_w,
+ position=(x, y - 14),
+ transition=Text.Transition.FADE_IN,
+ vr_depth=-5,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ scale=txt2_s,
+ flatness=1.0,
+ shadow=0.5,
+ color=cl2,
+ transition_delay=delay + 0.1,
+ transition_out_delay=out_delay_fin,
+ ).autoretain()
+ )
if hmo:
txtactor = Text(
@@ -749,39 +993,46 @@ class Achievement:
shadow=0.5,
color=(1, 1, 0.6, 1),
transition_delay=delay + 0.1,
- transition_out_delay=out_delay_fin).autoretain()
+ transition_out_delay=out_delay_fin,
+ ).autoretain()
txtactor.node.rotate = 10
objs.append(txtactor)
# Ticket-award.
award_x = -100
objs.append(
- Text(_ba.charstr(SpecialChar.TICKET),
- host_only=True,
- position=(x + award_x + 33, y + 7),
- transition=Text.Transition.FADE_IN,
- scale=1.5,
- h_attach=h_attach,
- v_attach=v_attach,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=(1, 1, 1, 0.2 if hmo else 0.4),
- transition_delay=delay + 0.05,
- transition_out_delay=out_delay_fin).autoretain())
+ Text(
+ _ba.charstr(SpecialChar.TICKET),
+ host_only=True,
+ position=(x + award_x + 33, y + 7),
+ transition=Text.Transition.FADE_IN,
+ scale=1.5,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=(1, 1, 1, 0.2 if hmo else 0.4),
+ transition_delay=delay + 0.05,
+ transition_out_delay=out_delay_fin,
+ ).autoretain()
+ )
objs.append(
- Text('+' + str(self.get_award_ticket_value()),
- host_only=True,
- position=(x + award_x + 28, y + 16),
- transition=Text.Transition.FADE_IN,
- scale=0.7,
- flatness=1,
- h_attach=h_attach,
- v_attach=v_attach,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=cl2,
- transition_delay=delay + 0.05,
- transition_out_delay=out_delay_fin).autoretain())
+ Text(
+ '+' + str(self.get_award_ticket_value()),
+ host_only=True,
+ position=(x + award_x + 28, y + 16),
+ transition=Text.Transition.FADE_IN,
+ scale=0.7,
+ flatness=1,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=cl2,
+ transition_delay=delay + 0.05,
+ transition_out_delay=out_delay_fin,
+ ).autoretain()
+ )
else:
complete = self.complete
@@ -790,61 +1041,77 @@ class Achievement:
if hmo and not complete:
c_icon = (c_icon[0], c_icon[1], c_icon[2], c_icon[3] * 0.3)
objs.append(
- Image(self.get_icon_texture(complete),
- host_only=True,
- color=c_icon,
- position=(x - 25, y + 5),
- attach=attach,
- vr_depth=4,
- transition=Image.Transition.IN_RIGHT,
- transition_delay=delay,
- transition_out_delay=None,
- scale=(40, 40)).autoretain())
+ Image(
+ self.get_icon_texture(complete),
+ host_only=True,
+ color=c_icon,
+ position=(x - 25, y + 5),
+ attach=attach,
+ vr_depth=4,
+ transition=Image.Transition.IN_RIGHT,
+ transition_delay=delay,
+ transition_out_delay=None,
+ scale=(40, 40),
+ ).autoretain()
+ )
if complete:
objs.append(
- Image(_ba.gettexture('achievementOutline'),
- host_only=True,
- model_transparent=_ba.getmodel('achievementOutline'),
- color=(2, 1.4, 0.4, 1),
- vr_depth=8,
- position=(x - 25, y + 5),
- attach=attach,
- transition=Image.Transition.IN_RIGHT,
- transition_delay=delay,
- transition_out_delay=None,
- scale=(40, 40)).autoretain())
+ Image(
+ _ba.gettexture('achievementOutline'),
+ host_only=True,
+ model_transparent=_ba.getmodel('achievementOutline'),
+ color=(2, 1.4, 0.4, 1),
+ vr_depth=8,
+ position=(x - 25, y + 5),
+ attach=attach,
+ transition=Image.Transition.IN_RIGHT,
+ transition_delay=delay,
+ transition_out_delay=None,
+ scale=(40, 40),
+ ).autoretain()
+ )
else:
if not complete:
award_x = -100
objs.append(
- Text(_ba.charstr(SpecialChar.TICKET),
- host_only=True,
- position=(x + award_x + 33, y + 7),
- transition=Text.Transition.IN_RIGHT,
- scale=1.5,
- h_attach=h_attach,
- v_attach=v_attach,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=(1, 1, 1, 0.4) if complete else
- (1, 1, 1, (0.1 if hmo else 0.2)),
- transition_delay=delay + 0.05,
- transition_out_delay=None).autoretain())
+ Text(
+ _ba.charstr(SpecialChar.TICKET),
+ host_only=True,
+ position=(x + award_x + 33, y + 7),
+ transition=Text.Transition.IN_RIGHT,
+ scale=1.5,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=(1, 1, 1, 0.4)
+ if complete
+ else (1, 1, 1, (0.1 if hmo else 0.2)),
+ transition_delay=delay + 0.05,
+ transition_out_delay=None,
+ ).autoretain()
+ )
objs.append(
- Text('+' + str(self.get_award_ticket_value()),
- host_only=True,
- position=(x + award_x + 28, y + 16),
- transition=Text.Transition.IN_RIGHT,
- scale=0.7,
- flatness=1,
- h_attach=h_attach,
- v_attach=v_attach,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=((0.8, 0.93, 0.8, 1.0) if complete else
- (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
- transition_delay=delay + 0.05,
- transition_out_delay=None).autoretain())
+ Text(
+ '+' + str(self.get_award_ticket_value()),
+ host_only=True,
+ position=(x + award_x + 28, y + 16),
+ transition=Text.Transition.IN_RIGHT,
+ scale=0.7,
+ flatness=1,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=(
+ (0.8, 0.93, 0.8, 1.0)
+ if complete
+ else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))
+ ),
+ transition_delay=delay + 0.05,
+ transition_out_delay=None,
+ ).autoretain()
+ )
# Show 'hard-mode-only' only over incomplete achievements
# when that's the case.
@@ -865,41 +1132,53 @@ class Achievement:
shadow=0.5,
color=(1, 1, 0.6, 1),
transition_delay=delay + 0.05,
- transition_out_delay=None).autoretain()
+ transition_out_delay=None,
+ ).autoretain()
assert txtactor.node
txtactor.node.rotate = 10
objs.append(txtactor)
objs.append(
- Text(self.display_name,
- host_only=True,
- maxwidth=300,
- position=(x, y + 2),
- transition=Text.Transition.IN_RIGHT,
- scale=0.85,
- flatness=0.6,
- h_attach=h_attach,
- v_attach=v_attach,
- color=((0.8, 0.93, 0.8, 1.0) if complete else
- (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
- transition_delay=delay + 0.05,
- transition_out_delay=None).autoretain())
+ Text(
+ self.display_name,
+ host_only=True,
+ maxwidth=300,
+ position=(x, y + 2),
+ transition=Text.Transition.IN_RIGHT,
+ scale=0.85,
+ flatness=0.6,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ color=(
+ (0.8, 0.93, 0.8, 1.0)
+ if complete
+ else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))
+ ),
+ transition_delay=delay + 0.05,
+ transition_out_delay=None,
+ ).autoretain()
+ )
objs.append(
- Text(self.description_complete
- if complete else self.description,
- host_only=True,
- maxwidth=400,
- position=(x, y - 14),
- transition=Text.Transition.IN_RIGHT,
- vr_depth=-5,
- h_attach=h_attach,
- v_attach=v_attach,
- scale=0.62,
- flatness=1.0,
- color=((0.6, 0.6, 0.6, 1.0) if complete else
- (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))),
- transition_delay=delay + 0.1,
- transition_out_delay=None).autoretain())
+ Text(
+ self.description_complete if complete else self.description,
+ host_only=True,
+ maxwidth=400,
+ position=(x, y - 14),
+ transition=Text.Transition.IN_RIGHT,
+ vr_depth=-5,
+ h_attach=h_attach,
+ v_attach=v_attach,
+ scale=0.62,
+ flatness=1.0,
+ color=(
+ (0.6, 0.6, 0.6, 1.0)
+ if complete
+ else (0.6, 0.6, 0.6, (0.2 if hmo else 0.4))
+ ),
+ transition_delay=delay + 0.1,
+ transition_out_delay=None,
+ ).autoretain()
+ )
return objs
def _getconfig(self) -> dict[str, Any]:
@@ -907,15 +1186,17 @@ class Achievement:
Return the sub-dict in settings where this achievement's
state is stored, creating it if need be.
"""
- val: dict[str, Any] = (_ba.app.config.setdefault(
- 'Achievements', {}).setdefault(self._name, {'Complete': False}))
+ val: dict[str, Any] = _ba.app.config.setdefault(
+ 'Achievements', {}
+ ).setdefault(self._name, {'Complete': False})
assert isinstance(val, dict)
return val
def _remove_banner_slot(self) -> None:
assert self._completion_banner_slot is not None
_ba.app.ach.achievement_completion_banner_slots.remove(
- self._completion_banner_slot)
+ self._completion_banner_slot
+ )
self._completion_banner_slot = None
def show_completion_banner(self, sound: bool = True) -> None:
@@ -927,6 +1208,7 @@ class Achievement:
from ba._language import Lstr
from ba._messages import DieMessage
from ba._generated.enums import TimeType, SpecialChar
+
app = _ba.app
app.ach.last_achievement_display_time = _ba.time(TimeType.REAL)
@@ -948,8 +1230,8 @@ class Achievement:
_ba.playsound(_ba.getsound('achievement'), host_only=True)
else:
_ba.timer(
- 0.5,
- lambda: _ba.playsound(_ba.getsound('ding'), host_only=True))
+ 0.5, lambda: _ba.playsound(_ba.getsound('ding'), host_only=True)
+ )
in_time = 0.300
out_time = 3.5
@@ -967,74 +1249,83 @@ class Achievement:
# Use a real-timer in the UI context so the removal runs even
# if our activity/session dies.
with _ba.Context('ui'):
- _ba.timer(in_time + out_time,
- self._remove_banner_slot,
- timetype=TimeType.REAL)
+ _ba.timer(
+ in_time + out_time,
+ self._remove_banner_slot,
+ timetype=TimeType.REAL,
+ )
break
i += 1
assert self._completion_banner_slot is not None
y_offs = 110 * self._completion_banner_slot
objs: list[ba.Actor] = []
- obj = Image(_ba.gettexture('shadow'),
- position=(-30, 30 + y_offs),
- front=True,
- attach=Image.Attach.BOTTOM_CENTER,
- transition=Image.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth - 100,
- transition_delay=in_time,
- transition_out_delay=out_time,
- color=(0.0, 0.1, 0, 1),
- scale=(1000, 300)).autoretain()
+ obj = Image(
+ _ba.gettexture('shadow'),
+ position=(-30, 30 + y_offs),
+ front=True,
+ attach=Image.Attach.BOTTOM_CENTER,
+ transition=Image.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth - 100,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ color=(0.0, 0.1, 0, 1),
+ scale=(1000, 300),
+ ).autoretain()
objs.append(obj)
assert obj.node
obj.node.host_only = True
- obj = Image(_ba.gettexture('light'),
- position=(-180, 60 + y_offs),
- front=True,
- attach=Image.Attach.BOTTOM_CENTER,
- vr_depth=base_vr_depth,
- transition=Image.Transition.IN_BOTTOM,
- transition_delay=in_time,
- transition_out_delay=out_time,
- color=(1.8, 1.8, 1.0, 0.0),
- scale=(40, 300)).autoretain()
+ obj = Image(
+ _ba.gettexture('light'),
+ position=(-180, 60 + y_offs),
+ front=True,
+ attach=Image.Attach.BOTTOM_CENTER,
+ vr_depth=base_vr_depth,
+ transition=Image.Transition.IN_BOTTOM,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ color=(1.8, 1.8, 1.0, 0.0),
+ scale=(40, 300),
+ ).autoretain()
objs.append(obj)
assert obj.node
obj.node.host_only = True
obj.node.premultiplied = True
combine = _ba.newnode('combine', owner=obj.node, attrs={'size': 2})
_gameutils.animate(
- combine, 'input0', {
+ combine,
+ 'input0',
+ {
in_time: 0,
in_time + 0.4: 30,
in_time + 0.5: 40,
in_time + 0.6: 30,
- in_time + 2.0: 0
- })
+ in_time + 2.0: 0,
+ },
+ )
_gameutils.animate(
- combine, 'input1', {
+ combine,
+ 'input1',
+ {
in_time: 0,
in_time + 0.4: 200,
in_time + 0.5: 500,
in_time + 0.6: 200,
- in_time + 2.0: 0
- })
+ in_time + 2.0: 0,
+ },
+ )
combine.connectattr('output', obj.node, 'scale')
- _gameutils.animate(obj.node,
- 'rotate', {
- 0: 0.0,
- 0.35: 360.0
- },
- loop=True)
- obj = Image(self.get_icon_texture(True),
- position=(-180, 60 + y_offs),
- attach=Image.Attach.BOTTOM_CENTER,
- front=True,
- vr_depth=base_vr_depth - 10,
- transition=Image.Transition.IN_BOTTOM,
- transition_delay=in_time,
- transition_out_delay=out_time,
- scale=(100, 100)).autoretain()
+ _gameutils.animate(obj.node, 'rotate', {0: 0.0, 0.35: 360.0}, loop=True)
+ obj = Image(
+ self.get_icon_texture(True),
+ position=(-180, 60 + y_offs),
+ attach=Image.Attach.BOTTOM_CENTER,
+ front=True,
+ vr_depth=base_vr_depth - 10,
+ transition=Image.Transition.IN_BOTTOM,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ scale=(100, 100),
+ ).autoretain()
objs.append(obj)
assert obj.node
obj.node.host_only = True
@@ -1047,7 +1338,7 @@ class Achievement:
in_time + 0.4: 1.5 * color[0],
in_time + 0.5: 6.0 * color[0],
in_time + 0.6: 1.5 * color[0],
- in_time + 2.0: 1.0 * color[0]
+ in_time + 2.0: 1.0 * color[0],
}
_gameutils.animate(combine, 'input0', keys)
keys = {
@@ -1055,7 +1346,7 @@ class Achievement:
in_time + 0.4: 1.5 * color[1],
in_time + 0.5: 6.0 * color[1],
in_time + 0.6: 1.5 * color[1],
- in_time + 2.0: 1.0 * color[1]
+ in_time + 2.0: 1.0 * color[1],
}
_gameutils.animate(combine, 'input1', keys)
keys = {
@@ -1063,21 +1354,23 @@ class Achievement:
in_time + 0.4: 1.5 * color[2],
in_time + 0.5: 6.0 * color[2],
in_time + 0.6: 1.5 * color[2],
- in_time + 2.0: 1.0 * color[2]
+ in_time + 2.0: 1.0 * color[2],
}
_gameutils.animate(combine, 'input2', keys)
combine.connectattr('output', obj.node, 'color')
- obj = Image(_ba.gettexture('achievementOutline'),
- model_transparent=_ba.getmodel('achievementOutline'),
- position=(-180, 60 + y_offs),
- front=True,
- attach=Image.Attach.BOTTOM_CENTER,
- vr_depth=base_vr_depth,
- transition=Image.Transition.IN_BOTTOM,
- transition_delay=in_time,
- transition_out_delay=out_time,
- scale=(100, 100)).autoretain()
+ obj = Image(
+ _ba.gettexture('achievementOutline'),
+ model_transparent=_ba.getmodel('achievementOutline'),
+ position=(-180, 60 + y_offs),
+ front=True,
+ attach=Image.Attach.BOTTOM_CENTER,
+ vr_depth=base_vr_depth,
+ transition=Image.Transition.IN_BOTTOM,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ scale=(100, 100),
+ ).autoretain()
assert obj.node
obj.node.host_only = True
@@ -1089,7 +1382,7 @@ class Achievement:
in_time + 0.4: 1.5 * color[0],
in_time + 0.5: 6.0 * color[0],
in_time + 0.6: 1.5 * color[0],
- in_time + 2.0: 1.0 * color[0]
+ in_time + 2.0: 1.0 * color[0],
}
_gameutils.animate(combine, 'input0', keys)
keys = {
@@ -1097,7 +1390,7 @@ class Achievement:
in_time + 0.4: 1.5 * color[1],
in_time + 0.5: 6.0 * color[1],
in_time + 0.6: 1.5 * color[1],
- in_time + 2.0: 1.0 * color[1]
+ in_time + 2.0: 1.0 * color[1],
}
_gameutils.animate(combine, 'input1', keys)
keys = {
@@ -1105,116 +1398,130 @@ class Achievement:
in_time + 0.4: 1.5 * color[2],
in_time + 0.5: 6.0 * color[2],
in_time + 0.6: 1.5 * color[2],
- in_time + 2.0: 1.0 * color[2]
+ in_time + 2.0: 1.0 * color[2],
}
_gameutils.animate(combine, 'input2', keys)
combine.connectattr('output', obj.node, 'color')
objs.append(obj)
- objt = Text(Lstr(value='${A}:',
- subs=[('${A}', Lstr(resource='achievementText'))]),
- position=(-120, 91 + y_offs),
- front=True,
- v_attach=Text.VAttach.BOTTOM,
- vr_depth=base_vr_depth - 10,
- transition=Text.Transition.IN_BOTTOM,
- flatness=0.5,
- transition_delay=in_time,
- transition_out_delay=out_time,
- color=(1, 1, 1, 0.8),
- scale=0.65).autoretain()
+ objt = Text(
+ Lstr(
+ value='${A}:', subs=[('${A}', Lstr(resource='achievementText'))]
+ ),
+ position=(-120, 91 + y_offs),
+ front=True,
+ v_attach=Text.VAttach.BOTTOM,
+ vr_depth=base_vr_depth - 10,
+ transition=Text.Transition.IN_BOTTOM,
+ flatness=0.5,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ color=(1, 1, 1, 0.8),
+ scale=0.65,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
- objt = Text(self.display_name,
- position=(-120, 50 + y_offs),
- front=True,
- v_attach=Text.VAttach.BOTTOM,
- transition=Text.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth,
- flatness=0.5,
- transition_delay=in_time,
- transition_out_delay=out_time,
- flash=True,
- color=(1, 0.8, 0, 1.0),
- scale=1.5).autoretain()
+ objt = Text(
+ self.display_name,
+ position=(-120, 50 + y_offs),
+ front=True,
+ v_attach=Text.VAttach.BOTTOM,
+ transition=Text.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth,
+ flatness=0.5,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ flash=True,
+ color=(1, 0.8, 0, 1.0),
+ scale=1.5,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
- objt = Text(_ba.charstr(SpecialChar.TICKET),
- position=(-120 - 170 + 5, 75 + y_offs - 20),
- front=True,
- v_attach=Text.VAttach.BOTTOM,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth,
- transition_delay=in_time,
- transition_out_delay=out_time,
- flash=True,
- color=(0.5, 0.5, 0.5, 1),
- scale=3.0).autoretain()
+ objt = Text(
+ _ba.charstr(SpecialChar.TICKET),
+ position=(-120 - 170 + 5, 75 + y_offs - 20),
+ front=True,
+ v_attach=Text.VAttach.BOTTOM,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ flash=True,
+ color=(0.5, 0.5, 0.5, 1),
+ scale=3.0,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
- objt = Text('+' + str(self.get_award_ticket_value()),
- position=(-120 - 180 + 5, 80 + y_offs - 20),
- v_attach=Text.VAttach.BOTTOM,
- front=True,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth,
- flatness=0.5,
- shadow=1.0,
- transition_delay=in_time,
- transition_out_delay=out_time,
- flash=True,
- color=(0, 1, 0, 1),
- scale=1.5).autoretain()
+ objt = Text(
+ '+' + str(self.get_award_ticket_value()),
+ position=(-120 - 180 + 5, 80 + y_offs - 20),
+ v_attach=Text.VAttach.BOTTOM,
+ front=True,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth,
+ flatness=0.5,
+ shadow=1.0,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ flash=True,
+ color=(0, 1, 0, 1),
+ scale=1.5,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
# Add the 'x 2' if we've got pro.
if app.accounts_v1.have_pro():
- objt = Text('x 2',
- position=(-120 - 180 + 45, 80 + y_offs - 50),
- v_attach=Text.VAttach.BOTTOM,
- front=True,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth,
- flatness=0.5,
- shadow=1.0,
- transition_delay=in_time,
- transition_out_delay=out_time,
- flash=True,
- color=(0.4, 0, 1, 1),
- scale=0.9).autoretain()
+ objt = Text(
+ 'x 2',
+ position=(-120 - 180 + 45, 80 + y_offs - 50),
+ v_attach=Text.VAttach.BOTTOM,
+ front=True,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth,
+ flatness=0.5,
+ shadow=1.0,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ flash=True,
+ color=(0.4, 0, 1, 1),
+ scale=0.9,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
- objt = Text(self.description_complete,
- position=(-120, 30 + y_offs),
- front=True,
- v_attach=Text.VAttach.BOTTOM,
- transition=Text.Transition.IN_BOTTOM,
- vr_depth=base_vr_depth - 10,
- flatness=0.5,
- transition_delay=in_time,
- transition_out_delay=out_time,
- color=(1.0, 0.7, 0.5, 1.0),
- scale=0.8).autoretain()
+ objt = Text(
+ self.description_complete,
+ position=(-120, 30 + y_offs),
+ front=True,
+ v_attach=Text.VAttach.BOTTOM,
+ transition=Text.Transition.IN_BOTTOM,
+ vr_depth=base_vr_depth - 10,
+ flatness=0.5,
+ transition_delay=in_time,
+ transition_out_delay=out_time,
+ color=(1.0, 0.7, 0.5, 1.0),
+ scale=0.8,
+ ).autoretain()
objs.append(objt)
assert objt.node
objt.node.host_only = True
for actor in objs:
- _ba.timer(out_time + 1.000,
- WeakCall(actor.handlemessage, DieMessage()))
+ _ba.timer(
+ out_time + 1.000, WeakCall(actor.handlemessage, DieMessage())
+ )
diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py
index 68d740d6..69482643 100644
--- a/assets/src/ba_data/python/ba/_activity.py
+++ b/assets/src/ba_data/python/ba/_activity.py
@@ -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:
diff --git a/assets/src/ba_data/python/ba/_activitytypes.py b/assets/src/ba_data/python/ba/_activitytypes.py
index 78eb6064..5fcf5316 100644
--- a/assets/src/ba_data/python/ba/_activitytypes.py
+++ b/assets/src/ba_data/python/ba/_activitytypes.py
@@ -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,
+ )
diff --git a/assets/src/ba_data/python/ba/_ads.py b/assets/src/ba_data/python/ba/_ads.py
index 6cf7730f..243b29d1 100644
--- a/assets/src/ba_data/python/ba/_ads.py
+++ b/assets/src/ba_data/python/ba/_ads.py
@@ -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.
diff --git a/assets/src/ba_data/python/ba/_analytics.py b/assets/src/ba_data/python/ba/_analytics.py
index 27c89964..a3c25e81 100644
--- a/assets/src/ba_data/python/ba/_analytics.py
+++ b/assets/src/ba_data/python/ba/_analytics.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_app.py b/assets/src/ba_data/python/ba/_app.py
index b976517b..46ac8e6f 100644
--- a/assets/src/ba_data/python/ba/_app.py
+++ b/assets/src/ba_data/python/ba/_app.py
@@ -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/', '')
diff --git a/assets/src/ba_data/python/ba/_appconfig.py b/assets/src/ba_data/python/ba/_appconfig.py
index 4fd28deb..b4339718 100644
--- a/assets/src/ba_data/python/ba/_appconfig.py
+++ b/assets/src/ba_data/python/ba/_appconfig.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_appdelegate.py b/assets/src/ba_data/python/ba/_appdelegate.py
index 9f967aeb..b21ec101 100644
--- a/assets/src/ba_data/python/ba/_appdelegate.py
+++ b/assets/src/ba_data/python/ba/_appdelegate.py
@@ -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"
+ )
diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py
index d7c2cfe9..148cef9c 100644
--- a/assets/src/ba_data/python/ba/_apputils.py
+++ b/assets/src/ba_data/python/ba/_apputils.py
@@ -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
+ )
diff --git a/assets/src/ba_data/python/ba/_assetmanager.py b/assets/src/ba_data/python/ba/_assetmanager.py
index e57c921d..2e48619e 100644
--- a/assets/src/ba_data/python/ba/_assetmanager.py
+++ b/assets/src/ba_data/python/ba/_assetmanager.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_asyncio.py b/assets/src/ba_data/python/ba/_asyncio.py
index 3639d2af..5dbf8edc 100644
--- a/assets/src/ba_data/python/ba/_asyncio.py
+++ b/assets/src/ba_data/python/ba/_asyncio.py
@@ -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):
diff --git a/assets/src/ba_data/python/ba/_benchmark.py b/assets/src/ba_data/python/ba/_benchmark.py
index 53ea3f5d..f8821653 100644
--- a/assets/src/ba_data/python/ba/_benchmark.py
+++ b/assets/src/ba_data/python/ba/_benchmark.py
@@ -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
+ )
diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py
index f53ebb88..6c84aacb 100644
--- a/assets/src/ba_data/python/ba/_bootstrap.py
+++ b/assets/src/ba_data/python/ba/_bootstrap.py
@@ -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)
diff --git a/assets/src/ba_data/python/ba/_campaign.py b/assets/src/ba_data/python/ba/_campaign.py
index 4b79c66a..b0242ffa 100644
--- a/assets/src/ba_data/python/ba/_campaign.py
+++ b/assets/src/ba_data/python/ba/_campaign.py
@@ -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',
+ ),
],
- ))
+ )
+ )
diff --git a/assets/src/ba_data/python/ba/_cloud.py b/assets/src/ba_data/python/ba/_cloud.py
index ebbd99a4..029ed482 100644
--- a/assets/src/ba_data/python/ba/_cloud.py
+++ b/assets/src/ba_data/python/ba/_cloud.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py
index 6e380d84..0df8beb2 100644
--- a/assets/src/ba_data/python/ba/_coopgame.py
+++ b/assets/src/ba_data/python/ba/_coopgame.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_coopsession.py b/assets/src/ba_data/python/ba/_coopsession.py
index b04b5915..36f56327 100644
--- a/assets/src/ba_data/python/ba/_coopsession.py
+++ b/assets/src/ba_data/python/ba/_coopsession.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_dependency.py b/assets/src/ba_data/python/ba/_dependency.py
index d3a6d4e2..a8cb4624 100644
--- a/assets/src/ba_data/python/ba/_dependency.py
+++ b/assets/src/ba_data/python/ba/_dependency.py
@@ -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:
diff --git a/assets/src/ba_data/python/ba/_dualteamsession.py b/assets/src/ba_data/python/ba/_dualteamsession.py
index 0b7a327b..6870fede 100644
--- a/assets/src/ba_data/python/ba/_dualteamsession.py
+++ b/assets/src/ba_data/python/ba/_dualteamsession.py
@@ -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}
+ )
+ )
diff --git a/assets/src/ba_data/python/ba/_error.py b/assets/src/ba_data/python/ba/_error.py
index 5e6aba42..fb8803b6 100644
--- a/assets/src/ba_data/python/ba/_error.py
+++ b/assets/src/ba_data/python/ba/_error.py
@@ -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:
diff --git a/assets/src/ba_data/python/ba/_freeforallsession.py b/assets/src/ba_data/python/ba/_freeforallsession.py
index a33c8278..4be431dc 100644
--- a/assets/src/ba_data/python/ba/_freeforallsession.py
+++ b/assets/src/ba_data/python/ba/_freeforallsession.py
@@ -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},
+ )
+ )
diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py
index 46dcdf09..697c14ad 100644
--- a/assets/src/ba_data/python/ba/_gameactivity.py
+++ b/assets/src/ba_data/python/ba/_gameactivity.py
@@ -37,6 +37,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
Category: **Gameplay Classes**
"""
+
# pylint: disable=too-many-public-methods
# Tips to be presented to the user at the start of the game.
@@ -89,14 +90,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""
delegate = _ba.app.delegate
assert delegate is not None
- delegate.create_default_game_settings_ui(cls, sessiontype, settings,
- completion_call)
+ delegate.create_default_game_settings_ui(
+ cls, sessiontype, settings, completion_call
+ )
@classmethod
def getscoreconfig(cls) -> ba.ScoreConfig:
"""Return info about game scoring setup; can be overridden by games."""
- return (cls.scoreconfig
- if cls.scoreconfig is not None else ScoreConfig())
+ return cls.scoreconfig if cls.scoreconfig is not None else ScoreConfig()
@classmethod
def getname(cls) -> str:
@@ -119,11 +120,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
# their own and should not rely on hard-coded settings names.
if settings is not None:
if 'Solo Mode' in settings and settings['Solo Mode']:
- name = Lstr(resource='soloNameFilterText',
- subs=[('${NAME}', name)])
+ name = Lstr(
+ resource='soloNameFilterText', subs=[('${NAME}', name)]
+ )
if 'Epic Mode' in settings and settings['Epic Mode']:
- name = Lstr(resource='epicNameFilterText',
- subs=[('${NAME}', name)])
+ name = Lstr(
+ resource='epicNameFilterText', subs=[('${NAME}', name)]
+ )
return name
@@ -145,7 +148,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
@classmethod
def get_description_display_string(
- cls, sessiontype: type[ba.Session]) -> ba.Lstr:
+ cls, sessiontype: type[ba.Session]
+ ) -> ba.Lstr:
"""Return a translated version of get_description().
Sub-classes should override get_description(); not this.
@@ -155,7 +159,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
@classmethod
def get_available_settings(
- cls, sessiontype: type[ba.Session]) -> list[ba.Setting]:
+ cls, sessiontype: type[ba.Session]
+ ) -> list[ba.Setting]:
"""Return a list of settings relevant to this game type when
running under the provided session type.
"""
@@ -184,19 +189,33 @@ class GameActivity(Activity[PlayerType, TeamType]):
# In newer configs, map is in settings; it used to be in the
# config root.
if 'map' in config['settings']:
- sval = Lstr(value='${NAME} @ ${MAP}',
- subs=[('${NAME}', name),
- ('${MAP}',
- _map.get_map_display_string(
- _map.get_filtered_map_name(
- config['settings']['map'])))])
+ sval = Lstr(
+ value='${NAME} @ ${MAP}',
+ subs=[
+ ('${NAME}', name),
+ (
+ '${MAP}',
+ _map.get_map_display_string(
+ _map.get_filtered_map_name(
+ config['settings']['map']
+ )
+ ),
+ ),
+ ],
+ )
elif 'map' in config:
- sval = Lstr(value='${NAME} @ ${MAP}',
- subs=[('${NAME}', name),
- ('${MAP}',
- _map.get_map_display_string(
- _map.get_filtered_map_name(config['map'])))
- ])
+ sval = Lstr(
+ value='${NAME} @ ${MAP}',
+ subs=[
+ ('${NAME}', name),
+ (
+ '${MAP}',
+ _map.get_map_display_string(
+ _map.get_filtered_map_name(config['map'])
+ ),
+ ),
+ ],
+ )
else:
print('invalid game config - expected map entry under settings')
sval = Lstr(value='???')
@@ -242,11 +261,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
self._is_waiting_for_continue = False
self._continue_cost = _internal.get_v1_account_misc_read_val(
- 'continueStartCost', 25)
+ 'continueStartCost', 25
+ )
self._continue_cost_mult = _internal.get_v1_account_misc_read_val(
- 'continuesMult', 2)
+ 'continuesMult', 2
+ )
self._continue_cost_offset = _internal.get_v1_account_misc_read_val(
- 'continuesOffset', 0)
+ 'continuesOffset', 0
+ )
@property
def map(self) -> ba.Map:
@@ -273,11 +295,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
# FIXME: Should clean this up.
try:
from ba._coopsession import CoopSession
+
if isinstance(self.session, CoopSession):
campaign = self.session.campaign
assert campaign is not None
return campaign.getlevel(
- self.session.campaign_level_name).displayname
+ self.session.campaign_level_name
+ ).displayname
except Exception:
print_error('error getting campaign level name')
return self.get_instance_display_string()
@@ -348,6 +372,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
if music is not None:
from ba import _music
+
_music.setmusic(music)
def on_continue(self) -> None:
@@ -365,14 +390,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
if do_continue:
_ba.playsound(_ba.getsound('shieldUp'))
_ba.playsound(_ba.getsound('cashRegister'))
- _internal.add_transaction({
- 'type': 'CONTINUE',
- 'cost': self._continue_cost
- })
+ _internal.add_transaction(
+ {'type': 'CONTINUE', 'cost': self._continue_cost}
+ )
_internal.run_transactions()
self._continue_cost = (
- self._continue_cost * self._continue_cost_mult +
- self._continue_cost_offset)
+ self._continue_cost * self._continue_cost_mult
+ + self._continue_cost_offset
+ )
self.on_continue()
else:
self.end_game()
@@ -392,8 +417,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
from ba._generated.enums import TimeType
try:
- if _internal.get_v1_account_misc_read_val('enableContinues',
- False):
+ if _internal.get_v1_account_misc_read_val('enableContinues', False):
session = self.session
# We only support continuing in non-tournament games.
@@ -409,8 +433,10 @@ class GameActivity(Activity[PlayerType, TeamType]):
# Only attempt this if we're not currently paused
# and there appears to be no UI.
- if (not gnode.paused
- and not _ba.app.ui.has_main_menu_window()):
+ if (
+ not gnode.paused
+ and not _ba.app.ui.has_main_menu_window()
+ ):
self._is_waiting_for_continue = True
with _ba.Context('ui'):
_ba.timer(
@@ -419,10 +445,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
self,
self._continue_cost,
continue_call=WeakCall(
- self._continue_choice, True),
+ self._continue_choice, True
+ ),
cancel_call=WeakCall(
- self._continue_choice, False)),
- timetype=TimeType.REAL)
+ self._continue_choice, False
+ ),
+ ),
+ timetype=TimeType.REAL,
+ )
return
except Exception:
@@ -432,6 +462,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
def on_begin(self) -> None:
from ba._analytics import game_begin_analytics
+
super().on_begin()
game_begin_analytics()
@@ -459,20 +490,22 @@ class GameActivity(Activity[PlayerType, TeamType]):
_internal.tournament_query(
args={
'tournamentIDs': [tournament_id],
- 'source': 'in-game time remaining query'
+ 'source': 'in-game time remaining query',
},
callback=WeakCall(self._on_tournament_query_response),
)
- def _on_tournament_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_tournament_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
if data is not None:
data_t = data['t'] # This used to be the whole payload.
# Keep our cached tourney info up to date
_ba.app.accounts_v1.cache_tournament_info(data_t)
self._setup_tournament_time_limit(
- max(5, data_t[0]['timeRemaining']))
+ max(5, data_t[0]['timeRemaining'])
+ )
def on_player_join(self, player: PlayerType) -> None:
super().on_player_join(player)
@@ -489,9 +522,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
killer = msg.getkillerplayer(self.playertype)
# Inform our stats of the demise.
- self.stats.player_was_killed(player,
- killed=msg.killed,
- killer=killer)
+ self.stats.player_was_killed(
+ player, killed=msg.killed, killer=killer
+ )
# Award the killer points if he's on a different team.
# FIXME: This should not be linked to Spaz actors.
@@ -500,12 +533,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
assert isinstance(killer.actor, Spaz)
pts, importance = killer.actor.get_death_points(msg.how)
if not self.has_ended():
- self.stats.player_scored(killer,
- pts,
- kill=True,
- victim_player=player,
- importance=importance,
- showpoints=self.show_kill_points)
+ self.stats.player_scored(
+ killer,
+ pts,
+ kill=True,
+ victim_player=player,
+ importance=importance,
+ showpoints=self.show_kill_points,
+ )
else:
return super().handlemessage(msg)
return None
@@ -520,6 +555,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
from ba._freeforallsession import FreeForAllSession
from ba._gameutils import animate
from ba._nodeactor import NodeActor
+
sb_name = self.get_instance_scoreboard_display_string()
# The description can be either a string or a sequence with args
@@ -533,43 +569,51 @@ class GameActivity(Activity[PlayerType, TeamType]):
if not isinstance(sb_desc_l[0], str):
raise TypeError('Invalid format for instance description.')
- is_empty = (sb_desc_l[0] == '')
+ is_empty = sb_desc_l[0] == ''
subs = []
for i in range(len(sb_desc_l) - 1):
subs.append(('${ARG' + str(i + 1) + '}', str(sb_desc_l[i + 1])))
- translation = Lstr(translate=('gameDescriptions', sb_desc_l[0]),
- subs=subs)
+ translation = Lstr(
+ translate=('gameDescriptions', sb_desc_l[0]), subs=subs
+ )
sb_desc = translation
vrmode = _ba.app.vr_mode
yval = -34 if is_empty else -20
yval -= 16
- sbpos = ((15, yval) if isinstance(self.session, FreeForAllSession) else
- (15, yval))
+ sbpos = (
+ (15, yval)
+ if isinstance(self.session, FreeForAllSession)
+ else (15, yval)
+ )
self._game_scoreboard_name_text = NodeActor(
- _ba.newnode('text',
- attrs={
- 'text': sb_name,
- 'maxwidth': 300,
- 'position': sbpos,
- 'h_attach': 'left',
- 'vr_depth': 10,
- 'v_attach': 'top',
- 'v_align': 'bottom',
- 'color': (1.0, 1.0, 1.0, 1.0),
- 'shadow': 1.0 if vrmode else 0.6,
- 'flatness': 1.0 if vrmode else 0.5,
- 'scale': 1.1
- }))
+ _ba.newnode(
+ 'text',
+ attrs={
+ 'text': sb_name,
+ 'maxwidth': 300,
+ 'position': sbpos,
+ 'h_attach': 'left',
+ 'vr_depth': 10,
+ 'v_attach': 'top',
+ 'v_align': 'bottom',
+ 'color': (1.0, 1.0, 1.0, 1.0),
+ 'shadow': 1.0 if vrmode else 0.6,
+ 'flatness': 1.0 if vrmode else 0.5,
+ 'scale': 1.1,
+ },
+ )
+ )
assert self._game_scoreboard_name_text.node
- animate(self._game_scoreboard_name_text.node, 'opacity', {
- 0: 0.0,
- 1.0: 1.0
- })
+ animate(
+ self._game_scoreboard_name_text.node, 'opacity', {0: 0.0, 1.0: 1.0}
+ )
- descpos = (((17, -44 +
- 10) if isinstance(self.session, FreeForAllSession) else
- (17, -44 + 10)))
+ descpos = (
+ (17, -44 + 10)
+ if isinstance(self.session, FreeForAllSession)
+ else (17, -44 + 10)
+ )
self._game_scoreboard_description_text = NodeActor(
_ba.newnode(
'text',
@@ -583,28 +627,34 @@ class GameActivity(Activity[PlayerType, TeamType]):
'v_align': 'top',
'shadow': 1.0 if vrmode else 0.7,
'flatness': 1.0 if vrmode else 0.8,
- 'color': (1, 1, 1, 1) if vrmode else (0.9, 0.9, 0.9, 1.0)
- }))
+ 'color': (1, 1, 1, 1) if vrmode else (0.9, 0.9, 0.9, 1.0),
+ },
+ )
+ )
assert self._game_scoreboard_description_text.node
- animate(self._game_scoreboard_description_text.node, 'opacity', {
- 0: 0.0,
- 1.0: 1.0
- })
+ animate(
+ self._game_scoreboard_description_text.node,
+ 'opacity',
+ {0: 0.0, 1.0: 1.0},
+ )
def _show_info(self) -> None:
"""Show the game description."""
from ba._gameutils import animate
from bastd.actor.zoomtext import ZoomText
+
name = self.get_instance_display_string()
- ZoomText(name,
- maxwidth=800,
- lifespan=2.5,
- jitter=2.0,
- position=(0, 180),
- flash=False,
- color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25),
- trailcolor=(0.15, 0.05, 1.0, 0.0)).autoretain()
+ ZoomText(
+ name,
+ maxwidth=800,
+ lifespan=2.5,
+ jitter=2.0,
+ position=(0, 180),
+ flash=False,
+ color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25),
+ trailcolor=(0.15, 0.05, 1.0, 0.0),
+ ).autoretain()
_ba.timer(0.2, Call(_ba.playsound, _ba.getsound('gong')))
# The description can be either a string or a sequence with args
@@ -620,36 +670,36 @@ class GameActivity(Activity[PlayerType, TeamType]):
subs = []
for i in range(len(desc_l) - 1):
subs.append(('${ARG' + str(i + 1) + '}', str(desc_l[i + 1])))
- translation = Lstr(translate=('gameDescriptions', desc_l[0]),
- subs=subs)
+ translation = Lstr(translate=('gameDescriptions', desc_l[0]), subs=subs)
# Do some standard filters (epic mode, etc).
if self.settings_raw.get('Epic Mode', False):
- translation = Lstr(resource='epicDescriptionFilterText',
- subs=[('${DESCRIPTION}', translation)])
+ translation = Lstr(
+ resource='epicDescriptionFilterText',
+ subs=[('${DESCRIPTION}', translation)],
+ )
vrmode = _ba.app.vr_mode
- dnode = _ba.newnode('text',
- attrs={
- 'v_attach': 'center',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'color': (1, 1, 1, 1),
- 'shadow': 1.0 if vrmode else 0.5,
- 'flatness': 1.0 if vrmode else 0.5,
- 'vr_depth': -30,
- 'position': (0, 80),
- 'scale': 1.2,
- 'maxwidth': 700,
- 'text': translation
- })
- cnode = _ba.newnode('combine',
- owner=dnode,
- attrs={
- 'input0': 1.0,
- 'input1': 1.0,
- 'input2': 1.0,
- 'size': 4
- })
+ dnode = _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'center',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'color': (1, 1, 1, 1),
+ 'shadow': 1.0 if vrmode else 0.5,
+ 'flatness': 1.0 if vrmode else 0.5,
+ 'vr_depth': -30,
+ 'position': (0, 80),
+ 'scale': 1.2,
+ 'maxwidth': 700,
+ 'text': translation,
+ },
+ )
+ cnode = _ba.newnode(
+ 'combine',
+ owner=dnode,
+ attrs={'input0': 1.0, 'input1': 1.0, 'input2': 1.0, 'size': 4},
+ )
cnode.connectattr('output', dnode, 'color')
keys = {0.5: 0, 1.0: 1.0, 2.5: 1.0, 4.0: 0.0}
animate(cnode, 'input3', keys)
@@ -663,8 +713,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
# If there's any tips left on the list, display one.
if self.tips:
tip = self.tips.pop(random.randrange(len(self.tips)))
- tip_title = Lstr(value='${A}:',
- subs=[('${A}', Lstr(resource='tipText'))])
+ tip_title = Lstr(
+ value='${A}:', subs=[('${A}', Lstr(resource='tipText'))]
+ )
icon: ba.Texture | None = None
sound: ba.Sound | None = None
if isinstance(tip, GameTip):
@@ -674,80 +725,84 @@ class GameActivity(Activity[PlayerType, TeamType]):
assert isinstance(tip, str)
# Do a few substitutions.
- tip_lstr = Lstr(translate=('tips', tip),
- subs=[('${PICKUP}',
- _ba.charstr(SpecialChar.TOP_BUTTON))])
+ tip_lstr = Lstr(
+ translate=('tips', tip),
+ subs=[('${PICKUP}', _ba.charstr(SpecialChar.TOP_BUTTON))],
+ )
base_position = (75, 50)
tip_scale = 0.8
tip_title_scale = 1.2
vrmode = _ba.app.vr_mode
t_offs = -350.0
- tnode = _ba.newnode('text',
- attrs={
- 'text': tip_lstr,
- 'scale': tip_scale,
- 'maxwidth': 900,
- 'position': (base_position[0] + t_offs,
- base_position[1]),
- 'h_align': 'left',
- 'vr_depth': 300,
- 'shadow': 1.0 if vrmode else 0.5,
- 'flatness': 1.0 if vrmode else 0.5,
- 'v_align': 'center',
- 'v_attach': 'bottom'
- })
- t2pos = (base_position[0] + t_offs - (20 if icon is None else 82),
- base_position[1] + 2)
- t2node = _ba.newnode('text',
- owner=tnode,
- attrs={
- 'text': tip_title,
- 'scale': tip_title_scale,
- 'position': t2pos,
- 'h_align': 'right',
- 'vr_depth': 300,
- 'shadow': 1.0 if vrmode else 0.5,
- 'flatness': 1.0 if vrmode else 0.5,
- 'maxwidth': 140,
- 'v_align': 'center',
- 'v_attach': 'bottom'
- })
+ tnode = _ba.newnode(
+ 'text',
+ attrs={
+ 'text': tip_lstr,
+ 'scale': tip_scale,
+ 'maxwidth': 900,
+ 'position': (base_position[0] + t_offs, base_position[1]),
+ 'h_align': 'left',
+ 'vr_depth': 300,
+ 'shadow': 1.0 if vrmode else 0.5,
+ 'flatness': 1.0 if vrmode else 0.5,
+ 'v_align': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
+ t2pos = (
+ base_position[0] + t_offs - (20 if icon is None else 82),
+ base_position[1] + 2,
+ )
+ t2node = _ba.newnode(
+ 'text',
+ owner=tnode,
+ attrs={
+ 'text': tip_title,
+ 'scale': tip_title_scale,
+ 'position': t2pos,
+ 'h_align': 'right',
+ 'vr_depth': 300,
+ 'shadow': 1.0 if vrmode else 0.5,
+ 'flatness': 1.0 if vrmode else 0.5,
+ 'maxwidth': 140,
+ 'v_align': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
if icon is not None:
ipos = (base_position[0] + t_offs - 40, base_position[1] + 1)
- img = _ba.newnode('image',
- attrs={
- 'texture': icon,
- 'position': ipos,
- 'scale': (50, 50),
- 'opacity': 1.0,
- 'vr_depth': 315,
- 'color': (1, 1, 1),
- 'absolute_scale': True,
- 'attach': 'bottomCenter'
- })
+ img = _ba.newnode(
+ 'image',
+ attrs={
+ 'texture': icon,
+ 'position': ipos,
+ 'scale': (50, 50),
+ 'opacity': 1.0,
+ 'vr_depth': 315,
+ 'color': (1, 1, 1),
+ 'absolute_scale': True,
+ 'attach': 'bottomCenter',
+ },
+ )
animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
_ba.timer(5.0, img.delete)
if sound is not None:
_ba.playsound(sound)
- combine = _ba.newnode('combine',
- owner=tnode,
- attrs={
- 'input0': 1.0,
- 'input1': 0.8,
- 'input2': 1.0,
- 'size': 4
- })
+ combine = _ba.newnode(
+ 'combine',
+ owner=tnode,
+ attrs={'input0': 1.0, 'input1': 0.8, 'input2': 1.0, 'size': 4},
+ )
combine.connectattr('output', tnode, 'color')
combine.connectattr('output', t2node, 'color')
animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
_ba.timer(5.0, tnode.delete)
- 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:
from ba._gameresults import GameResults
# If results is a standard team-game-results, associate it with us
@@ -757,14 +812,18 @@ class GameActivity(Activity[PlayerType, TeamType]):
# If we had a standard time-limit that had not expired, stop it so
# it doesnt tick annoyingly.
- if (self._standard_time_limit_time is not None
- and self._standard_time_limit_time > 0):
+ if (
+ self._standard_time_limit_time is not None
+ and self._standard_time_limit_time > 0
+ ):
self._standard_time_limit_timer = None
self._standard_time_limit_text = None
# Ditto with tournament time limits.
- if (self._tournament_time_limit is not None
- and self._tournament_time_limit > 0):
+ if (
+ self._tournament_time_limit is not None
+ and self._tournament_time_limit > 0
+ ):
self._tournament_time_limit_timer = None
self._tournament_time_limit_text = None
self._tournament_time_limit_title_text = None
@@ -779,12 +838,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
yet; this way things like the standard time-limit
(ba.GameActivity.setup_standard_time_limit()) will work with the game.
"""
- print('WARNING: default end_game() implementation called;'
- ' your game should override this.')
+ print(
+ 'WARNING: default end_game() implementation called;'
+ ' your game should override this.'
+ )
- def respawn_player(self,
- player: PlayerType,
- respawn_time: float | None = None) -> None:
+ def respawn_player(
+ self, player: PlayerType, respawn_time: float | None = None
+ ) -> None:
"""
Given a ba.Player, sets up a standard respawn timer,
along with the standard counter display, etc.
@@ -817,10 +878,13 @@ class GameActivity(Activity[PlayerType, TeamType]):
if player.actor and not self.has_ended():
from bastd.actor.respawnicon import RespawnIcon
+
player.customdata['respawn_timer'] = _ba.Timer(
- respawn_time, WeakCall(self.spawn_player_if_exists, player))
+ respawn_time, WeakCall(self.spawn_player_if_exists, player)
+ )
player.customdata['respawn_icon'] = RespawnIcon(
- player, respawn_time)
+ player, respawn_time
+ )
def spawn_player_if_exists(self, player: PlayerType) -> None:
"""
@@ -841,10 +905,12 @@ class GameActivity(Activity[PlayerType, TeamType]):
return self.spawn_player_spaz(player)
- def spawn_player_spaz(self,
- player: PlayerType,
- position: Sequence[float] = (0, 0, 0),
- angle: float | None = None) -> PlayerSpaz:
+ def spawn_player_spaz(
+ self,
+ player: PlayerType,
+ position: Sequence[float] = (0, 0, 0),
+ angle: float | None = None,
+ ) -> PlayerSpaz:
"""Create and wire up a ba.PlayerSpaz for the provided ba.Player."""
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
@@ -852,6 +918,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
from ba._gameutils import animate
from ba._coopsession import CoopSession
from bastd.actor.playerspaz import PlayerSpaz
+
name = player.getname()
color = player.color
highlight = player.highlight
@@ -862,10 +929,12 @@ class GameActivity(Activity[PlayerType, TeamType]):
light_color = _math.normalized_color(color)
display_color = _ba.safecolor(color, target_intensity=0.75)
- spaz = playerspaztype(color=color,
- highlight=highlight,
- character=player.character,
- player=player)
+ spaz = playerspaztype(
+ color=color,
+ highlight=highlight,
+ character=player.character,
+ player=player,
+ )
player.actor = spaz
assert spaz.node
@@ -874,13 +943,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
# material that allows us to collide with the player-walls.
# FIXME: Need to generalize this.
if isinstance(self.session, CoopSession) and self.map.getname() in [
- 'Courtyard', 'Tower D'
+ 'Courtyard',
+ 'Tower D',
]:
mat = self.map.preloaddata['collide_with_wall_material']
assert isinstance(spaz.node.materials, tuple)
assert isinstance(spaz.node.roller_materials, tuple)
- spaz.node.materials += (mat, )
- spaz.node.roller_materials += (mat, )
+ spaz.node.materials += (mat,)
+ spaz.node.roller_materials += (mat,)
spaz.node.name = name
spaz.node.name_color = display_color
@@ -889,8 +959,9 @@ class GameActivity(Activity[PlayerType, TeamType]):
# Move to the stand position and add a flash of light.
spaz.handlemessage(
StandMessage(
- position,
- angle if angle is not None else random.uniform(0, 360)))
+ position, angle if angle is not None else random.uniform(0, 360)
+ )
+ )
_ba.playsound(self._spawn_sound, 1, position=spaz.node.position)
light = _ba.newnode('light', attrs={'color': light_color})
spaz.node.connectattr('position', light, 'position')
@@ -902,10 +973,12 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""Create standard powerup drops for the current map."""
# pylint: disable=cyclic-import
from bastd.actor.powerupbox import DEFAULT_POWERUP_INTERVAL
- self._powerup_drop_timer = _ba.Timer(DEFAULT_POWERUP_INTERVAL,
- WeakCall(
- self._standard_drop_powerups),
- repeat=True)
+
+ self._powerup_drop_timer = _ba.Timer(
+ DEFAULT_POWERUP_INTERVAL,
+ WeakCall(self._standard_drop_powerups),
+ repeat=True,
+ )
self._standard_drop_powerups()
if enable_tnt:
self._tnt_spawners = {}
@@ -914,10 +987,12 @@ class GameActivity(Activity[PlayerType, TeamType]):
def _standard_drop_powerup(self, index: int, expire: bool = True) -> None:
# pylint: disable=cyclic-import
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory
+
PowerupBox(
position=self.map.powerup_spawn_points[index],
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(),
- expire=expire).autoretain()
+ expire=expire,
+ ).autoretain()
def _standard_drop_powerups(self) -> None:
"""Standard powerup drop."""
@@ -931,6 +1006,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""Standard tnt drop."""
# pylint: disable=cyclic-import
from bastd.actor.bomb import TNTSpawner
+
for i, point in enumerate(self.map.tnt_points):
assert self._tnt_spawners is not None
if self._tnt_spawners.get(i) is None:
@@ -944,38 +1020,44 @@ class GameActivity(Activity[PlayerType, TeamType]):
If the time-limit expires, end_game() will be called.
"""
from ba._nodeactor import NodeActor
+
if duration <= 0.0:
return
self._standard_time_limit_time = int(duration)
self._standard_time_limit_timer = _ba.Timer(
- 1.0, WeakCall(self._standard_time_limit_tick), repeat=True)
+ 1.0, WeakCall(self._standard_time_limit_tick), repeat=True
+ )
self._standard_time_limit_text = NodeActor(
- _ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'left',
- 'color': (1.0, 1.0, 1.0, 0.5),
- 'position': (-25, -30),
- 'flatness': 1.0,
- 'scale': 0.9
- }))
+ _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'left',
+ 'color': (1.0, 1.0, 1.0, 0.5),
+ 'position': (-25, -30),
+ 'flatness': 1.0,
+ 'scale': 0.9,
+ },
+ )
+ )
self._standard_time_limit_text_input = NodeActor(
- _ba.newnode('timedisplay',
- attrs={
- 'time2': duration * 1000,
- 'timemin': 0
- }))
- self.globalsnode.connectattr('time',
- self._standard_time_limit_text_input.node,
- 'time1')
+ _ba.newnode(
+ 'timedisplay', attrs={'time2': duration * 1000, 'timemin': 0}
+ )
+ )
+ self.globalsnode.connectattr(
+ 'time', self._standard_time_limit_text_input.node, 'time1'
+ )
assert self._standard_time_limit_text_input.node
assert self._standard_time_limit_text.node
self._standard_time_limit_text_input.node.connectattr(
- 'output', self._standard_time_limit_text.node, 'text')
+ 'output', self._standard_time_limit_text.node, 'text'
+ )
def _standard_time_limit_tick(self) -> None:
from ba._gameutils import animate
+
assert self._standard_time_limit_time is not None
self._standard_time_limit_time -= 1
if self._standard_time_limit_time <= 10:
@@ -984,11 +1066,14 @@ class GameActivity(Activity[PlayerType, TeamType]):
assert self._standard_time_limit_text.node
self._standard_time_limit_text.node.scale = 1.3
self._standard_time_limit_text.node.position = (-30, -45)
- cnode = _ba.newnode('combine',
- owner=self._standard_time_limit_text.node,
- attrs={'size': 4})
- cnode.connectattr('output',
- self._standard_time_limit_text.node, 'color')
+ cnode = _ba.newnode(
+ 'combine',
+ owner=self._standard_time_limit_text.node,
+ attrs={'size': 4},
+ )
+ cnode.connectattr(
+ 'output', self._standard_time_limit_text.node, 'color'
+ )
animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True)
animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True)
animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True)
@@ -997,16 +1082,18 @@ class GameActivity(Activity[PlayerType, TeamType]):
if self._standard_time_limit_time <= 0:
self._standard_time_limit_timer = None
self.end_game()
- node = _ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'color': (1, 0.7, 0, 1),
- 'position': (0, -90),
- 'scale': 1.2,
- 'text': Lstr(resource='timeExpiredText')
- })
+ node = _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'color': (1, 0.7, 0, 1),
+ 'position': (0, -90),
+ 'scale': 1.2,
+ 'text': Lstr(resource='timeExpiredText'),
+ },
+ )
_ba.playsound(_ba.getsound('refWhistle'))
animate(node, 'scale', {0.0: 0.0, 0.1: 1.4, 0.15: 1.2})
@@ -1019,6 +1106,7 @@ class GameActivity(Activity[PlayerType, TeamType]):
"""
from ba._nodeactor import NodeActor
from ba._generated.enums import TimeType
+
if duration <= 0.0:
return
self._tournament_time_limit = int(duration)
@@ -1031,49 +1119,61 @@ class GameActivity(Activity[PlayerType, TeamType]):
1.0,
WeakCall(self._tournament_time_limit_tick),
repeat=True,
- timetype=TimeType.BASE)
+ timetype=TimeType.BASE,
+ )
self._tournament_time_limit_title_text = NodeActor(
- _ba.newnode('text',
- attrs={
- 'v_attach': 'bottom',
- 'h_attach': 'left',
- 'h_align': 'center',
- 'v_align': 'center',
- 'vr_depth': 300,
- 'maxwidth': 100,
- 'color': (1.0, 1.0, 1.0, 0.5),
- 'position': (60, 50),
- 'flatness': 1.0,
- 'scale': 0.5,
- 'text': Lstr(resource='tournamentText')
- }))
+ _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'bottom',
+ 'h_attach': 'left',
+ 'h_align': 'center',
+ 'v_align': 'center',
+ 'vr_depth': 300,
+ 'maxwidth': 100,
+ 'color': (1.0, 1.0, 1.0, 0.5),
+ 'position': (60, 50),
+ 'flatness': 1.0,
+ 'scale': 0.5,
+ 'text': Lstr(resource='tournamentText'),
+ },
+ )
+ )
self._tournament_time_limit_text = NodeActor(
- _ba.newnode('text',
- attrs={
- 'v_attach': 'bottom',
- 'h_attach': 'left',
- 'h_align': 'center',
- 'v_align': 'center',
- 'vr_depth': 300,
- 'maxwidth': 100,
- 'color': (1.0, 1.0, 1.0, 0.5),
- 'position': (60, 30),
- 'flatness': 1.0,
- 'scale': 0.9
- }))
+ _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'bottom',
+ 'h_attach': 'left',
+ 'h_align': 'center',
+ 'v_align': 'center',
+ 'vr_depth': 300,
+ 'maxwidth': 100,
+ 'color': (1.0, 1.0, 1.0, 0.5),
+ 'position': (60, 30),
+ 'flatness': 1.0,
+ 'scale': 0.9,
+ },
+ )
+ )
self._tournament_time_limit_text_input = NodeActor(
- _ba.newnode('timedisplay',
- attrs={
- 'timemin': 0,
- 'time2': self._tournament_time_limit * 1000
- }))
+ _ba.newnode(
+ 'timedisplay',
+ attrs={
+ 'timemin': 0,
+ 'time2': self._tournament_time_limit * 1000,
+ },
+ )
+ )
assert self._tournament_time_limit_text.node
assert self._tournament_time_limit_text_input.node
self._tournament_time_limit_text_input.node.connectattr(
- 'output', self._tournament_time_limit_text.node, 'text')
+ 'output', self._tournament_time_limit_text.node, 'text'
+ )
def _tournament_time_limit_tick(self) -> None:
from ba._gameutils import animate
+
assert self._tournament_time_limit is not None
self._tournament_time_limit -= 1
if self._tournament_time_limit <= 10:
@@ -1089,13 +1189,16 @@ class GameActivity(Activity[PlayerType, TeamType]):
cnode = _ba.newnode(
'combine',
owner=self._tournament_time_limit_text.node,
- attrs={'size': 4})
- cnode.connectattr('output',
- self._tournament_time_limit_title_text.node,
- 'color')
- cnode.connectattr('output',
- self._tournament_time_limit_text.node,
- 'color')
+ attrs={'size': 4},
+ )
+ cnode.connectattr(
+ 'output',
+ self._tournament_time_limit_title_text.node,
+ 'color',
+ )
+ cnode.connectattr(
+ 'output', self._tournament_time_limit_text.node, 'color'
+ )
animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True)
animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True)
animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True)
@@ -1104,18 +1207,22 @@ class GameActivity(Activity[PlayerType, TeamType]):
if self._tournament_time_limit <= 0:
self._tournament_time_limit_timer = None
self.end_game()
- tval = Lstr(resource='tournamentTimeExpiredText',
- fallback_resource='timeExpiredText')
- node = _ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'color': (1, 0.7, 0, 1),
- 'position': (0, -200),
- 'scale': 1.6,
- 'text': tval
- })
+ tval = Lstr(
+ resource='tournamentTimeExpiredText',
+ fallback_resource='timeExpiredText',
+ )
+ node = _ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'color': (1, 0.7, 0, 1),
+ 'position': (0, -200),
+ 'scale': 1.6,
+ 'text': tval,
+ },
+ )
_ba.playsound(_ba.getsound('refWhistle'))
animate(node, 'scale', {0: 0.0, 0.1: 1.4, 0.15: 1.2})
@@ -1124,14 +1231,17 @@ class GameActivity(Activity[PlayerType, TeamType]):
assert self._tournament_time_limit_text_input is not None
assert self._tournament_time_limit_text_input.node
self._tournament_time_limit_text_input.node.time2 = (
- self._tournament_time_limit * 1000)
+ self._tournament_time_limit * 1000
+ )
- def show_zoom_message(self,
- message: ba.Lstr,
- color: Sequence[float] = (0.9, 0.4, 0.0),
- scale: float = 0.8,
- duration: float = 2.0,
- trail: bool = False) -> None:
+ def show_zoom_message(
+ self,
+ message: ba.Lstr,
+ color: Sequence[float] = (0.9, 0.4, 0.0),
+ scale: float = 0.8,
+ duration: float = 2.0,
+ trail: bool = False,
+ ) -> None:
"""Zooming text used to announce game names and winners."""
# pylint: disable=cyclic-import
from bastd.actor.zoomtext import ZoomText
@@ -1141,19 +1251,23 @@ class GameActivity(Activity[PlayerType, TeamType]):
i = 0
cur_time = _ba.time()
while True:
- if (i not in self._zoom_message_times
- or self._zoom_message_times[i] < cur_time):
+ if (
+ i not in self._zoom_message_times
+ or self._zoom_message_times[i] < cur_time
+ ):
self._zoom_message_times[i] = cur_time + duration
break
i += 1
- ZoomText(message,
- lifespan=duration,
- jitter=2.0,
- position=(0, 200 - i * 100),
- scale=scale,
- maxwidth=800,
- trail=trail,
- color=color).autoretain()
+ ZoomText(
+ message,
+ lifespan=duration,
+ jitter=2.0,
+ position=(0, 200 - i * 100),
+ scale=scale,
+ maxwidth=800,
+ trail=trail,
+ color=color,
+ ).autoretain()
def _calc_map_name(self, settings: dict) -> str:
map_name: str
@@ -1164,7 +1278,8 @@ class GameActivity(Activity[PlayerType, TeamType]):
# list of supported ones.
unowned_maps = _store.get_unowned_maps()
valid_maps: list[str] = [
- m for m in self.get_supported_maps(type(self.session))
+ m
+ for m in self.get_supported_maps(type(self.session))
if m not in unowned_maps
]
if not valid_maps:
diff --git a/assets/src/ba_data/python/ba/_gameresults.py b/assets/src/ba_data/python/ba/_gameresults.py
index d580e866..7f223157 100644
--- a/assets/src/ba_data/python/ba/_gameresults.py
+++ b/assets/src/ba_data/python/ba/_gameresults.py
@@ -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] = []
diff --git a/assets/src/ba_data/python/ba/_gameutils.py b/assets/src/ba_data/python/ba/_gameutils.py
index 97cac63a..2d73d258 100644
--- a/assets/src/ba_data/python/ba/_gameutils.py
+++ b/assets/src/ba_data/python/ba/_gameutils.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_general.py b/assets/src/ba_data/python/ba/_general.py
index f40fa093..dbd58099 100644
--- a/assets/src/ba_data/python/ba/_general.py
+++ b/assets/src/ba_data/python/ba/_general.py
@@ -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 ('')
+ return (
+ ''
+ )
class _Call:
@@ -244,8 +264,15 @@ class _Call:
return self._call(*self._args + args_extra, **self._keywds)
def __str__(self) -> str:
- return ('')
+ return (
+ ''
+ )
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 ''
@@ -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)
diff --git a/assets/src/ba_data/python/ba/_hooks.py b/assets/src/ba_data/python/ba/_hooks.py
index ab7e36cd..632fa5c5 100644
--- a/assets/src/ba_data/python/ba/_hooks.py
+++ b/assets/src/ba_data/python/ba/_hooks.py
@@ -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())
diff --git a/assets/src/ba_data/python/ba/_input.py b/assets/src/ba_data/python/ba/_input.py
index fc21a436..682dd046 100644
--- a/assets/src/ba_data/python/ba/_input.py
+++ b/assets/src/ba_data/python/ba/_input.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_internal.py b/assets/src/ba_data/python/ba/_internal.py
index 5637e6a5..6d6e6009 100644
--- a/assets/src/ba_data/python/ba/_internal.py
+++ b/assets/src/ba_data/python/ba/_internal.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_language.py b/assets/src/ba_data/python/ba/_language.py
index dbd740e6..9d1fbecc 100644
--- a/assets/src/ba_data/python/ba/_language.py
+++ b/assets/src/ba_data/python/ba/_language.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_level.py b/assets/src/ba_data/python/ba/_level.py
index 124f0689..6c1426da 100644
--- a/assets/src/ba_data/python/ba/_level.py
+++ b/assets/src/ba_data/python/ba/_level.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_lobby.py b/assets/src/ba_data/python/ba/_lobby.py
index 31fcfa59..1bfcfeb3 100644
--- a/assets/src/ba_data/python/ba/_lobby.py
+++ b/assets/src/ba_data/python/ba/_lobby.py
@@ -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 '' 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:
diff --git a/assets/src/ba_data/python/ba/_map.py b/assets/src/ba_data/python/ba/_map.py
index 122e4df6..b3a5c675 100644
--- a/assets/src/ba_data/python/ba/_map.py
+++ b/assets/src/ba_data/python/ba/_map.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_math.py b/assets/src/ba_data/python/ba/_math.py
index 856d4268..90ce3cb6 100644
--- a/assets/src/ba_data/python/ba/_math.py
+++ b/assets/src/ba_data/python/ba/_math.py
@@ -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, ...]:
diff --git a/assets/src/ba_data/python/ba/_messages.py b/assets/src/ba_data/python/ba/_messages.py
index 1ea794ca..df2701bc 100644
--- a/assets/src/ba_data/python/ba/_messages.py
+++ b/assets/src/ba_data/python/ba/_messages.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_meta.py b/assets/src/ba_data/python/ba/_meta.py
index 1faca382..5f05d4c3 100644
--- a/assets/src/ba_data/python/ba/_meta.py
+++ b/assets/src/ba_data/python/ba/_meta.py
@@ -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 " 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 " line found;'
- ' ignoring module.')
+ ' ignoring module.'
+ )
return None
diff --git a/assets/src/ba_data/python/ba/_multiteamsession.py b/assets/src/ba_data/python/ba/_multiteamsession.py
index cfe4a7c9..d653ff95 100644
--- a/assets/src/ba_data/python/ba/_multiteamsession.py
+++ b/assets/src/ba_data/python/ba/_multiteamsession.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_music.py b/assets/src/ba_data/python/ba/_music.py
index 4940e4c3..79693c4e 100644
--- a/assets/src/ba_data/python/ba/_music.py
+++ b/assets/src/ba_data/python/ba/_music.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_net.py b/assets/src/ba_data/python/ba/_net.py
index 7fac86fb..2a0d4217 100644
--- a/assets/src/ba_data/python/ba/_net.py
+++ b/assets/src/ba_data/python/ba/_net.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py
index 3e56fcfe..32d17206 100644
--- a/assets/src/ba_data/python/ba/_player.py
+++ b/assets/src/ba_data/python/ba/_player.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_playlist.py b/assets/src/ba_data/python/ba/_playlist.py
index 5518601a..5576cfe4 100644
--- a/assets/src/ba_data/python/ba/_playlist.py
+++ b/assets/src/ba_data/python/ba/_playlist.py
@@ -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'
- }]
+ ]
diff --git a/assets/src/ba_data/python/ba/_plugin.py b/assets/src/ba_data/python/ba/_plugin.py
index d05cdbe9..efe1ef69 100644
--- a/assets/src/ba_data/python/ba/_plugin.py
+++ b/assets/src/ba_data/python/ba/_plugin.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_powerup.py b/assets/src/ba_data/python/ba/_powerup.py
index 8ca65a0b..53d662af 100644
--- a/assets/src/ba_data/python/ba/_powerup.py
+++ b/assets/src/ba_data/python/ba/_powerup.py
@@ -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),
+ )
diff --git a/assets/src/ba_data/python/ba/_profile.py b/assets/src/ba_data/python/ba/_profile.py
index 308b6bfb..2a24d980 100644
--- a/assets/src/ba_data/python/ba/_profile.py
+++ b/assets/src/ba_data/python/ba/_profile.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_score.py b/assets/src/ba_data/python/ba/_score.py
index 8b8d009a..b371e698 100644
--- a/assets/src/ba_data/python/ba/_score.py
+++ b/assets/src/ba_data/python/ba/_score.py
@@ -18,6 +18,7 @@ class ScoreType(Enum):
Category: **Enums**
"""
+
SECONDS = 's'
MILLISECONDS = 'ms'
POINTS = 'p'
diff --git a/assets/src/ba_data/python/ba/_servermode.py b/assets/src/ba_data/python/ba/_servermode.py
index 8c8ed981..f5a40551 100644
--- a/assets/src/ba_data/python/ba/_servermode.py
+++ b/assets/src/ba_data/python/ba/_servermode.py
@@ -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)
diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py
index 02309f4b..11f653cb 100644
--- a/assets/src/ba_data/python/ba/_session.py
+++ b/assets/src/ba_data/python/ba/_session.py
@@ -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:
diff --git a/assets/src/ba_data/python/ba/_settings.py b/assets/src/ba_data/python/ba/_settings.py
index cf590b52..3534fdb9 100644
--- a/assets/src/ba_data/python/ba/_settings.py
+++ b/assets/src/ba_data/python/ba/_settings.py
@@ -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]]
diff --git a/assets/src/ba_data/python/ba/_stats.py b/assets/src/ba_data/python/ba/_stats.py
index 71f088d1..0f68beb7 100644
--- a/assets/src/ba_data/python/ba/_stats.py
+++ b/assets/src/ba_data/python/ba/_stats.py
@@ -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')
diff --git a/assets/src/ba_data/python/ba/_store.py b/assets/src/ba_data/python/ba/_store.py
index f56b0a84..850a0aa0 100644
--- a/assets/src/ba_data/python/ba/_store.py
+++ b/assets/src/ba_data/python/ba/_store.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_team.py b/assets/src/ba_data/python/ba/_team.py
index dac90ae9..9e7390fd 100644
--- a/assets/src/ba_data/python/ba/_team.py
+++ b/assets/src/ba_data/python/ba/_team.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_teamgame.py b/assets/src/ba_data/python/ba/_teamgame.py
index ccf94189..5a6aaed9 100644
--- a/assets/src/ba_data/python/ba/_teamgame.py
+++ b/assets/src/ba_data/python/ba/_teamgame.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_tips.py b/assets/src/ba_data/python/ba/_tips.py
index c6e8955e..604d825e 100644
--- a/assets/src/ba_data/python/ba/_tips.py
+++ b/assets/src/ba_data/python/ba/_tips.py
@@ -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 += [
diff --git a/assets/src/ba_data/python/ba/_tournament.py b/assets/src/ba_data/python/ba/_tournament.py
index ec1618ae..9694e387 100644
--- a/assets/src/ba_data/python/ba/_tournament.py
+++ b/assets/src/ba_data/python/ba/_tournament.py
@@ -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
diff --git a/assets/src/ba_data/python/ba/_ui.py b/assets/src/ba_data/python/ba/_ui.py
index 8ec2e275..c1aba303 100644
--- a/assets/src/ba_data/python/ba/_ui.py
+++ b/assets/src/ba_data/python/ba/_ui.py
@@ -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()
diff --git a/assets/src/ba_data/python/ba/_workspace.py b/assets/src/ba_data/python/ba/_workspace.py
index 16c9fcab..33b3b702 100644
--- a/assets/src/ba_data/python/ba/_workspace.py
+++ b/assets/src/ba_data/python/ba/_workspace.py
@@ -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)
diff --git a/assets/src/ba_data/python/ba/internal.py b/assets/src/ba_data/python/ba/internal.py
index fdf99803..cab4bab7 100644
--- a/assets/src/ba_data/python/ba/internal.py
+++ b/assets/src/ba_data/python/ba/internal.py
@@ -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',
diff --git a/assets/src/ba_data/python/ba/macmusicapp.py b/assets/src/ba_data/python/ba/macmusicapp.py
index 4a758726..38f4e152 100644
--- a/assets/src/ba_data/python/ba/macmusicapp.py
+++ b/assets/src/ba_data/python/ba/macmusicapp.py
@@ -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)))
+ )
diff --git a/assets/src/ba_data/python/ba/modutils.py b/assets/src/ba_data/python/ba/modutils.py
index c081d79c..2bda5c49 100644
--- a/assets/src/ba_data/python/ba/modutils.py
+++ b/assets/src/ba_data/python/ba/modutils.py
@@ -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.')
diff --git a/assets/src/ba_data/python/ba/osmusic.py b/assets/src/ba_data/python/ba/osmusic.py
index 624e6816..9c3f48e5 100644
--- a/assets/src/ba_data/python/ba/osmusic.py
+++ b/assets/src/ba_data/python/ba/osmusic.py
@@ -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}', '') + '; ' +
- str(error))
+ err_str = (
+ rstr.replace('${MUSIC}', '') + '; ' + 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 = ''
- _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,
+ )
diff --git a/assets/src/ba_data/python/ba/ui/__init__.py b/assets/src/ba_data/python/ba/ui/__init__.py
index 67e5d936..df7999f2 100644
--- a/assets/src/ba_data/python/ba/ui/__init__.py
+++ b/assets/src/ba_data/python/ba/ui/__init__.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/activity/coopjoin.py b/assets/src/ba_data/python/bastd/activity/coopjoin.py
index b8cc8af5..ebe9dea4 100644
--- a/assets/src/ba_data/python/bastd/activity/coopjoin.py
+++ b/assets/src/ba_data/python/bastd/activity/coopjoin.py
@@ -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()
diff --git a/assets/src/ba_data/python/bastd/activity/coopscore.py b/assets/src/ba_data/python/bastd/activity/coopscore.py
index 5e71b055..5c32fdeb 100644
--- a/assets/src/ba_data/python/bastd/activity/coopscore.py
+++ b/assets/src/ba_data/python/bastd/activity/coopscore.py
@@ -49,12 +49,16 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._campaign: ba.Campaign = settings['campaign']
self._have_achievements = bool(
- ba.app.ach.achievements_for_coop_level(self._campaign.name + ':' +
- settings['level']))
+ ba.app.ach.achievements_for_coop_level(
+ self._campaign.name + ':' + settings['level']
+ )
+ )
- self._account_type = (ba.internal.get_v1_account_type()
- if ba.internal.get_v1_account_state()
- == 'signed_in' else None)
+ self._account_type = (
+ ba.internal.get_v1_account_type()
+ if ba.internal.get_v1_account_state() == 'signed_in'
+ else None
+ )
self._game_service_icon_color: Sequence[float] | None
self._game_service_achievements_texture: ba.Texture | None
@@ -75,10 +79,12 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._account_has_achievements = True
elif self._account_type == 'Google Play':
self._game_service_icon_color = (0.8, 1.0, 0.6)
- self._game_service_achievements_texture = (
- ba.gettexture('googlePlayAchievementsIcon'))
- self._game_service_leaderboards_texture = (
- ba.gettexture('googlePlayLeaderboardsIcon'))
+ self._game_service_achievements_texture = ba.gettexture(
+ 'googlePlayAchievementsIcon'
+ )
+ self._game_service_leaderboards_texture = ba.gettexture(
+ 'googlePlayLeaderboardsIcon'
+ )
self._account_has_achievements = True
else:
self._game_service_icon_color = None
@@ -140,8 +146,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._score_order: str
if 'score_order' in settings:
if not settings['score_order'] in ['increasing', 'decreasing']:
- raise ValueError('Invalid score order: ' +
- settings['score_order'])
+ raise ValueError(
+ 'Invalid score order: ' + settings['score_order']
+ )
self._score_order = settings['score_order']
else:
self._score_order = 'increasing'
@@ -150,8 +157,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._score_type: str
if 'score_type' in settings:
if not settings['score_type'] in ['points', 'time']:
- raise ValueError('Invalid score type: ' +
- settings['score_type'])
+ raise ValueError(
+ 'Invalid score type: ' + settings['score_type']
+ )
self._score_type = settings['score_type']
else:
self._score_type = 'points'
@@ -161,18 +169,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
assert isinstance(self._level_name, str)
self._game_name_str = self._campaign.name + ':' + self._level_name
- self._game_config_str = str(len(
- self._playerinfos)) + 'p' + self._campaign.getlevel(
- self._level_name).get_score_version_string().replace(' ', '_')
+ self._game_config_str = (
+ str(len(self._playerinfos))
+ + 'p'
+ + self._campaign.getlevel(self._level_name)
+ .get_score_version_string()
+ .replace(' ', '_')
+ )
# If game-center/etc scores are available we show our friends'
# scores. Otherwise we show our local high scores.
self._show_friend_scores = ba.internal.game_service_has_leaderboard(
- self._game_name_str, self._game_config_str)
+ self._game_name_str, self._game_config_str
+ )
try:
self._old_best_rank = self._campaign.getlevel(
- self._level_name).rating
+ self._level_name
+ ).rating
except Exception:
self._old_best_rank = 0.0
@@ -188,14 +202,16 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
def on_transition_in(self) -> None:
from bastd.actor import background # FIXME NO BSSTD
+
ba.set_analytics_screen('Coop Score Screen')
super().on_transition_in()
- self._background = background.Background(fade_time=0.45,
- start_faded=False,
- show_logo=True)
+ self._background = background.Background(
+ fade_time=0.45, start_faded=False, show_logo=True
+ )
def _ui_menu(self) -> None:
from bastd.ui import specialoffer
+
if specialoffer.show_offer():
return
ba.containerwidget(edit=self._root_ui, transition='out_left')
@@ -205,6 +221,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
def _ui_restart(self) -> None:
from bastd.ui.tournamententry import TournamentEntryWindow
from bastd.ui import specialoffer
+
if specialoffer.show_offer():
return
@@ -214,20 +231,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if self._tournament_time_remaining is None:
ba.screenmessage(
ba.Lstr(resource='tournamentCheckingStateText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
if self._tournament_time_remaining <= 0:
- ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
# If there are currently fewer players than our session min,
# don't allow.
if len(self.players) < self.session.min_players:
- ba.screenmessage(ba.Lstr(resource='notEnoughPlayersRemainingText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notEnoughPlayersRemainingText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -241,7 +262,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
TournamentEntryWindow(
tournament_id=tournament_id,
tournament_activity=self,
- position=self._restart_button.get_screen_space_center())
+ position=self._restart_button.get_screen_space_center(),
+ )
else:
ba.containerwidget(edit=self._root_ui, transition='out_left')
self.can_show_ad_on_death = True
@@ -250,13 +272,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
def _ui_next(self) -> None:
from bastd.ui.specialoffer import show_offer
+
if show_offer():
return
# If we didn't just complete this level but are choosing to play the
# next one, set it as current (this won't happen otherwise).
- if (self._is_complete and self._is_more_levels
- and not self._newly_complete):
+ if (
+ self._is_complete
+ and self._is_more_levels
+ and not self._newly_complete
+ ):
assert self._next_level_name is not None
self._campaign.set_selected_level(self._next_level_name)
ba.containerwidget(edit=self._root_ui, transition='out_left')
@@ -264,9 +290,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self.end({'outcome': 'next_level'})
def _ui_gc(self) -> None:
- ba.internal.show_online_score_ui('leaderboard',
- game=self._game_name_str,
- game_version=self._game_config_str)
+ ba.internal.show_online_score_ui(
+ 'leaderboard',
+ game=self._game_name_str,
+ game_version=self._game_config_str,
+ )
def _ui_show_achievements(self) -> None:
ba.internal.show_online_score_ui('achievements')
@@ -274,8 +302,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
def _ui_worlds_best(self) -> None:
if self._score_link is None:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='scoreListUnavailableText'),
- color=(1, 0.5, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='scoreListUnavailableText'), color=(1, 0.5, 0)
+ )
else:
ba.open_url(self._score_link)
@@ -288,12 +317,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
scale=0.54,
h_align=Text.HAlign.CENTER,
color=(0.5, 0.7, 0.5, 1),
- position=(300, -235))
+ position=(300, -235),
+ )
ba.playsound(ba.getsound('error'))
ba.timer(
2.0,
- ba.WeakCall(self._next_level_error.handlemessage,
- ba.DieMessage()))
+ ba.WeakCall(
+ self._next_level_error.handlemessage, ba.DieMessage()
+ ),
+ )
def _should_show_worlds_best_button(self) -> bool:
# Link is too complicated to display with no browser.
@@ -320,8 +352,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if not self.players:
return
- rootc = self._root_ui = ba.containerwidget(size=(0, 0),
- transition='in_right')
+ rootc = self._root_ui = ba.containerwidget(
+ size=(0, 0), transition='in_right'
+ )
h_offs = 7.0
v_offs = -280.0
@@ -334,31 +367,34 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.internal.set_ui_input_device(None) # Menu is up for grabs.
if self._show_friend_scores:
- ba.buttonwidget(parent=rootc,
- color=(0.45, 0.4, 0.5),
- position=(h_offs - 520, v_offs + 480),
- size=(300, 60),
- label=ba.Lstr(resource='topFriendsText'),
- on_activate_call=ba.WeakCall(self._ui_gc),
- transition_delay=delay + 0.5,
- icon=self._game_service_leaderboards_texture,
- icon_color=self._game_service_icon_color,
- autoselect=True,
- selectable=can_select_extra_buttons)
+ ba.buttonwidget(
+ parent=rootc,
+ color=(0.45, 0.4, 0.5),
+ position=(h_offs - 520, v_offs + 480),
+ size=(300, 60),
+ label=ba.Lstr(resource='topFriendsText'),
+ on_activate_call=ba.WeakCall(self._ui_gc),
+ transition_delay=delay + 0.5,
+ icon=self._game_service_leaderboards_texture,
+ icon_color=self._game_service_icon_color,
+ autoselect=True,
+ selectable=can_select_extra_buttons,
+ )
if self._have_achievements and self._account_has_achievements:
- ba.buttonwidget(parent=rootc,
- color=(0.45, 0.4, 0.5),
- position=(h_offs - 520, v_offs + 450 - 235 + 40),
- size=(300, 60),
- label=ba.Lstr(resource='achievementsText'),
- on_activate_call=ba.WeakCall(
- self._ui_show_achievements),
- transition_delay=delay + 1.5,
- icon=self._game_service_achievements_texture,
- icon_color=self._game_service_icon_color,
- autoselect=True,
- selectable=can_select_extra_buttons)
+ ba.buttonwidget(
+ parent=rootc,
+ color=(0.45, 0.4, 0.5),
+ position=(h_offs - 520, v_offs + 450 - 235 + 40),
+ size=(300, 60),
+ label=ba.Lstr(resource='achievementsText'),
+ on_activate_call=ba.WeakCall(self._ui_show_achievements),
+ transition_delay=delay + 1.5,
+ icon=self._game_service_achievements_texture,
+ icon_color=self._game_service_icon_color,
+ autoselect=True,
+ selectable=can_select_extra_buttons,
+ )
if self._should_show_worlds_best_button():
ba.buttonwidget(
@@ -367,48 +403,57 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
position=(160, v_offs + 480),
size=(350, 62),
label=ba.Lstr(resource='tournamentStandingsText')
- if self.session.tournament_id is not None else ba.Lstr(
- resource='worldsBestScoresText') if self._score_type
- == 'points' else ba.Lstr(resource='worldsBestTimesText'),
+ if self.session.tournament_id is not None
+ else ba.Lstr(resource='worldsBestScoresText')
+ if self._score_type == 'points'
+ else ba.Lstr(resource='worldsBestTimesText'),
autoselect=True,
on_activate_call=ba.WeakCall(self._ui_worlds_best),
transition_delay=delay + 1.9,
- selectable=can_select_extra_buttons)
+ selectable=can_select_extra_buttons,
+ )
else:
pass
- show_next_button = self._is_more_levels and not (ba.app.demo_mode
- or ba.app.arcade_mode)
+ show_next_button = self._is_more_levels and not (
+ ba.app.demo_mode or ba.app.arcade_mode
+ )
if not show_next_button:
h_offs += 70
- menu_button = ba.buttonwidget(parent=rootc,
- autoselect=True,
- position=(h_offs - 130 - 60, v_offs),
- size=(110, 85),
- label='',
- on_activate_call=ba.WeakCall(
- self._ui_menu))
- ba.imagewidget(parent=rootc,
- draw_controller=menu_button,
- position=(h_offs - 130 - 60 + 22, v_offs + 14),
- size=(60, 60),
- texture=self._menu_icon_texture,
- opacity=0.8)
+ menu_button = ba.buttonwidget(
+ parent=rootc,
+ autoselect=True,
+ position=(h_offs - 130 - 60, v_offs),
+ size=(110, 85),
+ label='',
+ on_activate_call=ba.WeakCall(self._ui_menu),
+ )
+ ba.imagewidget(
+ parent=rootc,
+ draw_controller=menu_button,
+ position=(h_offs - 130 - 60 + 22, v_offs + 14),
+ size=(60, 60),
+ texture=self._menu_icon_texture,
+ opacity=0.8,
+ )
self._restart_button = restart_button = ba.buttonwidget(
parent=rootc,
autoselect=True,
position=(h_offs - 60, v_offs),
size=(110, 85),
label='',
- on_activate_call=ba.WeakCall(self._ui_restart))
- ba.imagewidget(parent=rootc,
- draw_controller=restart_button,
- position=(h_offs - 60 + 19, v_offs + 7),
- size=(70, 70),
- texture=self._replay_icon_texture,
- opacity=0.8)
+ on_activate_call=ba.WeakCall(self._ui_restart),
+ )
+ ba.imagewidget(
+ parent=rootc,
+ draw_controller=restart_button,
+ position=(h_offs - 60 + 19, v_offs + 7),
+ size=(70, 70),
+ texture=self._replay_icon_texture,
+ opacity=0.8,
+ )
next_button: ba.Widget | None = None
@@ -425,24 +470,30 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
button_sound = False
image_opacity = 0.2
color = (0.3, 0.3, 0.3)
- next_button = ba.buttonwidget(parent=rootc,
- autoselect=True,
- position=(h_offs + 130 - 60, v_offs),
- size=(110, 85),
- label='',
- on_activate_call=call,
- color=color,
- enable_sound=button_sound)
- ba.imagewidget(parent=rootc,
- draw_controller=next_button,
- position=(h_offs + 130 - 60 + 12, v_offs + 5),
- size=(80, 80),
- texture=self._next_level_icon_texture,
- opacity=image_opacity)
+ next_button = ba.buttonwidget(
+ parent=rootc,
+ autoselect=True,
+ position=(h_offs + 130 - 60, v_offs),
+ size=(110, 85),
+ label='',
+ on_activate_call=call,
+ color=color,
+ enable_sound=button_sound,
+ )
+ ba.imagewidget(
+ parent=rootc,
+ draw_controller=next_button,
+ position=(h_offs + 130 - 60 + 12, v_offs + 5),
+ size=(80, 80),
+ texture=self._next_level_icon_texture,
+ opacity=image_opacity,
+ )
x_offs_extra = 0 if show_next_button else -100
- self._corner_button_offs = (h_offs + 300.0 + 100.0 + x_offs_extra,
- v_offs + 560.0)
+ self._corner_button_offs = (
+ h_offs + 300.0 + 100.0 + x_offs_extra,
+ v_offs + 560.0,
+ )
if ba.app.demo_mode or ba.app.arcade_mode:
self._league_rank_button = None
@@ -456,7 +507,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
color=(0.4, 0.4, 0.9),
textcolor=(0.9, 0.9, 2.0),
transition_delay=0.0,
- smooth_update_delay=5.0)
+ smooth_update_delay=5.0,
+ )
self._store_button_instance = StoreButton(
parent=rootc,
position=(h_offs + 400 + 100 + x_offs_extra, v_offs + 560),
@@ -467,20 +519,24 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
button_type='square',
color=(0.35, 0.25, 0.45),
textcolor=(0.9, 0.7, 1.0),
- transition_delay=0.0)
+ transition_delay=0.0,
+ )
- ba.containerwidget(edit=rootc,
- selected_child=next_button if
- (self._newly_complete and self._victory
- and show_next_button) else restart_button,
- on_cancel_call=menu_button.activate)
+ ba.containerwidget(
+ edit=rootc,
+ selected_child=next_button
+ if (self._newly_complete and self._victory and show_next_button)
+ else restart_button,
+ on_cancel_call=menu_button.activate,
+ )
self._update_corner_button_positions()
self._update_corner_button_positions_timer = ba.Timer(
1.0,
ba.WeakCall(self._update_corner_button_positions),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
def _update_corner_button_positions(self) -> None:
offs = -55 if ba.internal.is_party_icon_visible() else 0
@@ -497,8 +553,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# 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)
@@ -518,9 +577,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# (though theoretically that should be ok).
if not self.is_transitioning_out() and player:
player.assigninput(
- (ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS,
- ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS),
- self._player_press)
+ (
+ ba.InputType.JUMP_PRESS,
+ ba.InputType.PUNCH_PRESS,
+ ba.InputType.BOMB_PRESS,
+ ba.InputType.PICK_UP_PRESS,
+ ),
+ self._player_press,
+ )
def on_player_join(self, player: ba.Player) -> None:
super().on_player_join(player)
@@ -528,7 +592,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if ba.app.server is not None:
# Host can't press retry button, so anyone can do it instead.
time_till_assign = max(
- 0, self._birth_time + self._min_view_time - ba.time())
+ 0, self._birth_time + self._min_view_time - ba.time()
+ )
ba.timer(time_till_assign, ba.WeakCall(self._safe_assign, player))
@@ -545,123 +610,156 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
levels = self._campaign.levels
level = self._campaign.getlevel(self._level_name)
self._was_complete = level.complete
- self._is_complete = (self._was_complete or self._victory)
- self._newly_complete = (self._is_complete and not self._was_complete)
- self._is_more_levels = ((level.index < len(levels) - 1)
- and self._campaign.sequential)
+ self._is_complete = self._was_complete or self._victory
+ self._newly_complete = self._is_complete and not self._was_complete
+ self._is_more_levels = (
+ level.index < len(levels) - 1
+ ) and self._campaign.sequential
# Any time we complete a level, set the next one as unlocked.
if self._is_complete and self._is_more_levels:
- ba.internal.add_transaction({
- 'type': 'COMPLETE_LEVEL',
- 'campaign': self._campaign.name,
- 'level': self._level_name
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'COMPLETE_LEVEL',
+ 'campaign': self._campaign.name,
+ 'level': self._level_name,
+ }
+ )
self._next_level_name = levels[level.index + 1].name
# If this is the first time we completed it, set the next one
# as current.
if self._newly_complete:
cfg = ba.app.config
- cfg['Selected Coop Game'] = (self._campaign.name + ':' +
- self._next_level_name)
+ cfg['Selected Coop Game'] = (
+ self._campaign.name + ':' + self._next_level_name
+ )
cfg.commit()
self._campaign.set_selected_level(self._next_level_name)
ba.timer(1.0, ba.WeakCall(self.request_ui))
- if (self._is_complete and self._victory and self._is_more_levels
- and not (ba.app.demo_mode or ba.app.arcade_mode)):
- Text(ba.Lstr(value='${A}:\n',
- subs=[('${A}', ba.Lstr(resource='levelUnlockedText'))
- ]) if self._newly_complete else
- ba.Lstr(value='${A}:\n',
- subs=[('${A}', ba.Lstr(resource='nextLevelText'))]),
- transition=Text.Transition.IN_RIGHT,
- transition_delay=5.2,
- flash=self._newly_complete,
- scale=0.54,
- h_align=Text.HAlign.CENTER,
- maxwidth=270,
- color=(0.5, 0.7, 0.5, 1),
- position=(270, -235)).autoretain()
+ if (
+ self._is_complete
+ and self._victory
+ and self._is_more_levels
+ and not (ba.app.demo_mode or ba.app.arcade_mode)
+ ):
+ Text(
+ ba.Lstr(
+ value='${A}:\n',
+ subs=[('${A}', ba.Lstr(resource='levelUnlockedText'))],
+ )
+ if self._newly_complete
+ else ba.Lstr(
+ value='${A}:\n',
+ subs=[('${A}', ba.Lstr(resource='nextLevelText'))],
+ ),
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=5.2,
+ flash=self._newly_complete,
+ scale=0.54,
+ h_align=Text.HAlign.CENTER,
+ maxwidth=270,
+ color=(0.5, 0.7, 0.5, 1),
+ position=(270, -235),
+ ).autoretain()
assert self._next_level_name is not None
- Text(ba.Lstr(translate=('coopLevelNames', self._next_level_name)),
- transition=Text.Transition.IN_RIGHT,
- transition_delay=5.2,
- flash=self._newly_complete,
- scale=0.7,
- h_align=Text.HAlign.CENTER,
- maxwidth=205,
- color=(0.5, 0.7, 0.5, 1),
- position=(270, -255)).autoretain()
+ Text(
+ ba.Lstr(translate=('coopLevelNames', self._next_level_name)),
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=5.2,
+ flash=self._newly_complete,
+ scale=0.7,
+ h_align=Text.HAlign.CENTER,
+ maxwidth=205,
+ color=(0.5, 0.7, 0.5, 1),
+ position=(270, -255),
+ ).autoretain()
if self._newly_complete:
ba.timer(5.2, ba.Call(ba.playsound, self._cashregistersound))
ba.timer(5.2, ba.Call(ba.playsound, self._dingsound))
offs_x = -195
if len(self._playerinfos) > 1:
- pstr = ba.Lstr(value='- ${A} -',
- subs=[('${A}',
- ba.Lstr(resource='multiPlayerCountText',
- subs=[('${COUNT}',
- str(len(self._playerinfos)))
- ]))])
+ pstr = ba.Lstr(
+ value='- ${A} -',
+ subs=[
+ (
+ '${A}',
+ ba.Lstr(
+ resource='multiPlayerCountText',
+ subs=[('${COUNT}', str(len(self._playerinfos)))],
+ ),
+ )
+ ],
+ )
else:
- pstr = ba.Lstr(value='- ${A} -',
- subs=[('${A}',
- ba.Lstr(resource='singlePlayerCountText'))])
- ZoomText(self._campaign.getlevel(self._level_name).displayname,
- maxwidth=800,
- flash=False,
- trail=False,
- color=(0.5, 1, 0.5, 1),
- h_align='center',
- scale=0.4,
- position=(0, 292),
- jitter=1.0).autoretain()
- Text(pstr,
- maxwidth=300,
- transition=Text.Transition.FADE_IN,
- scale=0.7,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=(0.5, 0.7, 0.5, 1),
- position=(0, 230)).autoretain()
+ pstr = ba.Lstr(
+ value='- ${A} -',
+ subs=[('${A}', ba.Lstr(resource='singlePlayerCountText'))],
+ )
+ ZoomText(
+ self._campaign.getlevel(self._level_name).displayname,
+ maxwidth=800,
+ flash=False,
+ trail=False,
+ color=(0.5, 1, 0.5, 1),
+ h_align='center',
+ scale=0.4,
+ position=(0, 292),
+ jitter=1.0,
+ ).autoretain()
+ Text(
+ pstr,
+ maxwidth=300,
+ transition=Text.Transition.FADE_IN,
+ scale=0.7,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=(0.5, 0.7, 0.5, 1),
+ position=(0, 230),
+ ).autoretain()
if ba.app.server is None:
# If we're running in normal non-headless build, show this text
# because only host can continue the game.
adisp = ba.internal.get_v1_account_display_string()
- txt = Text(ba.Lstr(resource='waitingForHostText',
- subs=[('${HOST}', adisp)]),
- maxwidth=300,
- transition=Text.Transition.FADE_IN,
- transition_delay=8.0,
- scale=0.85,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- color=(1, 1, 0, 1),
- position=(0, -230)).autoretain()
+ txt = Text(
+ ba.Lstr(
+ resource='waitingForHostText', subs=[('${HOST}', adisp)]
+ ),
+ maxwidth=300,
+ transition=Text.Transition.FADE_IN,
+ transition_delay=8.0,
+ scale=0.85,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ color=(1, 1, 0, 1),
+ position=(0, -230),
+ ).autoretain()
assert txt.node
txt.node.client_only = True
else:
# In headless build, anyone can continue the game.
sval = ba.Lstr(resource='pressAnyButtonPlayAgainText')
- Text(sval,
- v_attach=Text.VAttach.BOTTOM,
- h_align=Text.HAlign.CENTER,
- flash=True,
- vr_depth=50,
- position=(0, 60),
- 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(
+ sval,
+ v_attach=Text.VAttach.BOTTOM,
+ h_align=Text.HAlign.CENTER,
+ flash=True,
+ vr_depth=50,
+ position=(0, 60),
+ 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()
if self._score is not None:
- ba.timer(0.35,
- ba.Call(ba.playsound, self._score_display_sound_small))
+ ba.timer(
+ 0.35, ba.Call(ba.playsound, self._score_display_sound_small)
+ )
# Vestigial remain; this stuff should just be instance vars.
self._show_info = {}
@@ -672,51 +770,63 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ba.pushcall(ba.WeakCall(self._show_fail))
self._name_str = name_str = ', '.join(
- [p.name for p in self._playerinfos])
+ [p.name for p in self._playerinfos]
+ )
if self._show_friend_scores:
self._friends_loading_status = Text(
- ba.Lstr(value='${A}...',
- subs=[('${A}', ba.Lstr(resource='loadingText'))]),
+ ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
position=(-405, 150 + 30),
color=(1, 1, 1, 0.4),
transition=Text.Transition.FADE_IN,
scale=0.7,
- transition_delay=2.0)
- self._score_loading_status = Text(ba.Lstr(
- value='${A}...', subs=[('${A}', ba.Lstr(resource='loadingText'))]),
- position=(280, 150 + 30),
- color=(1, 1, 1, 0.4),
- transition=Text.Transition.FADE_IN,
- scale=0.7,
- transition_delay=2.0)
+ transition_delay=2.0,
+ )
+ self._score_loading_status = Text(
+ ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
+ position=(280, 150 + 30),
+ color=(1, 1, 1, 0.4),
+ transition=Text.Transition.FADE_IN,
+ scale=0.7,
+ transition_delay=2.0,
+ )
if self._score is not None:
ba.timer(0.4, ba.WeakCall(self._play_drumroll))
# Add us to high scores, filter, and store.
our_high_scores_all = self._campaign.getlevel(
- self._level_name).get_high_scores()
+ self._level_name
+ ).get_high_scores()
our_high_scores = our_high_scores_all.setdefault(
- str(len(self._playerinfos)) + ' Player', [])
+ str(len(self._playerinfos)) + ' Player', []
+ )
if self._score is not None:
our_score: list | None = [
- self._score, {
- 'players': [{
- 'name': p.name,
- 'character': p.character
- } for p in self._playerinfos]
- }
+ self._score,
+ {
+ 'players': [
+ {'name': p.name, 'character': p.character}
+ for p in self._playerinfos
+ ]
+ },
]
our_high_scores.append(our_score)
else:
our_score = None
try:
- our_high_scores.sort(reverse=self._score_order == 'increasing',
- key=lambda x: x[0])
+ our_high_scores.sort(
+ reverse=self._score_order == 'increasing', key=lambda x: x[0]
+ )
except Exception:
ba.print_exception('Error sorting scores.')
print(f'our_high_scores: {our_high_scores}')
@@ -724,15 +834,18 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
del our_high_scores[10:]
if self._score is not None:
- sver = (self._campaign.getlevel(
- self._level_name).get_score_version_string())
- ba.internal.add_transaction({
- 'type': 'SET_LEVEL_LOCAL_HIGH_SCORES',
- 'campaign': self._campaign.name,
- 'level': self._level_name,
- 'scoreVersion': sver,
- 'scores': our_high_scores_all
- })
+ sver = self._campaign.getlevel(
+ self._level_name
+ ).get_score_version_string()
+ ba.internal.add_transaction(
+ {
+ 'type': 'SET_LEVEL_LOCAL_HIGH_SCORES',
+ 'campaign': self._campaign.name,
+ 'level': self._level_name,
+ 'scoreVersion': sver,
+ 'scores': our_high_scores_all,
+ }
+ )
if ba.internal.get_v1_account_state() != 'signed_in':
# We expect this only in kiosk mode; complain otherwise.
if not (ba.app.demo_mode or ba.app.arcade_mode):
@@ -750,12 +863,14 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._score,
ba.WeakCall(self._got_score_results),
ba.WeakCall(self._got_friend_score_results)
- if self._show_friend_scores else None,
+ if self._show_friend_scores
+ else None,
order=self._score_order,
tournament_id=self.session.tournament_id,
score_type=self._score_type,
campaign=self._campaign.name,
- level=self._level_name)
+ level=self._level_name,
+ )
# Apply the transactions we've been adding locally.
ba.internal.run_transactions()
@@ -765,16 +880,19 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ts_height = 300
ts_h_offs = 210
v_offs = 40
- txt = Text(ba.Lstr(resource='tournamentStandingsText')
- if self.session.tournament_id is not None else ba.Lstr(
- resource='worldsBestScoresText') if self._score_type
- == 'points' else ba.Lstr(resource='worldsBestTimesText'),
- maxwidth=210,
- position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
- transition=Text.Transition.IN_LEFT,
- v_align=Text.VAlign.CENTER,
- scale=1.2,
- transition_delay=2.2).autoretain()
+ txt = Text(
+ ba.Lstr(resource='tournamentStandingsText')
+ if self.session.tournament_id is not None
+ else ba.Lstr(resource='worldsBestScoresText')
+ if self._score_type == 'points'
+ else ba.Lstr(resource='worldsBestTimesText'),
+ maxwidth=210,
+ position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
+ transition=Text.Transition.IN_LEFT,
+ v_align=Text.VAlign.CENTER,
+ scale=1.2,
+ transition_delay=2.2,
+ ).autoretain()
# If we've got a button on the server, only show this on clients.
if self._should_show_worlds_best_button():
@@ -788,14 +906,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ts_height = 300
ts_h_offs = -480
v_offs = 40
- txt = Text(ba.Lstr(resource='topFriendsText'),
- maxwidth=210,
- position=(ts_h_offs - 10,
- ts_height / 2 + 25 + v_offs + 20),
- transition=Text.Transition.IN_RIGHT,
- v_align=Text.VAlign.CENTER,
- scale=1.2,
- transition_delay=1.8).autoretain()
+ txt = Text(
+ ba.Lstr(resource='topFriendsText'),
+ maxwidth=210,
+ position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
+ transition=Text.Transition.IN_RIGHT,
+ v_align=Text.VAlign.CENTER,
+ scale=1.2,
+ transition_delay=1.8,
+ ).autoretain()
assert txt.node
txt.node.client_only = True
else:
@@ -803,14 +922,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
ts_height = 300
ts_h_offs = -480
v_offs = 40
- Text(ba.Lstr(resource='yourBestScoresText') if self._score_type
- == 'points' else ba.Lstr(resource='yourBestTimesText'),
- maxwidth=210,
- position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
- transition=Text.Transition.IN_RIGHT,
- v_align=Text.VAlign.CENTER,
- scale=1.2,
- transition_delay=1.8).autoretain()
+ Text(
+ ba.Lstr(resource='yourBestScoresText')
+ if self._score_type == 'points'
+ else ba.Lstr(resource='yourBestTimesText'),
+ maxwidth=210,
+ position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 20),
+ transition=Text.Transition.IN_RIGHT,
+ v_align=Text.VAlign.CENTER,
+ scale=1.2,
+ transition_delay=1.8,
+ ).autoretain()
display_scores = list(our_high_scores)
display_count = 5
@@ -835,21 +957,23 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
scale = 0.5
times: list[tuple[float, float]] = []
for i in range(display_count):
- times.insert(random.randrange(0,
- len(times) + 1),
- (1.9 + i * 0.05, 2.3 + i * 0.05))
+ times.insert(
+ random.randrange(0, len(times) + 1),
+ (1.9 + i * 0.05, 2.3 + i * 0.05),
+ )
for i in range(display_count):
try:
if display_scores[i][1] is None:
name_str = '-'
else:
# noinspection PyUnresolvedReferences
- name_str = ', '.join([
- p['name'] for p in display_scores[i][1]['players']
- ])
+ name_str = ', '.join(
+ [p['name'] for p in display_scores[i][1]['players']]
+ )
except Exception:
ba.print_exception(
- f'Error calcing name_str for {display_scores}')
+ f'Error calcing name_str for {display_scores}'
+ )
name_str = '-'
if display_scores[i] == our_score and not showed_ours:
flash = True
@@ -864,31 +988,49 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
color1 = (0.6, 0.6, 0.6, 1.0)
tdelay1 = times[i][0]
tdelay2 = times[i][1]
- Text(str(display_scores[i][0]) if self._score_type == 'points'
- else ba.timestring(display_scores[i][0] * 10,
- timeformat=ba.TimeFormat.MILLISECONDS,
- suppress_format_warning=True),
- position=(ts_h_offs + 20 + h_offs_extra,
- v_offs_extra + ts_height / 2 + -ts_height *
- (i + 1) / 10 + v_offs + 11.0),
- h_align=Text.HAlign.RIGHT,
- v_align=Text.VAlign.CENTER,
- color=color0,
- flash=flash,
- transition=Text.Transition.IN_RIGHT,
- transition_delay=tdelay1).autoretain()
+ Text(
+ str(display_scores[i][0])
+ if self._score_type == 'points'
+ else ba.timestring(
+ display_scores[i][0] * 10,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ suppress_format_warning=True,
+ ),
+ position=(
+ ts_h_offs + 20 + h_offs_extra,
+ v_offs_extra
+ + ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs
+ + 11.0,
+ ),
+ h_align=Text.HAlign.RIGHT,
+ v_align=Text.VAlign.CENTER,
+ color=color0,
+ flash=flash,
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=tdelay1,
+ ).autoretain()
- Text(ba.Lstr(value=name_str),
- position=(ts_h_offs + 35 + h_offs_extra,
- v_offs_extra + ts_height / 2 + -ts_height *
- (i + 1) / 10 + v_offs_names + v_offs + 11.0),
- maxwidth=80.0 + 100.0 * len(self._playerinfos),
- v_align=Text.VAlign.CENTER,
- color=color1,
- flash=flash,
- scale=scale,
- transition=Text.Transition.IN_RIGHT,
- transition_delay=tdelay2).autoretain()
+ Text(
+ ba.Lstr(value=name_str),
+ position=(
+ ts_h_offs + 35 + h_offs_extra,
+ v_offs_extra
+ + ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs_names
+ + v_offs
+ + 11.0,
+ ),
+ maxwidth=80.0 + 100.0 * len(self._playerinfos),
+ v_align=Text.VAlign.CENTER,
+ color=color1,
+ flash=flash,
+ scale=scale,
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=tdelay2,
+ ).autoretain()
# Show achievements for this level.
ts_height = -150
@@ -900,19 +1042,21 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# version, etc).
if self._have_achievements:
if not self._account_has_achievements:
- Text(ba.Lstr(resource='achievementsText'),
- position=(ts_h_offs - 10,
- ts_height / 2 + 25 + v_offs + 3),
- maxwidth=210,
- host_only=True,
- transition=Text.Transition.IN_RIGHT,
- v_align=Text.VAlign.CENTER,
- scale=1.2,
- transition_delay=2.8).autoretain()
+ Text(
+ ba.Lstr(resource='achievementsText'),
+ position=(ts_h_offs - 10, ts_height / 2 + 25 + v_offs + 3),
+ maxwidth=210,
+ host_only=True,
+ transition=Text.Transition.IN_RIGHT,
+ v_align=Text.VAlign.CENTER,
+ scale=1.2,
+ transition_delay=2.8,
+ ).autoretain()
assert self._game_name_str is not None
achievements = ba.app.ach.achievements_for_coop_level(
- self._game_name_str)
+ self._game_name_str
+ )
hval = -455
vval = -100
tdelay = 0.0
@@ -925,12 +1069,15 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
def _play_drumroll(self) -> None:
ba.NodeActor(
- ba.newnode('sound',
- attrs={
- 'sound': self.drum_roll_sound,
- 'positional': False,
- 'loop': False
- })).autoretain()
+ ba.newnode(
+ 'sound',
+ attrs={
+ 'sound': self.drum_roll_sound,
+ 'positional': False,
+ 'loop': False,
+ },
+ )
+ ).autoretain()
def _got_friend_score_results(self, results: list[Any] | None) -> None:
@@ -939,6 +1086,7 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
from efro.util import asserttype
+
# delay a bit if results come in too fast
assert self._begin_time is not None
base_delay = max(0, 1.9 - (ba.time() - self._begin_time))
@@ -955,7 +1103,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
color=(1, 1, 1, 0.4),
transition=Text.Transition.FADE_IN,
transition_delay=base_delay + 0.8,
- scale=0.7)
+ scale=0.7,
+ )
return
self._friends_loading_status = None
@@ -974,8 +1123,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
results.remove(score)
break
results.append(our_score_entry)
- results.sort(reverse=self._score_order == 'increasing',
- key=lambda x: asserttype(x[0], int))
+ results.sort(
+ reverse=self._score_order == 'increasing',
+ key=lambda x: asserttype(x[0], int),
+ )
# If we're not submitting our own score, we still want to change the
# name of our own score to 'Me'.
@@ -995,9 +1146,10 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
results = results[:5]
times: list[tuple[float, float]] = []
for i in range(len(results)):
- times.insert(random.randrange(0,
- len(times) + 1),
- (base_delay + i * 0.05, base_delay + 0.3 + i * 0.05))
+ times.insert(
+ random.randrange(0, len(times) + 1),
+ (base_delay + i * 0.05, base_delay + 0.3 + i * 0.05),
+ )
for i, tval in enumerate(results):
score = int(tval[0])
name_str = tval[1]
@@ -1019,33 +1171,50 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
tdelay1 = times[i][0]
tdelay2 = times[i][1]
if name_str != '-':
- Text(str(score) if self._score_type == 'points' else
- ba.timestring(score * 10,
- timeformat=ba.TimeFormat.MILLISECONDS),
- position=(ts_h_offs + 20 + h_offs_extra,
- v_offs_extra + ts_height / 2 + -ts_height *
- (i + 1) / 10 + v_offs + 11.0),
- h_align=Text.HAlign.RIGHT,
- v_align=Text.VAlign.CENTER,
- color=color0,
- flash=flash,
- transition=Text.Transition.IN_RIGHT,
- transition_delay=tdelay1).autoretain()
+ Text(
+ str(score)
+ if self._score_type == 'points'
+ else ba.timestring(
+ score * 10, timeformat=ba.TimeFormat.MILLISECONDS
+ ),
+ position=(
+ ts_h_offs + 20 + h_offs_extra,
+ v_offs_extra
+ + ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs
+ + 11.0,
+ ),
+ h_align=Text.HAlign.RIGHT,
+ v_align=Text.VAlign.CENTER,
+ color=color0,
+ flash=flash,
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=tdelay1,
+ ).autoretain()
else:
if is_me:
print('Error: got empty name_str on score result:', tval)
- Text(ba.Lstr(value=name_str),
- position=(ts_h_offs + 35 + h_offs_extra,
- v_offs_extra + ts_height / 2 + -ts_height *
- (i + 1) / 10 + v_offs_names + v_offs + 11.0),
- color=color1,
- maxwidth=160.0,
- v_align=Text.VAlign.CENTER,
- flash=flash,
- scale=scale,
- transition=Text.Transition.IN_RIGHT,
- transition_delay=tdelay2).autoretain()
+ Text(
+ ba.Lstr(value=name_str),
+ position=(
+ ts_h_offs + 35 + h_offs_extra,
+ v_offs_extra
+ + ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs_names
+ + v_offs
+ + 11.0,
+ ),
+ color=color1,
+ maxwidth=160.0,
+ v_align=Text.VAlign.CENTER,
+ flash=flash,
+ scale=scale,
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=tdelay2,
+ ).autoretain()
def _got_score_results(self, results: dict[str, Any] | None) -> None:
@@ -1071,16 +1240,20 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
color=(1, 1, 1, 0.4),
transition=Text.Transition.FADE_IN,
transition_delay=base_delay + 0.3,
- scale=0.7)
+ scale=0.7,
+ )
else:
self._score_link = results['link']
assert self._score_link is not None
# Prepend our master-server addr if its a relative addr.
- if (not self._score_link.startswith('http://')
- and not self._score_link.startswith('https://')):
+ if not self._score_link.startswith(
+ 'http://'
+ ) and not self._score_link.startswith('https://'):
self._score_link = (
- ba.internal.get_master_server_address() + '/' +
- self._score_link)
+ ba.internal.get_master_server_address()
+ + '/'
+ + self._score_link
+ )
self._score_loading_status = None
if 'tournamentSecondsRemaining' in results:
secs_remaining = results['tournamentSecondsRemaining']
@@ -1089,9 +1262,11 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
self._tournament_time_remaining_text_timer = ba.Timer(
1.0,
ba.WeakCall(
- self._update_tournament_time_remaining_text),
+ self._update_tournament_time_remaining_text
+ ),
repeat=True,
- timetype=ba.TimeType.BASE)
+ timetype=ba.TimeType.BASE,
+ )
assert self._show_info is not None
self._show_info['results'] = results
@@ -1101,11 +1276,13 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
else:
self._show_info['tops'] = []
offs_x = -195
- available = (self._show_info['results'] is not None)
+ available = self._show_info['results'] is not None
if self._score is not None:
- ba.timer((1.5 + base_delay),
- ba.WeakCall(self._show_world_rank, offs_x),
- timetype=ba.TimeType.BASE)
+ ba.timer(
+ (1.5 + base_delay),
+ ba.WeakCall(self._show_world_rank, offs_x),
+ timetype=ba.TimeType.BASE,
+ )
ts_h_offs = 200
ts_height = 300
@@ -1115,17 +1292,25 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# Show the number of games represented by this
# list (except for in tournaments).
if self.session.tournament_id is None:
- Text(ba.Lstr(resource='lastGamesText',
- subs=[
- ('${COUNT}',
- str(self._show_info['results']['total']))
- ]),
- position=(ts_h_offs - 35 + 95,
- ts_height / 2 + 6 + v_offs),
- color=(0.4, 0.4, 0.4, 1.0),
- scale=0.7,
- transition=Text.Transition.IN_RIGHT,
- transition_delay=base_delay + 0.3).autoretain()
+ Text(
+ ba.Lstr(
+ resource='lastGamesText',
+ subs=[
+ (
+ '${COUNT}',
+ str(self._show_info['results']['total']),
+ )
+ ],
+ ),
+ position=(
+ ts_h_offs - 35 + 95,
+ ts_height / 2 + 6 + v_offs,
+ ),
+ color=(0.4, 0.4, 0.4, 1.0),
+ scale=0.7,
+ transition=Text.Transition.IN_RIGHT,
+ transition_delay=base_delay + 0.3,
+ ).autoretain()
else:
v_offs += 20
@@ -1151,9 +1336,9 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
times: list[tuple[float, float]] = []
for i in range(len(self._show_info['tops'])):
times.insert(
- random.randrange(0,
- len(times) + 1),
- (base_delay + i * 0.05, base_delay + 0.4 + i * 0.05))
+ random.randrange(0, len(times) + 1),
+ (base_delay + i * 0.05, base_delay + 0.4 + i * 0.05),
+ )
for i, tval in enumerate(self._show_info['tops']):
score = int(tval[0])
name_str = tval[1]
@@ -1175,44 +1360,63 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
tdelay2 = times[i][1]
if name_str != '-':
- Text(str(score) if self._score_type == 'points' else
- ba.timestring(
- score * 10,
- timeformat=ba.TimeFormat.MILLISECONDS),
- position=(ts_h_offs + 20 + h_offs_extra,
- ts_height / 2 + -ts_height *
- (i + 1) / 10 + v_offs + 11.0),
- h_align=Text.HAlign.RIGHT,
- v_align=Text.VAlign.CENTER,
- color=color0,
- flash=flash,
- transition=Text.Transition.IN_LEFT,
- transition_delay=tdelay1).autoretain()
- Text(ba.Lstr(value=name_str),
- position=(ts_h_offs + 35 + h_offs_extra,
- ts_height / 2 + -ts_height * (i + 1) / 10 +
- v_offs_names + v_offs + 11.0),
- maxwidth=80.0 + 100.0 * len(self._playerinfos),
- v_align=Text.VAlign.CENTER,
- color=color1,
- flash=flash,
- scale=scale,
- transition=Text.Transition.IN_LEFT,
- transition_delay=tdelay2).autoretain()
+ Text(
+ str(score)
+ if self._score_type == 'points'
+ else ba.timestring(
+ score * 10,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ ),
+ position=(
+ ts_h_offs + 20 + h_offs_extra,
+ ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs
+ + 11.0,
+ ),
+ h_align=Text.HAlign.RIGHT,
+ v_align=Text.VAlign.CENTER,
+ color=color0,
+ flash=flash,
+ transition=Text.Transition.IN_LEFT,
+ transition_delay=tdelay1,
+ ).autoretain()
+ Text(
+ ba.Lstr(value=name_str),
+ position=(
+ ts_h_offs + 35 + h_offs_extra,
+ ts_height / 2
+ + -ts_height * (i + 1) / 10
+ + v_offs_names
+ + v_offs
+ + 11.0,
+ ),
+ maxwidth=80.0 + 100.0 * len(self._playerinfos),
+ v_align=Text.VAlign.CENTER,
+ color=color1,
+ flash=flash,
+ scale=scale,
+ transition=Text.Transition.IN_LEFT,
+ transition_delay=tdelay2,
+ ).autoretain()
def _show_tips(self) -> None:
from bastd.actor.tipstext import TipsText
+
TipsText(offs_y=30).autoretain()
def _update_tournament_time_remaining_text(self) -> None:
if self._tournament_time_remaining is None:
return
self._tournament_time_remaining = max(
- 0, self._tournament_time_remaining - 1)
+ 0, self._tournament_time_remaining - 1
+ )
if self._tournament_time_remaining_text is not None:
- val = ba.timestring(self._tournament_time_remaining,
- suppress_format_warning=True,
- centi=False)
+ val = ba.timestring(
+ self._tournament_time_remaining,
+ suppress_format_warning=True,
+ centi=False,
+ )
self._tournament_time_remaining_text.node.text = val
def _show_world_rank(self, offs_x: float) -> None:
@@ -1221,16 +1425,23 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
from ba.internal import get_tournament_prize_strings
+
assert self._show_info is not None
- available = (self._show_info['results'] is not None)
+ available = self._show_info['results'] is not None
if available:
- error = (self._show_info['results']['error']
- if 'error' in self._show_info['results'] else None)
+ error = (
+ self._show_info['results']['error']
+ if 'error' in self._show_info['results']
+ else None
+ )
rank = self._show_info['results']['rank']
total = self._show_info['results']['total']
- rating = (10.0 if total == 1 else 10.0 * (1.0 - (float(rank - 1) /
- (total - 1))))
+ rating = (
+ 10.0
+ if total == 1
+ else 10.0 * (1.0 - (float(rank - 1) / (total - 1)))
+ )
player_rank = self._show_info['results']['playerRank']
best_player_rank = self._show_info['results']['bestPlayerRank']
else:
@@ -1241,15 +1452,17 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
# If we've got tournament-seconds-remaining, show it.
if self._tournament_time_remaining is not None:
- Text(ba.Lstr(resource='coopSelectWindow.timeRemainingText'),
- position=(-360, -70 - 100),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.8,
- maxwidth=300,
- transition_delay=2.0).autoretain()
+ Text(
+ ba.Lstr(resource='coopSelectWindow.timeRemainingText'),
+ position=(-360, -70 - 100),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.8,
+ maxwidth=300,
+ transition_delay=2.0,
+ ).autoretain()
self._tournament_time_remaining_text = Text(
'',
position=(-360, -110 - 100),
@@ -1259,7 +1472,8 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
transition=Text.Transition.FADE_IN,
scale=1.6,
maxwidth=150,
- transition_delay=2.0)
+ transition_delay=2.0,
+ )
# If we're a tournament, show prizes.
try:
@@ -1267,105 +1481,135 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
if tournament_id is not None:
if tournament_id in ba.app.accounts_v1.tournament_info:
tourney_info = ba.app.accounts_v1.tournament_info[
- tournament_id]
+ tournament_id
+ ]
# pylint: disable=unbalanced-tuple-unpacking
- pr1, pv1, pr2, pv2, pr3, pv3 = (
- get_tournament_prize_strings(tourney_info))
+ pr1, pv1, pr2, pv2, pr3, pv3 = get_tournament_prize_strings(
+ tourney_info
+ )
# pylint: enable=unbalanced-tuple-unpacking
- Text(ba.Lstr(resource='coopSelectWindow.prizesText'),
- position=(-360, -70 + 77),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=1.0,
- maxwidth=300,
- transition_delay=2.0).autoretain()
+ Text(
+ ba.Lstr(resource='coopSelectWindow.prizesText'),
+ position=(-360, -70 + 77),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=1.0,
+ maxwidth=300,
+ transition_delay=2.0,
+ ).autoretain()
vval = -107 + 70
for rng, val in ((pr1, pv1), (pr2, pv2), (pr3, pv3)):
- Text(rng,
- position=(-410 + 10, vval),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.RIGHT,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.6,
- maxwidth=300,
- transition_delay=2.0).autoretain()
- Text(val,
- position=(-390 + 10, vval),
- color=(0.7, 0.7, 0.7, 1.0),
- h_align=Text.HAlign.LEFT,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.8,
- maxwidth=300,
- transition_delay=2.0).autoretain()
+ Text(
+ rng,
+ position=(-410 + 10, vval),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.RIGHT,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.6,
+ maxwidth=300,
+ transition_delay=2.0,
+ ).autoretain()
+ Text(
+ val,
+ position=(-390 + 10, vval),
+ color=(0.7, 0.7, 0.7, 1.0),
+ h_align=Text.HAlign.LEFT,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.8,
+ maxwidth=300,
+ transition_delay=2.0,
+ ).autoretain()
vval -= 35
except Exception:
ba.print_exception('Error showing prize ranges.')
if self._do_new_rating:
if error:
- ZoomText(ba.Lstr(resource='failText'),
- flash=True,
- trail=True,
- scale=1.0 if available else 0.333,
- tilt_translate=0.11,
- h_align='center',
- position=(190 + offs_x, -60),
- maxwidth=200,
- jitter=1.0).autoretain()
- Text(ba.Lstr(translate=('serverResponses', error)),
- position=(0, -140),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.9,
- maxwidth=400,
- transition_delay=1.0).autoretain()
+ ZoomText(
+ ba.Lstr(resource='failText'),
+ flash=True,
+ trail=True,
+ scale=1.0 if available else 0.333,
+ tilt_translate=0.11,
+ h_align='center',
+ position=(190 + offs_x, -60),
+ maxwidth=200,
+ jitter=1.0,
+ ).autoretain()
+ Text(
+ ba.Lstr(translate=('serverResponses', error)),
+ position=(0, -140),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.9,
+ maxwidth=400,
+ transition_delay=1.0,
+ ).autoretain()
else:
- ZoomText((('#' + str(player_rank)) if player_rank is not None
- else ba.Lstr(resource='unavailableText')),
- flash=True,
- trail=True,
- scale=1.0 if available else 0.333,
- tilt_translate=0.11,
- h_align='center',
- position=(190 + offs_x, -60),
- maxwidth=200,
- jitter=1.0).autoretain()
+ ZoomText(
+ (
+ ('#' + str(player_rank))
+ if player_rank is not None
+ else ba.Lstr(resource='unavailableText')
+ ),
+ flash=True,
+ trail=True,
+ scale=1.0 if available else 0.333,
+ tilt_translate=0.11,
+ h_align='center',
+ position=(190 + offs_x, -60),
+ maxwidth=200,
+ jitter=1.0,
+ ).autoretain()
- Text(ba.Lstr(value='${A}:',
- subs=[('${A}', ba.Lstr(resource='rankText'))]),
- position=(0, 36),
- maxwidth=300,
- transition=Text.Transition.FADE_IN,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition_delay=0).autoretain()
+ Text(
+ ba.Lstr(
+ value='${A}:',
+ subs=[('${A}', ba.Lstr(resource='rankText'))],
+ ),
+ position=(0, 36),
+ maxwidth=300,
+ transition=Text.Transition.FADE_IN,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition_delay=0,
+ ).autoretain()
if best_player_rank is not None:
- Text(ba.Lstr(resource='currentStandingText',
- fallback_resource='bestRankText',
- subs=[('${RANK}', str(best_player_rank))]),
- position=(0, -155),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.7,
- transition_delay=1.0).autoretain()
+ Text(
+ ba.Lstr(
+ resource='currentStandingText',
+ fallback_resource='bestRankText',
+ subs=[('${RANK}', str(best_player_rank))],
+ ),
+ position=(0, -155),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.7,
+ transition_delay=1.0,
+ ).autoretain()
else:
- ZoomText((f'{rating:.1f}' if available else ba.Lstr(
- resource='unavailableText')),
- flash=True,
- trail=True,
- scale=0.6 if available else 0.333,
- tilt_translate=0.11,
- h_align='center',
- position=(190 + offs_x, -94),
- maxwidth=200,
- jitter=1.0).autoretain()
+ ZoomText(
+ (
+ f'{rating:.1f}'
+ if available
+ else ba.Lstr(resource='unavailableText')
+ ),
+ flash=True,
+ trail=True,
+ scale=0.6 if available else 0.333,
+ tilt_translate=0.11,
+ h_align='center',
+ position=(190 + offs_x, -94),
+ maxwidth=200,
+ jitter=1.0,
+ ).autoretain()
if available:
if rating >= 9.5:
@@ -1380,56 +1624,68 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
star_x = 135 + offs_x
for _i in range(stars):
img = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': star_tex,
- 'position': (star_x, -16),
- 'scale': (62, 62),
- 'opacity': 1.0,
- 'color': (2.2, 1.2, 0.3),
- 'absolute_scale': True
- })).autoretain()
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': star_tex,
+ 'position': (star_x, -16),
+ 'scale': (62, 62),
+ 'opacity': 1.0,
+ 'color': (2.2, 1.2, 0.3),
+ 'absolute_scale': True,
+ },
+ )
+ ).autoretain()
assert img.node
ba.animate(img.node, 'opacity', {0.15: 0, 0.4: 1})
star_x += 60
for _i in range(3 - stars):
img = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': star_tex,
- 'position': (star_x, -16),
- 'scale': (62, 62),
- 'opacity': 1.0,
- 'color': (0.3, 0.3, 0.3),
- 'absolute_scale': True
- })).autoretain()
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': star_tex,
+ 'position': (star_x, -16),
+ 'scale': (62, 62),
+ 'opacity': 1.0,
+ 'color': (0.3, 0.3, 0.3),
+ 'absolute_scale': True,
+ },
+ )
+ ).autoretain()
assert img.node
ba.animate(img.node, 'opacity', {0.15: 0, 0.4: 1})
star_x += 60
- def dostar(count: int, xval: float, offs_y: float,
- score: str) -> None:
- Text(score + ' =',
- position=(xval, -64 + offs_y),
- color=(0.6, 0.6, 0.6, 0.6),
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.4,
- transition_delay=1.0).autoretain()
+ def dostar(
+ count: int, xval: float, offs_y: float, score: str
+ ) -> None:
+ Text(
+ score + ' =',
+ position=(xval, -64 + offs_y),
+ color=(0.6, 0.6, 0.6, 0.6),
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.4,
+ transition_delay=1.0,
+ ).autoretain()
stx = xval + 20
for _i2 in range(count):
img2 = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': star_tex,
- 'position': (stx, -64 + offs_y),
- 'scale': (12, 12),
- 'opacity': 0.7,
- 'color': (2.2, 1.2, 0.3),
- 'absolute_scale': True
- })).autoretain()
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': star_tex,
+ 'position': (stx, -64 + offs_y),
+ 'scale': (12, 12),
+ 'opacity': 0.7,
+ 'color': (2.2, 1.2, 0.3),
+ 'absolute_scale': True,
+ },
+ )
+ ).autoretain()
assert img2.node
ba.animate(img2.node, 'opacity', {1.0: 0.0, 1.5: 0.5})
stx += 13.0
@@ -1443,95 +1699,138 @@ class CoopScoreScreen(ba.Activity[ba.Player, ba.Team]):
best_rank = 0.0
if available:
- Text(ba.Lstr(
- resource='outOfText',
- subs=[('${RANK}',
- str(int(self._show_info['results']['rank']))),
- ('${ALL}', str(self._show_info['results']['total']))
- ]),
- position=(0, -155 if self._newly_complete else -145),
- color=(1, 1, 1, 0.7),
- h_align=Text.HAlign.CENTER,
- transition=Text.Transition.FADE_IN,
- scale=0.55,
- transition_delay=1.0).autoretain()
+ Text(
+ ba.Lstr(
+ resource='outOfText',
+ subs=[
+ (
+ '${RANK}',
+ str(int(self._show_info['results']['rank'])),
+ ),
+ (
+ '${ALL}',
+ str(self._show_info['results']['total']),
+ ),
+ ],
+ ),
+ position=(0, -155 if self._newly_complete else -145),
+ color=(1, 1, 1, 0.7),
+ h_align=Text.HAlign.CENTER,
+ transition=Text.Transition.FADE_IN,
+ scale=0.55,
+ transition_delay=1.0,
+ ).autoretain()
- new_best = (best_rank > self._old_best_rank and best_rank > 0.0)
- was_string = ba.Lstr(value=' ${A}',
- subs=[('${A}',
- ba.Lstr(resource='scoreWasText')),
- ('${COUNT}', str(self._old_best_rank))])
+ new_best = best_rank > self._old_best_rank and best_rank > 0.0
+ was_string = ba.Lstr(
+ value=' ${A}',
+ subs=[
+ ('${A}', ba.Lstr(resource='scoreWasText')),
+ ('${COUNT}', str(self._old_best_rank)),
+ ],
+ )
if not self._newly_complete:
- Text(ba.Lstr(value='${A}${B}',
- subs=[('${A}',
- ba.Lstr(resource='newPersonalBestText')),
- ('${B}', was_string)]) if new_best else
- ba.Lstr(resource='bestRatingText',
- subs=[('${RATING}', str(best_rank))]),
- position=(0, -165),
- color=(1, 1, 1, 0.7),
- flash=new_best,
- h_align=Text.HAlign.CENTER,
- transition=(Text.Transition.IN_RIGHT
- if new_best else Text.Transition.FADE_IN),
- scale=0.5,
- transition_delay=1.0).autoretain()
+ Text(
+ ba.Lstr(
+ value='${A}${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='newPersonalBestText')),
+ ('${B}', was_string),
+ ],
+ )
+ if new_best
+ else ba.Lstr(
+ resource='bestRatingText',
+ subs=[('${RATING}', str(best_rank))],
+ ),
+ position=(0, -165),
+ color=(1, 1, 1, 0.7),
+ flash=new_best,
+ h_align=Text.HAlign.CENTER,
+ transition=(
+ Text.Transition.IN_RIGHT
+ if new_best
+ else Text.Transition.FADE_IN
+ ),
+ scale=0.5,
+ transition_delay=1.0,
+ ).autoretain()
- Text(ba.Lstr(value='${A}:',
- subs=[('${A}', ba.Lstr(resource='ratingText'))]),
- position=(0, 36),
- maxwidth=300,
- transition=Text.Transition.FADE_IN,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition_delay=0).autoretain()
+ Text(
+ ba.Lstr(
+ value='${A}:',
+ subs=[('${A}', ba.Lstr(resource='ratingText'))],
+ ),
+ position=(0, 36),
+ maxwidth=300,
+ transition=Text.Transition.FADE_IN,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition_delay=0,
+ ).autoretain()
ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound))
if not error:
ba.timer(0.35, ba.Call(ba.playsound, self.cymbal_sound))
def _show_fail(self) -> None:
- ZoomText(ba.Lstr(resource='failText'),
- maxwidth=300,
- flash=False,
- trail=True,
- h_align='center',
- tilt_translate=0.11,
- position=(0, 40),
- jitter=1.0).autoretain()
+ ZoomText(
+ ba.Lstr(resource='failText'),
+ maxwidth=300,
+ flash=False,
+ trail=True,
+ h_align='center',
+ tilt_translate=0.11,
+ position=(0, 40),
+ jitter=1.0,
+ ).autoretain()
if self._fail_message is not None:
- Text(self._fail_message,
- h_align=Text.HAlign.CENTER,
- position=(0, -130),
- maxwidth=300,
- color=(1, 1, 1, 0.5),
- transition=Text.Transition.FADE_IN,
- transition_delay=1.0).autoretain()
+ Text(
+ self._fail_message,
+ h_align=Text.HAlign.CENTER,
+ position=(0, -130),
+ maxwidth=300,
+ color=(1, 1, 1, 0.5),
+ transition=Text.Transition.FADE_IN,
+ transition_delay=1.0,
+ ).autoretain()
ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound))
def _show_score_val(self, offs_x: float) -> None:
assert self._score_type is not None
assert self._score is not None
- ZoomText((str(self._score) if self._score_type == 'points' else
- ba.timestring(self._score * 10,
- timeformat=ba.TimeFormat.MILLISECONDS)),
- maxwidth=300,
- flash=True,
- trail=True,
- scale=1.0 if self._score_type == 'points' else 0.6,
- h_align='center',
- tilt_translate=0.11,
- position=(190 + offs_x, 115),
- jitter=1.0).autoretain()
- Text(ba.Lstr(
- value='${A}:', subs=[('${A}', ba.Lstr(
- resource='finalScoreText'))]) if self._score_type == 'points'
- else ba.Lstr(value='${A}:',
- subs=[('${A}', ba.Lstr(resource='finalTimeText'))]),
- maxwidth=300,
- position=(0, 200),
- transition=Text.Transition.FADE_IN,
- h_align=Text.HAlign.CENTER,
- v_align=Text.VAlign.CENTER,
- transition_delay=0).autoretain()
+ ZoomText(
+ (
+ str(self._score)
+ if self._score_type == 'points'
+ else ba.timestring(
+ self._score * 10, timeformat=ba.TimeFormat.MILLISECONDS
+ )
+ ),
+ maxwidth=300,
+ flash=True,
+ trail=True,
+ scale=1.0 if self._score_type == 'points' else 0.6,
+ h_align='center',
+ tilt_translate=0.11,
+ position=(190 + offs_x, 115),
+ jitter=1.0,
+ ).autoretain()
+ Text(
+ ba.Lstr(
+ value='${A}:',
+ subs=[('${A}', ba.Lstr(resource='finalScoreText'))],
+ )
+ if self._score_type == 'points'
+ else ba.Lstr(
+ value='${A}:',
+ subs=[('${A}', ba.Lstr(resource='finalTimeText'))],
+ ),
+ maxwidth=300,
+ position=(0, 200),
+ transition=Text.Transition.FADE_IN,
+ h_align=Text.HAlign.CENTER,
+ v_align=Text.VAlign.CENTER,
+ transition_delay=0,
+ ).autoretain()
ba.timer(0.35, ba.Call(ba.playsound, self._score_display_sound))
diff --git a/assets/src/ba_data/python/bastd/activity/drawscore.py b/assets/src/ba_data/python/bastd/activity/drawscore.py
index 38109f7b..c9a8c95f 100644
--- a/assets/src/ba_data/python/bastd/activity/drawscore.py
+++ b/assets/src/ba_data/python/bastd/activity/drawscore.py
@@ -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))
diff --git a/assets/src/ba_data/python/bastd/activity/dualteamscore.py b/assets/src/ba_data/python/bastd/activity/dualteamscore.py
index aae7fdeb..d00bae88 100644
--- a/assets/src/ba_data/python/bastd/activity/dualteamscore.py
+++ b/assets/src/ba_data/python/bastd/activity/dualteamscore.py
@@ -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()
diff --git a/assets/src/ba_data/python/bastd/activity/freeforallvictory.py b/assets/src/ba_data/python/bastd/activity/freeforallvictory.py
index 5161c2be..d4a5c584 100644
--- a/assets/src/ba_data/python/bastd/activity/freeforallvictory.py
+++ b/assets/src/ba_data/python/bastd/activity/freeforallvictory.py
@@ -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)
diff --git a/assets/src/ba_data/python/bastd/activity/multiteamjoin.py b/assets/src/ba_data/python/bastd/activity/multiteamjoin.py
index 92e75f86..ae2e7e52 100644
--- a/assets/src/ba_data/python/bastd/activity/multiteamjoin.py
+++ b/assets/src/ba_data/python/bastd/activity/multiteamjoin.py
@@ -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()
diff --git a/assets/src/ba_data/python/bastd/activity/multiteamscore.py b/assets/src/ba_data/python/bastd/activity/multiteamscore.py
index 3eac9b7f..c053660f 100644
--- a/assets/src/ba_data/python/bastd/activity/multiteamscore.py
+++ b/assets/src/ba_data/python/bastd/activity/multiteamscore.py
@@ -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,
+ )
diff --git a/assets/src/ba_data/python/bastd/activity/multiteamvictory.py b/assets/src/ba_data/python/bastd/activity/multiteamvictory.py
index 8c4a60cc..5ce4d466 100644
--- a/assets/src/ba_data/python/bastd/activity/multiteamvictory.py
+++ b/assets/src/ba_data/python/bastd/activity/multiteamvictory.py
@@ -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()
diff --git a/assets/src/ba_data/python/bastd/actor/background.py b/assets/src/ba_data/python/bastd/actor/background.py
index f9c486ba..4c99717a 100644
--- a/assets/src/ba_data/python/bastd/actor/background.py
+++ b/assets/src/ba_data/python/bastd/actor/background.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py
index 626a25ae..43becf1c 100644
--- a/assets/src/ba_data/python/bastd/actor/bomb.py
+++ b/assets/src/ba_data/python/bastd/actor/bomb.py
@@ -172,11 +172,13 @@ class BombFactory:
self.debris_fall_sound = ba.getsound('debrisFall')
self.wood_debris_fall_sound = ba.getsound('woodDebrisFall')
- self.explode_sounds = (ba.getsound('explosion01'),
- ba.getsound('explosion02'),
- ba.getsound('explosion03'),
- ba.getsound('explosion04'),
- ba.getsound('explosion05'))
+ self.explode_sounds = (
+ ba.getsound('explosion01'),
+ ba.getsound('explosion02'),
+ ba.getsound('explosion03'),
+ ba.getsound('explosion04'),
+ ba.getsound('explosion05'),
+ )
self.freeze_sound = ba.getsound('freeze')
self.fuse_sound = ba.getsound('fuse01')
@@ -209,8 +211,9 @@ class BombFactory:
actions=('modify_part_collision', 'use_node_collide', False),
)
- self.bomb_material.add_actions(actions=('modify_part_collision',
- 'friction', 0.3))
+ self.bomb_material.add_actions(
+ actions=('modify_part_collision', 'friction', 0.3)
+ )
self.land_mine_no_explode_material = ba.Material()
self.land_mine_blast_material = ba.Material()
@@ -220,11 +223,13 @@ class BombFactory:
'and',
('they_are_older_than', 200),
'and',
- ('eval_colliding', ),
+ ('eval_colliding',),
'and',
(
- ('they_dont_have_material',
- self.land_mine_no_explode_material),
+ (
+ 'they_dont_have_material',
+ self.land_mine_no_explode_material,
+ ),
'and',
(
('they_have_material', shared.object_material),
@@ -243,7 +248,7 @@ class BombFactory:
'and',
('they_are_older_than', 200),
'and',
- ('eval_colliding', ),
+ ('eval_colliding',),
'and',
(
('they_have_material', shared.footing_material),
@@ -264,8 +269,10 @@ class BombFactory:
),
)
- self.dink_sounds = (ba.getsound('bombDrop01'),
- ba.getsound('bombDrop02'))
+ self.dink_sounds = (
+ ba.getsound('bombDrop01'),
+ ba.getsound('bombDrop02'),
+ )
self.sticky_impact_sound = ba.getsound('stickyImpact')
self.roll_sound = ba.getsound('bombRoll01')
@@ -275,12 +282,15 @@ class BombFactory:
actions=(
('impact_sound', self.dink_sounds, 2, 0.8),
('roll_sound', self.roll_sound, 3, 6),
- ))
+ ),
+ )
- self.sticky_material.add_actions(actions=(('modify_part_collision',
- 'stiffness', 0.1),
- ('modify_part_collision',
- 'damping', 1.0)))
+ self.sticky_material.add_actions(
+ actions=(
+ ('modify_part_collision', 'stiffness', 0.1),
+ ('modify_part_collision', 'damping', 1.0),
+ )
+ )
self.sticky_material.add_actions(
conditions=(
@@ -322,14 +332,16 @@ class Blast(ba.Actor):
category: Gameplay Classes
"""
- def __init__(self,
- position: Sequence[float] = (0.0, 1.0, 0.0),
- velocity: Sequence[float] = (0.0, 0.0, 0.0),
- blast_radius: float = 2.0,
- blast_type: str = 'normal',
- source_player: ba.Player | None = None,
- hit_type: str = 'explosion',
- hit_subtype: str = 'normal'):
+ def __init__(
+ self,
+ position: Sequence[float] = (0.0, 1.0, 0.0),
+ velocity: Sequence[float] = (0.0, 0.0, 0.0),
+ blast_radius: float = 2.0,
+ blast_type: str = 'normal',
+ source_player: ba.Player | None = None,
+ hit_type: str = 'explosion',
+ hit_subtype: str = 'normal',
+ ):
"""Instantiate with given values."""
# bah; get off my lawn!
@@ -356,7 +368,7 @@ class Blast(ba.Actor):
'position': (position[0], position[1] - 0.1, position[2]),
'scale': (self.radius, self.radius, self.radius),
'type': 'sphere',
- 'materials': rmats
+ 'materials': rmats,
},
)
@@ -364,44 +376,54 @@ class Blast(ba.Actor):
# Throw in an explosion and flash.
evel = (velocity[0], max(-1.0, velocity[1]), velocity[2])
- explosion = ba.newnode('explosion',
- attrs={
- 'position': position,
- 'velocity': evel,
- 'radius': self.radius,
- 'big': (self.blast_type == 'tnt')
- })
+ explosion = ba.newnode(
+ 'explosion',
+ attrs={
+ 'position': position,
+ 'velocity': evel,
+ 'radius': self.radius,
+ 'big': (self.blast_type == 'tnt'),
+ },
+ )
if self.blast_type == 'ice':
explosion.color = (0, 0.05, 0.4)
ba.timer(1.0, explosion.delete)
if self.blast_type != 'ice':
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(1.0 + random.random() * 4),
- emit_type='tendrils',
- tendril_type='thin_smoke')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 4),
- emit_type='tendrils',
- tendril_type='ice' if self.blast_type == 'ice' else 'smoke')
- ba.emitfx(position=position,
- emit_type='distortion',
- spread=1.0 if self.blast_type == 'tnt' else 2.0)
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(1.0 + random.random() * 4),
+ emit_type='tendrils',
+ tendril_type='thin_smoke',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 4),
+ emit_type='tendrils',
+ tendril_type='ice' if self.blast_type == 'ice' else 'smoke',
+ )
+ ba.emitfx(
+ position=position,
+ emit_type='distortion',
+ spread=1.0 if self.blast_type == 'tnt' else 2.0,
+ )
# And emit some shrapnel.
if self.blast_type == 'ice':
def emit() -> None:
- ba.emitfx(position=position,
- velocity=velocity,
- count=30,
- spread=2.0,
- scale=0.4,
- chunk_type='ice',
- emit_type='stickers')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=30,
+ spread=2.0,
+ scale=0.4,
+ chunk_type='ice',
+ emit_type='stickers',
+ )
# It looks better if we delay a bit.
ba.timer(0.05, emit)
@@ -409,35 +431,45 @@ class Blast(ba.Actor):
elif self.blast_type == 'sticky':
def emit() -> None:
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- spread=0.7,
- chunk_type='slime')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- scale=0.5,
- spread=0.7,
- chunk_type='slime')
- ba.emitfx(position=position,
- velocity=velocity,
- count=15,
- scale=0.6,
- chunk_type='slime',
- emit_type='stickers')
- ba.emitfx(position=position,
- velocity=velocity,
- count=20,
- scale=0.7,
- chunk_type='spark',
- emit_type='stickers')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(6.0 + random.random() * 12),
- scale=0.8,
- spread=1.5,
- chunk_type='spark')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ spread=0.7,
+ chunk_type='slime',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ scale=0.5,
+ spread=0.7,
+ chunk_type='slime',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=15,
+ scale=0.6,
+ chunk_type='slime',
+ emit_type='stickers',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=20,
+ scale=0.7,
+ chunk_type='spark',
+ emit_type='stickers',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(6.0 + random.random() * 12),
+ scale=0.8,
+ spread=1.5,
+ chunk_type='spark',
+ )
# It looks better if we delay a bit.
ba.timer(0.05, emit)
@@ -445,28 +477,36 @@ class Blast(ba.Actor):
elif self.blast_type == 'impact':
def emit() -> None:
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- scale=0.8,
- chunk_type='metal')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- scale=0.4,
- chunk_type='metal')
- ba.emitfx(position=position,
- velocity=velocity,
- count=20,
- scale=0.7,
- chunk_type='spark',
- emit_type='stickers')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(8.0 + random.random() * 15),
- scale=0.8,
- spread=1.5,
- chunk_type='spark')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ scale=0.8,
+ chunk_type='metal',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ scale=0.4,
+ chunk_type='metal',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=20,
+ scale=0.7,
+ chunk_type='spark',
+ emit_type='stickers',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(8.0 + random.random() * 15),
+ scale=0.8,
+ spread=1.5,
+ chunk_type='spark',
+ )
# It looks better if we delay a bit.
ba.timer(0.05, emit)
@@ -475,38 +515,48 @@ class Blast(ba.Actor):
def emit() -> None:
if self.blast_type != 'tnt':
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- chunk_type='rock')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(4.0 + random.random() * 8),
- scale=0.5,
- chunk_type='rock')
- ba.emitfx(position=position,
- velocity=velocity,
- count=30,
- scale=1.0 if self.blast_type == 'tnt' else 0.7,
- chunk_type='spark',
- emit_type='stickers')
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(18.0 + random.random() * 20),
- scale=1.0 if self.blast_type == 'tnt' else 0.8,
- spread=1.5,
- chunk_type='spark')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ chunk_type='rock',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(4.0 + random.random() * 8),
+ scale=0.5,
+ chunk_type='rock',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=30,
+ scale=1.0 if self.blast_type == 'tnt' else 0.7,
+ chunk_type='spark',
+ emit_type='stickers',
+ )
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(18.0 + random.random() * 20),
+ scale=1.0 if self.blast_type == 'tnt' else 0.8,
+ spread=1.5,
+ chunk_type='spark',
+ )
# TNT throws splintery chunks.
if self.blast_type == 'tnt':
def emit_splinters() -> None:
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(20.0 + random.random() * 25),
- scale=0.8,
- spread=1.0,
- chunk_type='splinter')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(20.0 + random.random() * 25),
+ scale=0.8,
+ spread=1.0,
+ chunk_type='splinter',
+ )
ba.timer(0.01, emit_splinters)
@@ -514,26 +564,29 @@ class Blast(ba.Actor):
if self.blast_type == 'tnt' or random.random() < 0.1:
def emit_extra_sparks() -> None:
- ba.emitfx(position=position,
- velocity=velocity,
- count=int(10.0 + random.random() * 20),
- scale=0.8,
- spread=1.5,
- chunk_type='spark')
+ ba.emitfx(
+ position=position,
+ velocity=velocity,
+ count=int(10.0 + random.random() * 20),
+ scale=0.8,
+ spread=1.5,
+ chunk_type='spark',
+ )
ba.timer(0.02, emit_extra_sparks)
# It looks better if we delay a bit.
ba.timer(0.05, emit)
- lcolor = ((0.6, 0.6, 1.0) if self.blast_type == 'ice' else
- (1, 0.3, 0.1))
- light = ba.newnode('light',
- attrs={
- 'position': position,
- 'volume_intensity_scale': 10.0,
- 'color': lcolor
- })
+ lcolor = (0.6, 0.6, 1.0) if self.blast_type == 'ice' else (1, 0.3, 0.1)
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': position,
+ 'volume_intensity_scale': 10.0,
+ 'color': lcolor,
+ },
+ )
scl = random.uniform(0.6, 0.9)
scorch_radius = light_radius = self.radius
@@ -544,7 +597,9 @@ class Blast(ba.Actor):
iscale = 1.6
ba.animate(
- light, 'intensity', {
+ light,
+ 'intensity',
+ {
0: 2.0 * iscale,
scl * 0.02: 0.1 * iscale,
scl * 0.025: 0.2 * iscale,
@@ -553,25 +608,31 @@ class Blast(ba.Actor):
scl * 0.08: 4.0 * iscale,
scl * 0.2: 0.6 * iscale,
scl * 2.0: 0.00 * iscale,
- scl * 3.0: 0.0
- })
+ scl * 3.0: 0.0,
+ },
+ )
ba.animate(
- light, 'radius', {
+ light,
+ 'radius',
+ {
0: light_radius * 0.2,
scl * 0.05: light_radius * 0.55,
scl * 0.1: light_radius * 0.3,
scl * 0.3: light_radius * 0.15,
- scl * 1.0: light_radius * 0.05
- })
+ scl * 1.0: light_radius * 0.05,
+ },
+ )
ba.timer(scl * 3.0, light.delete)
# Make a scorch that fades over time.
- scorch = ba.newnode('scorch',
- attrs={
- 'position': position,
- 'size': scorch_radius * 0.5,
- 'big': (self.blast_type == 'tnt')
- })
+ scorch = ba.newnode(
+ 'scorch',
+ attrs={
+ 'position': position,
+ 'size': scorch_radius * 0.5,
+ 'big': (self.blast_type == 'tnt'),
+ },
+ )
if self.blast_type == 'ice':
scorch.color = (1, 1, 1.5)
@@ -622,17 +683,20 @@ class Blast(ba.Actor):
mag *= 2.0
node.handlemessage(
- ba.HitMessage(pos=nodepos,
- velocity=(0, 0, 0),
- magnitude=mag,
- hit_type=self.hit_type,
- hit_subtype=self.hit_subtype,
- radius=self.radius,
- source_player=ba.existing(self._source_player)))
+ ba.HitMessage(
+ pos=nodepos,
+ velocity=(0, 0, 0),
+ magnitude=mag,
+ hit_type=self.hit_type,
+ hit_subtype=self.hit_subtype,
+ radius=self.radius,
+ source_player=ba.existing(self._source_player),
+ )
+ )
if self.blast_type == 'ice':
- ba.playsound(BombFactory.get().freeze_sound,
- 10,
- position=nodepos)
+ ba.playsound(
+ BombFactory.get().freeze_sound, 10, position=nodepos
+ )
node.handlemessage(ba.FreezeMessage())
else:
@@ -651,14 +715,16 @@ class Bomb(ba.Actor):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
- def __init__(self,
- position: Sequence[float] = (0.0, 1.0, 0.0),
- velocity: Sequence[float] = (0.0, 0.0, 0.0),
- bomb_type: str = 'normal',
- blast_radius: float = 2.0,
- bomb_scale: float = 1.0,
- source_player: ba.Player | None = None,
- owner: ba.Node | None = None):
+ def __init__(
+ self,
+ position: Sequence[float] = (0.0, 1.0, 0.0),
+ velocity: Sequence[float] = (0.0, 0.0, 0.0),
+ bomb_type: str = 'normal',
+ blast_radius: float = 2.0,
+ bomb_scale: float = 1.0,
+ source_player: ba.Player | None = None,
+ owner: ba.Node | None = None,
+ ):
"""Create a new Bomb.
bomb_type can be 'ice','impact','land_mine','normal','sticky', or
@@ -670,8 +736,14 @@ class Bomb(ba.Actor):
shared = SharedObjects.get()
factory = BombFactory.get()
- if bomb_type not in ('ice', 'impact', 'land_mine', 'normal', 'sticky',
- 'tnt'):
+ if bomb_type not in (
+ 'ice',
+ 'impact',
+ 'land_mine',
+ 'normal',
+ 'sticky',
+ 'tnt',
+ ):
raise ValueError('invalid bomb type: ' + bomb_type)
self.bomb_type = bomb_type
@@ -718,78 +790,88 @@ class Bomb(ba.Actor):
# wanna add this even in the tnt case?
materials: tuple[ba.Material, ...]
if self.bomb_type == 'tnt':
- materials = (factory.bomb_material, shared.footing_material,
- shared.object_material)
+ materials = (
+ factory.bomb_material,
+ shared.footing_material,
+ shared.object_material,
+ )
else:
materials = (factory.bomb_material, shared.object_material)
if self.bomb_type == 'impact':
- materials = materials + (factory.impact_blast_material, )
+ materials = materials + (factory.impact_blast_material,)
elif self.bomb_type == 'land_mine':
- materials = materials + (factory.land_mine_no_explode_material, )
+ materials = materials + (factory.land_mine_no_explode_material,)
if self.bomb_type == 'sticky':
- materials = materials + (factory.sticky_material, )
+ materials = materials + (factory.sticky_material,)
else:
- materials = materials + (factory.normal_sound_material, )
+ materials = materials + (factory.normal_sound_material,)
if self.bomb_type == 'land_mine':
fuse_time = None
- self.node = ba.newnode('prop',
- delegate=self,
- attrs={
- 'position': position,
- 'velocity': velocity,
- 'model': factory.land_mine_model,
- 'light_model': factory.land_mine_model,
- 'body': 'landMine',
- 'body_scale': self.scale,
- 'shadow_size': 0.44,
- 'color_texture': factory.land_mine_tex,
- 'reflection': 'powerup',
- 'reflection_scale': [1.0],
- 'materials': materials
- })
+ self.node = ba.newnode(
+ 'prop',
+ delegate=self,
+ attrs={
+ 'position': position,
+ 'velocity': velocity,
+ 'model': factory.land_mine_model,
+ 'light_model': factory.land_mine_model,
+ 'body': 'landMine',
+ 'body_scale': self.scale,
+ 'shadow_size': 0.44,
+ 'color_texture': factory.land_mine_tex,
+ 'reflection': 'powerup',
+ 'reflection_scale': [1.0],
+ 'materials': materials,
+ },
+ )
elif self.bomb_type == 'tnt':
fuse_time = None
- self.node = ba.newnode('prop',
- delegate=self,
- attrs={
- 'position': position,
- 'velocity': velocity,
- 'model': factory.tnt_model,
- 'light_model': factory.tnt_model,
- 'body': 'crate',
- 'body_scale': self.scale,
- 'shadow_size': 0.5,
- 'color_texture': factory.tnt_tex,
- 'reflection': 'soft',
- 'reflection_scale': [0.23],
- 'materials': materials
- })
+ self.node = ba.newnode(
+ 'prop',
+ delegate=self,
+ attrs={
+ 'position': position,
+ 'velocity': velocity,
+ 'model': factory.tnt_model,
+ 'light_model': factory.tnt_model,
+ 'body': 'crate',
+ 'body_scale': self.scale,
+ 'shadow_size': 0.5,
+ 'color_texture': factory.tnt_tex,
+ 'reflection': 'soft',
+ 'reflection_scale': [0.23],
+ 'materials': materials,
+ },
+ )
elif self.bomb_type == 'impact':
fuse_time = 20.0
- self.node = ba.newnode('prop',
- delegate=self,
- attrs={
- 'position': position,
- 'velocity': velocity,
- 'body': 'sphere',
- 'body_scale': self.scale,
- 'model': factory.impact_bomb_model,
- 'shadow_size': 0.3,
- 'color_texture': factory.impact_tex,
- 'reflection': 'powerup',
- 'reflection_scale': [1.5],
- 'materials': materials
- })
+ self.node = ba.newnode(
+ 'prop',
+ delegate=self,
+ attrs={
+ 'position': position,
+ 'velocity': velocity,
+ 'body': 'sphere',
+ 'body_scale': self.scale,
+ 'model': factory.impact_bomb_model,
+ 'shadow_size': 0.3,
+ 'color_texture': factory.impact_tex,
+ 'reflection': 'powerup',
+ 'reflection_scale': [1.5],
+ 'materials': materials,
+ },
+ )
self.arm_timer = ba.Timer(
- 0.2, ba.WeakCall(self.handlemessage, ArmMessage()))
+ 0.2, ba.WeakCall(self.handlemessage, ArmMessage())
+ )
self.warn_timer = ba.Timer(
- fuse_time - 1.7, ba.WeakCall(self.handlemessage,
- WarnMessage()))
+ fuse_time - 1.7, ba.WeakCall(self.handlemessage, WarnMessage())
+ )
else:
fuse_time = 3.0
@@ -809,49 +891,55 @@ class Bomb(ba.Actor):
tex = factory.sticky_tex
else:
tex = factory.regular_tex
- self.node = ba.newnode('bomb',
- delegate=self,
- attrs={
- 'position': position,
- 'velocity': velocity,
- 'model': model,
- 'body_scale': self.scale,
- 'shadow_size': 0.3,
- 'color_texture': tex,
- 'sticky': sticky,
- 'owner': owner,
- 'reflection': rtype,
- 'reflection_scale': [rscale],
- 'materials': materials
- })
+ self.node = ba.newnode(
+ 'bomb',
+ delegate=self,
+ attrs={
+ 'position': position,
+ 'velocity': velocity,
+ 'model': model,
+ 'body_scale': self.scale,
+ 'shadow_size': 0.3,
+ 'color_texture': tex,
+ 'sticky': sticky,
+ 'owner': owner,
+ 'reflection': rtype,
+ 'reflection_scale': [rscale],
+ 'materials': materials,
+ },
+ )
- sound = ba.newnode('sound',
- owner=self.node,
- attrs={
- 'sound': factory.fuse_sound,
- 'volume': 0.25
- })
+ sound = ba.newnode(
+ 'sound',
+ owner=self.node,
+ attrs={'sound': factory.fuse_sound, 'volume': 0.25},
+ )
self.node.connectattr('position', sound, 'position')
ba.animate(self.node, 'fuse_length', {0.0: 1.0, fuse_time: 0.0})
# Light the fuse!!!
if self.bomb_type not in ('land_mine', 'tnt'):
assert fuse_time is not None
- ba.timer(fuse_time,
- ba.WeakCall(self.handlemessage, ExplodeMessage()))
+ ba.timer(
+ fuse_time, ba.WeakCall(self.handlemessage, ExplodeMessage())
+ )
- ba.animate(self.node, 'model_scale', {
- 0: 0,
- 0.2: 1.3 * self.scale,
- 0.26: self.scale
- })
+ ba.animate(
+ self.node,
+ 'model_scale',
+ {0: 0, 0.2: 1.3 * self.scale, 0.26: self.scale},
+ )
- 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
- return (player if isinstance(player, playertype) and player.exists()
- else None)
+ return (
+ player
+ if isinstance(player, playertype) and player.exists()
+ else None
+ )
def on_expire(self) -> None:
super().on_expire()
@@ -876,17 +964,22 @@ class Bomb(ba.Actor):
# throwing/etc.)
node_delegate = node.getdelegate(object)
if node:
- if (self.bomb_type == 'impact' and
- (node is self.owner or
- (isinstance(node_delegate, Bomb) and node_delegate.bomb_type
- == 'impact' and node_delegate.owner is self.owner))):
+ if self.bomb_type == 'impact' and (
+ node is self.owner
+ or (
+ isinstance(node_delegate, Bomb)
+ and node_delegate.bomb_type == 'impact'
+ and node_delegate.owner is self.owner
+ )
+ ):
return
self.handlemessage(ExplodeMessage())
def _handle_dropped(self) -> None:
if self.bomb_type == 'land_mine':
self.arm_timer = ba.Timer(
- 1.25, ba.WeakCall(self.handlemessage, ArmMessage()))
+ 1.25, ba.WeakCall(self.handlemessage, ArmMessage())
+ )
# Once we've thrown a sticky bomb we can stick to it.
elif self.bomb_type == 'sticky':
@@ -899,13 +992,17 @@ class Bomb(ba.Actor):
def _handle_splat(self) -> None:
node = ba.getcollision().opposingnode
- if (node is not self.owner
- and ba.time() - self._last_sticky_sound_time > 1.0):
+ if (
+ node is not self.owner
+ and ba.time() - self._last_sticky_sound_time > 1.0
+ ):
self._last_sticky_sound_time = ba.time()
assert self.node
- ba.playsound(BombFactory.get().sticky_impact_sound,
- 2.0,
- position=self.node.position)
+ ba.playsound(
+ BombFactory.get().sticky_impact_sound,
+ 2.0,
+ position=self.node.position,
+ )
def add_explode_callback(self, call: Callable[[Bomb, Blast], Any]) -> None:
"""Add a call to be run when the bomb has exploded.
@@ -920,13 +1017,15 @@ class Bomb(ba.Actor):
return
self._exploded = True
if self.node:
- blast = Blast(position=self.node.position,
- velocity=self.node.velocity,
- blast_radius=self.blast_radius,
- blast_type=self.bomb_type,
- source_player=ba.existing(self._source_player),
- hit_type=self.hit_type,
- hit_subtype=self.hit_subtype).autoretain()
+ blast = Blast(
+ position=self.node.position,
+ velocity=self.node.velocity,
+ blast_radius=self.blast_radius,
+ blast_type=self.bomb_type,
+ source_player=ba.existing(self._source_player),
+ hit_type=self.hit_type,
+ hit_subtype=self.hit_subtype,
+ ).autoretain()
for callback in self._explode_callbacks:
callback(self, blast)
@@ -937,9 +1036,9 @@ class Bomb(ba.Actor):
def _handle_warn(self) -> None:
if self.texture_sequence and self.node:
self.texture_sequence.rate = 30
- ba.playsound(BombFactory.get().warn_sound,
- 0.5,
- position=self.node.position)
+ ba.playsound(
+ BombFactory.get().warn_sound, 0.5, position=self.node.position
+ )
def _add_material(self, material: ba.Material) -> None:
if not self.node:
@@ -947,7 +1046,7 @@ class Bomb(ba.Actor):
materials = self.node.materials
if material not in materials:
assert isinstance(materials, tuple)
- self.node.materials = materials + (material, )
+ self.node.materials = materials + (material,)
def arm(self) -> None:
"""Arm the bomb (for land-mines and impact-bombs).
@@ -960,46 +1059,54 @@ class Bomb(ba.Actor):
intex: Sequence[ba.Texture]
if self.bomb_type == 'land_mine':
intex = (factory.land_mine_lit_tex, factory.land_mine_tex)
- self.texture_sequence = ba.newnode('texture_sequence',
- owner=self.node,
- attrs={
- 'rate': 30,
- 'input_textures': intex
- })
+ self.texture_sequence = ba.newnode(
+ 'texture_sequence',
+ owner=self.node,
+ attrs={'rate': 30, 'input_textures': intex},
+ )
ba.timer(0.5, self.texture_sequence.delete)
# We now make it explodable.
ba.timer(
0.25,
- ba.WeakCall(self._add_material,
- factory.land_mine_blast_material))
+ ba.WeakCall(
+ self._add_material, factory.land_mine_blast_material
+ ),
+ )
elif self.bomb_type == 'impact':
- intex = (factory.impact_lit_tex, factory.impact_tex,
- factory.impact_tex)
- self.texture_sequence = ba.newnode('texture_sequence',
- owner=self.node,
- attrs={
- 'rate': 100,
- 'input_textures': intex
- })
+ intex = (
+ factory.impact_lit_tex,
+ factory.impact_tex,
+ factory.impact_tex,
+ )
+ self.texture_sequence = ba.newnode(
+ 'texture_sequence',
+ owner=self.node,
+ attrs={'rate': 100, 'input_textures': intex},
+ )
ba.timer(
0.25,
- ba.WeakCall(self._add_material,
- factory.land_mine_blast_material))
+ ba.WeakCall(
+ self._add_material, factory.land_mine_blast_material
+ ),
+ )
else:
- raise Exception('arm() should only be called '
- 'on land-mines or impact bombs')
- self.texture_sequence.connectattr('output_texture', self.node,
- 'color_texture')
+ raise RuntimeError(
+ 'arm() should only be called on land-mines or impact bombs'
+ )
+ self.texture_sequence.connectattr(
+ 'output_texture', self.node, 'color_texture'
+ )
ba.playsound(factory.activate_sound, 0.5, position=self.node.position)
def _handle_hit(self, msg: ba.HitMessage) -> None:
- ispunched = (msg.srcnode and msg.srcnode.getnodetype() == 'spaz')
+ ispunched = msg.srcnode and msg.srcnode.getnodetype() == 'spaz'
# Normal bombs are triggered by non-punch impacts;
# impact-bombs by all impacts.
- if (not self._exploded and
- (not ispunched or self.bomb_type in ['impact', 'land_mine'])):
+ if not self._exploded and (
+ not ispunched or self.bomb_type in ['impact', 'land_mine']
+ ):
# Also lets change the owner of the bomb to whoever is setting
# us off. (this way points for big chain reactions go to the
@@ -1018,15 +1125,27 @@ class Bomb(ba.Actor):
# self.hit_type = msg.hit_type
# self.hit_subtype = msg.hit_subtype
- ba.timer(0.1 + random.random() * 0.1,
- ba.WeakCall(self.handlemessage, ExplodeMessage()))
+ ba.timer(
+ 0.1 + random.random() * 0.1,
+ ba.WeakCall(self.handlemessage, ExplodeMessage()),
+ )
assert self.node
- 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.velocity[0], msg.velocity[1],
- msg.velocity[2])
+ 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.velocity[0],
+ msg.velocity[1],
+ msg.velocity[2],
+ )
if msg.srcnode:
pass
@@ -1077,9 +1196,9 @@ class TNTSpawner:
self._update()
# Go with slightly more than 1 second to avoid timer stacking.
- self._update_timer = ba.Timer(1.1,
- ba.WeakCall(self._update),
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.1, ba.WeakCall(self._update), repeat=True
+ )
def _update(self) -> None:
tnt_alive = self._tnt is not None and self._tnt.node
diff --git a/assets/src/ba_data/python/bastd/actor/controlsguide.py b/assets/src/ba_data/python/bastd/actor/controlsguide.py
index 40e82f67..e7779f97 100644
--- a/assets/src/ba_data/python/bastd/actor/controlsguide.py
+++ b/assets/src/ba_data/python/bastd/actor/controlsguide.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/flag.py b/assets/src/ba_data/python/bastd/actor/flag.py
index deaa6207..b1c5693b 100644
--- a/assets/src/ba_data/python/bastd/actor/flag.py
+++ b/assets/src/ba_data/python/bastd/actor/flag.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/image.py b/assets/src/ba_data/python/bastd/actor/image.py
index 71f23ef4..51774b9d 100644
--- a/assets/src/ba_data/python/bastd/actor/image.py
+++ b/assets/src/ba_data/python/bastd/actor/image.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/onscreencountdown.py b/assets/src/ba_data/python/bastd/actor/onscreencountdown.py
index 618c0c4f..2210f51a 100644
--- a/assets/src/ba_data/python/bastd/actor/onscreencountdown.py
+++ b/assets/src/ba_data/python/bastd/actor/onscreencountdown.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/onscreentimer.py b/assets/src/ba_data/python/bastd/actor/onscreentimer.py
index bf5e8f15..99deae56 100644
--- a/assets/src/ba_data/python/bastd/actor/onscreentimer.py
+++ b/assets/src/ba_data/python/bastd/actor/onscreentimer.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py
index 8c0a6a97..269b8053 100644
--- a/assets/src/ba_data/python/bastd/actor/playerspaz.py
+++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py
@@ -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.
diff --git a/assets/src/ba_data/python/bastd/actor/popuptext.py b/assets/src/ba_data/python/bastd/actor/popuptext.py
index ab270dd7..115b3598 100644
--- a/assets/src/ba_data/python/bastd/actor/popuptext.py
+++ b/assets/src/ba_data/python/bastd/actor/popuptext.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/powerupbox.py b/assets/src/ba_data/python/bastd/actor/powerupbox.py
index 868667c3..adc31158 100644
--- a/assets/src/ba_data/python/bastd/actor/powerupbox.py
+++ b/assets/src/ba_data/python/bastd/actor/powerupbox.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/respawnicon.py b/assets/src/ba_data/python/bastd/actor/respawnicon.py
index 66a5cbc6..0679522c 100644
--- a/assets/src/ba_data/python/bastd/actor/respawnicon.py
+++ b/assets/src/ba_data/python/bastd/actor/respawnicon.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/scoreboard.py b/assets/src/ba_data/python/bastd/actor/scoreboard.py
index d72e883a..de4f8677 100644
--- a/assets/src/ba_data/python/bastd/actor/scoreboard.py
+++ b/assets/src/ba_data/python/bastd/actor/scoreboard.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/spawner.py b/assets/src/ba_data/python/bastd/actor/spawner.py
index 41e2c339..3d006a26 100644
--- a/assets/src/ba_data/python/bastd/actor/spawner.py
+++ b/assets/src/ba_data/python/bastd/actor/spawner.py
@@ -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)
+ )
diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py
index d227789b..4d1529fe 100644
--- a/assets/src/ba_data/python/bastd/actor/spaz.py
+++ b/assets/src/ba_data/python/bastd/actor/spaz.py
@@ -63,15 +63,17 @@ class Spaz(ba.Actor):
default_boxing_gloves = False
default_shields = False
- def __init__(self,
- color: Sequence[float] = (1.0, 1.0, 1.0),
- highlight: Sequence[float] = (0.5, 0.5, 0.5),
- character: str = 'Spaz',
- source_player: ba.Player | None = None,
- start_invincible: bool = True,
- can_accept_powerups: bool = True,
- powerups_expire: bool = False,
- demo_mode: bool = False):
+ def __init__(
+ self,
+ color: Sequence[float] = (1.0, 1.0, 1.0),
+ highlight: Sequence[float] = (0.5, 0.5, 0.5),
+ character: str = 'Spaz',
+ source_player: ba.Player | None = None,
+ start_invincible: bool = True,
+ can_accept_powerups: bool = True,
+ powerups_expire: bool = False,
+ demo_mode: bool = False,
+ ):
"""Create a spaz with the requested color, character, etc."""
# pylint: disable=too-many-statements
@@ -104,8 +106,9 @@ class Spaz(ba.Actor):
self._cursed = False
self._connected_to_player: ba.Player | None = None
materials = [
- factory.spaz_material, shared.object_material,
- shared.player_material
+ factory.spaz_material,
+ shared.object_material,
+ shared.player_material,
]
roller_materials = [factory.roller_material, shared.player_material]
extras_material = []
@@ -153,19 +156,18 @@ class Spaz(ba.Actor):
'punch_materials': punchmats,
'pickup_materials': pickupmats,
'invincible': start_invincible,
- 'source_player': source_player
- })
+ 'source_player': source_player,
+ },
+ )
self.shield: ba.Node | None = None
if start_invincible:
- def _safesetattr(node: ba.Node | None, attr: str,
- val: Any) -> None:
+ def _safesetattr(node: ba.Node | None, attr: str, val: Any) -> None:
if node:
setattr(node, attr, val)
- ba.timer(1.0, ba.Call(_safesetattr, self.node, 'invincible',
- False))
+ ba.timer(1.0, ba.Call(_safesetattr, self.node, 'invincible', False))
self.hitpoints = 1000
self.hitpoints_max = 1000
self.shield_hitpoints: int | None = None
@@ -211,8 +213,7 @@ class Spaz(ba.Actor):
self._bomb_held = False
if self.default_shields:
self.equip_shields()
- self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor],
- Any]] = []
+ self._dropped_bomb_callbacks: list[Callable[[Spaz, ba.Actor], Any]] = []
self._score_text: ba.Node | None = None
self._score_text_hide_timer: ba.Timer | None = None
@@ -234,7 +235,8 @@ class Spaz(ba.Actor):
self.pick_up_powerup_callback = None
def add_dropped_bomb_callback(
- self, call: Callable[[Spaz, ba.Actor], Any]) -> None:
+ self, call: Callable[[Spaz, ba.Actor], Any]
+ ) -> None:
"""
Add a call to be run whenever this Spaz drops a bomb.
The spaz and the newly-dropped bomb are passed as arguments.
@@ -251,18 +253,20 @@ class Spaz(ba.Actor):
def _hide_score_text(self) -> None:
if self._score_text:
assert isinstance(self._score_text.scale, float)
- ba.animate(self._score_text, 'scale', {
- 0.0: self._score_text.scale,
- 0.2: 0.0
- })
+ ba.animate(
+ self._score_text,
+ 'scale',
+ {0.0: self._score_text.scale, 0.2: 0.0},
+ )
def _turbo_filter_add_press(self, source: str) -> None:
"""
Can pass all button presses through here; if we see an obscene number
of them in a short time let's shame/pushish this guy for using turbo
"""
- t_ms = ba.time(timetype=ba.TimeType.BASE,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ t_ms = ba.time(
+ timetype=ba.TimeType.BASE, timeformat=ba.TimeFormat.MILLISECONDS
+ )
assert isinstance(t_ms, int)
t_bucket = int(t_ms / 1000)
if t_bucket == self._turbo_filter_time_bucket:
@@ -270,7 +274,8 @@ class Spaz(ba.Actor):
# multiple actions).
if t_ms != self._turbo_filter_times.get(source, 0):
self._turbo_filter_counts[source] = (
- self._turbo_filter_counts.get(source, 0) + 1)
+ self._turbo_filter_counts.get(source, 0) + 1
+ )
self._turbo_filter_times[source] = t_ms
# (uncomment to debug; prints what this count is at)
# ba.screenmessage( str(source) + " "
@@ -284,23 +289,32 @@ class Spaz(ba.Actor):
now = ba.time(ba.TimeType.REAL)
if now > ba.app.last_spaz_turbo_warn_time + 30.0:
ba.app.last_spaz_turbo_warn_time = now
- ba.screenmessage(ba.Lstr(
- translate=('statements',
- ('Warning to ${NAME}: '
+ ba.screenmessage(
+ ba.Lstr(
+ translate=(
+ 'statements',
+ (
+ 'Warning to ${NAME}: '
'turbo / button-spamming knocks'
- ' you out.')),
- subs=[('${NAME}', self.node.name)]),
- color=(1, 0.5, 0))
+ ' you out.'
+ ),
+ ),
+ subs=[('${NAME}', self.node.name)],
+ ),
+ color=(1, 0.5, 0),
+ )
ba.playsound(ba.getsound('error'))
else:
self._turbo_filter_times = {}
self._turbo_filter_time_bucket = t_bucket
self._turbo_filter_counts = {source: 1}
- def set_score_text(self,
- text: str | ba.Lstr,
- color: Sequence[float] = (1.0, 1.0, 0.4),
- flash: bool = False) -> None:
+ def set_score_text(
+ self,
+ text: str | ba.Lstr,
+ color: Sequence[float] = (1.0, 1.0, 0.4),
+ flash: bool = False,
+ ) -> None:
"""
Utility func to show a message momentarily over our spaz that follows
him around; Handy for score updates and things.
@@ -310,24 +324,25 @@ class Spaz(ba.Actor):
return
if not self._score_text:
start_scale = 0.0
- mnode = ba.newnode('math',
- owner=self.node,
- attrs={
- 'input1': (0, 1.4, 0),
- 'operation': 'add'
- })
+ mnode = ba.newnode(
+ 'math',
+ owner=self.node,
+ attrs={'input1': (0, 1.4, 0), 'operation': 'add'},
+ )
self.node.connectattr('torso_position', mnode, 'input2')
- self._score_text = ba.newnode('text',
- owner=self.node,
- attrs={
- 'text': text,
- 'in_world': True,
- 'shadow': 1.0,
- 'flatness': 1.0,
- 'color': color_fin,
- 'scale': 0.02,
- 'h_align': 'center'
- })
+ self._score_text = ba.newnode(
+ 'text',
+ owner=self.node,
+ attrs={
+ 'text': text,
+ 'in_world': True,
+ 'shadow': 1.0,
+ 'flatness': 1.0,
+ 'color': color_fin,
+ 'scale': 0.02,
+ 'h_align': 'center',
+ },
+ )
mnode.connectattr('output', self._score_text, 'position')
else:
self._score_text.color = color_fin
@@ -335,25 +350,26 @@ class Spaz(ba.Actor):
start_scale = self._score_text.scale
self._score_text.text = text
if flash:
- combine = ba.newnode('combine',
- owner=self._score_text,
- attrs={'size': 3})
+ combine = ba.newnode(
+ 'combine', owner=self._score_text, attrs={'size': 3}
+ )
scl = 1.8
offs = 0.5
tval = 0.300
for i in range(3):
cl1 = offs + scl * color_fin[i]
cl2 = color_fin[i]
- ba.animate(combine, 'input' + str(i), {
- 0.5 * tval: cl2,
- 0.75 * tval: cl1,
- 1.0 * tval: cl2
- })
+ ba.animate(
+ combine,
+ 'input' + str(i),
+ {0.5 * tval: cl2, 0.75 * tval: cl1, 1.0 * tval: cl2},
+ )
combine.connectattr('output', self._score_text, 'color')
ba.animate(self._score_text, 'scale', {0.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 on_jump_press(self) -> None:
"""
@@ -438,8 +454,12 @@ class Spaz(ba.Actor):
if not self.node.hold_node:
ba.timer(
0.1,
- ba.WeakCall(self._safe_play_sound,
- SpazFactory.get().swish_sound, 0.8))
+ ba.WeakCall(
+ self._safe_play_sound,
+ SpazFactory.get().swish_sound,
+ 0.8,
+ ),
+ )
self._turbo_filter_add_press('punch')
def _safe_play_sound(self, sound: ba.Sound, volume: float) -> None:
@@ -585,8 +605,9 @@ class Spaz(ba.Actor):
for attr in ['materials', 'roller_materials']:
materials = getattr(self.node, attr)
if factory.curse_material not in materials:
- setattr(self.node, attr,
- materials + (factory.curse_material, ))
+ setattr(
+ self.node, attr, materials + (factory.curse_material,)
+ )
# None specifies no time limit
assert self.node
@@ -596,8 +617,9 @@ class Spaz(ba.Actor):
# Note: curse-death-time takes milliseconds.
tval = ba.time()
assert isinstance(tval, (float, int))
- self.node.curse_death_time = int(1000.0 *
- (tval + self.curse_time))
+ self.node.curse_death_time = int(
+ 1000.0 * (tval + self.curse_time)
+ )
ba.timer(5.0, ba.WeakCall(self.curse_explode))
def equip_boxing_gloves(self) -> None:
@@ -626,12 +648,11 @@ class Spaz(ba.Actor):
factory = SpazFactory.get()
if self.shield is None:
- self.shield = ba.newnode('shield',
- owner=self.node,
- attrs={
- 'color': (0.3, 0.2, 2.0),
- 'radius': 1.3
- })
+ self.shield = ba.newnode(
+ 'shield',
+ owner=self.node,
+ attrs={'color': (0.3, 0.2, 2.0), 'radius': 1.3},
+ )
self.node.connectattr('position_center', self.shield, 'position')
self.shield_hitpoints = self.shield_hitpoints_max = 650
self.shield_decay_rate = factory.shield_decay_rate if decay else 0
@@ -639,9 +660,9 @@ class Spaz(ba.Actor):
ba.playsound(factory.shield_up_sound, 1.0, position=self.node.position)
if self.shield_decay_rate > 0:
- self.shield_decay_timer = ba.Timer(0.5,
- ba.WeakCall(self.shield_decay),
- repeat=True)
+ self.shield_decay_timer = ba.Timer(
+ 0.5, ba.WeakCall(self.shield_decay), repeat=True
+ )
# So user can see the decay.
self.shield.always_show_health_bar = True
@@ -649,19 +670,23 @@ class Spaz(ba.Actor):
"""Called repeatedly to decay shield HP over time."""
if self.shield:
assert self.shield_hitpoints is not None
- self.shield_hitpoints = (max(
- 0, self.shield_hitpoints - self.shield_decay_rate))
+ self.shield_hitpoints = max(
+ 0, self.shield_hitpoints - self.shield_decay_rate
+ )
assert self.shield_hitpoints is not None
self.shield.hurt = (
- 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max)
+ 1.0 - float(self.shield_hitpoints) / self.shield_hitpoints_max
+ )
if self.shield_hitpoints <= 0:
self.shield.delete()
self.shield = None
self.shield_decay_timer = None
assert self.node
- ba.playsound(SpazFactory.get().shield_down_sound,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().shield_down_sound,
+ 1.0,
+ position=self.node.position,
+ )
else:
self.shield_decay_timer = None
@@ -705,15 +730,18 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
self.node.mini_billboard_1_start_time = t_ms
self.node.mini_billboard_1_end_time = (
- t_ms + POWERUP_WEAR_OFF_TIME)
- self._multi_bomb_wear_off_flash_timer = (ba.Timer(
+ t_ms + POWERUP_WEAR_OFF_TIME
+ )
+ self._multi_bomb_wear_off_flash_timer = ba.Timer(
(POWERUP_WEAR_OFF_TIME - 2000),
ba.WeakCall(self._multi_bomb_wear_off_flash),
- timeformat=ba.TimeFormat.MILLISECONDS))
- self._multi_bomb_wear_off_timer = (ba.Timer(
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ self._multi_bomb_wear_off_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME,
ba.WeakCall(self._multi_bomb_wear_off),
- timeformat=ba.TimeFormat.MILLISECONDS))
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
elif msg.poweruptype == 'land_mines':
self.set_land_mine_count(min(self.land_mine_count + 3, 3))
elif msg.poweruptype == 'impact_bombs':
@@ -726,15 +754,18 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
self.node.mini_billboard_2_start_time = t_ms
self.node.mini_billboard_2_end_time = (
- t_ms + POWERUP_WEAR_OFF_TIME)
- self._bomb_wear_off_flash_timer = (ba.Timer(
+ t_ms + POWERUP_WEAR_OFF_TIME
+ )
+ self._bomb_wear_off_flash_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME - 2000,
ba.WeakCall(self._bomb_wear_off_flash),
- timeformat=ba.TimeFormat.MILLISECONDS))
- self._bomb_wear_off_timer = (ba.Timer(
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ self._bomb_wear_off_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME,
ba.WeakCall(self._bomb_wear_off),
- timeformat=ba.TimeFormat.MILLISECONDS))
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
elif msg.poweruptype == 'sticky_bombs':
self.bomb_type = 'sticky'
tex = self._get_bomb_type_tex()
@@ -745,15 +776,18 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
self.node.mini_billboard_2_start_time = t_ms
self.node.mini_billboard_2_end_time = (
- t_ms + POWERUP_WEAR_OFF_TIME)
- self._bomb_wear_off_flash_timer = (ba.Timer(
+ t_ms + POWERUP_WEAR_OFF_TIME
+ )
+ self._bomb_wear_off_flash_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME - 2000,
ba.WeakCall(self._bomb_wear_off_flash),
- timeformat=ba.TimeFormat.MILLISECONDS))
- self._bomb_wear_off_timer = (ba.Timer(
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ self._bomb_wear_off_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME,
ba.WeakCall(self._bomb_wear_off),
- timeformat=ba.TimeFormat.MILLISECONDS))
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
elif msg.poweruptype == 'punch':
tex = PowerupBoxFactory.get().tex_punch
self._flash_billboard(tex)
@@ -765,15 +799,18 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
self.node.mini_billboard_3_start_time = t_ms
self.node.mini_billboard_3_end_time = (
- t_ms + POWERUP_WEAR_OFF_TIME)
- self._boxing_gloves_wear_off_flash_timer = (ba.Timer(
+ t_ms + POWERUP_WEAR_OFF_TIME
+ )
+ self._boxing_gloves_wear_off_flash_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME - 2000,
ba.WeakCall(self._gloves_wear_off_flash),
- timeformat=ba.TimeFormat.MILLISECONDS))
- self._boxing_gloves_wear_off_timer = (ba.Timer(
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ self._boxing_gloves_wear_off_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME,
ba.WeakCall(self._gloves_wear_off),
- timeformat=ba.TimeFormat.MILLISECONDS))
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
elif msg.poweruptype == 'shield':
factory = SpazFactory.get()
@@ -791,15 +828,18 @@ class Spaz(ba.Actor):
assert isinstance(t_ms, int)
self.node.mini_billboard_2_start_time = t_ms
self.node.mini_billboard_2_end_time = (
- t_ms + POWERUP_WEAR_OFF_TIME)
- self._bomb_wear_off_flash_timer = (ba.Timer(
+ t_ms + POWERUP_WEAR_OFF_TIME
+ )
+ self._bomb_wear_off_flash_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME - 2000,
ba.WeakCall(self._bomb_wear_off_flash),
- timeformat=ba.TimeFormat.MILLISECONDS))
- self._bomb_wear_off_timer = (ba.Timer(
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ self._bomb_wear_off_timer = ba.Timer(
POWERUP_WEAR_OFF_TIME,
ba.WeakCall(self._bomb_wear_off),
- timeformat=ba.TimeFormat.MILLISECONDS))
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
elif msg.poweruptype == 'health':
if self._cursed:
self._cursed = False
@@ -810,9 +850,14 @@ class Spaz(ba.Actor):
materials = getattr(self.node, attr)
if factory.curse_material in materials:
setattr(
- self.node, attr,
- tuple(m for m in materials
- if m != factory.curse_material))
+ self.node,
+ attr,
+ tuple(
+ m
+ for m in materials
+ if m != factory.curse_material
+ ),
+ )
self.node.curse_death_time = 0
self.hitpoints = self.hitpoints_max
self._flash_billboard(PowerupBoxFactory.get().tex_health)
@@ -829,17 +874,18 @@ class Spaz(ba.Actor):
if not self.node:
return None
if self.node.invincible:
- ba.playsound(SpazFactory.get().block_sound,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().block_sound,
+ 1.0,
+ position=self.node.position,
+ )
return None
if self.shield:
return None
if not self.frozen:
self.frozen = True
self.node.frozen = True
- ba.timer(5.0, ba.WeakCall(self.handlemessage,
- ba.ThawMessage()))
+ ba.timer(5.0, ba.WeakCall(self.handlemessage, ba.ThawMessage()))
# Instantly shatter if we're already dead.
# (otherwise its hard to tell we're dead)
if self.hitpoints <= 0:
@@ -854,17 +900,21 @@ class Spaz(ba.Actor):
if not self.node:
return None
if self.node.invincible:
- ba.playsound(SpazFactory.get().block_sound,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().block_sound,
+ 1.0,
+ position=self.node.position,
+ )
return True
# If we were recently hit, don't count this as another.
# (so punch flurries and bomb pileups essentially count as 1 hit)
local_time = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
assert isinstance(local_time, int)
- if (self._last_hit_time is None
- or local_time - self._last_hit_time > 1000):
+ if (
+ self._last_hit_time is None
+ or local_time - self._last_hit_time > 1000
+ ):
self._num_times_hit += 1
self._last_hit_time = local_time
@@ -881,17 +931,29 @@ class Spaz(ba.Actor):
# theoretical damage; not apply the impulse.
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], mag,
- velocity_mag, msg.radius, 1, 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],
+ mag,
+ velocity_mag,
+ msg.radius,
+ 1,
+ msg.force_direction[0],
+ msg.force_direction[1],
+ msg.force_direction[2],
+ )
damage = damage_scale * self.node.damage
assert self.shield_hitpoints is not None
self.shield_hitpoints -= int(damage)
self.shield.hurt = (
- 1.0 -
- float(self.shield_hitpoints) / self.shield_hitpoints_max)
+ 1.0
+ - float(self.shield_hitpoints) / self.shield_hitpoints_max
+ )
# Its a cleaner event if a hit just kills the shield
# without damaging the player.
@@ -903,34 +965,44 @@ class Spaz(ba.Actor):
# FIXME: Transition out perhaps?
self.shield.delete()
self.shield = None
- ba.playsound(SpazFactory.get().shield_down_sound,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().shield_down_sound,
+ 1.0,
+ position=self.node.position,
+ )
# Emit some cool looking sparks when the shield dies.
npos = self.node.position
- ba.emitfx(position=(npos[0], npos[1] + 0.9, npos[2]),
- velocity=self.node.velocity,
- count=random.randrange(20, 30),
- scale=1.0,
- spread=0.6,
- chunk_type='spark')
+ ba.emitfx(
+ position=(npos[0], npos[1] + 0.9, npos[2]),
+ velocity=self.node.velocity,
+ count=random.randrange(20, 30),
+ scale=1.0,
+ spread=0.6,
+ chunk_type='spark',
+ )
else:
- ba.playsound(SpazFactory.get().shield_hit_sound,
- 0.5,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().shield_hit_sound,
+ 0.5,
+ position=self.node.position,
+ )
# Emit some cool looking sparks on shield hit.
assert msg.force_direction is not None
- ba.emitfx(position=msg.pos,
- velocity=(msg.force_direction[0] * 1.0,
- msg.force_direction[1] * 1.0,
- msg.force_direction[2] * 1.0),
- count=min(30, 5 + int(damage * 0.005)),
- scale=0.5,
- spread=0.3,
- chunk_type='spark')
+ ba.emitfx(
+ position=msg.pos,
+ velocity=(
+ msg.force_direction[0] * 1.0,
+ msg.force_direction[1] * 1.0,
+ msg.force_direction[2] * 1.0,
+ ),
+ count=min(30, 5 + int(damage * 0.005)),
+ scale=0.5,
+ spread=0.3,
+ chunk_type='spark',
+ )
# If they passed our spillover threshold,
# pass damage along to spaz.
@@ -947,16 +1019,28 @@ class Spaz(ba.Actor):
shield_leftover_ratio = 1.0
if msg.flat_damage:
- damage = int(msg.flat_damage * self.impact_scale *
- shield_leftover_ratio)
+ damage = int(
+ msg.flat_damage * self.impact_scale * shield_leftover_ratio
+ )
else:
# Hit it with an impulse and get the resulting damage.
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], mag,
- velocity_mag, 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],
+ mag,
+ velocity_mag,
+ msg.radius,
+ 0,
+ msg.force_direction[0],
+ msg.force_direction[1],
+ msg.force_direction[2],
+ )
damage = int(damage_scale * self.node.damage)
self.node.handlemessage('hurt_sound')
@@ -968,15 +1052,20 @@ class Spaz(ba.Actor):
# If damage was significant, lets show it.
if damage >= 350:
assert msg.force_direction is not None
- ba.show_damage_count('-' + str(int(damage / 10)) + '%',
- msg.pos, msg.force_direction)
+ ba.show_damage_count(
+ '-' + str(int(damage / 10)) + '%',
+ msg.pos,
+ msg.force_direction,
+ )
# Let's always add in a super-punch sound with boxing
# gloves just to differentiate them.
if msg.hit_subtype == 'super_punch':
- ba.playsound(SpazFactory.get().punch_sound_stronger,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().punch_sound_stronger,
+ 1.0,
+ position=self.node.position,
+ )
if damage >= 500:
sounds = SpazFactory.get().punch_sound_strong
sound = sounds[random.randrange(len(sounds))]
@@ -988,28 +1077,38 @@ class Spaz(ba.Actor):
# Throw up some chunks.
assert msg.force_direction is not None
- ba.emitfx(position=msg.pos,
- velocity=(msg.force_direction[0] * 0.5,
- msg.force_direction[1] * 0.5,
- msg.force_direction[2] * 0.5),
- count=min(10, 1 + int(damage * 0.0025)),
- scale=0.3,
- spread=0.03)
+ ba.emitfx(
+ position=msg.pos,
+ velocity=(
+ msg.force_direction[0] * 0.5,
+ msg.force_direction[1] * 0.5,
+ msg.force_direction[2] * 0.5,
+ ),
+ count=min(10, 1 + int(damage * 0.0025)),
+ scale=0.3,
+ spread=0.03,
+ )
- ba.emitfx(position=msg.pos,
- chunk_type='sweat',
- velocity=(msg.force_direction[0] * 1.3,
- msg.force_direction[1] * 1.3 + 5.0,
- msg.force_direction[2] * 1.3),
- count=min(30, 1 + int(damage * 0.04)),
- scale=0.9,
- spread=0.28)
+ ba.emitfx(
+ position=msg.pos,
+ chunk_type='sweat',
+ velocity=(
+ msg.force_direction[0] * 1.3,
+ msg.force_direction[1] * 1.3 + 5.0,
+ msg.force_direction[2] * 1.3,
+ ),
+ count=min(30, 1 + int(damage * 0.04)),
+ scale=0.9,
+ spread=0.28,
+ )
# Momentary flash.
hurtiness = damage * 0.003
- punchpos = (msg.pos[0] + msg.force_direction[0] * 0.02,
- msg.pos[1] + msg.force_direction[1] * 0.02,
- msg.pos[2] + msg.force_direction[2] * 0.02)
+ punchpos = (
+ msg.pos[0] + msg.force_direction[0] * 0.02,
+ msg.pos[1] + msg.force_direction[1] * 0.02,
+ msg.pos[2] + msg.force_direction[2] * 0.02,
+ )
flash_color = (1.0, 0.8, 0.4)
light = ba.newnode(
'light',
@@ -1018,27 +1117,34 @@ class Spaz(ba.Actor):
'radius': 0.12 + hurtiness * 0.12,
'intensity': 0.3 * (1.0 + 1.0 * hurtiness),
'height_attenuated': False,
- 'color': flash_color
- })
+ 'color': flash_color,
+ },
+ )
ba.timer(0.06, light.delete)
- flash = ba.newnode('flash',
- attrs={
- 'position': punchpos,
- 'size': 0.17 + 0.17 * hurtiness,
- 'color': flash_color
- })
+ flash = ba.newnode(
+ 'flash',
+ attrs={
+ 'position': punchpos,
+ 'size': 0.17 + 0.17 * hurtiness,
+ 'color': flash_color,
+ },
+ )
ba.timer(0.06, flash.delete)
if msg.hit_type == 'impact':
assert msg.force_direction is not None
- ba.emitfx(position=msg.pos,
- velocity=(msg.force_direction[0] * 2.0,
- msg.force_direction[1] * 2.0,
- msg.force_direction[2] * 2.0),
- count=min(10, 1 + int(damage * 0.01)),
- scale=0.4,
- spread=0.1)
+ ba.emitfx(
+ position=msg.pos,
+ velocity=(
+ msg.force_direction[0] * 2.0,
+ msg.force_direction[1] * 2.0,
+ msg.force_direction[2] * 2.0,
+ ),
+ count=min(10, 1 + int(damage * 0.01)),
+ scale=0.4,
+ spread=0.1,
+ )
if self.hitpoints > 0:
# It's kinda crappy to die from impacts, so lets reduce
@@ -1055,22 +1161,26 @@ class Spaz(ba.Actor):
if damage > 0.0 and self.node.hold_node:
self.node.hold_node = None
self.hitpoints -= damage
- self.node.hurt = 1.0 - float(
- self.hitpoints) / self.hitpoints_max
+ self.node.hurt = (
+ 1.0 - float(self.hitpoints) / self.hitpoints_max
+ )
# If we're cursed, *any* damage blows us up.
if self._cursed and damage > 0:
ba.timer(
0.05,
- ba.WeakCall(self.curse_explode,
- msg.get_source_player(ba.Player)))
+ ba.WeakCall(
+ self.curse_explode, msg.get_source_player(ba.Player)
+ ),
+ )
# If we're frozen, shatter.. otherwise die if we hit zero
if self.frozen and (damage > 200 or self.hitpoints <= 0):
self.shatter()
elif self.hitpoints <= 0:
self.node.handlemessage(
- ba.DieMessage(how=ba.DeathType.IMPACT))
+ ba.DieMessage(how=ba.DeathType.IMPACT)
+ )
# If we're dead, take a look at the smoothed damage value
# (which gives us a smoothed average of recent damage) and shatter
@@ -1102,12 +1212,19 @@ class Spaz(ba.Actor):
self.handlemessage(ba.DieMessage(how=ba.DeathType.FALL))
elif isinstance(msg, ba.StandMessage):
- self._last_stand_pos = (msg.position[0], msg.position[1],
- msg.position[2])
+ self._last_stand_pos = (
+ msg.position[0],
+ msg.position[1],
+ msg.position[2],
+ )
if self.node:
- self.node.handlemessage('stand', msg.position[0],
- msg.position[1], msg.position[2],
- msg.angle)
+ self.node.handlemessage(
+ 'stand',
+ msg.position[0],
+ msg.position[1],
+ msg.position[2],
+ msg.angle,
+ )
elif isinstance(msg, CurseExplodeMessage):
self.curse_explode()
@@ -1120,8 +1237,9 @@ class Spaz(ba.Actor):
# Only allow one hit per node per punch.
if node and (node not in self._punched_nodes):
- punch_momentum_angular = (self.node.punch_momentum_angular *
- self._punch_power_scale)
+ punch_momentum_angular = (
+ self.node.punch_momentum_angular * self._punch_power_scale
+ )
punch_power = self.node.punch_power * self._punch_power_scale
# Ok here's the deal: we pass along our base velocity for use
@@ -1155,8 +1273,13 @@ class Spaz(ba.Actor):
source_player=self.source_player,
force_direction=punchdir,
hit_type='punch',
- hit_subtype=('super_punch' if self._has_boxing_gloves
- else 'default')))
+ hit_subtype=(
+ 'super_punch'
+ if self._has_boxing_gloves
+ else 'default'
+ ),
+ )
+ )
# Also apply opposite to ourself for the first punch only.
# This is given as a constant force so that it is more
@@ -1167,9 +1290,16 @@ class Spaz(ba.Actor):
if self._hockey:
mag *= 0.5
if len(self._punched_nodes) == 1:
- self.node.handlemessage('kick_back', ppos[0], ppos[1],
- ppos[2], punchdir[0], punchdir[1],
- punchdir[2], mag)
+ self.node.handlemessage(
+ 'kick_back',
+ ppos[0],
+ ppos[1],
+ ppos[2],
+ punchdir[0],
+ punchdir[1],
+ punchdir[2],
+ mag,
+ )
elif isinstance(msg, PickupMessage):
if not self.node:
return None
@@ -1190,8 +1320,11 @@ class Spaz(ba.Actor):
# If we're grabbing the pelvis of a non-shattered spaz, we wanna
# grab the torso instead.
- if (opposingnode.getnodetype() == 'spaz'
- and not opposingnode.shattered and opposingbody == 4):
+ if (
+ opposingnode.getnodetype() == 'spaz'
+ and not opposingnode.shattered
+ and opposingbody == 4
+ ):
opposingbody = 1
# Special case - if we're holding a flag, don't replace it
@@ -1233,18 +1366,21 @@ class Spaz(ba.Actor):
dropping_bomb = True
bomb_type = self.bomb_type
- bomb = stdbomb.Bomb(position=(pos[0], pos[1] - 0.0, pos[2]),
- velocity=(vel[0], vel[1], vel[2]),
- bomb_type=bomb_type,
- blast_radius=self.blast_radius,
- source_player=self.source_player,
- owner=self.node).autoretain()
+ bomb = stdbomb.Bomb(
+ position=(pos[0], pos[1] - 0.0, pos[2]),
+ velocity=(vel[0], vel[1], vel[2]),
+ bomb_type=bomb_type,
+ blast_radius=self.blast_radius,
+ source_player=self.source_player,
+ owner=self.node,
+ ).autoretain()
assert bomb.node
if dropping_bomb:
self.bomb_count -= 1
bomb.node.add_death_action(
- ba.WeakCall(self.handlemessage, BombDiedMessage()))
+ ba.WeakCall(self.handlemessage, BombDiedMessage())
+ )
self._pick_up(bomb.node)
for clb in self._dropped_bomb_callbacks:
@@ -1265,7 +1401,8 @@ class Spaz(ba.Actor):
if self.land_mine_count != 0:
self.node.counter_text = 'x' + str(self.land_mine_count)
self.node.counter_texture = (
- PowerupBoxFactory.get().tex_land_mines)
+ PowerupBoxFactory.get().tex_land_mines
+ )
else:
self.node.counter_text = ''
@@ -1281,8 +1418,10 @@ class Spaz(ba.Actor):
velocity=self.node.velocity,
blast_radius=3.0,
blast_type='normal',
- source_player=(source_player if source_player else
- self.source_player)).autoretain()
+ source_player=(
+ source_player if source_player else self.source_player
+ ),
+ ).autoretain()
self._cursed = False
def shatter(self, extreme: bool = False) -> None:
@@ -1293,42 +1432,49 @@ class Spaz(ba.Actor):
assert self.node
if self.frozen:
# Momentary flash of light.
- light = ba.newnode('light',
- attrs={
- 'position': self.node.position,
- 'radius': 0.5,
- 'height_attenuated': False,
- 'color': (0.8, 0.8, 1.0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self.node.position,
+ 'radius': 0.5,
+ 'height_attenuated': False,
+ 'color': (0.8, 0.8, 1.0),
+ },
+ )
- ba.animate(light, 'intensity', {
- 0.0: 3.0,
- 0.04: 0.5,
- 0.08: 0.07,
- 0.3: 0
- })
+ ba.animate(
+ light, 'intensity', {0.0: 3.0, 0.04: 0.5, 0.08: 0.07, 0.3: 0}
+ )
ba.timer(0.3, light.delete)
# Emit ice chunks.
- ba.emitfx(position=self.node.position,
- velocity=self.node.velocity,
- count=int(random.random() * 10.0 + 10.0),
- scale=0.6,
- spread=0.2,
- chunk_type='ice')
- ba.emitfx(position=self.node.position,
- velocity=self.node.velocity,
- count=int(random.random() * 10.0 + 10.0),
- scale=0.3,
- spread=0.2,
- chunk_type='ice')
- ba.playsound(SpazFactory.get().shatter_sound,
- 1.0,
- position=self.node.position)
+ ba.emitfx(
+ position=self.node.position,
+ velocity=self.node.velocity,
+ count=int(random.random() * 10.0 + 10.0),
+ scale=0.6,
+ spread=0.2,
+ chunk_type='ice',
+ )
+ ba.emitfx(
+ position=self.node.position,
+ velocity=self.node.velocity,
+ count=int(random.random() * 10.0 + 10.0),
+ scale=0.3,
+ spread=0.2,
+ chunk_type='ice',
+ )
+ ba.playsound(
+ SpazFactory.get().shatter_sound,
+ 1.0,
+ position=self.node.position,
+ )
else:
- ba.playsound(SpazFactory.get().splatter_sound,
- 1.0,
- position=self.node.position)
+ ba.playsound(
+ SpazFactory.get().splatter_sound,
+ 1.0,
+ position=self.node.position,
+ )
self.handlemessage(ba.DieMessage())
self.node.shattered = 2 if extreme else 1
@@ -1337,10 +1483,13 @@ class Spaz(ba.Actor):
return
pos = self.node.position
self.handlemessage(
- ba.HitMessage(flat_damage=50.0 * intensity,
- pos=pos,
- force_direction=self.node.velocity,
- hit_type='impact'))
+ ba.HitMessage(
+ flat_damage=50.0 * intensity,
+ pos=pos,
+ force_direction=self.node.velocity,
+ hit_type='impact',
+ )
+ )
self.node.handlemessage('knockout', max(0.0, 50.0 * intensity))
sounds: Sequence[ba.Sound]
if intensity >= 5.0:
@@ -1366,12 +1515,11 @@ class Spaz(ba.Actor):
assert self.node
self.node.billboard_texture = tex
self.node.billboard_cross_out = False
- ba.animate(self.node, 'billboard_opacity', {
- 0.0: 0.0,
- 0.1: 1.0,
- 0.4: 1.0,
- 0.5: 0.0
- })
+ ba.animate(
+ self.node,
+ 'billboard_opacity',
+ {0.0: 0.0, 0.1: 1.0, 0.4: 1.0, 0.5: 0.0},
+ )
def set_bomb_count(self, count: int) -> None:
"""Sets the number of bombs this Spaz has."""
@@ -1398,8 +1546,10 @@ class Spaz(ba.Actor):
self._punch_cooldown = factory.punch_cooldown
self._has_boxing_gloves = False
if self.node:
- ba.playsound(PowerupBoxFactory.get().powerdown_sound,
- position=self.node.position)
+ ba.playsound(
+ PowerupBoxFactory.get().powerdown_sound,
+ position=self.node.position,
+ )
self.node.boxing_gloves = False
self.node.billboard_opacity = 0.0
@@ -1412,8 +1562,10 @@ class Spaz(ba.Actor):
def _multi_bomb_wear_off(self) -> None:
self.set_bomb_count(self.default_bomb_count)
if self.node:
- ba.playsound(PowerupBoxFactory.get().powerdown_sound,
- position=self.node.position)
+ ba.playsound(
+ PowerupBoxFactory.get().powerdown_sound,
+ position=self.node.position,
+ )
self.node.billboard_opacity = 0.0
def _bomb_wear_off_flash(self) -> None:
@@ -1425,6 +1577,8 @@ class Spaz(ba.Actor):
def _bomb_wear_off(self) -> None:
self.bomb_type = self.bomb_type_default
if self.node:
- ba.playsound(PowerupBoxFactory.get().powerdown_sound,
- position=self.node.position)
+ ba.playsound(
+ PowerupBoxFactory.get().powerdown_sound,
+ position=self.node.position,
+ )
self.node.billboard_opacity = 0.0
diff --git a/assets/src/ba_data/python/bastd/actor/spazappearance.py b/assets/src/ba_data/python/bastd/actor/spazappearance.py
index 801000e1..cdc00d7e 100644
--- a/assets/src/ba_data/python/bastd/actor/spazappearance.py
+++ b/assets/src/ba_data/python/bastd/actor/spazappearance.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/spazbot.py b/assets/src/ba_data/python/bastd/actor/spazbot.py
index cccf070d..9e0c6877 100644
--- a/assets/src/ba_data/python/bastd/actor/spazbot.py
+++ b/assets/src/ba_data/python/bastd/actor/spazbot.py
@@ -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."""
diff --git a/assets/src/ba_data/python/bastd/actor/spazfactory.py b/assets/src/ba_data/python/bastd/actor/spazfactory.py
index 2611d1c6..08cdde65 100644
--- a/assets/src/ba_data/python/bastd/actor/spazfactory.py
+++ b/assets/src/ba_data/python/bastd/actor/spazfactory.py
@@ -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]
diff --git a/assets/src/ba_data/python/bastd/actor/text.py b/assets/src/ba_data/python/bastd/actor/text.py
index fc560769..123ac3ec 100644
--- a/assets/src/ba_data/python/bastd/actor/text.py
+++ b/assets/src/ba_data/python/bastd/actor/text.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/actor/tipstext.py b/assets/src/ba_data/python/bastd/actor/tipstext.py
index 2cd5afd3..f642d959 100644
--- a/assets/src/ba_data/python/bastd/actor/tipstext.py
+++ b/assets/src/ba_data/python/bastd/actor/tipstext.py
@@ -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:
diff --git a/assets/src/ba_data/python/bastd/actor/zoomtext.py b/assets/src/ba_data/python/bastd/actor/zoomtext.py
index 708d9435..2cca306f 100644
--- a/assets/src/ba_data/python/bastd/actor/zoomtext.py
+++ b/assets/src/ba_data/python/bastd/actor/zoomtext.py
@@ -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})
diff --git a/assets/src/ba_data/python/bastd/appdelegate.py b/assets/src/ba_data/python/bastd/appdelegate.py
index 464117cd..746aa5d8 100644
--- a/assets/src/ba_data/python/bastd/appdelegate.py
+++ b/assets/src/ba_data/python/bastd/appdelegate.py
@@ -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()
+ )
diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py
index e9f4ff15..a2feb11a 100644
--- a/assets/src/ba_data/python/bastd/game/assault.py
+++ b/assets/src/ba_data/python/bastd/game/assault.py
@@ -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
+ )
diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py
index de3fb7e0..bb8ad9cd 100644
--- a/assets/src/ba_data/python/bastd/game/capturetheflag.py
+++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py
@@ -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
diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py
index 6f75748e..8604143c 100644
--- a/assets/src/ba_data/python/bastd/game/chosenone.py
+++ b/assets/src/ba_data/python/bastd/game/chosenone.py
@@ -42,8 +42,10 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
"""
name = 'Chosen One'
- description = ('Be the chosen one for a length of time to win.\n'
- 'Kill the chosen one to become it.')
+ description = (
+ 'Be the chosen one for a length of time to win.\n'
+ 'Kill the chosen one to become it.'
+ )
available_settings = [
ba.IntSetting(
'Chosen One Time',
@@ -99,7 +101,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
- 1: ba.getsound('announceOne')
+ 1: ba.getsound('announceOne'),
}
self._flag_spawn_pos: Sequence[float] | None = None
self._reset_region_material: ba.Material | None = None
@@ -113,8 +115,9 @@ class ChosenOneGame(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.CHOSEN_ONE)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.CHOSEN_ONE
+ )
def get_instance_description(self) -> str | Sequence:
return 'There can be only one.'
@@ -148,8 +151,7 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
- ('call', 'at_connect',
- ba.WeakCall(self._handle_reset_collide)),
+ ('call', 'at_connect', ba.WeakCall(self._handle_reset_collide)),
),
)
@@ -165,8 +167,9 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
'position': (pos[0], pos[1] + 0.75, pos[2]),
'scale': (0.5, 0.5, 0.5),
'type': 'sphere',
- 'materials': [self._reset_region_material]
- })
+ 'materials': [self._reset_region_material],
+ },
+ )
def _get_chosen_one_player(self) -> Player | None:
# Should never return invalid references; return None in that case.
@@ -190,13 +193,15 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
self._set_chosen_one_player(player)
def _flash_flag_spawn(self) -> None:
- light = ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'color': (1, 1, 1),
- 'radius': 0.3,
- 'height_attenuated': False
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'color': (1, 1, 1),
+ 'radius': 0.3,
+ 'height_attenuated': False,
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
@@ -213,26 +218,28 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
else:
scoring_team = player.team
assert self.stats
- self.stats.player_scored(player,
- 3,
- screenmessage=False,
- display=False)
+ self.stats.player_scored(
+ player, 3, screenmessage=False, display=False
+ )
scoring_team.time_remaining = max(
- 0, scoring_team.time_remaining - 1)
+ 0, scoring_team.time_remaining - 1
+ )
# Show the count over their head
if scoring_team.time_remaining > 0:
if isinstance(player.actor, PlayerSpaz) and player.actor:
player.actor.set_score_text(
- str(scoring_team.time_remaining))
+ str(scoring_team.time_remaining)
+ )
self._update_scoreboard()
# announce numbers we have sounds for
if scoring_team.time_remaining in self._countdownsounds:
ba.playsound(
- self._countdownsounds[scoring_team.time_remaining])
+ self._countdownsounds[scoring_team.time_remaining]
+ )
# Winner!
if scoring_team.time_remaining <= 0:
@@ -250,8 +257,9 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
def end_game(self) -> None:
results = ba.GameResults()
for team in self.teams:
- results.set_team_score(team,
- self._chosen_one_time - team.time_remaining)
+ results.set_team_score(
+ team, self._chosen_one_time - team.time_remaining
+ )
self.end(results=results, announce_delay=0)
def _set_chosen_one_player(self, player: Player | None) -> None:
@@ -261,23 +269,27 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._swipsound)
if not player:
assert self._flag_spawn_pos is not None
- self._flag = Flag(color=(1, 0.9, 0.2),
- position=self._flag_spawn_pos,
- touchable=False)
+ self._flag = Flag(
+ color=(1, 0.9, 0.2),
+ position=self._flag_spawn_pos,
+ touchable=False,
+ )
self._chosen_one_player = None
# Create a light to highlight the flag;
# this will go away when the flag dies.
- ba.newnode('light',
- owner=self._flag.node,
- attrs={
- 'position': self._flag_spawn_pos,
- 'intensity': 0.6,
- 'height_attenuated': False,
- 'volume_intensity_scale': 0.1,
- 'radius': 0.1,
- 'color': (1.2, 1.2, 0.4)
- })
+ ba.newnode(
+ 'light',
+ owner=self._flag.node,
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'intensity': 0.6,
+ 'height_attenuated': False,
+ 'volume_intensity_scale': 0.1,
+ 'radius': 0.1,
+ 'color': (1.2, 1.2, 0.4),
+ },
+ )
# Also an extra momentary flash.
self._flash_flag_spawn()
@@ -302,26 +314,29 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
for c in ba.normalized_color(player.team.color)
]
light = player.chosen_light = ba.NodeActor(
- ba.newnode('light',
- attrs={
- 'intensity': 0.6,
- 'height_attenuated': False,
- 'volume_intensity_scale': 0.1,
- 'radius': 0.13,
- 'color': color
- }))
+ ba.newnode(
+ 'light',
+ attrs={
+ 'intensity': 0.6,
+ 'height_attenuated': False,
+ 'volume_intensity_scale': 0.1,
+ 'radius': 0.13,
+ 'color': color,
+ },
+ )
+ )
assert light.node
- ba.animate(light.node,
- 'intensity', {
- 0: 1.0,
- 0.2: 0.4,
- 0.4: 1.0
- },
- loop=True)
+ ba.animate(
+ light.node,
+ 'intensity',
+ {0: 1.0, 0.2: 0.4, 0.4: 1.0},
+ loop=True,
+ )
assert isinstance(player.actor, PlayerSpaz)
- player.actor.node.connectattr('position', light.node,
- 'position')
+ player.actor.node.connectattr(
+ 'position', light.node, 'position'
+ )
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
@@ -330,16 +345,21 @@ class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
player = msg.getplayer(Player)
if player is self._get_chosen_one_player():
killerplayer = msg.getkillerplayer(Player)
- self._set_chosen_one_player(None if (
- killerplayer is None or killerplayer is player
- or not killerplayer.is_alive()) else killerplayer)
+ self._set_chosen_one_player(
+ None
+ if (
+ killerplayer is None
+ or killerplayer is player
+ or not killerplayer.is_alive()
+ )
+ else killerplayer
+ )
self.respawn_player(player)
else:
super().handlemessage(msg)
def _update_scoreboard(self) -> None:
for team in self.teams:
- self._scoreboard.set_team_value(team,
- team.time_remaining,
- self._chosen_one_time,
- countdown=True)
+ self._scoreboard.set_team_value(
+ team, team.time_remaining, self._chosen_one_time, countdown=True
+ )
diff --git a/assets/src/ba_data/python/bastd/game/conquest.py b/assets/src/ba_data/python/bastd/game/conquest.py
index 88650c08..13f6f29e 100644
--- a/assets/src/ba_data/python/bastd/game/conquest.py
+++ b/assets/src/ba_data/python/bastd/game/conquest.py
@@ -125,8 +125,9 @@ class ConquestGame(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.GRAND_ROMP)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.GRAND_ROMP
+ )
# We want flags to tell us they've been hit but not react physically.
self._extraflagmat.add_actions(
@@ -134,7 +135,8 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
actions=(
('modify_part_collision', 'collide', True),
('call', 'at_connect', self._handle_flag_player_collide),
- ))
+ ),
+ )
def get_instance_description(self) -> str | Sequence:
return 'Secure all ${ARG1} flags.', len(self.map.flag_points)
@@ -161,20 +163,22 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
# Set up flags with marker lights.
for i, flag_point in enumerate(self.map.flag_points):
point = flag_point
- flag = ConquestFlag(position=point,
- touchable=False,
- materials=[self._extraflagmat])
+ flag = ConquestFlag(
+ position=point, touchable=False, materials=[self._extraflagmat]
+ )
self._flags.append(flag)
Flag.project_stand(point)
- flag.light = ba.newnode('light',
- owner=flag.node,
- attrs={
- 'position': point,
- 'intensity': 0.25,
- 'height_attenuated': False,
- 'radius': 0.3,
- 'color': (1, 1, 1)
- })
+ flag.light = ba.newnode(
+ 'light',
+ owner=flag.node,
+ attrs={
+ 'position': point,
+ 'intensity': 0.25,
+ 'height_attenuated': False,
+ 'radius': 0.3,
+ 'color': (1, 1, 1),
+ },
+ )
# Give teams a flag to start with.
for i, team in enumerate(self.teams):
@@ -209,8 +213,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
player.respawn_icon = None
if team.flags_held == len(self._flags):
self.end_game()
- self._scoreboard.set_team_value(team, team.flags_held,
- len(self._flags))
+ self._scoreboard.set_team_value(
+ team, team.flags_held, len(self._flags)
+ )
def end_game(self) -> None:
results = ba.GameResults()
@@ -221,12 +226,14 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
assert flag.node
assert flag.light
- light = ba.newnode('light',
- attrs={
- 'position': flag.node.position,
- 'height_attenuated': False,
- 'color': flag.light.color
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': flag.node.position,
+ 'height_attenuated': False,
+ 'color': flag.light.color,
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True)
ba.timer(length, light.delete)
@@ -234,9 +241,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
collision = ba.getcollision()
try:
flag = collision.sourcenode.getdelegate(ConquestFlag, True)
- player = collision.opposingnode.getdelegate(PlayerSpaz,
- True).getplayer(
- Player, True)
+ player = collision.opposingnode.getdelegate(
+ PlayerSpaz, True
+ ).getplayer(Player, True)
except ba.NotFoundError:
return
assert flag.light
@@ -253,10 +260,12 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
# Respawn any players on this team that were in limbo due to the
# lack of a flag for their team.
for otherplayer in self.players:
- if (otherplayer.team is flag.team
- and otherplayer.actor is not None
- and not otherplayer.is_alive()
- and otherplayer.respawn_timer is None):
+ if (
+ otherplayer.team is flag.team
+ and otherplayer.actor is not None
+ and not otherplayer.is_alive()
+ and otherplayer.respawn_timer is None
+ ):
self.spawn_player(otherplayer)
def handlemessage(self, msg: Any) -> Any:
@@ -276,8 +285,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
def spawn_player(self, player: Player) -> ba.Actor:
# We spawn players at different places based on what flags are held.
- return self.spawn_player_spaz(player,
- self._get_player_spawn_position(player))
+ return self.spawn_player_spaz(
+ player, self._get_player_spawn_position(player)
+ )
def _get_player_spawn_position(self, player: Player) -> Sequence[float]:
@@ -298,8 +308,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
spt = self.map.spawn_by_flag_points[spawn]
our_pt = ba.Vec3(spt[0], spt[1], spt[2])
for otherspawn in [
- i for i in range(spawn_count)
- if self._flags[i].team is not player.team
+ i
+ for i in range(spawn_count)
+ if self._flags[i].team is not player.team
]:
spt = self.map.spawn_by_flag_points[otherspawn]
their_pt = ba.Vec3(spt[0], spt[1], spt[2])
@@ -311,6 +322,9 @@ class ConquestGame(ba.TeamGameActivity[Player, Team]):
pos = self.map.spawn_by_flag_points[closest_spawn]
x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3])
z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5])
- pos = (pos[0] + random.uniform(*x_range), pos[1],
- pos[2] + random.uniform(*z_range))
+ pos = (
+ pos[0] + random.uniform(*x_range),
+ pos[1],
+ pos[2] + random.uniform(*z_range),
+ )
return pos
diff --git a/assets/src/ba_data/python/bastd/game/deathmatch.py b/assets/src/ba_data/python/bastd/game/deathmatch.py
index b1ef3dc8..b82df09b 100644
--- a/assets/src/ba_data/python/bastd/game/deathmatch.py
+++ b/assets/src/ba_data/python/bastd/game/deathmatch.py
@@ -40,7 +40,8 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
@classmethod
def get_available_settings(
- cls, sessiontype: type[ba.Session]) -> list[ba.Setting]:
+ cls, sessiontype: type[ba.Session]
+ ) -> list[ba.Setting]:
settings = [
ba.IntSetting(
'Kills to Win Per Player',
@@ -81,14 +82,16 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
# suiciding until you get a good drop)
if issubclass(sessiontype, ba.FreeForAllSession):
settings.append(
- ba.BoolSetting('Allow Negative Scores', default=False))
+ ba.BoolSetting('Allow Negative Scores', default=False)
+ )
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
- return (issubclass(sessiontype, ba.DualTeamSession)
- or issubclass(sessiontype, ba.FreeForAllSession))
+ return issubclass(sessiontype, ba.DualTeamSession) or issubclass(
+ sessiontype, ba.FreeForAllSession
+ )
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
@@ -100,16 +103,17 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
self._score_to_win: int | None = None
self._dingsound = ba.getsound('dingSmall')
self._epic_mode = bool(settings['Epic Mode'])
- self._kills_to_win_per_player = int(
- settings['Kills to Win Per Player'])
+ self._kills_to_win_per_player = int(settings['Kills to Win Per Player'])
self._time_limit = float(settings['Time Limit'])
self._allow_negative_scores = bool(
- settings.get('Allow Negative Scores', False))
+ settings.get('Allow Negative Scores', False)
+ )
# Base class overrides.
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC if self._epic_mode else
- ba.MusicType.TO_THE_DEATH)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.TO_THE_DEATH
+ )
def get_instance_description(self) -> str | Sequence:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
@@ -127,8 +131,9 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
self.setup_standard_powerup_drops()
# Base kills needed to win on the size of the largest team.
- self._score_to_win = (self._kills_to_win_per_player *
- max(1, max(len(t.players) for t in self.teams)))
+ self._score_to_win = self._kills_to_win_per_player * max(
+ 1, max(len(t.players) for t in self.teams)
+ )
self._update_scoreboard()
def handlemessage(self, msg: Any) -> Any:
@@ -169,10 +174,11 @@ class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
# In FFA show scores since its hard to find on the scoreboard.
if isinstance(killer.actor, PlayerSpaz) and killer.actor:
- killer.actor.set_score_text(str(killer.team.score) + '/' +
- str(self._score_to_win),
- color=killer.team.color,
- flash=True)
+ killer.actor.set_score_text(
+ str(killer.team.score) + '/' + str(self._score_to_win),
+ color=killer.team.color,
+ flash=True,
+ )
self._update_scoreboard()
@@ -189,8 +195,9 @@ class DeathMatchGame(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
+ )
def end_game(self) -> None:
results = ba.GameResults()
diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py
index 1dc4de02..2bc0454e 100644
--- a/assets/src/ba_data/python/bastd/game/easteregghunt.py
+++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py
@@ -58,9 +58,11 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
- return (issubclass(sessiontype, ba.CoopSession)
- or issubclass(sessiontype, ba.DualTeamSession)
- or issubclass(sessiontype, ba.FreeForAllSession))
+ return (
+ issubclass(sessiontype, ba.CoopSession)
+ or issubclass(sessiontype, ba.DualTeamSession)
+ or issubclass(sessiontype, ba.FreeForAllSession)
+ )
def __init__(self, settings: dict):
super().__init__(settings)
@@ -78,7 +80,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
self.egg_material = ba.Material()
self.egg_material.add_actions(
conditions=('they_have_material', shared.player_material),
- actions=(('call', 'at_connect', self._on_egg_player_collide), ))
+ actions=(('call', 'at_connect', self._on_egg_player_collide),),
+ )
self._eggs: list[Egg] = []
self._update_timer: ba.Timer | None = None
self._countdown: OnScreenCountdown | None = None
@@ -86,8 +89,9 @@ class EasterEggHuntGame(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 on_team_join(self, team: Team) -> None:
if self.has_begun():
@@ -132,9 +136,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# who just left/etc.
try:
egg = collision.sourcenode.getdelegate(Egg, True)
- player = collision.opposingnode.getdelegate(PlayerSpaz,
- True).getplayer(
- Player, True)
+ player = collision.opposingnode.getdelegate(
+ PlayerSpaz, True
+ ).getplayer(Player, True)
except ba.NotFoundError:
return
@@ -153,13 +157,15 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
ba.playsound(self._collect_sound, 0.5, position=egg.node.position)
# Create a flash.
- light = ba.newnode('light',
- attrs={
- 'position': egg.node.position,
- 'height_attenuated': False,
- 'radius': 0.1,
- 'color': (1, 1, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': egg.node.position,
+ 'height_attenuated': False,
+ 'radius': 0.1,
+ 'color': (1, 1, 0),
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
ba.timer(0.200, light.delete)
egg.handlemessage(ba.DieMessage())
@@ -178,8 +184,9 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
# Occasionally spawn a land-mine in addition.
if self._pro_mode and random.random() < 0.25:
- mine = Bomb(position=(xpos, ypos, zpos),
- bomb_type='land_mine').autoretain()
+ mine = Bomb(
+ position=(xpos, ypos, zpos), bomb_type='land_mine'
+ ).autoretain()
mine.arm()
else:
self._eggs.append(Egg(position=(xpos, ypos, zpos)))
@@ -197,7 +204,8 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer(
- respawn_time, ba.Call(self.spawn_player_if_exists, player))
+ respawn_time, ba.Call(self.spawn_player_if_exists, player)
+ )
player.respawn_icon = RespawnIcon(player, respawn_time)
# Whenever our evil bunny dies, respawn him and spew some eggs.
@@ -208,9 +216,14 @@ class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
for _i in range(6):
spread = 0.4
self._eggs.append(
- Egg(position=(pos[0] + random.uniform(-spread, spread),
- pos[1] + random.uniform(-spread, spread),
- pos[2] + random.uniform(-spread, spread))))
+ Egg(
+ position=(
+ pos[0] + random.uniform(-spread, spread),
+ pos[1] + random.uniform(-spread, spread),
+ pos[2] + random.uniform(-spread, spread),
+ )
+ )
+ )
else:
# Default handler.
return super().handlemessage(msg)
@@ -238,24 +251,27 @@ class Egg(ba.Actor):
# Spawn just above the provided point.
self._spawn_pos = (position[0], position[1] + 1.0, position[2])
- ctex = (activity.egg_tex_1, activity.egg_tex_2,
- activity.egg_tex_3)[random.randrange(3)]
+ ctex = (activity.egg_tex_1, activity.egg_tex_2, activity.egg_tex_3)[
+ random.randrange(3)
+ ]
mats = [shared.object_material, activity.egg_material]
- self.node = ba.newnode('prop',
- delegate=self,
- attrs={
- 'model': activity.egg_model,
- 'color_texture': ctex,
- 'body': 'capsule',
- 'reflection': 'soft',
- 'model_scale': 0.5,
- 'body_scale': 0.6,
- 'density': 4.0,
- 'reflection_scale': [0.15],
- 'shadow_size': 0.6,
- 'position': self._spawn_pos,
- 'materials': mats
- })
+ self.node = ba.newnode(
+ 'prop',
+ delegate=self,
+ attrs={
+ 'model': activity.egg_model,
+ 'color_texture': ctex,
+ 'body': 'capsule',
+ 'reflection': 'soft',
+ 'model_scale': 0.5,
+ 'body_scale': 0.6,
+ 'density': 4.0,
+ 'reflection_scale': [0.15],
+ 'shadow_size': 0.6,
+ 'position': self._spawn_pos,
+ 'materials': mats,
+ },
+ )
def exists(self) -> bool:
return bool(self.node)
@@ -268,10 +284,20 @@ class Egg(ba.Actor):
if 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],
- 1.0 * msg.magnitude, 1.0 * 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],
+ 1.0 * msg.magnitude,
+ 1.0 * msg.velocity_magnitude,
+ msg.radius,
+ 0,
+ msg.force_direction[0],
+ msg.force_direction[1],
+ msg.force_direction[2],
+ )
else:
super().handlemessage(msg)
diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py
index e3d91e4a..db31fcb5 100644
--- a/assets/src/ba_data/python/bastd/game/elimination.py
+++ b/assets/src/ba_data/python/bastd/game/elimination.py
@@ -20,16 +20,18 @@ if TYPE_CHECKING:
class Icon(ba.Actor):
"""Creates in in-game icon on screen."""
- def __init__(self,
- player: Player,
- position: tuple[float, float],
- scale: float,
- show_lives: bool = True,
- show_death: bool = True,
- name_scale: float = 1.0,
- name_maxwidth: float = 115.0,
- flatness: float = 1.0,
- shadow: float = 1.0):
+ def __init__(
+ self,
+ player: Player,
+ position: tuple[float, float],
+ scale: float,
+ show_lives: bool = True,
+ show_death: bool = True,
+ name_scale: float = 1.0,
+ name_maxwidth: float = 115.0,
+ flatness: float = 1.0,
+ shadow: float = 1.0,
+ ):
super().__init__()
self._player = player
@@ -39,19 +41,21 @@ class Icon(ba.Actor):
self._outline_tex = ba.gettexture('characterIconMask')
icon = player.get_icon()
- self.node = ba.newnode('image',
- delegate=self,
- attrs={
- 'texture': icon['texture'],
- 'tint_texture': icon['tint_texture'],
- 'tint_color': icon['tint_color'],
- 'vr_depth': 400,
- 'tint2_color': icon['tint2_color'],
- 'mask_texture': self._outline_tex,
- 'opacity': 1.0,
- 'absolute_scale': True,
- 'attach': 'bottomCenter'
- })
+ self.node = ba.newnode(
+ 'image',
+ delegate=self,
+ attrs={
+ 'texture': icon['texture'],
+ 'tint_texture': icon['tint_texture'],
+ 'tint_color': icon['tint_color'],
+ 'vr_depth': 400,
+ 'tint2_color': icon['tint2_color'],
+ 'mask_texture': self._outline_tex,
+ 'opacity': 1.0,
+ 'absolute_scale': True,
+ 'attach': 'bottomCenter',
+ },
+ )
self._name_text = ba.newnode(
'text',
owner=self.node,
@@ -65,25 +69,29 @@ class Icon(ba.Actor):
'shadow': shadow,
'flatness': flatness,
'h_attach': 'center',
- 'v_attach': 'bottom'
- })
+ 'v_attach': 'bottom',
+ },
+ )
if self._show_lives:
- self._lives_text = ba.newnode('text',
- owner=self.node,
- attrs={
- 'text': 'x0',
- 'color': (1, 1, 0.5),
- 'h_align': 'left',
- 'vr_depth': 430,
- 'shadow': 1.0,
- 'flatness': 1.0,
- 'h_attach': 'center',
- 'v_attach': 'bottom'
- })
+ self._lives_text = ba.newnode(
+ 'text',
+ owner=self.node,
+ attrs={
+ 'text': 'x0',
+ 'color': (1, 1, 0.5),
+ 'h_align': 'left',
+ 'vr_depth': 430,
+ 'shadow': 1.0,
+ 'flatness': 1.0,
+ 'h_attach': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
self.set_position_and_scale(position, scale)
- def set_position_and_scale(self, position: tuple[float, float],
- scale: float) -> None:
+ def set_position_and_scale(
+ self, position: tuple[float, float], scale: float
+ ) -> None:
"""(Re)position the icon."""
assert self.node
self.node.position = position
@@ -91,8 +99,10 @@ class Icon(ba.Actor):
self._name_text.position = (position[0], position[1] + scale * 52.0)
self._name_text.scale = 1.0 * scale * self._name_scale
if self._show_lives:
- self._lives_text.position = (position[0] + scale * 10.0,
- position[1] - scale * 43.0)
+ self._lives_text.position = (
+ position[0] + scale * 10.0,
+ position[1] - scale * 43.0,
+ )
self._lives_text.scale = 1.0 * scale
def update_for_lives(self) -> None:
@@ -125,7 +135,9 @@ class Icon(ba.Actor):
return
if self._show_death:
ba.animate(
- self.node, 'opacity', {
+ self.node,
+ 'opacity',
+ {
0.00: 1.0,
0.05: 0.0,
0.10: 1.0,
@@ -137,8 +149,9 @@ class Icon(ba.Actor):
0.40: 1.0,
0.45: 0.0,
0.50: 1.0,
- 0.55: 0.2
- })
+ 0.55: 0.2,
+ },
+ )
lives = self._player.lives
if lives == 0:
ba.timer(0.6, self.update_for_lives)
@@ -172,9 +185,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
name = 'Elimination'
description = 'Last remaining alive wins.'
- scoreconfig = ba.ScoreConfig(label='Survived',
- scoretype=ba.ScoreType.SECONDS,
- none_is_winner=True)
+ scoreconfig = ba.ScoreConfig(
+ label='Survived', scoretype=ba.ScoreType.SECONDS, none_is_winner=True
+ )
# Show messages when players die since it's meaningful here.
announce_player_deaths = True
@@ -182,7 +195,8 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
@classmethod
def get_available_settings(
- cls, sessiontype: type[ba.Session]) -> list[ba.Setting]:
+ cls, sessiontype: type[ba.Session]
+ ) -> list[ba.Setting]:
settings = [
ba.IntSetting(
'Lives Per Player',
@@ -219,13 +233,15 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(ba.BoolSetting('Solo Mode', default=False))
settings.append(
- ba.BoolSetting('Balance Total Lives', default=False))
+ ba.BoolSetting('Balance Total Lives', default=False)
+ )
return settings
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
- return (issubclass(sessiontype, ba.DualTeamSession)
- or issubclass(sessiontype, ba.FreeForAllSession))
+ return issubclass(sessiontype, ba.DualTeamSession) or issubclass(
+ sessiontype, ba.FreeForAllSession
+ )
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
@@ -241,21 +257,29 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
self._lives_per_player = int(settings['Lives Per Player'])
self._time_limit = float(settings['Time Limit'])
self._balance_total_lives = bool(
- settings.get('Balance Total Lives', False))
+ settings.get('Balance Total Lives', False)
+ )
self._solo_mode = bool(settings.get('Solo Mode', False))
# Base class overrides:
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.SURVIVAL)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL
+ )
def get_instance_description(self) -> str | Sequence:
- return 'Last team standing wins.' if isinstance(
- self.session, ba.DualTeamSession) else 'Last one standing wins.'
+ return (
+ 'Last team standing wins.'
+ if isinstance(self.session, ba.DualTeamSession)
+ else 'Last one standing wins.'
+ )
def get_instance_description_short(self) -> str | Sequence:
- return 'last team standing wins' if isinstance(
- self.session, ba.DualTeamSession) else 'last one standing wins'
+ return (
+ 'last team standing wins'
+ if isinstance(self.session, ba.DualTeamSession)
+ else 'last one standing wins'
+ )
def on_player_join(self, player: Player) -> None:
player.lives = self._lives_per_player
@@ -280,35 +304,43 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
self.setup_standard_powerup_drops()
if self._solo_mode:
self._vs_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'position': (0, 105),
- 'h_attach': 'center',
- 'h_align': 'center',
- 'maxwidth': 200,
- 'shadow': 0.5,
- 'vr_depth': 390,
- 'scale': 0.6,
- 'v_attach': 'bottom',
- 'color': (0.8, 0.8, 0.3, 1.0),
- 'text': ba.Lstr(resource='vsText')
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'position': (0, 105),
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'maxwidth': 200,
+ 'shadow': 0.5,
+ 'vr_depth': 390,
+ 'scale': 0.6,
+ 'v_attach': 'bottom',
+ 'color': (0.8, 0.8, 0.3, 1.0),
+ 'text': ba.Lstr(resource='vsText'),
+ },
+ )
+ )
# If balance-team-lives is on, add lives to the smaller team until
# total lives match.
- if (isinstance(self.session, ba.DualTeamSession)
- and self._balance_total_lives and self.teams[0].players
- and self.teams[1].players):
+ if (
+ isinstance(self.session, ba.DualTeamSession)
+ and self._balance_total_lives
+ and self.teams[0].players
+ and self.teams[1].players
+ ):
if self._get_total_team_lives(
- self.teams[0]) < self._get_total_team_lives(self.teams[1]):
+ self.teams[0]
+ ) < self._get_total_team_lives(self.teams[1]):
lesser_team = self.teams[0]
greater_team = self.teams[1]
else:
lesser_team = self.teams[1]
greater_team = self.teams[0]
add_index = 0
- while (self._get_total_team_lives(lesser_team) <
- self._get_total_team_lives(greater_team)):
+ while self._get_total_team_lives(
+ lesser_team
+ ) < self._get_total_team_lives(greater_team):
lesser_team.players[add_index].lives += 1
add_index = (add_index + 1) % len(lesser_team.players)
@@ -367,22 +399,26 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
test_lives = 1
while True:
players_with_lives = [
- p for p in team.spawn_order
+ p
+ for p in team.spawn_order
if p and p.lives >= test_lives
]
if not players_with_lives:
break
for player in players_with_lives:
player.icons.append(
- Icon(player,
- position=(xval, (40 if is_first else 25)),
- scale=1.0 if is_first else 0.5,
- name_maxwidth=130 if is_first else 75,
- name_scale=0.8 if is_first else 1.0,
- flatness=0.0 if is_first else 1.0,
- shadow=0.5 if is_first else 1.0,
- show_death=is_first,
- show_lives=False))
+ Icon(
+ player,
+ position=(xval, (40 if is_first else 25)),
+ scale=1.0 if is_first else 0.5,
+ name_maxwidth=130 if is_first else 75,
+ name_scale=0.8 if is_first else 1.0,
+ flatness=0.0 if is_first else 1.0,
+ shadow=0.5 if is_first else 1.0,
+ show_death=is_first,
+ show_lives=False,
+ )
+ )
xval += x_offs * (0.8 if is_first else 0.56)
is_first = False
test_lives += 1
@@ -424,7 +460,8 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
for team in self.teams:
start_pos = ba.Vec3(self.map.get_start_position(team.id))
points.append(
- ((start_pos - player_pos).length(), start_pos))
+ ((start_pos - player_pos).length(), start_pos)
+ )
# Hmm.. we need to sorting vectors too?
points.sort(key=lambda x: x[0])
return points[-1][1]
@@ -447,12 +484,14 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
if not player or not player.is_alive() or not player.node:
return
- popuptext.PopupText('x' + str(player.lives - 1),
- color=(1, 1, 0, 1),
- offset=(0, -0.8, 0),
- random_offset=0.0,
- scale=1.8,
- position=player.node.position).autoretain()
+ popuptext.PopupText(
+ 'x' + str(player.lives - 1),
+ color=(1, 1, 0, 1),
+ offset=(0, -0.8, 0),
+ random_offset=0.0,
+ scale=1.8,
+ position=player.node.position,
+ ).autoretain()
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
@@ -487,8 +526,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
player.lives -= 1
if player.lives < 0:
ba.print_error(
- "Got lives < 0 in Elim; this shouldn't happen. solo:" +
- str(self._solo_mode))
+ "Got lives < 0 in Elim; this shouldn't happen. solo:"
+ + str(self._solo_mode)
+ )
player.lives = 0
# If we have any icons, update their state.
@@ -505,8 +545,9 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
# If the whole team is now dead, mark their survival time.
if self._get_total_team_lives(player.team) == 0:
assert self._start_time is not None
- player.team.survival_seconds = int(ba.time() -
- self._start_time)
+ player.team.survival_seconds = int(
+ ba.time() - self._start_time
+ )
else:
# Otherwise, in regular mode, respawn.
if not self._solo_mode:
@@ -540,9 +581,10 @@ class EliminationGame(ba.TeamGameActivity[Player, Team]):
def _get_living_teams(self) -> list[Team]:
return [
- team for team in self.teams
- if len(team.players) > 0 and any(player.lives > 0
- for player in team.players)
+ team
+ for team in self.teams
+ if len(team.players) > 0
+ and any(player.lives > 0 for player in team.players)
]
def end_game(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py
index 2faed91f..a88583e4 100644
--- a/assets/src/ba_data/python/bastd/game/football.py
+++ b/assets/src/ba_data/python/bastd/game/football.py
@@ -17,13 +17,28 @@ from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.respawnicon import RespawnIcon
from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox
-from bastd.actor.flag import (FlagFactory, Flag, FlagPickedUpMessage,
- FlagDroppedMessage, FlagDiedMessage)
-from bastd.actor.spazbot import (SpazBotDiedMessage, SpazBotPunchedMessage,
- SpazBotSet, BrawlerBotLite, BrawlerBot,
- BomberBotLite, BomberBot, TriggerBot,
- ChargerBot, TriggerBotPro, BrawlerBotPro,
- StickyBot, ExplodeyBot)
+from bastd.actor.flag import (
+ FlagFactory,
+ Flag,
+ FlagPickedUpMessage,
+ FlagDroppedMessage,
+ FlagDiedMessage,
+)
+from bastd.actor.spazbot import (
+ SpazBotDiedMessage,
+ SpazBotPunchedMessage,
+ SpazBotSet,
+ BrawlerBotLite,
+ BrawlerBot,
+ BomberBotLite,
+ BomberBot,
+ TriggerBot,
+ ChargerBot,
+ TriggerBotPro,
+ BrawlerBotPro,
+ StickyBot,
+ ExplodeyBot,
+)
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -35,23 +50,25 @@ class FootballFlag(Flag):
"""Custom flag class for football games."""
def __init__(self, position: Sequence[float]):
- super().__init__(position=position,
- dropped_timeout=20,
- color=(1.0, 1.0, 0.3))
+ super().__init__(
+ position=position, dropped_timeout=20, color=(1.0, 1.0, 0.3)
+ )
assert self.node
self.last_holding_player: ba.Player | None = None
self.node.is_area_of_interest = True
self.respawn_timer: ba.Timer | None = None
self.scored = False
self.held_count = 0
- self.light = ba.newnode('light',
- owner=self.node,
- attrs={
- 'intensity': 0.25,
- 'height_attenuated': False,
- 'radius': 0.2,
- 'color': (0.9, 0.7, 0.0)
- })
+ self.light = ba.newnode(
+ 'light',
+ owner=self.node,
+ attrs={
+ 'intensity': 0.25,
+ 'height_attenuated': False,
+ 'radius': 0.2,
+ 'color': (0.9, 0.7, 0.0),
+ },
+ )
self.node.connectattr('position', self.light, 'position')
@@ -135,7 +152,8 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score),
- ))
+ ),
+ )
self._flag_spawn_pos: Sequence[float] | None = None
self._score_regions: list[ba.NodeActor] = []
self._flag: FootballFlag | None = None
@@ -145,8 +163,9 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.FOOTBALL)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.FOOTBALL
+ )
def get_instance_description(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
@@ -170,27 +189,35 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
- self._flag_spawn_pos = (self.map.get_flag_position(None))
+ self._flag_spawn_pos = self.map.get_flag_position(None)
self._spawn_flag()
defs = self.map.defs
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal1'][0:3],
- 'scale': defs.boxes['goal1'][6:9],
- 'type': 'box',
- 'materials': (self._score_region_material, )
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal1'][0:3],
+ 'scale': defs.boxes['goal1'][6:9],
+ 'type': 'box',
+ 'materials': (self._score_region_material,),
+ },
+ )
+ )
+ )
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal2'][0:3],
- 'scale': defs.boxes['goal2'][6:9],
- 'type': 'box',
- 'materials': (self._score_region_material, )
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal2'][0:3],
+ 'scale': defs.boxes['goal2'][6:9],
+ 'type': 'box',
+ 'materials': (self._score_region_material,),
+ },
+ )
+ )
+ )
self._update_scoreboard()
ba.playsound(self._chant_sound)
@@ -225,11 +252,13 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
# If someone on this team was last to touch it,
# give them points.
assert self._flag is not None
- if (self._flag.last_holding_player
- and team == self._flag.last_holding_player.team):
- self.stats.player_scored(self._flag.last_holding_player,
- 50,
- big_message=True)
+ if (
+ self._flag.last_holding_player
+ and team == self._flag.last_holding_player.team
+ ):
+ self.stats.player_scored(
+ self._flag.last_holding_player, 50, big_message=True
+ )
# End the game if we won.
if team.score >= self._score_to_win:
self.end_game()
@@ -240,12 +269,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
# Kill the flag (it'll respawn shortly).
ba.timer(1.0, self._kill_flag)
- light = ba.newnode('light',
- attrs={
- 'position': ba.getcollision().position,
- 'height_attenuated': False,
- 'color': (1, 0, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': ba.getcollision().position,
+ 'height_attenuated': False,
+ 'color': (1, 0, 0),
+ },
+ )
ba.animate(light, 'intensity', {0.0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
ba.cameraflash(duration=10.0)
@@ -260,15 +291,17 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
def _update_scoreboard(self) -> None:
assert self._scoreboard is not 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:
if isinstance(msg, FlagPickedUpMessage):
assert isinstance(msg.flag, FootballFlag)
try:
msg.flag.last_holding_player = msg.node.getdelegate(
- PlayerSpaz, True).getplayer(Player, True)
+ PlayerSpaz, True
+ ).getplayer(Player, True)
except ba.NotFoundError:
pass
msg.flag.held_count += 1
@@ -288,21 +321,23 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
if not self.has_ended():
self._flag_respawn_timer = ba.Timer(3.0, self._spawn_flag)
self._flag_respawn_light = ba.NodeActor(
- ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'height_attenuated': False,
- 'radius': 0.15,
- 'color': (1.0, 1.0, 0.3)
- }))
+ ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'height_attenuated': False,
+ 'radius': 0.15,
+ 'color': (1.0, 1.0, 0.3),
+ },
+ )
+ )
assert self._flag_respawn_light.node
- ba.animate(self._flag_respawn_light.node,
- 'intensity', {
- 0.0: 0,
- 0.25: 0.15,
- 0.5: 0
- },
- loop=True)
+ ba.animate(
+ self._flag_respawn_light.node,
+ 'intensity',
+ {0.0: 0, 0.25: 0.15, 0.5: 0},
+ loop=True,
+ )
ba.timer(3.0, self._flag_respawn_light.node.delete)
else:
@@ -310,12 +345,14 @@ class FootballTeamGame(ba.TeamGameActivity[Player, Team]):
super().handlemessage(msg)
def _flash_flag_spawn(self) -> None:
- light = ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'height_attenuated': False,
- 'color': (1, 1, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'height_attenuated': False,
+ 'color': (1, 1, 0),
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
@@ -332,8 +369,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
name = 'Football'
tips = ['Use the pick-up button to grab the flag < ${PICKUP} >']
- scoreconfig = ba.ScoreConfig(scoretype=ba.ScoreType.MILLISECONDS,
- version='B')
+ scoreconfig = ba.ScoreConfig(
+ scoretype=ba.ScoreType.MILLISECONDS, version='B'
+ )
default_music = ba.MusicType.FOOTBALL
@@ -375,7 +413,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_score),
- ))
+ ),
+ )
self._powerup_center = (0, 2, 0)
self._powerup_spread = (10, 5.5)
self._player_has_dropped_bomb = False
@@ -412,34 +451,44 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
defs = self.map.defs
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal1'][0:3],
- 'scale': defs.boxes['goal1'][6:9],
- 'type': 'box',
- 'materials': [self._score_region_material]
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal1'][0:3],
+ 'scale': defs.boxes['goal1'][6:9],
+ 'type': 'box',
+ 'materials': [self._score_region_material],
+ },
+ )
+ )
+ )
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal2'][0:3],
- 'scale': defs.boxes['goal2'][6:9],
- 'type': 'box',
- 'materials': [self._score_region_material]
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal2'][0:3],
+ 'scale': defs.boxes['goal2'][6:9],
+ 'type': 'box',
+ 'materials': [self._score_region_material],
+ },
+ )
+ )
+ )
ba.playsound(self._chant_sound)
def on_begin(self) -> None:
# FIXME: Split this up a bit.
# pylint: disable=too-many-statements
from bastd.actor import controlsguide
+
super().on_begin()
# Show controls help in kiosk mode.
if ba.app.demo_mode or ba.app.arcade_mode:
- controlsguide.ControlsGuide(delay=3.0, lifespan=10.0,
- bright=True).autoretain()
+ controlsguide.ControlsGuide(
+ delay=3.0, lifespan=10.0, bright=True
+ ).autoretain()
assert self.initialplayerinfos is not None
abot: type[SpazBot]
bbot: type[SpazBot]
@@ -447,50 +496,64 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if self._preset in ['rookie', 'rookie_easy']:
self._exclude_powerups = ['curse']
self._have_tnt = False
- abot = (BrawlerBotLite
- if self._preset == 'rookie_easy' else BrawlerBot)
+ abot = (
+ BrawlerBotLite if self._preset == 'rookie_easy' else BrawlerBot
+ )
self._bot_types_initial = [abot] * len(self.initialplayerinfos)
- bbot = (BomberBotLite
- if self._preset == 'rookie_easy' else BomberBot)
- self._bot_types_7 = (
- [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
- cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot)
- self._bot_types_14 = (
- [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
+ bbot = BomberBotLite if self._preset == 'rookie_easy' else BomberBot
+ self._bot_types_7 = [bbot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
+ cbot = BomberBot if self._preset == 'rookie_easy' else TriggerBot
+ self._bot_types_14 = [cbot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
elif self._preset == 'tournament':
self._exclude_powerups = []
self._have_tnt = True
- self._bot_types_initial = (
- [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2))
- self._bot_types_7 = (
- [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
- self._bot_types_14 = (
- [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2))
+ self._bot_types_initial = [BrawlerBot] * (
+ 1 if len(self.initialplayerinfos) < 2 else 2
+ )
+ self._bot_types_7 = [TriggerBot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
+ self._bot_types_14 = [ChargerBot] * (
+ 1 if len(self.initialplayerinfos) < 4 else 2
+ )
elif self._preset in ['pro', 'pro_easy', 'tournament_pro']:
self._exclude_powerups = ['curse']
self._have_tnt = True
self._bot_types_initial = [ChargerBot] * len(
- self.initialplayerinfos)
- abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite)
+ self.initialplayerinfos
+ )
+ abot = BrawlerBot if self._preset == 'pro' else BrawlerBotLite
typed_bot_list: list[type[SpazBot]] = []
self._bot_types_7 = (
- typed_bot_list + [abot] + [BomberBot] *
- (1 if len(self.initialplayerinfos) < 3 else 2))
- bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot)
- self._bot_types_14 = (
- [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
+ typed_bot_list
+ + [abot]
+ + [BomberBot] * (1 if len(self.initialplayerinfos) < 3 else 2)
+ )
+ bbot = TriggerBotPro if self._preset == 'pro' else TriggerBot
+ self._bot_types_14 = [bbot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
elif self._preset in ['uber', 'uber_easy']:
self._exclude_powerups = []
self._have_tnt = True
- abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot)
- bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot)
+ abot = BrawlerBotPro if self._preset == 'uber' else BrawlerBot
+ bbot = TriggerBotPro if self._preset == 'uber' else TriggerBot
typed_bot_list_2: list[type[SpazBot]] = []
- self._bot_types_initial = (typed_bot_list_2 + [StickyBot] +
- [abot] * len(self.initialplayerinfos))
- self._bot_types_7 = (
- [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
- self._bot_types_14 = (
- [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
+ self._bot_types_initial = (
+ typed_bot_list_2
+ + [StickyBot]
+ + [abot] * len(self.initialplayerinfos)
+ )
+ self._bot_types_7 = [bbot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
+ self._bot_types_14 = [ExplodeyBot] * (
+ 1 if len(self.initialplayerinfos) < 3 else 2
+ )
else:
raise Exception()
@@ -502,9 +565,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# Make a bogus team for our bots.
bad_team_name = self.get_team_display_string('Bad Guys')
self._bot_team = Team()
- self._bot_team.manual_init(team_id=1,
- name=bad_team_name,
- color=(0.5, 0.4, 0.4))
+ self._bot_team.manual_init(
+ team_id=1, name=bad_team_name, color=(0.5, 0.4, 0.4)
+ )
for team in [self.teams[0], self._bot_team]:
team.score = 0
@@ -516,26 +579,32 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
assert isinstance(starttime_ms, int)
self._starttime_ms = starttime_ms
self._time_text = ba.NodeActor(
- 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, -50),
- 'scale': 1.3,
- 'text': ''
- }))
+ 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, -50),
+ 'scale': 1.3,
+ 'text': '',
+ },
+ )
+ )
self._time_text_input = ba.NodeActor(
- ba.newnode('timedisplay', attrs={'showsubseconds': True}))
- self.globalsnode.connectattr('time', self._time_text_input.node,
- 'time2')
+ ba.newnode('timedisplay', attrs={'showsubseconds': True})
+ )
+ self.globalsnode.connectattr(
+ 'time', self._time_text_input.node, 'time2'
+ )
assert self._time_text_input.node
assert self._time_text.node
- self._time_text_input.node.connectattr('output', self._time_text.node,
- 'text')
+ self._time_text_input.node.connectattr(
+ 'output', self._time_text.node, 'text'
+ )
# Our TNT spawner (if applicable).
if self._have_tnt:
@@ -554,15 +623,17 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# We want to move to the left by default.
spaz.target_point_default = ba.Vec3(0, 0, 0)
- def _spawn_bot(self,
- spaz_type: type[SpazBot],
- immediate: bool = False) -> None:
+ def _spawn_bot(
+ self, spaz_type: type[SpazBot], immediate: bool = False
+ ) -> None:
assert self._bot_team is not None
pos = self.map.get_start_position(self._bot_team.id)
- self._bots.spawn_bot(spaz_type,
- pos=pos,
- spawn_time=0.001 if immediate else 3.0,
- on_spawn_call=self._on_bot_spawn)
+ self._bots.spawn_bot(
+ spaz_type,
+ pos=pos,
+ spawn_time=0.001 if immediate else 3.0,
+ on_spawn_call=self._on_bot_spawn,
+ )
def _update_bots(self) -> None:
bots = self._bots.get_living_bots()
@@ -581,8 +652,10 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
for player in self.players:
if player.actor:
assert isinstance(player.actor, PlayerSpaz)
- if (player.actor.is_alive() and player.actor.node.hold_node
- == self._flag.node):
+ if (
+ player.actor.is_alive()
+ and player.actor.node.hold_node == self._flag.node
+ ):
return
flagpos = ba.Vec3(self._flag.node.position)
@@ -601,41 +674,52 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if closest_bot is not None:
closest_bot.target_flag = self._flag
- def _drop_powerup(self,
- index: int,
- poweruptype: str | None = None) -> None:
+ def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None:
if poweruptype is None:
- poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._exclude_powerups))
- PowerupBox(position=self.map.powerup_spawn_points[index],
- poweruptype=poweruptype).autoretain()
+ poweruptype = PowerupBoxFactory.get().get_random_powerup_type(
+ excludetypes=self._exclude_powerups
+ )
+ PowerupBox(
+ position=self.map.powerup_spawn_points[index],
+ poweruptype=poweruptype,
+ ).autoretain()
def _start_powerup_drops(self) -> None:
- self._powerup_drop_timer = ba.Timer(3.0,
- self._drop_powerups,
- repeat=True)
+ self._powerup_drop_timer = ba.Timer(
+ 3.0, self._drop_powerups, repeat=True
+ )
- def _drop_powerups(self,
- standard_points: bool = False,
- poweruptype: str | None = None) -> None:
+ def _drop_powerups(
+ self, standard_points: bool = False, poweruptype: str | None = None
+ ) -> None:
"""Generic powerup drop."""
if standard_points:
spawnpoints = self.map.powerup_spawn_points
for i, _point in enumerate(spawnpoints):
- ba.timer(1.0 + i * 0.5,
- ba.Call(self._drop_powerup, i, poweruptype))
+ ba.timer(
+ 1.0 + i * 0.5, ba.Call(self._drop_powerup, i, poweruptype)
+ )
else:
- point = (self._powerup_center[0] + random.uniform(
- -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
- self._powerup_center[1],
- self._powerup_center[2] + random.uniform(
- -self._powerup_spread[1], self._powerup_spread[1]))
+ point = (
+ self._powerup_center[0]
+ + random.uniform(
+ -1.0 * self._powerup_spread[0],
+ 1.0 * self._powerup_spread[0],
+ ),
+ self._powerup_center[1],
+ self._powerup_center[2]
+ + random.uniform(
+ -self._powerup_spread[1], self._powerup_spread[1]
+ ),
+ )
# Drop one random one somewhere.
PowerupBox(
position=point,
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._exclude_powerups)).autoretain()
+ excludetypes=self._exclude_powerups
+ ),
+ ).autoretain()
def _kill_flag(self) -> None:
try:
@@ -645,7 +729,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
ba.print_exception('Error in _kill_flag.')
def _handle_score(self) -> None:
- """ a point has been scored """
+ """a point has been scored"""
# FIXME tidy this up
# pylint: disable=too-many-branches
@@ -671,8 +755,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if i == 0:
for player in team.players:
if player.actor:
- player.actor.handlemessage(
- ba.CelebrateMessage(2.0))
+ player.actor.handlemessage(ba.CelebrateMessage(2.0))
else:
self._bots.celebrate(2.0)
@@ -699,12 +782,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
ba.timer(0.2, self._kill_flag)
self.update_scores()
- light = ba.newnode('light',
- attrs={
- 'position': ba.getcollision().position,
- 'height_attenuated': False,
- 'color': (1, 0, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': ba.getcollision().position,
+ 'height_attenuated': False,
+ 'color': (1, 0, 0),
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
if i == 0:
@@ -723,7 +808,7 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self.update_scores()
def update_scores(self) -> None:
- """ update scoreboard and check for winners """
+ """update scoreboard and check for winners"""
# FIXME: tidy this up
# pylint: disable=too-many-nested-blocks
have_scoring_team = False
@@ -743,41 +828,54 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
# Completion achievements.
assert self._bot_team is not None
if self._preset in ['rookie', 'rookie_easy']:
- self._award_achievement('Rookie Football Victory',
- sound=False)
+ self._award_achievement(
+ 'Rookie Football Victory', sound=False
+ )
if self._bot_team.score == 0:
self._award_achievement(
- 'Rookie Football Shutout', sound=False)
+ 'Rookie Football Shutout', sound=False
+ )
elif self._preset in ['pro', 'pro_easy']:
- self._award_achievement('Pro Football Victory',
- sound=False)
- if self._bot_team.score == 0:
- self._award_achievement('Pro Football Shutout',
- sound=False)
- elif self._preset in ['uber', 'uber_easy']:
- self._award_achievement('Uber Football Victory',
- sound=False)
+ self._award_achievement(
+ 'Pro Football Victory', sound=False
+ )
if self._bot_team.score == 0:
self._award_achievement(
- 'Uber Football Shutout', sound=False)
- if (not self._player_has_dropped_bomb
- and not self._player_has_punched):
- self._award_achievement('Got the Moves',
- sound=False)
+ 'Pro Football Shutout', sound=False
+ )
+ elif self._preset in ['uber', 'uber_easy']:
+ self._award_achievement(
+ 'Uber Football Victory', sound=False
+ )
+ if self._bot_team.score == 0:
+ self._award_achievement(
+ 'Uber Football Shutout', sound=False
+ )
+ if (
+ not self._player_has_dropped_bomb
+ and not self._player_has_punched
+ ):
+ self._award_achievement(
+ 'Got the Moves', sound=False
+ )
self._bots.stop_moving()
- self.show_zoom_message(ba.Lstr(resource='victoryText'),
- scale=1.0,
- duration=4.0)
+ self.show_zoom_message(
+ ba.Lstr(resource='victoryText'),
+ scale=1.0,
+ duration=4.0,
+ )
self.celebrate(10.0)
assert self._starttime_ms is not None
self._final_time_ms = int(
- ba.time(timeformat=ba.TimeFormat.MILLISECONDS) -
- self._starttime_ms)
+ ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
+ - self._starttime_ms
+ )
self._time_text_timer = None
- assert (self._time_text_input is not None
- and self._time_text_input.node)
- self._time_text_input.node.timemax = (
- self._final_time_ms)
+ assert (
+ self._time_text_input is not None
+ and self._time_text_input.node
+ )
+ self._time_text_input.node.timemax = self._final_time_ms
# FIXME: Does this still need to be deferred?
ba.pushcall(ba.Call(self.do_end, 'victory'))
@@ -787,18 +885,21 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
if outcome == 'defeat':
self.fade_to_red()
assert self._final_time_ms is not None
- scoreval = (None if outcome == 'defeat' else int(self._final_time_ms //
- 10))
- self.end(delay=3.0,
- results={
- 'outcome': outcome,
- 'score': scoreval,
- 'score_order': 'decreasing',
- 'playerinfos': self.initialplayerinfos
- })
+ scoreval = (
+ None if outcome == 'defeat' else int(self._final_time_ms // 10)
+ )
+ self.end(
+ delay=3.0,
+ results={
+ 'outcome': outcome,
+ 'score': scoreval,
+ 'score_order': 'decreasing',
+ 'playerinfos': self.initialplayerinfos,
+ },
+ )
def handlemessage(self, msg: Any) -> Any:
- """ handle high-level game messages """
+ """handle high-level game messages"""
if isinstance(msg, ba.PlayerDiedMessage):
# Augment standard behavior.
super().handlemessage(msg)
@@ -808,7 +909,8 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer(
- respawn_time, ba.Call(self.spawn_player_if_exists, player))
+ respawn_time, ba.Call(self.spawn_player_if_exists, player)
+ )
player.respawn_icon = RespawnIcon(player, respawn_time)
elif isinstance(msg, SpazBotDiedMessage):
@@ -829,28 +931,29 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
assert isinstance(msg.flag, FootballFlag)
msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag)
self._flag_respawn_light = ba.NodeActor(
- ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'height_attenuated': False,
- 'radius': 0.15,
- 'color': (1.0, 1.0, 0.3)
- }))
+ ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'height_attenuated': False,
+ 'radius': 0.15,
+ 'color': (1.0, 1.0, 0.3),
+ },
+ )
+ )
assert self._flag_respawn_light.node
- ba.animate(self._flag_respawn_light.node,
- 'intensity', {
- 0: 0,
- 0.25: 0.15,
- 0.5: 0
- },
- loop=True)
+ ba.animate(
+ self._flag_respawn_light.node,
+ 'intensity',
+ {0: 0, 0.25: 0.15, 0.5: 0},
+ loop=True,
+ )
ba.timer(3.0, self._flag_respawn_light.node.delete)
else:
return super().handlemessage(msg)
return None
- def _handle_player_dropped_bomb(self, player: Spaz,
- bomb: ba.Actor) -> None:
+ def _handle_player_dropped_bomb(self, player: Spaz, bomb: ba.Actor) -> None:
del player, bomb # Unused.
self._player_has_dropped_bomb = True
@@ -859,9 +962,9 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
self._player_has_punched = True
def spawn_player(self, player: Player) -> ba.Actor:
- spaz = self.spawn_player_spaz(player,
- position=self.map.get_start_position(
- player.team.id))
+ spaz = self.spawn_player_spaz(
+ player, position=self.map.get_start_position(player.team.id)
+ )
if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']:
spaz.impact_scale = 0.25
spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb)
@@ -869,12 +972,14 @@ class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
return spaz
def _flash_flag_spawn(self) -> None:
- light = ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'height_attenuated': False,
- 'color': (1, 1, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'height_attenuated': False,
+ 'color': (1, 1, 0),
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py
index 11110e78..d476aaad 100644
--- a/assets/src/ba_data/python/bastd/game/hockey.py
+++ b/assets/src/ba_data/python/bastd/game/hockey.py
@@ -41,19 +41,21 @@ class Puck(ba.Actor):
assert activity is not None
assert isinstance(activity, HockeyGame)
pmats = [shared.object_material, activity.puck_material]
- self.node = ba.newnode('prop',
- delegate=self,
- attrs={
- 'model': activity.puck_model,
- 'color_texture': activity.puck_tex,
- 'body': 'puck',
- 'reflection': 'soft',
- 'reflection_scale': [0.2],
- 'shadow_size': 1.0,
- 'is_area_of_interest': True,
- 'position': self._spawn_pos,
- 'materials': pmats
- })
+ self.node = ba.newnode(
+ 'prop',
+ delegate=self,
+ attrs={
+ 'model': activity.puck_model,
+ 'color_texture': activity.puck_tex,
+ 'body': 'puck',
+ 'reflection': 'soft',
+ 'reflection_scale': [0.2],
+ 'shadow_size': 1.0,
+ 'is_area_of_interest': True,
+ 'position': self._spawn_pos,
+ 'materials': pmats,
+ },
+ )
ba.animate(self.node, 'model_scale', {0: 0, 0.2: 1.3, 0.26: 1})
def handlemessage(self, msg: Any) -> Any:
@@ -73,11 +75,21 @@ class Puck(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], 1.0 * msg.magnitude,
- 1.0 * 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],
+ 1.0 * msg.magnitude,
+ 1.0 * msg.velocity_magnitude,
+ msg.radius,
+ 0,
+ msg.force_direction[0],
+ msg.force_direction[1],
+ msg.force_direction[2],
+ )
# If this hit came from a player, log them as the last to touch us.
s_player = msg.get_source_player(Player)
@@ -161,12 +173,13 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self.puck_tex = ba.gettexture('puckColor')
self._puck_sound = ba.getsound('metalHit')
self.puck_material = ba.Material()
- self.puck_material.add_actions(actions=(('modify_part_collision',
- 'friction', 0.5)))
- self.puck_material.add_actions(conditions=('they_have_material',
- shared.pickup_material),
- actions=('modify_part_collision',
- 'collide', False))
+ self.puck_material.add_actions(
+ actions=('modify_part_collision', 'friction', 0.5)
+ )
+ self.puck_material.add_actions(
+ conditions=('they_have_material', shared.pickup_material),
+ actions=('modify_part_collision', 'collide', False),
+ )
self.puck_material.add_actions(
conditions=(
('we_are_younger_than', 100),
@@ -175,29 +188,37 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
),
actions=('modify_node_collision', 'collide', False),
)
- self.puck_material.add_actions(conditions=('they_have_material',
- shared.footing_material),
- actions=('impact_sound',
- self._puck_sound, 0.2, 5))
+ self.puck_material.add_actions(
+ conditions=('they_have_material', shared.footing_material),
+ actions=('impact_sound', self._puck_sound, 0.2, 5),
+ )
# Keep track of which player last touched the puck
self.puck_material.add_actions(
conditions=('they_have_material', shared.player_material),
- actions=(('call', 'at_connect',
- self._handle_puck_player_collide), ))
+ actions=(('call', 'at_connect', self._handle_puck_player_collide),),
+ )
# We want the puck to kill powerups; not get stopped by them
self.puck_material.add_actions(
- conditions=('they_have_material',
- PowerupBoxFactory.get().powerup_material),
- actions=(('modify_part_collision', 'physical', False),
- ('message', 'their_node', 'at_connect', ba.DieMessage())))
+ conditions=(
+ 'they_have_material',
+ PowerupBoxFactory.get().powerup_material,
+ ),
+ actions=(
+ ('modify_part_collision', 'physical', False),
+ ('message', 'their_node', 'at_connect', ba.DieMessage()),
+ ),
+ )
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
conditions=('they_have_material', self.puck_material),
- actions=(('modify_part_collision', 'collide',
- True), ('modify_part_collision', 'physical', False),
- ('call', 'at_connect', self._handle_score)))
+ actions=(
+ ('modify_part_collision', 'collide', True),
+ ('modify_part_collision', 'physical', False),
+ ('call', 'at_connect', self._handle_score),
+ ),
+ )
self._puck_spawn_pos: Sequence[float] | None = None
self._score_regions: list[ba.NodeActor] | None = None
self._puck: Puck | None = None
@@ -205,8 +226,9 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.HOCKEY)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.HOCKEY
+ )
def get_instance_description(self) -> str | Sequence:
if self._score_to_win == 1:
@@ -231,22 +253,30 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
self._score_regions = []
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal1'][0:3],
- 'scale': defs.boxes['goal1'][6:9],
- 'type': 'box',
- 'materials': [self._score_region_material]
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal1'][0:3],
+ 'scale': defs.boxes['goal1'][6:9],
+ 'type': 'box',
+ 'materials': [self._score_region_material],
+ },
+ )
+ )
+ )
self._score_regions.append(
ba.NodeActor(
- ba.newnode('region',
- attrs={
- 'position': defs.boxes['goal2'][0:3],
- 'scale': defs.boxes['goal2'][6:9],
- 'type': 'box',
- 'materials': [self._score_region_material]
- })))
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': defs.boxes['goal2'][0:3],
+ 'scale': defs.boxes['goal2'][6:9],
+ 'type': 'box',
+ 'materials': [self._score_region_material],
+ },
+ )
+ )
+ )
self._update_scoreboard()
ba.playsound(self._chant_sound)
@@ -257,9 +287,9 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
collision = ba.getcollision()
try:
puck = collision.sourcenode.getdelegate(Puck, True)
- player = collision.opposingnode.getdelegate(PlayerSpaz,
- True).getplayer(
- Player, True)
+ player = collision.opposingnode.getdelegate(
+ PlayerSpaz, True
+ ).getplayer(Player, True)
except ba.NotFoundError:
return
@@ -297,12 +327,15 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
# If we've got the player from the scoring team that last
# touched us, give them points.
- if (scoring_team.id in self._puck.last_players_to_touch
- and self._puck.last_players_to_touch[scoring_team.id]):
+ if (
+ scoring_team.id in self._puck.last_players_to_touch
+ and self._puck.last_players_to_touch[scoring_team.id]
+ ):
self.stats.player_scored(
self._puck.last_players_to_touch[scoring_team.id],
100,
- big_message=True)
+ big_message=True,
+ )
# End game if we won.
if team.score >= self._score_to_win:
@@ -316,12 +349,14 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
# Kill the puck (it'll respawn itself shortly).
ba.timer(1.0, self._kill_puck)
- light = ba.newnode('light',
- attrs={
- 'position': ba.getcollision().position,
- 'height_attenuated': False,
- 'color': (1, 0, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': ba.getcollision().position,
+ 'height_attenuated': False,
+ 'color': (1, 0, 0),
+ },
+ )
ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
ba.timer(1.0, light.delete)
@@ -355,12 +390,14 @@ class HockeyGame(ba.TeamGameActivity[Player, Team]):
super().handlemessage(msg)
def _flash_puck_spawn(self) -> None:
- light = ba.newnode('light',
- attrs={
- 'position': self._puck_spawn_pos,
- 'height_attenuated': False,
- 'color': (1, 0, 0)
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._puck_spawn_pos,
+ 'height_attenuated': False,
+ 'color': (1, 0, 0),
+ },
+ )
ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
diff --git a/assets/src/ba_data/python/bastd/game/keepaway.py b/assets/src/ba_data/python/bastd/game/keepaway.py
index 3d3477e3..af7396e0 100644
--- a/assets/src/ba_data/python/bastd/game/keepaway.py
+++ b/assets/src/ba_data/python/bastd/game/keepaway.py
@@ -13,8 +13,12 @@ from typing import TYPE_CHECKING
import ba
from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.scoreboard import Scoreboard
-from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage,
- FlagPickedUpMessage)
+from bastd.actor.flag import (
+ Flag,
+ FlagDroppedMessage,
+ FlagDiedMessage,
+ FlagPickedUpMessage,
+)
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -22,6 +26,7 @@ if TYPE_CHECKING:
class FlagState(Enum):
"""States our single flag can be in."""
+
NEW = 0
UNCONTESTED = 1
CONTESTED = 2
@@ -82,8 +87,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
- return (issubclass(sessiontype, ba.DualTeamSession)
- or issubclass(sessiontype, ba.FreeForAllSession))
+ return issubclass(sessiontype, ba.DualTeamSession) or issubclass(
+ sessiontype, ba.FreeForAllSession
+ )
@classmethod
def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
@@ -104,7 +110,7 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
- 1: ba.getsound('announceOne')
+ 1: ba.getsound('announceOne'),
}
self._flag_spawn_pos: Sequence[float] | None = None
self._update_timer: ba.Timer | None = None
@@ -117,8 +123,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
self._time_limit = float(settings['Time Limit'])
self._epic_mode = bool(settings['Epic Mode'])
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.KEEP_AWAY)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.KEEP_AWAY
+ )
def get_instance_description(self) -> str | Sequence:
return 'Carry the flag for ${ARG1} seconds.', self._hold_time
@@ -149,10 +156,9 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
for player in self._holding_players:
if player:
assert self.stats
- self.stats.player_scored(player,
- 3,
- screenmessage=False,
- display=False)
+ self.stats.player_scored(
+ player, 3, screenmessage=False, display=False
+ )
scoreteam = self._scoring_team
@@ -189,10 +195,14 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
holdingflag = False
try:
assert isinstance(player.actor, (PlayerSpaz, type(None)))
- if (player.actor and player.actor.node
- and player.actor.node.hold_node):
+ if (
+ player.actor
+ and player.actor.node
+ and player.actor.node.hold_node
+ ):
holdingflag = (
- player.actor.node.hold_node.getnodetype() == 'flag')
+ player.actor.node.hold_node.getnodetype() == 'flag'
+ )
except Exception:
ba.print_exception('Error checking hold flag.')
if holdingflag:
@@ -230,34 +240,33 @@ class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
assert self._flag_spawn_pos is not None
self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos)
self._flag_state = FlagState.NEW
- self._flag_light = ba.newnode('light',
- owner=self._flag.node,
- attrs={
- 'intensity': 0.2,
- 'radius': 0.3,
- 'color': (0.2, 0.2, 0.2)
- })
+ self._flag_light = ba.newnode(
+ 'light',
+ owner=self._flag.node,
+ attrs={'intensity': 0.2, 'radius': 0.3, 'color': (0.2, 0.2, 0.2)},
+ )
assert self._flag.node
self._flag.node.connectattr('position', self._flag_light, 'position')
self._update_flag_state()
def _flash_flag_spawn(self) -> None:
- light = ba.newnode('light',
- attrs={
- 'position': self._flag_spawn_pos,
- 'color': (1, 1, 1),
- 'radius': 0.3,
- 'height_attenuated': False
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_spawn_pos,
+ 'color': (1, 1, 1),
+ 'radius': 0.3,
+ 'height_attenuated': False,
+ },
+ )
ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
ba.timer(1.0, light.delete)
def _update_scoreboard(self) -> None:
for team in self.teams:
- self._scoreboard.set_team_value(team,
- team.timeremaining,
- self._hold_time,
- countdown=True)
+ self._scoreboard.set_team_value(
+ team, team.timeremaining, self._hold_time, countdown=True
+ )
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
diff --git a/assets/src/ba_data/python/bastd/game/kingofthehill.py b/assets/src/ba_data/python/bastd/game/kingofthehill.py
index 677c9fdf..f70d13fe 100644
--- a/assets/src/ba_data/python/bastd/game/kingofthehill.py
+++ b/assets/src/ba_data/python/bastd/game/kingofthehill.py
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
class FlagState(Enum):
"""States our single flag can be in."""
+
NEW = 0
UNCONTESTED = 1
CONTESTED = 2
@@ -107,7 +108,7 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
4: ba.getsound('announceFour'),
3: ba.getsound('announceThree'),
2: ba.getsound('announceTwo'),
- 1: ba.getsound('announceOne')
+ 1: ba.getsound('announceOne'),
}
self._flag_pos: Sequence[float] | None = None
self._flag_state: FlagState | None = None
@@ -123,16 +124,24 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
actions=(
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
- ('call', 'at_connect',
- ba.Call(self._handle_player_flag_region_collide, True)),
- ('call', 'at_disconnect',
- ba.Call(self._handle_player_flag_region_collide, False)),
- ))
+ (
+ 'call',
+ 'at_connect',
+ ba.Call(self._handle_player_flag_region_collide, True),
+ ),
+ (
+ 'call',
+ 'at_disconnect',
+ ba.Call(self._handle_player_flag_region_collide, False),
+ ),
+ ),
+ )
# Base class overrides.
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.SCARY)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SCARY
+ )
def get_instance_description(self) -> str | Sequence:
return 'Secure the flag for ${ARG1} seconds.', self._hold_time
@@ -152,26 +161,30 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
ba.timer(1.0, self._tick, repeat=True)
self._flag_state = FlagState.NEW
Flag.project_stand(self._flag_pos)
- self._flag = Flag(position=self._flag_pos,
- touchable=False,
- color=(1, 1, 1))
- self._flag_light = ba.newnode('light',
- attrs={
- 'position': self._flag_pos,
- 'intensity': 0.2,
- 'height_attenuated': False,
- 'radius': 0.4,
- 'color': (0.2, 0.2, 0.2)
- })
+ self._flag = Flag(
+ position=self._flag_pos, touchable=False, color=(1, 1, 1)
+ )
+ self._flag_light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': self._flag_pos,
+ 'intensity': 0.2,
+ 'height_attenuated': False,
+ 'radius': 0.4,
+ 'color': (0.2, 0.2, 0.2),
+ },
+ )
# Flag region.
flagmats = [self._flag_region_material, shared.region_material]
- ba.newnode('region',
- attrs={
- 'position': self._flag_pos,
- 'scale': (1.8, 1.8, 1.8),
- 'type': 'sphere',
- 'materials': flagmats
- })
+ ba.newnode(
+ 'region',
+ attrs={
+ 'position': self._flag_pos,
+ 'scale': (1.8, 1.8, 1.8),
+ 'type': 'sphere',
+ 'materials': flagmats,
+ },
+ )
self._update_flag_state()
def _tick(self) -> None:
@@ -180,10 +193,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
# Give holding players points.
for player in self.players:
if player.time_at_flag > 0:
- self.stats.player_scored(player,
- 3,
- screenmessage=False,
- display=False)
+ self.stats.player_scored(
+ player, 3, screenmessage=False, display=False
+ )
if self._scoring_team is None:
scoring_team = None
else:
@@ -193,8 +205,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
if scoring_team.time_remaining > 0:
ba.playsound(self._tick_sound)
- scoring_team.time_remaining = max(0,
- scoring_team.time_remaining - 1)
+ scoring_team.time_remaining = max(
+ 0, scoring_team.time_remaining - 1
+ )
self._update_scoreboard()
if scoring_team.time_remaining > 0:
assert self._flag is not None
@@ -216,8 +229,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
self.end(results=results, announce_delay=0)
def _update_flag_state(self) -> None:
- holding_teams = set(player.team for player in self.players
- if player.time_at_flag)
+ holding_teams = set(
+ player.team for player in self.players if player.time_at_flag
+ )
prev_state = self._flag_state
assert self._flag_light
assert self._flag is not None
@@ -264,10 +278,9 @@ class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
def _update_scoreboard(self) -> None:
for team in self.teams:
- self._scoreboard.set_team_value(team,
- team.time_remaining,
- self._hold_time,
- countdown=True)
+ self._scoreboard.set_team_value(
+ team, team.time_remaining, self._hold_time, countdown=True
+ )
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
diff --git a/assets/src/ba_data/python/bastd/game/meteorshower.py b/assets/src/ba_data/python/bastd/game/meteorshower.py
index 2ca20125..754fa9a1 100644
--- a/assets/src/ba_data/python/bastd/game/meteorshower.py
+++ b/assets/src/ba_data/python/bastd/game/meteorshower.py
@@ -37,9 +37,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
name = 'Meteor Shower'
description = 'Dodge the falling bombs.'
available_settings = [ba.BoolSetting('Epic Mode', default=False)]
- scoreconfig = ba.ScoreConfig(label='Survived',
- scoretype=ba.ScoreType.MILLISECONDS,
- version='B')
+ scoreconfig = ba.ScoreConfig(
+ label='Survived', scoretype=ba.ScoreType.MILLISECONDS, version='B'
+ )
# Print messages when players die (since its meaningful in this game).
announce_player_deaths = True
@@ -56,9 +56,11 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
# We support teams, free-for-all, and co-op sessions.
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
- return (issubclass(sessiontype, ba.DualTeamSession)
- or issubclass(sessiontype, ba.FreeForAllSession)
- or issubclass(sessiontype, ba.CoopSession))
+ return (
+ issubclass(sessiontype, ba.DualTeamSession)
+ or issubclass(sessiontype, ba.FreeForAllSession)
+ or issubclass(sessiontype, ba.CoopSession)
+ )
def __init__(self, settings: dict):
super().__init__(settings)
@@ -69,8 +71,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
self._timer: OnScreenTimer | None = None
# Some base class overrides:
- self.default_music = (ba.MusicType.EPIC
- if self._epic_mode else ba.MusicType.SURVIVAL)
+ self.default_music = (
+ ba.MusicType.EPIC if self._epic_mode else ba.MusicType.SURVIVAL
+ )
if self._epic_mode:
self.slow_motion = True
@@ -110,9 +113,9 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
# Let's reconnect this player's controls to this
# spaz but *without* the ability to attack or pick stuff up.
- spaz.connect_controls_to_player(enable_punch=False,
- enable_bomb=False,
- enable_pickup=False)
+ spaz.connect_controls_to_player(
+ enable_punch=False, enable_bomb=False, enable_pickup=False
+ )
# Also lets have them make some noise when they die.
spaz.play_big_death_sound = True
@@ -168,8 +171,10 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
self.end_game()
def _set_meteor_timer(self) -> None:
- ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
- self._drop_bomb_cluster)
+ ba.timer(
+ (1.0 + 0.2 * random.random()) * self._meteor_time,
+ self._drop_bomb_cluster,
+ )
def _drop_bomb_cluster(self) -> None:
@@ -187,17 +192,24 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
for _i in range(random.randrange(1, 3)):
# Drop them somewhere within our bounds with velocity pointing
# toward the opposite side.
- pos = (-7.3 + 15.3 * random.random(), 11,
- -5.57 + 2.1 * random.random())
- dropdir = (-1.0 if pos[0] > 0 else 1.0)
- vel = ((-5.0 + random.random() * 30.0) * dropdir,
- random.uniform(-3.066, -4.12), 0)
+ pos = (
+ -7.3 + 15.3 * random.random(),
+ 11,
+ -5.57 + 2.1 * random.random(),
+ )
+ dropdir = -1.0 if pos[0] > 0 else 1.0
+ vel = (
+ (-5.0 + random.random() * 30.0) * dropdir,
+ random.uniform(-3.066, -4.12),
+ 0,
+ )
ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
delay += 0.1
self._set_meteor_timer()
- def _drop_bomb(self, position: Sequence[float],
- velocity: Sequence[float]) -> None:
+ def _drop_bomb(
+ self, position: Sequence[float], velocity: Sequence[float]
+ ) -> None:
Bomb(position=position, velocity=velocity).autoretain()
def _decrement_meteor_time(self) -> None:
@@ -247,8 +259,7 @@ class MeteorShowerGame(ba.TeamGameActivity[Player, Team]):
longest_life = 0.0
for player in team.players:
assert player.death_time is not None
- longest_life = max(longest_life,
- player.death_time - start_time)
+ longest_life = max(longest_life, player.death_time - start_time)
# Submit the score value in milliseconds.
results.set_team_score(team, int(1000.0 * longest_life))
diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py
index 35402632..343ac1eb 100644
--- a/assets/src/ba_data/python/bastd/game/ninjafight.py
+++ b/assets/src/ba_data/python/bastd/game/ninjafight.py
@@ -35,9 +35,9 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
name = 'Ninja Fight'
description = 'How fast can you defeat the ninjas?'
- scoreconfig = ba.ScoreConfig(label='Time',
- scoretype=ba.ScoreType.MILLISECONDS,
- lower_is_better=True)
+ scoreconfig = ba.ScoreConfig(
+ label='Time', scoretype=ba.ScoreType.MILLISECONDS, lower_is_better=True
+ )
default_music = ba.MusicType.TO_THE_DEATH
@classmethod
@@ -77,36 +77,57 @@ class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
# Spawn some baddies.
ba.timer(
- 1.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
+ 1.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(3, 3, -2), spawn_time=3.0
+ ),
+ )
ba.timer(
- 2.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
+ 2.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(-3, 3, -2), spawn_time=3.0
+ ),
+ )
ba.timer(
- 3.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
+ 3.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(5, 3, -2), spawn_time=3.0
+ ),
+ )
ba.timer(
- 4.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
+ 4.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(-5, 3, -2), spawn_time=3.0
+ ),
+ )
# Add some extras for multiplayer or pro mode.
assert self.initialplayerinfos is not None
if len(self.initialplayerinfos) > 2 or is_pro:
ba.timer(
- 5.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
+ 5.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(0, 3, -5), spawn_time=3.0
+ ),
+ )
if len(self.initialplayerinfos) > 3 or is_pro:
ba.timer(
- 6.0, lambda: self._bots.spawn_bot(
- ChargerBot, pos=(0, 3, 1), spawn_time=3.0))
+ 6.0,
+ lambda: self._bots.spawn_bot(
+ ChargerBot, pos=(0, 3, 1), spawn_time=3.0
+ ),
+ )
# Called for each spawning player.
def spawn_player(self, player: Player) -> ba.Actor:
# Let's spawn close to the center.
spawn_center = (0, 3, -2)
- pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
- spawn_center[2] + random.uniform(-1.5, 1.5))
+ pos = (
+ spawn_center[0] + random.uniform(-1.5, 1.5),
+ spawn_center[1],
+ spawn_center[2] + random.uniform(-1.5, 1.5),
+ )
return self.spawn_player_spaz(player, position=pos)
def _check_if_won(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py
index c3d51898..90aa87be 100644
--- a/assets/src/ba_data/python/bastd/game/onslaught.py
+++ b/assets/src/ba_data/python/bastd/game/onslaught.py
@@ -24,11 +24,27 @@ from bastd.actor.scoreboard import Scoreboard
from bastd.actor.controlsguide import ControlsGuide
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory
from bastd.actor.spazbot import (
- SpazBotDiedMessage, SpazBotSet, ChargerBot, StickyBot, BomberBot,
- BomberBotLite, BrawlerBot, BrawlerBotLite, TriggerBot, BomberBotStaticLite,
- TriggerBotStatic, BomberBotProStatic, TriggerBotPro, ExplodeyBot,
- BrawlerBotProShielded, ChargerBotProShielded, BomberBotPro,
- TriggerBotProShielded, BrawlerBotPro, BomberBotProShielded)
+ SpazBotDiedMessage,
+ SpazBotSet,
+ ChargerBot,
+ StickyBot,
+ BomberBot,
+ BomberBotLite,
+ BrawlerBot,
+ BrawlerBotLite,
+ TriggerBot,
+ BomberBotStaticLite,
+ TriggerBotStatic,
+ BomberBotProStatic,
+ TriggerBotPro,
+ ExplodeyBot,
+ BrawlerBotProShielded,
+ ChargerBotProShielded,
+ BomberBotPro,
+ TriggerBotProShielded,
+ BrawlerBotPro,
+ BomberBotProShielded,
+)
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -38,6 +54,7 @@ if TYPE_CHECKING:
@dataclass
class Wave:
"""A wave of enemies."""
+
entries: list[Spawn | Spacing | Delay | None]
base_angle: float = 0.0
@@ -45,6 +62,7 @@ class Wave:
@dataclass
class Spawn:
"""A bot spawn event in a wave."""
+
bottype: type[SpazBot] | str
point: Point | None = None
spacing: float = 5.0
@@ -53,17 +71,20 @@ class Spawn:
@dataclass
class Spacing:
"""Empty space in a wave."""
+
spacing: float = 5.0
@dataclass
class Delay:
"""A delay between events in a wave."""
+
duration: float
class Preset(Enum):
"""Game presets we support."""
+
TRAINING = 'training'
TRAINING_EASY = 'training_easy'
ROOKIE = 'rookie'
@@ -79,6 +100,7 @@ class Preset(Enum):
@unique
class Point(Enum):
"""Points on the map we can spawn at."""
+
LEFT_UPPER_MORE = 'bot_spawn_left_upper_more'
LEFT_UPPER = 'bot_spawn_left_upper'
TURRET_TOP_RIGHT = 'bot_spawn_turret_top_right'
@@ -134,7 +156,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
'It\'s easier to win with a friend or two helping.',
'If you stay in one place, you\'re toast. Run and dodge to survive..',
'Practice using your momentum to throw bombs more accurately.',
- 'Your punches do much more damage if you are running or spinning.'
+ 'Your punches do much more damage if you are running or spinning.',
]
# Show messages when players die since it matters here.
@@ -144,8 +166,12 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
self._preset = Preset(settings.get('preset', 'training'))
if self._preset in {
- Preset.TRAINING, Preset.TRAINING_EASY, Preset.PRO,
- Preset.PRO_EASY, Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT
+ Preset.TRAINING,
+ Preset.TRAINING_EASY,
+ Preset.PRO,
+ Preset.PRO_EASY,
+ Preset.ENDLESS,
+ Preset.ENDLESS_TOURNAMENT,
}:
settings['map'] = 'Doom Shroom'
else:
@@ -209,7 +235,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
ba.GameTip(
'Land-mines are a good way to stop speedy enemies.',
icon=ba.gettexture('powerupLandMines'),
- sound=ba.getsound('ding'))
+ sound=ba.getsound('ding'),
+ )
]
# Show special tnt tip on pro preset.
@@ -222,7 +249,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
'Take out a group of enemies by\n'
'setting off a bomb near a TNT box.',
icon=ba.gettexture('tnt'),
- sound=ba.getsound('ding'))
+ sound=ba.getsound('ding'),
+ )
]
# Show special curse tip on uber preset.
@@ -235,30 +263,37 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
'Curse boxes turn you into a ticking time bomb.\n'
'The only cure is to quickly grab a health-pack.',
icon=ba.gettexture('powerupCurse'),
- sound=ba.getsound('ding'))
+ sound=ba.getsound('ding'),
+ )
]
self._spawn_info_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'position': (15, -130),
- 'h_attach': 'left',
- 'v_attach': 'top',
- 'scale': 0.55,
- 'color': (0.3, 0.8, 0.3, 1.0),
- 'text': ''
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'position': (15, -130),
+ 'h_attach': 'left',
+ 'v_attach': 'top',
+ 'scale': 0.55,
+ 'color': (0.3, 0.8, 0.3, 1.0),
+ 'text': '',
+ },
+ )
+ )
ba.setmusic(ba.MusicType.ONSLAUGHT)
- self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
- score_split=0.5)
+ self._scoreboard = Scoreboard(
+ label=ba.Lstr(resource='scoreText'), score_split=0.5
+ )
def on_begin(self) -> None:
super().on_begin()
player_count = len(self.players)
hard = self._preset not in {
- Preset.TRAINING_EASY, Preset.ROOKIE_EASY, Preset.PRO_EASY,
- Preset.UBER_EASY
+ Preset.TRAINING_EASY,
+ Preset.ROOKIE_EASY,
+ Preset.PRO_EASY,
+ Preset.UBER_EASY,
}
if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}:
ControlsGuide(delay=3.0, lifespan=10.0, bright=True).autoretain()
@@ -266,251 +301,352 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
self._have_tnt = False
self._excluded_powerups = ['curse', 'land_mines']
self._waves = [
- Wave(base_angle=195,
- entries=[
- Spawn(BomberBotLite, spacing=5),
- ] * player_count),
- Wave(base_angle=130,
- entries=[
- Spawn(BrawlerBotLite, spacing=5),
- ] * player_count),
- Wave(base_angle=195,
- entries=[Spawn(BomberBotLite, spacing=10)] *
- (player_count + 1)),
- Wave(base_angle=130,
- entries=[
- Spawn(BrawlerBotLite, spacing=10),
- ] * (player_count + 1)),
- Wave(base_angle=130,
- entries=[
- Spawn(BrawlerBotLite, spacing=5)
- if player_count > 1 else None,
- Spawn(BrawlerBotLite, spacing=5),
- Spacing(30),
- Spawn(BomberBotLite, spacing=5)
- if player_count > 3 else None,
- Spawn(BomberBotLite, spacing=5),
- Spacing(30),
- Spawn(BrawlerBotLite, spacing=5),
- Spawn(BrawlerBotLite, spacing=5)
- if player_count > 2 else None,
- ]),
- Wave(base_angle=195,
- entries=[
- Spawn(TriggerBot, spacing=90),
- Spawn(TriggerBot, spacing=90)
- if player_count > 1 else None,
- ]),
+ Wave(
+ base_angle=195,
+ entries=[
+ Spawn(BomberBotLite, spacing=5),
+ ]
+ * player_count,
+ ),
+ Wave(
+ base_angle=130,
+ entries=[
+ Spawn(BrawlerBotLite, spacing=5),
+ ]
+ * player_count,
+ ),
+ Wave(
+ base_angle=195,
+ entries=[Spawn(BomberBotLite, spacing=10)]
+ * (player_count + 1),
+ ),
+ Wave(
+ base_angle=130,
+ entries=[
+ Spawn(BrawlerBotLite, spacing=10),
+ ]
+ * (player_count + 1),
+ ),
+ Wave(
+ base_angle=130,
+ entries=[
+ Spawn(BrawlerBotLite, spacing=5)
+ if player_count > 1
+ else None,
+ Spawn(BrawlerBotLite, spacing=5),
+ Spacing(30),
+ Spawn(BomberBotLite, spacing=5)
+ if player_count > 3
+ else None,
+ Spawn(BomberBotLite, spacing=5),
+ Spacing(30),
+ Spawn(BrawlerBotLite, spacing=5),
+ Spawn(BrawlerBotLite, spacing=5)
+ if player_count > 2
+ else None,
+ ],
+ ),
+ Wave(
+ base_angle=195,
+ entries=[
+ Spawn(TriggerBot, spacing=90),
+ Spawn(TriggerBot, spacing=90)
+ if player_count > 1
+ else None,
+ ],
+ ),
]
elif self._preset in {Preset.ROOKIE, Preset.ROOKIE_EASY}:
self._have_tnt = False
self._excluded_powerups = ['curse']
self._waves = [
- Wave(entries=[
- Spawn(ChargerBot, Point.LEFT_UPPER_MORE
- ) if player_count > 2 else None,
- Spawn(ChargerBot, Point.LEFT_UPPER),
- ]),
- Wave(entries=[
- Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
- Spawn(BrawlerBotLite, Point.RIGHT_UPPER),
- Spawn(BrawlerBotLite, Point.RIGHT_LOWER
- ) if player_count > 1 else None,
- Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT
- ) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT),
- Spawn(TriggerBot, Point.LEFT),
- Spawn(TriggerBot, Point.LEFT_LOWER
- ) if player_count > 1 else None,
- Spawn(TriggerBot, Point.LEFT_UPPER
- ) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(BrawlerBotLite, Point.TOP_RIGHT),
- Spawn(BrawlerBot, Point.TOP_HALF_RIGHT
- ) if player_count > 1 else None,
- Spawn(BrawlerBotLite, Point.TOP_LEFT),
- Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT
- ) if player_count > 2 else None,
- Spawn(BrawlerBot, Point.TOP),
- Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE),
- ]),
- Wave(entries=[
- Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT),
- Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT),
- Spawn(TriggerBot, Point.BOTTOM),
- Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT
- ) if player_count > 1 else None,
- Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT
- ) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT),
- Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
- Spawn(ChargerBot, Point.BOTTOM),
- Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT
- ) if player_count > 1 else None,
- Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT
- ) if player_count > 2 else None,
- ]),
+ Wave(
+ entries=[
+ Spawn(ChargerBot, Point.LEFT_UPPER_MORE)
+ if player_count > 2
+ else None,
+ Spawn(ChargerBot, Point.LEFT_UPPER),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
+ Spawn(BrawlerBotLite, Point.RIGHT_UPPER),
+ Spawn(BrawlerBotLite, Point.RIGHT_LOWER)
+ if player_count > 1
+ else None,
+ Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_RIGHT)
+ if player_count > 2
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotStaticLite, Point.TURRET_BOTTOM_LEFT),
+ Spawn(TriggerBot, Point.LEFT),
+ Spawn(TriggerBot, Point.LEFT_LOWER)
+ if player_count > 1
+ else None,
+ Spawn(TriggerBot, Point.LEFT_UPPER)
+ if player_count > 2
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BrawlerBotLite, Point.TOP_RIGHT),
+ Spawn(BrawlerBot, Point.TOP_HALF_RIGHT)
+ if player_count > 1
+ else None,
+ Spawn(BrawlerBotLite, Point.TOP_LEFT),
+ Spawn(BrawlerBotLite, Point.TOP_HALF_LEFT)
+ if player_count > 2
+ else None,
+ Spawn(BrawlerBot, Point.TOP),
+ Spawn(BomberBotStaticLite, Point.TURRET_TOP_MIDDLE),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_LEFT),
+ Spawn(TriggerBotStatic, Point.TURRET_BOTTOM_RIGHT),
+ Spawn(TriggerBot, Point.BOTTOM),
+ Spawn(TriggerBot, Point.BOTTOM_HALF_RIGHT)
+ if player_count > 1
+ else None,
+ Spawn(TriggerBot, Point.BOTTOM_HALF_LEFT)
+ if player_count > 2
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotStaticLite, Point.TURRET_TOP_LEFT),
+ Spawn(BomberBotStaticLite, Point.TURRET_TOP_RIGHT),
+ Spawn(ChargerBot, Point.BOTTOM),
+ Spawn(ChargerBot, Point.BOTTOM_HALF_LEFT)
+ if player_count > 1
+ else None,
+ Spawn(ChargerBot, Point.BOTTOM_HALF_RIGHT)
+ if player_count > 2
+ else None,
+ ]
+ ),
]
elif self._preset in {Preset.PRO, Preset.PRO_EASY}:
self._excluded_powerups = ['curse']
self._have_tnt = True
self._waves = [
- Wave(base_angle=-50,
- entries=[
- Spawn(BrawlerBot, spacing=12)
- if player_count > 3 else None,
- Spawn(BrawlerBot, spacing=12),
- Spawn(BomberBot, spacing=6),
- Spawn(BomberBot, spacing=6)
- if self._preset is Preset.PRO else None,
- Spawn(BomberBot, spacing=6)
- if player_count > 1 else None,
- Spawn(BrawlerBot, spacing=12),
- Spawn(BrawlerBot, spacing=12)
- if player_count > 2 else None,
- ]),
- Wave(base_angle=180,
- entries=[
- Spawn(BrawlerBot, spacing=6)
- if player_count > 3 else None,
- Spawn(BrawlerBot, spacing=6)
- if self._preset is Preset.PRO else None,
- Spawn(BrawlerBot, spacing=6),
- Spawn(ChargerBot, spacing=45),
- Spawn(ChargerBot, spacing=45)
- if player_count > 1 else None,
- Spawn(BrawlerBot, spacing=6),
- Spawn(BrawlerBot, spacing=6)
- if self._preset is Preset.PRO else None,
- Spawn(BrawlerBot, spacing=6)
- if player_count > 2 else None,
- ]),
- Wave(base_angle=0,
- entries=[
- Spawn(ChargerBot, spacing=30),
- Spawn(TriggerBot, spacing=30),
- Spawn(TriggerBot, spacing=30),
- Spawn(TriggerBot, spacing=30)
- if self._preset is Preset.PRO else None,
- Spawn(TriggerBot, spacing=30)
- if player_count > 1 else None,
- Spawn(TriggerBot, spacing=30)
- if player_count > 3 else None,
- Spawn(ChargerBot, spacing=30),
- ]),
- Wave(base_angle=90,
- entries=[
- Spawn(StickyBot, spacing=50),
- Spawn(StickyBot, spacing=50)
- if self._preset is Preset.PRO else None,
- Spawn(StickyBot, spacing=50),
- Spawn(StickyBot, spacing=50)
- if player_count > 1 else None,
- Spawn(StickyBot, spacing=50)
- if player_count > 3 else None,
- ]),
- Wave(base_angle=0,
- entries=[
- Spawn(TriggerBot, spacing=72),
- Spawn(TriggerBot, spacing=72),
- Spawn(TriggerBot, spacing=72)
- if self._preset is Preset.PRO else None,
- Spawn(TriggerBot, spacing=72),
- Spawn(TriggerBot, spacing=72),
- Spawn(TriggerBot, spacing=36)
- if player_count > 2 else None,
- ]),
- Wave(base_angle=30,
- entries=[
- Spawn(ChargerBotProShielded, spacing=50),
- Spawn(ChargerBotProShielded, spacing=50),
- Spawn(ChargerBotProShielded, spacing=50)
- if self._preset is Preset.PRO else None,
- Spawn(ChargerBotProShielded, spacing=50)
- if player_count > 1 else None,
- Spawn(ChargerBotProShielded, spacing=50)
- if player_count > 2 else None,
- ])
+ Wave(
+ base_angle=-50,
+ entries=[
+ Spawn(BrawlerBot, spacing=12)
+ if player_count > 3
+ else None,
+ Spawn(BrawlerBot, spacing=12),
+ Spawn(BomberBot, spacing=6),
+ Spawn(BomberBot, spacing=6)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(BomberBot, spacing=6)
+ if player_count > 1
+ else None,
+ Spawn(BrawlerBot, spacing=12),
+ Spawn(BrawlerBot, spacing=12)
+ if player_count > 2
+ else None,
+ ],
+ ),
+ Wave(
+ base_angle=180,
+ entries=[
+ Spawn(BrawlerBot, spacing=6)
+ if player_count > 3
+ else None,
+ Spawn(BrawlerBot, spacing=6)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(BrawlerBot, spacing=6),
+ Spawn(ChargerBot, spacing=45),
+ Spawn(ChargerBot, spacing=45)
+ if player_count > 1
+ else None,
+ Spawn(BrawlerBot, spacing=6),
+ Spawn(BrawlerBot, spacing=6)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(BrawlerBot, spacing=6)
+ if player_count > 2
+ else None,
+ ],
+ ),
+ Wave(
+ base_angle=0,
+ entries=[
+ Spawn(ChargerBot, spacing=30),
+ Spawn(TriggerBot, spacing=30),
+ Spawn(TriggerBot, spacing=30),
+ Spawn(TriggerBot, spacing=30)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(TriggerBot, spacing=30)
+ if player_count > 1
+ else None,
+ Spawn(TriggerBot, spacing=30)
+ if player_count > 3
+ else None,
+ Spawn(ChargerBot, spacing=30),
+ ],
+ ),
+ Wave(
+ base_angle=90,
+ entries=[
+ Spawn(StickyBot, spacing=50),
+ Spawn(StickyBot, spacing=50)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(StickyBot, spacing=50),
+ Spawn(StickyBot, spacing=50)
+ if player_count > 1
+ else None,
+ Spawn(StickyBot, spacing=50)
+ if player_count > 3
+ else None,
+ ],
+ ),
+ Wave(
+ base_angle=0,
+ entries=[
+ Spawn(TriggerBot, spacing=72),
+ Spawn(TriggerBot, spacing=72),
+ Spawn(TriggerBot, spacing=72)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(TriggerBot, spacing=72),
+ Spawn(TriggerBot, spacing=72),
+ Spawn(TriggerBot, spacing=36)
+ if player_count > 2
+ else None,
+ ],
+ ),
+ Wave(
+ base_angle=30,
+ entries=[
+ Spawn(ChargerBotProShielded, spacing=50),
+ Spawn(ChargerBotProShielded, spacing=50),
+ Spawn(ChargerBotProShielded, spacing=50)
+ if self._preset is Preset.PRO
+ else None,
+ Spawn(ChargerBotProShielded, spacing=50)
+ if player_count > 1
+ else None,
+ Spawn(ChargerBotProShielded, spacing=50)
+ if player_count > 2
+ else None,
+ ],
+ ),
]
elif self._preset in {Preset.UBER, Preset.UBER_EASY}:
# Show controls help in demo/arcade modes.
if ba.app.demo_mode or ba.app.arcade_mode:
- ControlsGuide(delay=3.0, lifespan=10.0,
- bright=True).autoretain()
+ ControlsGuide(
+ delay=3.0, lifespan=10.0, bright=True
+ ).autoretain()
self._have_tnt = True
self._excluded_powerups = []
self._waves = [
- Wave(entries=[
- Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT
- ) if hard else None,
- Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT),
- Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT
- ) if player_count > 2 else None,
- Spawn(ExplodeyBot, Point.TOP_RIGHT),
- Delay(4.0),
- Spawn(ExplodeyBot, Point.TOP_LEFT),
- ]),
- Wave(entries=[
- Spawn(ChargerBot, Point.LEFT),
- Spawn(ChargerBot, Point.RIGHT),
- Spawn(ChargerBot, Point.RIGHT_UPPER_MORE
- ) if player_count > 2 else None,
- Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT),
- Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
- ]),
- Wave(entries=[
- Spawn(TriggerBotPro, Point.TOP_RIGHT),
- Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE
- ) if player_count > 1 else None,
- Spawn(TriggerBotPro, Point.RIGHT_UPPER),
- Spawn(TriggerBotPro, Point.RIGHT_LOWER) if hard else None,
- Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE
- ) if player_count > 2 else None,
- Spawn(TriggerBotPro, Point.BOTTOM_RIGHT),
- ]),
- Wave(entries=[
- Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT),
- Spawn(ChargerBotProShielded, Point.BOTTOM
- ) if player_count > 2 else None,
- Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT),
- Spawn(ChargerBotProShielded, Point.TOP) if hard else None,
- Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE),
- ]),
- Wave(entries=[
- Spawn(ExplodeyBot, Point.LEFT_UPPER),
- Delay(1.0),
- Spawn(BrawlerBotProShielded, Point.LEFT_LOWER),
- Spawn(BrawlerBotProShielded, Point.LEFT_LOWER_MORE),
- Delay(4.0),
- Spawn(ExplodeyBot, Point.RIGHT_UPPER),
- Delay(1.0),
- Spawn(BrawlerBotProShielded, Point.RIGHT_LOWER),
- Spawn(BrawlerBotProShielded, Point.RIGHT_UPPER_MORE),
- Delay(4.0),
- Spawn(ExplodeyBot, Point.LEFT),
- Delay(5.0),
- Spawn(ExplodeyBot, Point.RIGHT),
- ]),
- Wave(entries=[
- Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT),
- Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
- Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT),
- Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT),
- Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT
- ) if hard else None,
- Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT
- ) if hard else None,
- ])
+ Wave(
+ entries=[
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT)
+ if hard
+ else None,
+ Spawn(
+ BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT
+ ),
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT)
+ if player_count > 2
+ else None,
+ Spawn(ExplodeyBot, Point.TOP_RIGHT),
+ Delay(4.0),
+ Spawn(ExplodeyBot, Point.TOP_LEFT),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(ChargerBot, Point.LEFT),
+ Spawn(ChargerBot, Point.RIGHT),
+ Spawn(ChargerBot, Point.RIGHT_UPPER_MORE)
+ if player_count > 2
+ else None,
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT),
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(TriggerBotPro, Point.TOP_RIGHT),
+ Spawn(TriggerBotPro, Point.RIGHT_UPPER_MORE)
+ if player_count > 1
+ else None,
+ Spawn(TriggerBotPro, Point.RIGHT_UPPER),
+ Spawn(TriggerBotPro, Point.RIGHT_LOWER)
+ if hard
+ else None,
+ Spawn(TriggerBotPro, Point.RIGHT_LOWER_MORE)
+ if player_count > 2
+ else None,
+ Spawn(TriggerBotPro, Point.BOTTOM_RIGHT),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(ChargerBotProShielded, Point.BOTTOM_RIGHT),
+ Spawn(ChargerBotProShielded, Point.BOTTOM)
+ if player_count > 2
+ else None,
+ Spawn(ChargerBotProShielded, Point.BOTTOM_LEFT),
+ Spawn(ChargerBotProShielded, Point.TOP)
+ if hard
+ else None,
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(ExplodeyBot, Point.LEFT_UPPER),
+ Delay(1.0),
+ Spawn(BrawlerBotProShielded, Point.LEFT_LOWER),
+ Spawn(BrawlerBotProShielded, Point.LEFT_LOWER_MORE),
+ Delay(4.0),
+ Spawn(ExplodeyBot, Point.RIGHT_UPPER),
+ Delay(1.0),
+ Spawn(BrawlerBotProShielded, Point.RIGHT_LOWER),
+ Spawn(BrawlerBotProShielded, Point.RIGHT_UPPER_MORE),
+ Delay(4.0),
+ Spawn(ExplodeyBot, Point.LEFT),
+ Delay(5.0),
+ Spawn(ExplodeyBot, Point.RIGHT),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_LEFT),
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_RIGHT),
+ Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_LEFT),
+ Spawn(BomberBotProStatic, Point.TURRET_BOTTOM_RIGHT),
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_LEFT)
+ if hard
+ else None,
+ Spawn(BomberBotProStatic, Point.TURRET_TOP_MIDDLE_RIGHT)
+ if hard
+ else None,
+ ]
+ ),
]
# We generate these on the fly in endless.
@@ -525,11 +661,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
# FIXME: Should migrate to use setup_standard_powerup_drops().
# Spit out a few powerups and start dropping more shortly.
- self._drop_powerups(standard_points=True,
- poweruptype='curse' if self._preset
- in [Preset.UBER, Preset.UBER_EASY] else
- ('land_mines' if self._preset
- in [Preset.ROOKIE, Preset.ROOKIE_EASY] else None))
+ self._drop_powerups(
+ standard_points=True,
+ poweruptype='curse'
+ if self._preset in [Preset.UBER, Preset.UBER_EASY]
+ else (
+ 'land_mines'
+ if self._preset in [Preset.ROOKIE, Preset.ROOKIE_EASY]
+ else None
+ ),
+ )
ba.timer(4.0, self._start_powerup_drops)
# Our TNT spawner (if applicable).
@@ -554,9 +695,14 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
totaldudes += dudes
return totalpts, totaldudes
- def _get_distribution(self, target_points: int, min_dudes: int,
- max_dudes: int, group_count: int,
- max_level: int) -> list[list[tuple[int, int]]]:
+ def _get_distribution(
+ self,
+ target_points: int,
+ min_dudes: int,
+ max_dudes: int,
+ group_count: int,
+ max_level: int,
+ ) -> list[list[tuple[int, int]]]:
"""Calculate a distribution of bad guys given some params."""
max_iterations = 10 + max_dudes * 2
@@ -571,11 +717,12 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if max_level > 3:
types.append(4)
for iteration in range(max_iterations):
- diff = self._add_dist_entry_if_possible(groups, max_dudes,
- target_points, types)
+ diff = self._add_dist_entry_if_possible(
+ groups, max_dudes, target_points, types
+ )
total_points, total_dudes = self._get_dist_grp_totals(groups)
- full = (total_points >= target_points)
+ full = total_points >= target_points
if full:
# Every so often, delete a random entry just to
@@ -585,14 +732,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
# If we don't have enough dudes, kill the group with
# the biggest point value.
- elif (total_dudes < min_dudes
- and iteration != max_iterations - 1):
+ elif (
+ total_dudes < min_dudes and iteration != max_iterations - 1
+ ):
self._delete_biggest_dist_entry(groups)
# If we've got too many dudes, kill the group with the
# smallest point value.
- elif (total_dudes > max_dudes
- and iteration != max_iterations - 1):
+ elif (
+ total_dudes > max_dudes and iteration != max_iterations - 1
+ ):
self._delete_smallest_dist_entry(groups)
# Close enough.. we're done.
@@ -602,9 +751,13 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
return groups
- def _add_dist_entry_if_possible(self, groups: list[list[tuple[int, int]]],
- max_dudes: int, target_points: int,
- types: list[int]) -> int:
+ def _add_dist_entry_if_possible(
+ self,
+ groups: list[list[tuple[int, int]]],
+ max_dudes: int,
+ target_points: int,
+ types: list[int],
+ ) -> int:
# See how much we're off our target by.
total_points, total_dudes = self._get_dist_grp_totals(groups)
diff = target_points - total_points
@@ -627,7 +780,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
return diff
def _delete_smallest_dist_entry(
- self, groups: list[list[tuple[int, int]]]) -> None:
+ self, groups: list[list[tuple[int, int]]]
+ ) -> None:
smallest_value = 9999
smallest_entry = None
smallest_entry_group = None
@@ -642,7 +796,8 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
smallest_entry_group.remove(smallest_entry)
def _delete_biggest_dist_entry(
- self, groups: list[list[tuple[int, int]]]) -> None:
+ self, groups: list[list[tuple[int, int]]]
+ ) -> None:
biggest_value = 9999
biggest_entry = None
biggest_entry_group = None
@@ -656,8 +811,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
assert biggest_entry_group is not None
biggest_entry_group.remove(biggest_entry)
- def _delete_random_dist_entry(self,
- groups: list[list[tuple[int, int]]]) -> None:
+ def _delete_random_dist_entry(
+ self, groups: list[list[tuple[int, int]]]
+ ) -> None:
entry_count = 0
for group in groups:
for _ in group:
@@ -676,59 +832,76 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
# We keep track of who got hurt each wave for score purposes.
player.has_been_hurt = False
- pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
- self._spawn_center[1],
- self._spawn_center[2] + random.uniform(-1.5, 1.5))
+ pos = (
+ self._spawn_center[0] + random.uniform(-1.5, 1.5),
+ self._spawn_center[1],
+ self._spawn_center[2] + random.uniform(-1.5, 1.5),
+ )
spaz = self.spawn_player_spaz(player, position=pos)
if self._preset in {
- Preset.TRAINING_EASY, Preset.ROOKIE_EASY, Preset.PRO_EASY,
- Preset.UBER_EASY
+ Preset.TRAINING_EASY,
+ Preset.ROOKIE_EASY,
+ Preset.PRO_EASY,
+ Preset.UBER_EASY,
}:
spaz.impact_scale = 0.25
spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb)
return spaz
- def _handle_player_dropped_bomb(self, player: ba.Actor,
- bomb: ba.Actor) -> None:
+ def _handle_player_dropped_bomb(
+ self, player: ba.Actor, bomb: ba.Actor
+ ) -> None:
del player, bomb # Unused.
self._player_has_dropped_bomb = True
- def _drop_powerup(self,
- index: int,
- poweruptype: str | None = None) -> None:
- poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
- forcetype=poweruptype, excludetypes=self._excluded_powerups))
- PowerupBox(position=self.map.powerup_spawn_points[index],
- poweruptype=poweruptype).autoretain()
+ def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None:
+ poweruptype = PowerupBoxFactory.get().get_random_powerup_type(
+ forcetype=poweruptype, excludetypes=self._excluded_powerups
+ )
+ PowerupBox(
+ position=self.map.powerup_spawn_points[index],
+ poweruptype=poweruptype,
+ ).autoretain()
def _start_powerup_drops(self) -> None:
- self._powerup_drop_timer = ba.Timer(3.0,
- ba.WeakCall(self._drop_powerups),
- repeat=True)
+ self._powerup_drop_timer = ba.Timer(
+ 3.0, ba.WeakCall(self._drop_powerups), repeat=True
+ )
- def _drop_powerups(self,
- standard_points: bool = False,
- poweruptype: str | None = None) -> None:
+ def _drop_powerups(
+ self, standard_points: bool = False, poweruptype: str | None = None
+ ) -> None:
"""Generic powerup drop."""
if standard_points:
points = self.map.powerup_spawn_points
for i in range(len(points)):
ba.timer(
1.0 + i * 0.5,
- ba.WeakCall(self._drop_powerup, i,
- poweruptype if i == 0 else None))
+ ba.WeakCall(
+ self._drop_powerup, i, poweruptype if i == 0 else None
+ ),
+ )
else:
- point = (self._powerup_center[0] + random.uniform(
- -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
- self._powerup_center[1],
- self._powerup_center[2] + random.uniform(
- -self._powerup_spread[1], self._powerup_spread[1]))
+ point = (
+ self._powerup_center[0]
+ + random.uniform(
+ -1.0 * self._powerup_spread[0],
+ 1.0 * self._powerup_spread[0],
+ ),
+ self._powerup_center[1],
+ self._powerup_center[2]
+ + random.uniform(
+ -self._powerup_spread[1], self._powerup_spread[1]
+ ),
+ )
# Drop one random one somewhere.
PowerupBox(
position=point,
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._excluded_powerups)).autoretain()
+ excludetypes=self._excluded_powerups
+ ),
+ ).autoretain()
def do_end(self, outcome: str, delay: float = 0.0) -> None:
"""End the game with the specified outcome."""
@@ -746,9 +919,10 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
'outcome': outcome,
'score': score,
'fail_message': fail_message,
- 'playerinfos': self.initialplayerinfos
+ 'playerinfos': self.initialplayerinfos,
},
- delay=delay)
+ delay=delay,
+ )
def _award_completion_achievements(self) -> None:
if self._preset in {Preset.TRAINING, Preset.TRAINING_EASY}:
@@ -770,23 +944,28 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
# If we have no living bots, go to the next wave.
assert self._bots is not None
- if (self._can_end_wave and not self._bots.have_living_bots()
- and not self._game_over):
+ if (
+ self._can_end_wave
+ and not self._bots.have_living_bots()
+ and not self._game_over
+ ):
self._can_end_wave = False
self._time_bonus_timer = None
self._time_bonus_text = None
if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
won = False
else:
- won = (self._wavenum == len(self._waves))
+ won = self._wavenum == len(self._waves)
base_delay = 4.0 if won else 0.0
# Reward time bonus.
if self._time_bonus > 0:
ba.timer(0, lambda: ba.playsound(self._cashregistersound))
- ba.timer(base_delay,
- ba.WeakCall(self._award_time_bonus, self._time_bonus))
+ ba.timer(
+ base_delay,
+ ba.WeakCall(self._award_time_bonus, self._time_bonus),
+ )
base_delay += 1.0
# Reward flawless bonus.
@@ -797,15 +976,16 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
have_flawless = True
ba.timer(
base_delay,
- ba.WeakCall(self._award_flawless_bonus, player))
+ ba.WeakCall(self._award_flawless_bonus, player),
+ )
player.has_been_hurt = False # reset
if have_flawless:
base_delay += 1.0
if won:
- self.show_zoom_message(ba.Lstr(resource='victoryText'),
- scale=1.0,
- duration=4.0)
+ self.show_zoom_message(
+ ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0
+ )
self.celebrate(20.0)
self._award_completion_achievements()
ba.timer(base_delay, ba.WeakCall(self._award_completion_bonus))
@@ -840,18 +1020,25 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
scale=1.4,
color=(0.6, 0.6, 1.0, 1.0),
title=ba.Lstr(resource='completionBonusText'),
- screenmessage=False)
+ screenmessage=False,
+ )
except Exception:
ba.print_exception()
def _award_time_bonus(self, bonus: int) -> None:
ba.playsound(self._cashregistersound)
- PopupText(ba.Lstr(value='+${A} ${B}',
- subs=[('${A}', str(bonus)),
- ('${B}', ba.Lstr(resource='timeBonusText'))]),
- color=(1, 1, 0.5, 1),
- scale=1.0,
- position=(0, 3, -1)).autoretain()
+ PopupText(
+ ba.Lstr(
+ value='+${A} ${B}',
+ subs=[
+ ('${A}', str(bonus)),
+ ('${B}', ba.Lstr(resource='timeBonusText')),
+ ],
+ ),
+ color=(1, 1, 0.5, 1),
+ scale=1.0,
+ position=(0, 3, -1),
+ ).autoretain()
self._score += self._time_bonus
self._update_scores()
@@ -866,14 +1053,15 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
scale=1.2,
color=(0.6, 1.0, 0.6, 1.0),
title=ba.Lstr(resource='flawlessWaveText'),
- screenmessage=False)
+ screenmessage=False,
+ )
except Exception:
ba.print_exception()
def _start_time_bonus_timer(self) -> None:
- self._time_bonus_timer = ba.Timer(1.0,
- ba.WeakCall(self._update_time_bonus),
- repeat=True)
+ self._time_bonus_timer = ba.Timer(
+ 1.0, ba.WeakCall(self._update_time_bonus), repeat=True
+ )
def _update_player_spawn_info(self) -> None:
@@ -885,27 +1073,34 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
else:
text: str | ba.Lstr = ''
for player in self.players:
- if (not player.is_alive()
- and (self._preset
- in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] or
- (player.respawn_wave <= len(self._waves)))):
- rtxt = ba.Lstr(resource='onslaughtRespawnText',
- subs=[('${PLAYER}', player.getname()),
- ('${WAVE}', str(player.respawn_wave))
- ])
- text = ba.Lstr(value='${A}${B}\n',
- subs=[
- ('${A}', text),
- ('${B}', rtxt),
- ])
+ if not player.is_alive() and (
+ self._preset in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT]
+ or (player.respawn_wave <= len(self._waves))
+ ):
+ rtxt = ba.Lstr(
+ resource='onslaughtRespawnText',
+ subs=[
+ ('${PLAYER}', player.getname()),
+ ('${WAVE}', str(player.respawn_wave)),
+ ],
+ )
+ text = ba.Lstr(
+ value='${A}${B}\n',
+ subs=[
+ ('${A}', text),
+ ('${B}', rtxt),
+ ],
+ )
self._spawn_info_text.node.text = text
def _respawn_players_for_wave(self) -> None:
# Respawn applicable players.
if self._wavenum > 1 and not self.is_waiting_for_continue():
for player in self.players:
- if (not player.is_alive()
- and player.respawn_wave == self._wavenum):
+ if (
+ not player.is_alive()
+ and player.respawn_wave == self._wavenum
+ ):
self.spawn_player(player)
self._update_player_spawn_info()
@@ -940,23 +1135,27 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
point = info.point
if point is not None:
assert bot_type_2 is not None
- spcall = ba.WeakCall(self.add_bot_at_point, point, bot_type_2,
- spawn_time)
+ spcall = ba.WeakCall(
+ self.add_bot_at_point, point, bot_type_2, spawn_time
+ )
ba.timer(tval, spcall)
tval += dtime
else:
spacing = info.spacing
bot_angle += spacing * 0.5
if bot_type_2 is not None:
- tcall = ba.WeakCall(self.add_bot_at_angle, bot_angle,
- bot_type_2, spawn_time)
+ tcall = ba.WeakCall(
+ self.add_bot_at_angle, bot_angle, bot_type_2, spawn_time
+ )
ba.timer(tval, tcall)
tval += dtime
bot_angle += spacing * 0.5
# We can end the wave after all the spawning happens.
- ba.timer(tval + spawn_time - dtime + 0.01,
- ba.WeakCall(self._set_can_end_wave))
+ ba.timer(
+ tval + spawn_time - dtime + 0.01,
+ ba.WeakCall(self._set_can_end_wave),
+ )
def _start_next_wave(self) -> None:
@@ -976,67 +1175,98 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
def _update_wave_ui_and_bonuses(self) -> None:
- self.show_zoom_message(ba.Lstr(value='${A} ${B}',
- subs=[('${A}',
- ba.Lstr(resource='waveText')),
- ('${B}', str(self._wavenum))]),
- scale=1.0,
- duration=1.0,
- trail=True)
+ self.show_zoom_message(
+ ba.Lstr(
+ value='${A} ${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='waveText')),
+ ('${B}', str(self._wavenum)),
+ ],
+ ),
+ scale=1.0,
+ duration=1.0,
+ trail=True,
+ )
# Reset our time bonus.
tbtcolor = (1, 1, 0, 1)
- tbttxt = ba.Lstr(value='${A}: ${B}',
- subs=[
- ('${A}', ba.Lstr(resource='timeBonusText')),
- ('${B}', str(self._time_bonus)),
- ])
+ tbttxt = ba.Lstr(
+ value='${A}: ${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='timeBonusText')),
+ ('${B}', str(self._time_bonus)),
+ ],
+ )
self._time_bonus_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'vr_depth': -30,
- 'color': tbtcolor,
- 'shadow': 1.0,
- 'flatness': 1.0,
- 'position': (0, -60),
- 'scale': 0.8,
- 'text': tbttxt
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'vr_depth': -30,
+ 'color': tbtcolor,
+ 'shadow': 1.0,
+ 'flatness': 1.0,
+ 'position': (0, -60),
+ 'scale': 0.8,
+ 'text': tbttxt,
+ },
+ )
+ )
ba.timer(5.0, ba.WeakCall(self._start_time_bonus_timer))
wtcolor = (1, 1, 1, 1)
wttxt = ba.Lstr(
value='${A} ${B}',
- subs=[('${A}', ba.Lstr(resource='waveText')),
- ('${B}', str(self._wavenum) +
- ('' if self._preset
- in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT] else
- ('/' + str(len(self._waves)))))])
+ subs=[
+ ('${A}', ba.Lstr(resource='waveText')),
+ (
+ '${B}',
+ str(self._wavenum)
+ + (
+ ''
+ if self._preset
+ in [Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT]
+ else ('/' + str(len(self._waves)))
+ ),
+ ),
+ ],
+ )
self._wave_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'vr_depth': -10,
- 'color': wtcolor,
- 'shadow': 1.0,
- 'flatness': 1.0,
- 'position': (0, -40),
- 'scale': 1.3,
- 'text': wttxt
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'vr_depth': -10,
+ 'color': wtcolor,
+ 'shadow': 1.0,
+ 'flatness': 1.0,
+ 'position': (0, -40),
+ 'scale': 1.3,
+ 'text': wttxt,
+ },
+ )
+ )
def _bot_levels_for_wave(self) -> list[list[type[SpazBot]]]:
level = self._wavenum
bot_types = [
- BomberBot, BrawlerBot, TriggerBot, ChargerBot, BomberBotPro,
- BrawlerBotPro, TriggerBotPro, BomberBotProShielded, ExplodeyBot,
- ChargerBotProShielded, StickyBot, BrawlerBotProShielded,
- TriggerBotProShielded
+ BomberBot,
+ BrawlerBot,
+ TriggerBot,
+ ChargerBot,
+ BomberBotPro,
+ BrawlerBotPro,
+ TriggerBotPro,
+ BomberBotProShielded,
+ ExplodeyBot,
+ ChargerBotProShielded,
+ StickyBot,
+ BrawlerBotProShielded,
+ TriggerBotProShielded,
]
if level > 5:
bot_types += [
@@ -1054,18 +1284,24 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
]
if level > 10:
bot_types += [
- TriggerBotProShielded, TriggerBotProShielded,
- TriggerBotProShielded, TriggerBotProShielded
+ TriggerBotProShielded,
+ TriggerBotProShielded,
+ TriggerBotProShielded,
+ TriggerBotProShielded,
]
if level > 13:
bot_types += [
- TriggerBotProShielded, TriggerBotProShielded,
- TriggerBotProShielded, TriggerBotProShielded
+ TriggerBotProShielded,
+ TriggerBotProShielded,
+ TriggerBotProShielded,
+ TriggerBotProShielded,
]
- bot_levels = [[b for b in bot_types if b.points_mult == 1],
- [b for b in bot_types if b.points_mult == 2],
- [b for b in bot_types if b.points_mult == 3],
- [b for b in bot_types if b.points_mult == 4]]
+ bot_levels = [
+ [b for b in bot_types if b.points_mult == 1],
+ [b for b in bot_types if b.points_mult == 2],
+ [b for b in bot_types if b.points_mult == 3],
+ [b for b in bot_types if b.points_mult == 4],
+ ]
# Make sure all lists have something in them
if not all(bot_levels):
@@ -1073,9 +1309,11 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
return bot_levels
def _add_entries_for_distribution_group(
- self, group: list[tuple[int, int]],
- bot_levels: list[list[type[SpazBot]]],
- all_entries: list[Spawn | Spacing | Delay | None]) -> None:
+ self,
+ group: list[tuple[int, int]],
+ bot_levels: list[list[type[SpazBot]]],
+ all_entries: list[Spawn | Spacing | Delay | None],
+ ) -> None:
entries: list[Spawn | Spacing | Delay | None] = []
for entry in group:
bot_level = bot_levels[entry[0] - 1]
@@ -1095,8 +1333,7 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
entries.append(Spawn(bot_type, spacing=spacing))
if entries:
all_entries += entries
- all_entries.append(
- Spacing(40.0 if random.random() < 0.5 else 80.0))
+ all_entries.append(Spacing(40.0 if random.random() < 0.5 else 80.0))
def _generate_random_wave(self) -> Wave:
level = self._wavenum
@@ -1105,16 +1342,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
target_points = level * 3 - 2
min_dudes = min(1 + level // 3, 10)
max_dudes = min(10, level + 1)
- max_level = 4 if level > 6 else (3 if level > 3 else
- (2 if level > 2 else 1))
+ max_level = (
+ 4 if level > 6 else (3 if level > 3 else (2 if level > 2 else 1))
+ )
group_count = 3
- distribution = self._get_distribution(target_points, min_dudes,
- max_dudes, group_count,
- max_level)
+ distribution = self._get_distribution(
+ target_points, min_dudes, max_dudes, group_count, max_level
+ )
all_entries: list[Spawn | Spacing | Delay | None] = []
for group in distribution:
- self._add_entries_for_distribution_group(group, bot_levels,
- all_entries)
+ self._add_entries_for_distribution_group(
+ group, bot_levels, all_entries
+ )
angle_rand = random.random()
if angle_rand > 0.75:
base_angle = 130.0
@@ -1128,10 +1367,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
wave = Wave(base_angle=base_angle, entries=all_entries)
return wave
- def add_bot_at_point(self,
- point: Point,
- spaz_type: type[SpazBot],
- spawn_time: float = 1.0) -> None:
+ def add_bot_at_point(
+ self, point: Point, spaz_type: type[SpazBot], spawn_time: float = 1.0
+ ) -> None:
"""Add a new bot at a specified named point."""
if self._game_over:
return
@@ -1140,10 +1378,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
assert self._bots is not None
self._bots.spawn_bot(spaz_type, pos=pointpos, spawn_time=spawn_time)
- def add_bot_at_angle(self,
- angle: float,
- spaz_type: type[SpazBot],
- spawn_time: float = 1.0) -> None:
+ def add_bot_at_angle(
+ self, angle: float, spaz_type: type[SpazBot], spawn_time: float = 1.0
+ ) -> None:
"""Add a new bot at a specified angle (for circular maps)."""
if self._game_over:
return
@@ -1160,15 +1397,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
assert self._time_bonus_text.node
self._time_bonus_text.node.text = ba.Lstr(
value='${A}: ${B}',
- subs=[('${A}', ba.Lstr(resource='timeBonusText')),
- ('${B}', str(self._time_bonus))])
+ subs=[
+ ('${A}', ba.Lstr(resource='timeBonusText')),
+ ('${B}', str(self._time_bonus)),
+ ],
+ )
else:
self._time_bonus_text = None
def _start_updating_waves(self) -> None:
- self._wave_update_timer = ba.Timer(2.0,
- ba.WeakCall(self._update_waves),
- repeat=True)
+ self._wave_update_timer = ba.Timer(
+ 2.0, ba.WeakCall(self._update_waves), repeat=True
+ )
def _update_scores(self) -> None:
score = self._score
@@ -1218,15 +1458,18 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
target = None
killerplayer = msg.killerplayer
- self.stats.player_scored(killerplayer,
- pts,
- target=target,
- kill=True,
- screenmessage=False,
- importance=importance)
- ba.playsound(self._dingsound
- if importance == 1 else self._dingsoundhigh,
- volume=0.6)
+ self.stats.player_scored(
+ killerplayer,
+ pts,
+ target=target,
+ kill=True,
+ screenmessage=False,
+ importance=importance,
+ )
+ ba.playsound(
+ self._dingsound if importance == 1 else self._dingsoundhigh,
+ volume=0.6,
+ )
# Normally we pull scores from the score-set, but if there's
# no player lets be explicit.
@@ -1258,8 +1501,9 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if msg.spazbot.last_attacked_type == ('explosion', 'tnt'):
self._tnt_kills += 1
if self._tnt_kills >= 6:
- ba.timer(0.5, ba.WeakCall(self._award_achievement,
- 'TNT Terror'))
+ ba.timer(
+ 0.5, ba.WeakCall(self._award_achievement, 'TNT Terror')
+ )
def _handle_pro_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
@@ -1269,19 +1513,21 @@ class OnslaughtGame(ba.CoopGameActivity[Player, Team]):
if self._tnt_kills >= 3:
ba.timer(
0.5,
- ba.WeakCall(self._award_achievement,
- 'Boom Goes the Dynamite'))
+ ba.WeakCall(
+ self._award_achievement, 'Boom Goes the Dynamite'
+ ),
+ )
- def _handle_rookie_kill_achievements(self,
- msg: SpazBotDiedMessage) -> None:
+ def _handle_rookie_kill_achievements(self, msg: SpazBotDiedMessage) -> None:
# Land-mine achievement:
if msg.spazbot.last_attacked_type == ('explosion', 'land_mine'):
self._land_mine_kills += 1
if self._land_mine_kills >= 3:
self._award_achievement('Mine Games')
- def _handle_training_kill_achievements(self,
- msg: SpazBotDiedMessage) -> None:
+ def _handle_training_kill_achievements(
+ self, msg: SpazBotDiedMessage
+ ) -> None:
# Toss-off-map achievement:
if msg.spazbot.last_attacked_type == ('picked_up', 'default'):
self._throw_off_kills += 1
diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py
index e21fbe55..cbf320c9 100644
--- a/assets/src/ba_data/python/bastd/game/race.py
+++ b/assets/src/ba_data/python/bastd/game/race.py
@@ -25,6 +25,7 @@ if TYPE_CHECKING:
@dataclass
class RaceMine:
"""Holds info about a mine on the track."""
+
point: Sequence[float]
mine: Bomb | None
@@ -45,8 +46,9 @@ class RaceRegion(ba.Actor):
'position': pt[:3],
'scale': (pt[3] * 2.0, pt[4] * 2.0, pt[5] * 2.0),
'type': 'box',
- 'materials': [activity.race_region_material]
- })
+ 'materials': [activity.race_region_material],
+ },
+ )
class Player(ba.Player['Team']):
@@ -76,13 +78,14 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
name = 'Race'
description = 'Run real fast!'
- scoreconfig = ba.ScoreConfig(label='Time',
- lower_is_better=True,
- scoretype=ba.ScoreType.MILLISECONDS)
+ scoreconfig = ba.ScoreConfig(
+ label='Time', lower_is_better=True, scoretype=ba.ScoreType.MILLISECONDS
+ )
@classmethod
def get_available_settings(
- cls, sessiontype: type[ba.Session]) -> list[ba.Setting]:
+ cls, sessiontype: type[ba.Session]
+ ) -> list[ba.Setting]:
settings = [
ba.IntSetting('Laps', min_value=1, default=3, increment=1),
ba.IntChoiceSetting(
@@ -124,7 +127,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# We have some specific settings in teams mode.
if issubclass(sessiontype, ba.DualTeamSession):
settings.append(
- ba.BoolSetting('Entire Team Must Finish', default=False))
+ ba.BoolSetting('Entire Team Must Finish', default=False)
+ )
return settings
@classmethod
@@ -159,7 +163,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._bomb_spawn_timer: ba.Timer | None = None
self._laps = int(settings['Laps'])
self._entire_team_must_finish = bool(
- settings.get('Entire Team Must Finish', False))
+ settings.get('Entire Team Must Finish', False)
+ )
self._time_limit = float(settings['Time Limit'])
self._mine_spawning = int(settings['Mine Spawning'])
self._bomb_spawning = int(settings['Bomb Spawning'])
@@ -167,12 +172,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Base class overrides.
self.slow_motion = self._epic_mode
- self.default_music = (ba.MusicType.EPIC_RACE
- if self._epic_mode else ba.MusicType.RACE)
+ self.default_music = (
+ ba.MusicType.EPIC_RACE if self._epic_mode else ba.MusicType.RACE
+ )
def get_instance_description(self) -> str | Sequence:
- if (isinstance(self.session, ba.DualTeamSession)
- and self._entire_team_must_finish):
+ if (
+ isinstance(self.session, ba.DualTeamSession)
+ and self._entire_team_must_finish
+ ):
t_str = ' Your entire team has to finish.'
else:
t_str = ''
@@ -191,14 +199,14 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
shared = SharedObjects.get()
pts = self.map.get_def_points('race_point')
mat = self.race_region_material = ba.Material()
- mat.add_actions(conditions=('they_have_material',
- shared.player_material),
- actions=(
- ('modify_part_collision', 'collide', True),
- ('modify_part_collision', 'physical', False),
- ('call', 'at_connect',
- self._handle_race_point_collide),
- ))
+ mat.add_actions(
+ conditions=('they_have_material', shared.player_material),
+ actions=(
+ ('modify_part_collision', 'collide', True),
+ ('modify_part_collision', 'physical', False),
+ ('call', 'at_connect', self._handle_race_point_collide),
+ ),
+ )
for rpt in pts:
self._regions.append(RaceRegion(rpt, len(self._regions)))
@@ -206,13 +214,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
assert isinstance(player.actor, PlayerSpaz)
assert player.actor.node
pos = player.actor.node.position
- light = ba.newnode('light',
- attrs={
- 'position': pos,
- 'color': (1, 1, 0),
- 'height_attenuated': False,
- 'radius': 0.4
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': pos,
+ 'color': (1, 1, 0),
+ 'height_attenuated': False,
+ 'radius': 0.4,
+ },
+ )
ba.timer(0.5, light.delete)
ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})
@@ -248,11 +258,17 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
if player.is_alive():
assert player.actor
player.actor.handlemessage(ba.DieMessage())
- ba.screenmessage(ba.Lstr(
- translate=('statements', 'Killing ${NAME} for'
- ' skipping part of the track!'),
- subs=[('${NAME}', player.getname(full=True))]),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ translate=(
+ 'statements',
+ 'Killing ${NAME} for'
+ ' skipping part of the track!',
+ ),
+ subs=[('${NAME}', player.getname(full=True))],
+ ),
+ color=(1, 0, 0),
+ )
else:
# If this player is in first, note that this is the
# front-most race-point.
@@ -267,8 +283,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# In teams mode with all-must-finish on, the team lap
# value is the min of all team players.
# Otherwise its the max.
- if isinstance(self.session, ba.DualTeamSession
- ) and self._entire_team_must_finish:
+ if (
+ isinstance(self.session, ba.DualTeamSession)
+ and self._entire_team_must_finish
+ ):
team.lap = min(p.lap for p in team.players)
else:
team.lap = max(p.lap for p in team.players)
@@ -281,9 +299,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
if isinstance(self.session, ba.DualTeamSession):
assert self._team_finish_pts is not None
if self._team_finish_pts > 0:
- self.stats.player_scored(player,
- self._team_finish_pts,
- screenmessage=False)
+ self.stats.player_scored(
+ player,
+ self._team_finish_pts,
+ screenmessage=False,
+ )
self._team_finish_pts -= 25
# Flash where the player is.
@@ -291,7 +311,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
player.finished = True
assert player.actor
player.actor.handlemessage(
- ba.DieMessage(immediate=True))
+ ba.DieMessage(immediate=True)
+ )
# Makes sure noone behind them passes them in rank
# while finishing.
@@ -318,35 +339,41 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Print their lap number over their head.
try:
assert isinstance(player.actor, PlayerSpaz)
- mathnode = ba.newnode('math',
- owner=player.actor.node,
- attrs={
- 'input1': (0, 1.9, 0),
- 'operation': 'add'
- })
+ mathnode = ba.newnode(
+ 'math',
+ owner=player.actor.node,
+ attrs={
+ 'input1': (0, 1.9, 0),
+ 'operation': 'add',
+ },
+ )
player.actor.node.connectattr(
- 'torso_position', mathnode, 'input2')
- tstr = ba.Lstr(resource='lapNumberText',
- subs=[('${CURRENT}',
- str(player.lap + 1)),
- ('${TOTAL}', str(self._laps))
- ])
- txtnode = ba.newnode('text',
- owner=mathnode,
- attrs={
- 'text': tstr,
- 'in_world': True,
- 'color': (1, 1, 0, 1),
- 'scale': 0.015,
- 'h_align': 'center'
- })
+ 'torso_position', mathnode, 'input2'
+ )
+ tstr = ba.Lstr(
+ resource='lapNumberText',
+ subs=[
+ ('${CURRENT}', str(player.lap + 1)),
+ ('${TOTAL}', str(self._laps)),
+ ],
+ )
+ txtnode = ba.newnode(
+ 'text',
+ owner=mathnode,
+ attrs={
+ 'text': tstr,
+ 'in_world': True,
+ 'color': (1, 1, 0, 1),
+ 'scale': 0.015,
+ 'h_align': 'center',
+ },
+ )
mathnode.connectattr('output', txtnode, 'position')
- ba.animate(txtnode, 'scale', {
- 0.0: 0,
- 0.2: 0.019,
- 2.0: 0.019,
- 2.2: 0
- })
+ ba.animate(
+ txtnode,
+ 'scale',
+ {0.0: 0, 0.2: 0.019, 2.0: 0.019, 2.2: 0},
+ )
ba.timer(2.3, mathnode.delete)
except Exception:
ba.print_exception('Error printing lap.')
@@ -360,14 +387,23 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# A player leaving disqualifies the team if 'Entire Team Must Finish'
# is on (otherwise in teams mode everyone could just leave except the
# leading player to win).
- if (isinstance(self.session, ba.DualTeamSession)
- and self._entire_team_must_finish):
- ba.screenmessage(ba.Lstr(
- translate=('statements',
- '${TEAM} is disqualified because ${PLAYER} left'),
- subs=[('${TEAM}', player.team.name),
- ('${PLAYER}', player.getname(full=True))]),
- color=(1, 1, 0))
+ if (
+ isinstance(self.session, ba.DualTeamSession)
+ and self._entire_team_must_finish
+ ):
+ ba.screenmessage(
+ ba.Lstr(
+ translate=(
+ 'statements',
+ '${TEAM} is disqualified because ${PLAYER} left',
+ ),
+ subs=[
+ ('${TEAM}', player.team.name),
+ ('${PLAYER}', player.getname(full=True)),
+ ],
+ ),
+ color=(1, 1, 0),
+ )
player.team.finished = True
player.team.time = None
player.team.lap = 0
@@ -390,8 +426,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
if not distances:
teams_dist = 0.0
else:
- if (isinstance(self.session, ba.DualTeamSession)
- and self._entire_team_must_finish):
+ if (
+ isinstance(self.session, ba.DualTeamSession)
+ and self._entire_team_must_finish
+ ):
teams_dist = min(distances)
else:
teams_dist = max(distances)
@@ -400,10 +438,12 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
teams_dist,
self._laps,
flash=(teams_dist >= float(self._laps)),
- show_value=False)
+ show_value=False,
+ )
def on_begin(self) -> None:
from bastd.actor.onscreentimer import OnScreenTimer
+
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
@@ -411,18 +451,21 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Throw a timer up on-screen.
self._time_text = ba.NodeActor(
- 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, -50),
- 'scale': 1.4,
- 'text': ''
- }))
+ 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, -50),
+ 'scale': 1.4,
+ 'text': '',
+ },
+ )
+ )
self._timer = OnScreenTimer()
if self._mine_spawning != 0:
@@ -431,16 +474,18 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
for p in self.map.get_def_points('race_mine')
]
if self._race_mines:
- self._race_mine_timer = ba.Timer(0.001 * self._mine_spawning,
- self._update_race_mine,
- repeat=True)
+ self._race_mine_timer = ba.Timer(
+ 0.001 * self._mine_spawning,
+ self._update_race_mine,
+ repeat=True,
+ )
- self._scoreboard_timer = ba.Timer(0.25,
- self._update_scoreboard,
- repeat=True)
- self._player_order_update_timer = ba.Timer(0.25,
- self._update_player_order,
- repeat=True)
+ self._scoreboard_timer = ba.Timer(
+ 0.25, self._update_scoreboard, repeat=True
+ )
+ self._player_order_update_timer = ba.Timer(
+ 0.25, self._update_player_order, repeat=True
+ )
if self.slow_motion:
t_scale = 0.4
@@ -458,22 +503,27 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._start_lights = []
for i in range(4):
- lnub = ba.newnode('image',
- attrs={
- 'texture': ba.gettexture('nub'),
- 'opacity': 1.0,
- 'absolute_scale': True,
- 'position': (-75 + i * 50, light_y),
- 'scale': (50, 50),
- 'attach': 'center'
- })
+ lnub = ba.newnode(
+ 'image',
+ attrs={
+ 'texture': ba.gettexture('nub'),
+ 'opacity': 1.0,
+ 'absolute_scale': True,
+ 'position': (-75 + i * 50, light_y),
+ 'scale': (50, 50),
+ 'attach': 'center',
+ },
+ )
ba.animate(
- lnub, 'opacity', {
+ lnub,
+ 'opacity',
+ {
4.0 * t_scale: 0,
5.0 * t_scale: 1.0,
12.0 * t_scale: 1.0,
- 12.5 * t_scale: 0.0
- })
+ 12.5 * t_scale: 0.0,
+ },
+ )
ba.timer(13.0 * t_scale, lnub.delete)
self._start_lights.append(lnub)
@@ -512,9 +562,9 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
self._timer.start()
if self._bomb_spawning != 0:
- self._bomb_spawn_timer = ba.Timer(0.001 * self._bomb_spawning,
- self._spawn_bomb,
- repeat=True)
+ self._bomb_spawn_timer = ba.Timer(
+ 0.001 * self._bomb_spawning, self._spawn_bomb, repeat=True
+ )
self._race_started = True
@@ -531,8 +581,11 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
r_index = player.last_region
rg1 = self._regions[r_index]
r1pt = ba.Vec3(rg1.pos[:3])
- rg2 = self._regions[0] if r_index == len(
- self._regions) - 1 else self._regions[r_index + 1]
+ rg2 = (
+ self._regions[0]
+ if r_index == len(self._regions) - 1
+ else self._regions[r_index + 1]
+ )
r2pt = ba.Vec3(rg2.pos[:3])
r2dist = (pos - r2pt).length()
amt = 1.0 - (r2dist / (r2pt - r1pt).length())
@@ -558,14 +611,24 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
- x_range = ((-0.5, 0.5) if pos[3] == 0 else
- (-region_scale * pos[3], region_scale * pos[3]))
- z_range = ((-0.5, 0.5) if pos[5] == 0 else
- (-region_scale * pos[5], region_scale * pos[5]))
- pos = (pos[0] + random.uniform(*x_range), pos[1] + 1.0,
- pos[2] + random.uniform(*z_range))
- ba.timer(random.uniform(0.0, 2.0),
- ba.WeakCall(self._spawn_bomb_at_pos, pos))
+ x_range = (
+ (-0.5, 0.5)
+ if pos[3] == 0
+ else (-region_scale * pos[3], region_scale * pos[3])
+ )
+ z_range = (
+ (-0.5, 0.5)
+ if pos[5] == 0
+ else (-region_scale * pos[5], region_scale * pos[5])
+ )
+ pos = (
+ pos[0] + random.uniform(*x_range),
+ pos[1] + 1.0,
+ pos[2] + random.uniform(*z_range),
+ )
+ ba.timer(
+ random.uniform(0.0, 2.0), ba.WeakCall(self._spawn_bomb_at_pos, pos)
+ )
def _spawn_bomb_at_pos(self, pos: Sequence[float]) -> None:
if self.has_ended():
@@ -581,13 +644,15 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
def _flash_mine(self, i: int) -> None:
assert self._race_mines is not None
rmine = self._race_mines[i]
- light = ba.newnode('light',
- attrs={
- 'position': rmine.point[:3],
- 'color': (1, 0.2, 0.2),
- 'radius': 0.1,
- 'height_attenuated': False
- })
+ light = ba.newnode(
+ 'light',
+ attrs={
+ 'position': rmine.point[:3],
+ 'color': (1, 0.2, 0.2),
+ 'radius': 0.1,
+ 'height_attenuated': False,
+ },
+ )
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
ba.timer(1.0, light.delete)
@@ -616,37 +681,48 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Don't use the full region so we're less likely to spawn off a cliff.
region_scale = 0.8
- x_range = ((-0.5, 0.5) if pos[3] == 0 else
- (-region_scale * pos[3], region_scale * pos[3]))
- z_range = ((-0.5, 0.5) if pos[5] == 0 else
- (-region_scale * pos[5], region_scale * pos[5]))
- pos = (pos[0] + random.uniform(*x_range), pos[1],
- pos[2] + random.uniform(*z_range))
+ x_range = (
+ (-0.5, 0.5)
+ if pos[3] == 0
+ else (-region_scale * pos[3], region_scale * pos[3])
+ )
+ z_range = (
+ (-0.5, 0.5)
+ if pos[5] == 0
+ else (-region_scale * pos[5], region_scale * pos[5])
+ )
+ pos = (
+ pos[0] + random.uniform(*x_range),
+ pos[1],
+ pos[2] + random.uniform(*z_range),
+ )
spaz = self.spawn_player_spaz(
- player, position=pos, angle=90 if not self._race_started else None)
+ player, position=pos, angle=90 if not self._race_started else None
+ )
assert spaz.node
# Prevent controlling of characters before the start of the race.
if not self._race_started:
spaz.disconnect_controls_from_player()
- mathnode = ba.newnode('math',
- owner=spaz.node,
- attrs={
- 'input1': (0, 1.4, 0),
- 'operation': 'add'
- })
+ mathnode = ba.newnode(
+ 'math',
+ owner=spaz.node,
+ attrs={'input1': (0, 1.4, 0), 'operation': 'add'},
+ )
spaz.node.connectattr('torso_position', mathnode, 'input2')
- distance_txt = ba.newnode('text',
- owner=spaz.node,
- attrs={
- 'text': '',
- 'in_world': True,
- 'color': (1, 1, 0.4),
- 'scale': 0.02,
- 'h_align': 'center'
- })
+ distance_txt = ba.newnode(
+ 'text',
+ owner=spaz.node,
+ attrs={
+ 'text': '',
+ 'in_world': True,
+ 'color': (1, 1, 0.4),
+ 'scale': 0.02,
+ 'h_align': 'center',
+ },
+ )
player.distance_txt = distance_txt
mathnode.connectattr('output', distance_txt, 'position')
return spaz
@@ -661,7 +737,8 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# Count the number of teams that have completed the race.
teams_completed = len(
- [t for t in self.teams if t.finished and t.time is not None])
+ [t for t in self.teams if t.finished and t.time is not None]
+ )
if teams_completed > 0:
session = self.session
@@ -690,8 +767,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
assert self._timer is not None
if self._timer.has_started():
self._timer.stop(
- endtime=None if self._last_team_time is None else (
- self._timer.getstarttime() + self._last_team_time))
+ endtime=None
+ if self._last_team_time is None
+ else (self._timer.getstarttime() + self._last_team_time)
+ )
results = ba.GameResults()
@@ -705,9 +784,10 @@ class RaceGame(ba.TeamGameActivity[Player, Team]):
# We don't announce a winner in ffa mode since its probably been a
# while since the first place guy crossed the finish line so it seems
# odd to be announcing that now.
- self.end(results=results,
- announce_winning_team=isinstance(self.session,
- ba.DualTeamSession))
+ self.end(
+ results=results,
+ announce_winning_team=isinstance(self.session, ba.DualTeamSession),
+ )
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ba.PlayerDiedMessage):
diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py
index b80f3ece..d8f5e4b4 100644
--- a/assets/src/ba_data/python/bastd/game/runaround.py
+++ b/assets/src/ba_data/python/bastd/game/runaround.py
@@ -23,10 +23,23 @@ from bastd.actor.respawnicon import RespawnIcon
from bastd.actor.powerupbox import PowerupBox, PowerupBoxFactory
from bastd.gameutils import SharedObjects
from bastd.actor.spazbot import (
- SpazBotSet, SpazBot, SpazBotDiedMessage, BomberBot, BrawlerBot, TriggerBot,
- TriggerBotPro, BomberBotProShielded, TriggerBotProShielded, ChargerBot,
- ChargerBotProShielded, StickyBot, ExplodeyBot, BrawlerBotProShielded,
- BomberBotPro, BrawlerBotPro)
+ SpazBotSet,
+ SpazBot,
+ SpazBotDiedMessage,
+ BomberBot,
+ BrawlerBot,
+ TriggerBot,
+ TriggerBotPro,
+ BomberBotProShielded,
+ TriggerBotProShielded,
+ ChargerBot,
+ ChargerBotProShielded,
+ StickyBot,
+ ExplodeyBot,
+ BrawlerBotProShielded,
+ BomberBotPro,
+ BrawlerBotPro,
+)
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -34,6 +47,7 @@ if TYPE_CHECKING:
class Preset(Enum):
"""Play presets."""
+
ENDLESS = 'endless'
ENDLESS_TOURNAMENT = 'endless_tournament'
PRO = 'pro'
@@ -46,6 +60,7 @@ class Preset(Enum):
class Point(Enum):
"""Where we can spawn stuff and the corresponding map attr name."""
+
BOTTOM_LEFT = 'bot_spawn_bottom_left'
BOTTOM_RIGHT = 'bot_spawn_bottom_right'
START = 'bot_spawn_start'
@@ -54,6 +69,7 @@ class Point(Enum):
@dataclass
class Spawn:
"""Defines a bot spawn event."""
+
# noinspection PyUnresolvedReferences
type: type[SpazBot]
path: int = 0
@@ -63,12 +79,14 @@ class Spawn:
@dataclass
class Spacing:
"""Defines spacing between spawns."""
+
duration: float
@dataclass
class Wave:
"""Defines a wave of enemies."""
+
entries: list[Spawn | Spacing | None]
@@ -92,7 +110,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
tips = [
'Jump just as you\'re throwing to get bombs up to the highest levels.',
'No, you can\'t get up on the ledge. You have to throw bombs.',
- 'Whip back and forth to get more distance on your throws..'
+ 'Whip back and forth to get more distance on your throws..',
]
default_music = ba.MusicType.MARCHING
@@ -110,7 +128,7 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
ChargerBot: 1.0,
ChargerBotProShielded: 1.0,
ExplodeyBot: 1.0,
- StickyBot: 0.5
+ StickyBot: 0.5,
}
def __init__(self, settings: dict):
@@ -134,7 +152,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._powerup_center = self._map_type.defs.boxes['powerup_region'][0:3]
self._powerup_spread = (
self._map_type.defs.boxes['powerup_region'][6] * 0.5,
- self._map_type.defs.boxes['powerup_region'][8] * 0.5)
+ self._map_type.defs.boxes['powerup_region'][8] * 0.5,
+ )
self._score_region_material = ba.Material()
self._score_region_material.add_actions(
@@ -143,7 +162,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
('modify_part_collision', 'collide', True),
('modify_part_collision', 'physical', False),
('call', 'at_connect', self._handle_reached_end),
- ))
+ ),
+ )
self._last_wave_end_time = ba.time()
self._player_has_picked_up_powerup = False
@@ -175,8 +195,9 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
def on_transition_in(self) -> None:
super().on_transition_in()
- self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
- score_split=0.5)
+ self._scoreboard = Scoreboard(
+ label=ba.Lstr(resource='scoreText'), score_split=0.5
+ )
self._score_region = ba.NodeActor(
ba.newnode(
'region',
@@ -184,8 +205,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
'position': self.map.defs.boxes['score_region'][0:3],
'scale': self.map.defs.boxes['score_region'][6:9],
'type': 'box',
- 'materials': [self._score_region_material]
- }))
+ 'materials': [self._score_region_material],
+ },
+ )
+ )
def on_begin(self) -> None:
super().on_begin()
@@ -196,178 +219,222 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._exclude_powerups = ['curse']
self._have_tnt = True
self._waves = [
- Wave(entries=[
- Spawn(BomberBot, path=3 if hard else 2),
- Spawn(BomberBot, path=2),
- Spawn(BomberBot, path=2) if hard else None,
- Spawn(BomberBot, path=2) if player_count > 1 else None,
- Spawn(BomberBot, path=1) if hard else None,
- Spawn(BomberBot, path=1) if player_count > 2 else None,
- Spawn(BomberBot, path=1) if player_count > 3 else None,
- ]),
- Wave(entries=[
- Spawn(BomberBot, path=1) if hard else None,
- Spawn(BomberBot, path=2) if hard else None,
- Spawn(BomberBot, path=2),
- Spawn(BomberBot, path=2),
- Spawn(BomberBot, path=2) if player_count > 3 else None,
- Spawn(BrawlerBot, path=3),
- Spawn(BrawlerBot, path=3),
- Spawn(BrawlerBot, path=3) if hard else None,
- Spawn(BrawlerBot, path=3) if player_count > 1 else None,
- Spawn(BrawlerBot, path=3) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(ChargerBot, path=2) if hard else None,
- Spawn(ChargerBot, path=2) if player_count > 2 else None,
- Spawn(TriggerBot, path=2),
- Spawn(TriggerBot, path=2) if player_count > 1 else None,
- Spacing(duration=3.0),
- Spawn(BomberBot, path=2) if hard else None,
- Spawn(BomberBot, path=2) if hard else None,
- Spawn(BomberBot, path=2),
- Spawn(BomberBot, path=3) if hard else None,
- Spawn(BomberBot, path=3),
- Spawn(BomberBot, path=3),
- Spawn(BomberBot, path=3) if player_count > 3 else None,
- ]),
- Wave(entries=[
- Spawn(TriggerBot, path=1) if hard else None,
- Spacing(duration=1.0) if hard else None,
- Spawn(TriggerBot, path=2),
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=3),
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=1) if hard else None,
- Spacing(duration=1.0) if hard else None,
- Spawn(TriggerBot, path=2),
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=3),
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=1) if (
- player_count > 1 and hard) else None,
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=2) if player_count > 2 else None,
- Spacing(duration=1.0),
- Spawn(TriggerBot, path=3) if player_count > 3 else None,
- Spacing(duration=1.0),
- ]),
- Wave(entries=[
- Spawn(ChargerBotProShielded if hard else ChargerBot,
- path=1),
- Spawn(BrawlerBot, path=2) if hard else None,
- Spawn(BrawlerBot, path=2),
- Spawn(BrawlerBot, path=2),
- Spawn(BrawlerBot, path=3) if hard else None,
- Spawn(BrawlerBot, path=3),
- Spawn(BrawlerBot, path=3),
- Spawn(BrawlerBot, path=3) if player_count > 1 else None,
- Spawn(BrawlerBot, path=3) if player_count > 2 else None,
- Spawn(BrawlerBot, path=3) if player_count > 3 else None,
- ]),
- Wave(entries=[
- Spawn(BomberBotProShielded, path=3),
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=2),
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=1) if hard else None,
- Spacing(duration=1.0) if hard else None,
- Spawn(BomberBotProShielded, path=3),
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=2),
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=1) if hard else None,
- Spacing(duration=1.5) if hard else None,
- Spawn(BomberBotProShielded, path=3
- ) if player_count > 1 else None,
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=2
- ) if player_count > 2 else None,
- Spacing(duration=1.5),
- Spawn(BomberBotProShielded, path=1
- ) if player_count > 3 else None,
- ]),
+ Wave(
+ entries=[
+ Spawn(BomberBot, path=3 if hard else 2),
+ Spawn(BomberBot, path=2),
+ Spawn(BomberBot, path=2) if hard else None,
+ Spawn(BomberBot, path=2) if player_count > 1 else None,
+ Spawn(BomberBot, path=1) if hard else None,
+ Spawn(BomberBot, path=1) if player_count > 2 else None,
+ Spawn(BomberBot, path=1) if player_count > 3 else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBot, path=1) if hard else None,
+ Spawn(BomberBot, path=2) if hard else None,
+ Spawn(BomberBot, path=2),
+ Spawn(BomberBot, path=2),
+ Spawn(BomberBot, path=2) if player_count > 3 else None,
+ Spawn(BrawlerBot, path=3),
+ Spawn(BrawlerBot, path=3),
+ Spawn(BrawlerBot, path=3) if hard else None,
+ Spawn(BrawlerBot, path=3) if player_count > 1 else None,
+ Spawn(BrawlerBot, path=3) if player_count > 2 else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(ChargerBot, path=2) if hard else None,
+ Spawn(ChargerBot, path=2) if player_count > 2 else None,
+ Spawn(TriggerBot, path=2),
+ Spawn(TriggerBot, path=2) if player_count > 1 else None,
+ Spacing(duration=3.0),
+ Spawn(BomberBot, path=2) if hard else None,
+ Spawn(BomberBot, path=2) if hard else None,
+ Spawn(BomberBot, path=2),
+ Spawn(BomberBot, path=3) if hard else None,
+ Spawn(BomberBot, path=3),
+ Spawn(BomberBot, path=3),
+ Spawn(BomberBot, path=3) if player_count > 3 else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(TriggerBot, path=1) if hard else None,
+ Spacing(duration=1.0) if hard else None,
+ Spawn(TriggerBot, path=2),
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=3),
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=1) if hard else None,
+ Spacing(duration=1.0) if hard else None,
+ Spawn(TriggerBot, path=2),
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=3),
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=1)
+ if (player_count > 1 and hard)
+ else None,
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=2) if player_count > 2 else None,
+ Spacing(duration=1.0),
+ Spawn(TriggerBot, path=3) if player_count > 3 else None,
+ Spacing(duration=1.0),
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(
+ ChargerBotProShielded if hard else ChargerBot,
+ path=1,
+ ),
+ Spawn(BrawlerBot, path=2) if hard else None,
+ Spawn(BrawlerBot, path=2),
+ Spawn(BrawlerBot, path=2),
+ Spawn(BrawlerBot, path=3) if hard else None,
+ Spawn(BrawlerBot, path=3),
+ Spawn(BrawlerBot, path=3),
+ Spawn(BrawlerBot, path=3) if player_count > 1 else None,
+ Spawn(BrawlerBot, path=3) if player_count > 2 else None,
+ Spawn(BrawlerBot, path=3) if player_count > 3 else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotProShielded, path=3),
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=2),
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=1) if hard else None,
+ Spacing(duration=1.0) if hard else None,
+ Spawn(BomberBotProShielded, path=3),
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=2),
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=1) if hard else None,
+ Spacing(duration=1.5) if hard else None,
+ Spawn(BomberBotProShielded, path=3)
+ if player_count > 1
+ else None,
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=2)
+ if player_count > 2
+ else None,
+ Spacing(duration=1.5),
+ Spawn(BomberBotProShielded, path=1)
+ if player_count > 3
+ else None,
+ ]
+ ),
]
elif self._preset in {
- Preset.UBER_EASY, Preset.UBER, Preset.TOURNAMENT_UBER
+ Preset.UBER_EASY,
+ Preset.UBER,
+ Preset.TOURNAMENT_UBER,
}:
self._exclude_powerups = []
self._have_tnt = True
self._waves = [
- Wave(entries=[
- Spawn(TriggerBot, path=1) if hard else None,
- Spawn(TriggerBot, path=2),
- Spawn(TriggerBot, path=2),
- Spawn(TriggerBot, path=3),
- Spawn(BrawlerBotPro if hard else BrawlerBot,
- point=Point.BOTTOM_LEFT),
- Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT
- ) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(ChargerBot, path=2),
- Spawn(ChargerBot, path=3),
- Spawn(ChargerBot, path=1) if hard else None,
- Spawn(ChargerBot, path=2),
- Spawn(ChargerBot, path=3),
- Spawn(ChargerBot, path=1) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(BomberBotProShielded, path=1) if hard else None,
- Spawn(BomberBotProShielded, path=2),
- Spawn(BomberBotProShielded, path=2),
- Spawn(BomberBotProShielded, path=3),
- Spawn(BomberBotProShielded, path=3),
- Spawn(ChargerBot, point=Point.BOTTOM_RIGHT),
- Spawn(ChargerBot, point=Point.BOTTOM_LEFT
- ) if player_count > 2 else None,
- ]),
- Wave(entries=[
- Spawn(TriggerBotPro, path=1) if hard else None,
- Spawn(TriggerBotPro, path=1 if hard else 2),
- Spawn(TriggerBotPro, path=1 if hard else 2),
- Spawn(TriggerBotPro, path=1 if hard else 2),
- Spawn(TriggerBotPro, path=1 if hard else 2),
- Spawn(TriggerBotPro, path=1 if hard else 2),
- Spawn(TriggerBotPro, path=1 if hard else 2
- ) if player_count > 1 else None,
- Spawn(TriggerBotPro, path=1 if hard else 2
- ) if player_count > 3 else None,
- ]),
- Wave(entries=[
- Spawn(TriggerBotProShielded if hard else TriggerBotPro,
- point=Point.BOTTOM_LEFT),
- Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT
- ) if hard else None,
- Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT
- ) if player_count > 2 else None,
- Spawn(BomberBot, path=3),
- Spawn(BomberBot, path=3),
- Spacing(duration=5.0),
- Spawn(BrawlerBot, path=2),
- Spawn(BrawlerBot, path=2),
- Spacing(duration=5.0),
- Spawn(TriggerBot, path=1) if hard else None,
- Spawn(TriggerBot, path=1) if hard else None,
- ]),
- Wave(entries=[
- Spawn(BomberBotProShielded, path=2),
- Spawn(BomberBotProShielded, path=2) if hard else None,
- Spawn(StickyBot, point=Point.BOTTOM_RIGHT),
- Spawn(BomberBotProShielded, path=2),
- Spawn(BomberBotProShielded, path=2),
- Spawn(StickyBot, point=Point.BOTTOM_RIGHT
- ) if player_count > 2 else None,
- Spawn(BomberBotProShielded, path=2),
- Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT),
- Spawn(BomberBotProShielded, path=2),
- Spawn(BomberBotProShielded, path=2
- ) if player_count > 1 else None,
- Spacing(duration=5.0),
- Spawn(StickyBot, point=Point.BOTTOM_LEFT),
- Spacing(duration=2.0),
- Spawn(ExplodeyBot, point=Point.BOTTOM_RIGHT),
- ]),
+ Wave(
+ entries=[
+ Spawn(TriggerBot, path=1) if hard else None,
+ Spawn(TriggerBot, path=2),
+ Spawn(TriggerBot, path=2),
+ Spawn(TriggerBot, path=3),
+ Spawn(
+ BrawlerBotPro if hard else BrawlerBot,
+ point=Point.BOTTOM_LEFT,
+ ),
+ Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT)
+ if player_count > 2
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(ChargerBot, path=2),
+ Spawn(ChargerBot, path=3),
+ Spawn(ChargerBot, path=1) if hard else None,
+ Spawn(ChargerBot, path=2),
+ Spawn(ChargerBot, path=3),
+ Spawn(ChargerBot, path=1) if player_count > 2 else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotProShielded, path=1) if hard else None,
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(BomberBotProShielded, path=3),
+ Spawn(BomberBotProShielded, path=3),
+ Spawn(ChargerBot, point=Point.BOTTOM_RIGHT),
+ Spawn(ChargerBot, point=Point.BOTTOM_LEFT)
+ if player_count > 2
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(TriggerBotPro, path=1) if hard else None,
+ Spawn(TriggerBotPro, path=1 if hard else 2),
+ Spawn(TriggerBotPro, path=1 if hard else 2),
+ Spawn(TriggerBotPro, path=1 if hard else 2),
+ Spawn(TriggerBotPro, path=1 if hard else 2),
+ Spawn(TriggerBotPro, path=1 if hard else 2),
+ Spawn(TriggerBotPro, path=1 if hard else 2)
+ if player_count > 1
+ else None,
+ Spawn(TriggerBotPro, path=1 if hard else 2)
+ if player_count > 3
+ else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(
+ TriggerBotProShielded if hard else TriggerBotPro,
+ point=Point.BOTTOM_LEFT,
+ ),
+ Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT)
+ if hard
+ else None,
+ Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT)
+ if player_count > 2
+ else None,
+ Spawn(BomberBot, path=3),
+ Spawn(BomberBot, path=3),
+ Spacing(duration=5.0),
+ Spawn(BrawlerBot, path=2),
+ Spawn(BrawlerBot, path=2),
+ Spacing(duration=5.0),
+ Spawn(TriggerBot, path=1) if hard else None,
+ Spawn(TriggerBot, path=1) if hard else None,
+ ]
+ ),
+ Wave(
+ entries=[
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(BomberBotProShielded, path=2) if hard else None,
+ Spawn(StickyBot, point=Point.BOTTOM_RIGHT),
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(StickyBot, point=Point.BOTTOM_RIGHT)
+ if player_count > 2
+ else None,
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT),
+ Spawn(BomberBotProShielded, path=2),
+ Spawn(BomberBotProShielded, path=2)
+ if player_count > 1
+ else None,
+ Spacing(duration=5.0),
+ Spawn(StickyBot, point=Point.BOTTOM_LEFT),
+ Spacing(duration=2.0),
+ Spawn(ExplodeyBot, point=Point.BOTTOM_RIGHT),
+ ]
+ ),
]
elif self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
self._exclude_powerups = []
@@ -385,20 +452,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# Make sure to stay out of the way of menu/party buttons in the corner.
uiscale = ba.app.ui.uiscale
- l_offs = (-80 if uiscale is ba.UIScale.SMALL else
- -40 if uiscale is ba.UIScale.MEDIUM else 0)
+ l_offs = (
+ -80
+ if uiscale is ba.UIScale.SMALL
+ else -40
+ if uiscale is ba.UIScale.MEDIUM
+ else 0
+ )
self._lives_bg = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': self._heart_tex,
- 'model_opaque': self._heart_model_opaque,
- 'model_transparent': self._heart_model_transparent,
- 'attach': 'topRight',
- 'scale': (90, 90),
- 'position': (-110 + l_offs, -50),
- 'color': (1, 0.2, 0.2)
- }))
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': self._heart_tex,
+ 'model_opaque': self._heart_model_opaque,
+ 'model_transparent': self._heart_model_transparent,
+ 'attach': 'topRight',
+ 'scale': (90, 90),
+ 'position': (-110 + l_offs, -50),
+ 'color': (1, 0.2, 0.2),
+ },
+ )
+ )
# FIXME; should not set things based on vr mode.
# (won't look right to non-vr connected clients, etc)
vrmode = ba.app.vr_mode
@@ -415,8 +490,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
'vr_depth': 10,
'position': (-113 + l_offs, -69),
'scale': 1.3,
- 'text': str(self._lives)
- }))
+ 'text': str(self._lives),
+ },
+ )
+ )
ba.timer(2.0, self._start_updating_waves)
@@ -428,16 +505,14 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._flawless = False
pos = spaz.node.position
ba.playsound(self._bad_guy_score_sound, position=pos)
- light = ba.newnode('light',
- attrs={
- 'position': pos,
- 'radius': 0.5,
- 'color': (1, 0, 0)
- })
+ light = ba.newnode(
+ 'light', attrs={'position': pos, 'radius': 0.5, 'color': (1, 0, 0)}
+ )
ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False)
ba.timer(1.0, light.delete)
spaz.handlemessage(
- ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL))
+ ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL)
+ )
if self._lives > 0:
self._lives -= 1
@@ -456,26 +531,43 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
for _i in range(4):
ba.timer(
delay,
- ba.Call(_safesetattr, self._lives_text.node, 'color',
- (1, 0, 0, 1.0)))
+ ba.Call(
+ _safesetattr,
+ self._lives_text.node,
+ 'color',
+ (1, 0, 0, 1.0),
+ ),
+ )
assert self._lives_bg is not None
assert self._lives_bg.node
ba.timer(
delay,
- ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5))
+ ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5),
+ )
delay += 0.125
ba.timer(
delay,
- ba.Call(_safesetattr, self._lives_text.node, 'color',
- (1.0, 1.0, 0.0, 1.0)))
+ ba.Call(
+ _safesetattr,
+ self._lives_text.node,
+ 'color',
+ (1.0, 1.0, 0.0, 1.0),
+ ),
+ )
ba.timer(
delay,
- ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0))
+ ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0),
+ )
delay += 0.125
ba.timer(
delay,
- ba.Call(_safesetattr, self._lives_text.node, 'color',
- (0.8, 0.8, 0.8, 1.0)))
+ ba.Call(
+ _safesetattr,
+ self._lives_text.node,
+ 'color',
+ (0.8, 0.8, 0.8, 1.0),
+ ),
+ )
def on_continue(self) -> None:
self._lives = 3
@@ -485,9 +577,11 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
self._bots.start_moving()
def spawn_player(self, player: Player) -> ba.Actor:
- pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
- self._spawn_center[1],
- self._spawn_center[2] + random.uniform(-1.5, 1.5))
+ pos = (
+ self._spawn_center[0] + random.uniform(-1.5, 1.5),
+ self._spawn_center[1],
+ self._spawn_center[2] + random.uniform(-1.5, 1.5),
+ )
spaz = self.spawn_player_spaz(player, position=pos)
if self._preset in {Preset.PRO_EASY, Preset.UBER_EASY}:
spaz.impact_scale = 0.25
@@ -500,21 +594,22 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
del player # Unused.
self._player_has_picked_up_powerup = True
- def _drop_powerup(self,
- index: int,
- poweruptype: str | None = None) -> None:
+ def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None:
if poweruptype is None:
- poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._exclude_powerups))
- PowerupBox(position=self.map.powerup_spawn_points[index],
- poweruptype=poweruptype).autoretain()
+ poweruptype = PowerupBoxFactory.get().get_random_powerup_type(
+ excludetypes=self._exclude_powerups
+ )
+ PowerupBox(
+ position=self.map.powerup_spawn_points[index],
+ poweruptype=poweruptype,
+ ).autoretain()
def _start_powerup_drops(self) -> None:
ba.timer(3.0, self._drop_powerups, repeat=True)
- def _drop_powerups(self,
- standard_points: bool = False,
- force_first: str | None = None) -> None:
+ def _drop_powerups(
+ self, standard_points: bool = False, force_first: str | None = None
+ ) -> None:
"""Generic powerup drop."""
# If its been a minute since our last wave finished emerging, stop
@@ -530,22 +625,32 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
for i in range(len(points)):
ba.timer(
1.0 + i * 0.5,
- ba.Call(self._drop_powerup, i,
- force_first if i == 0 else None))
+ ba.Call(
+ self._drop_powerup, i, force_first if i == 0 else None
+ ),
+ )
else:
- pos = (self._powerup_center[0] + random.uniform(
- -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
- self._powerup_center[1],
- self._powerup_center[2] + random.uniform(
- -self._powerup_spread[1], self._powerup_spread[1]))
+ pos = (
+ self._powerup_center[0]
+ + random.uniform(
+ -1.0 * self._powerup_spread[0],
+ 1.0 * self._powerup_spread[0],
+ ),
+ self._powerup_center[1],
+ self._powerup_center[2]
+ + random.uniform(
+ -self._powerup_spread[1], self._powerup_spread[1]
+ ),
+ )
# drop one random one somewhere..
assert self._exclude_powerups is not None
PowerupBox(
position=pos,
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._exclude_powerups +
- extra_excludes)).autoretain()
+ excludetypes=self._exclude_powerups + extra_excludes
+ ),
+ ).autoretain()
def end_game(self) -> None:
ba.pushcall(ba.Call(self.do_end, 'defeat'))
@@ -569,13 +674,15 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
score = None
fail_message = ba.Lstr(resource='reachWave2Text')
- self.end(delay=delay,
- results={
- 'outcome': outcome,
- 'score': score,
- 'fail_message': fail_message,
- 'playerinfos': self.initialplayerinfos
- })
+ self.end(
+ delay=delay,
+ results={
+ 'outcome': outcome,
+ 'score': score,
+ 'fail_message': fail_message,
+ 'playerinfos': self.initialplayerinfos,
+ },
+ )
def _on_got_scores_to_beat(self, scores: list[dict[str, Any]]) -> None:
self._show_standard_scores_to_beat_ui(scores)
@@ -584,8 +691,12 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# pylint: disable=too-many-branches
# If we have no living bots, go to the next wave.
- if (self._can_end_wave and not self._bots.have_living_bots()
- and not self._game_over and self._lives > 0):
+ if (
+ self._can_end_wave
+ and not self._bots.have_living_bots()
+ and not self._game_over
+ and self._lives > 0
+ ):
self._can_end_wave = False
self._time_bonus_timer = None
@@ -595,14 +706,16 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
won = False
else:
assert self._waves is not None
- won = (self._wavenum == len(self._waves))
+ won = self._wavenum == len(self._waves)
# Reward time bonus.
base_delay = 4.0 if won else 0
if self._time_bonus > 0:
ba.timer(0, ba.Call(ba.playsound, self._cashregistersound))
- ba.timer(base_delay,
- ba.Call(self._award_time_bonus, self._time_bonus))
+ ba.timer(
+ base_delay,
+ ba.Call(self._award_time_bonus, self._time_bonus),
+ )
base_delay += 1.0
# Reward flawless bonus.
@@ -616,25 +729,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# Completion achievements:
if self._preset in {Preset.PRO, Preset.PRO_EASY}:
- self._award_achievement('Pro Runaround Victory',
- sound=False)
+ self._award_achievement(
+ 'Pro Runaround Victory', sound=False
+ )
if self._lives == self._start_lives:
self._award_achievement('The Wall', sound=False)
if not self._player_has_picked_up_powerup:
- self._award_achievement('Precision Bombing',
- sound=False)
+ self._award_achievement(
+ 'Precision Bombing', sound=False
+ )
elif self._preset in {Preset.UBER, Preset.UBER_EASY}:
- self._award_achievement('Uber Runaround Victory',
- sound=False)
+ self._award_achievement(
+ 'Uber Runaround Victory', sound=False
+ )
if self._lives == self._start_lives:
self._award_achievement('The Great Wall', sound=False)
if not self._a_player_has_been_killed:
self._award_achievement('Stayin\' Alive', sound=False)
# Give remaining players some points and have them celebrate.
- self.show_zoom_message(ba.Lstr(resource='victoryText'),
- scale=1.0,
- duration=4.0)
+ self.show_zoom_message(
+ ba.Lstr(resource='victoryText'), scale=1.0, duration=4.0
+ )
self.celebrate(10.0)
ba.timer(base_delay, self._award_lives_bonus)
@@ -659,71 +775,98 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
def _award_completion_bonus(self) -> None:
bonus = 200
ba.playsound(self._cashregistersound)
- PopupText(ba.Lstr(value='+${A} ${B}',
- subs=[('${A}', str(bonus)),
- ('${B}',
- ba.Lstr(resource='completionBonusText'))]),
- color=(0.7, 0.7, 1.0, 1),
- scale=1.6,
- position=(0, 1.5, -1)).autoretain()
+ PopupText(
+ ba.Lstr(
+ value='+${A} ${B}',
+ subs=[
+ ('${A}', str(bonus)),
+ ('${B}', ba.Lstr(resource='completionBonusText')),
+ ],
+ ),
+ color=(0.7, 0.7, 1.0, 1),
+ scale=1.6,
+ position=(0, 1.5, -1),
+ ).autoretain()
self._score += bonus
self._update_scores()
def _award_lives_bonus(self) -> None:
bonus = self._lives * 30
ba.playsound(self._cashregistersound)
- PopupText(ba.Lstr(value='+${A} ${B}',
- subs=[('${A}', str(bonus)),
- ('${B}', ba.Lstr(resource='livesBonusText'))]),
- color=(0.7, 1.0, 0.3, 1),
- scale=1.3,
- position=(0, 1, -1)).autoretain()
+ PopupText(
+ ba.Lstr(
+ value='+${A} ${B}',
+ subs=[
+ ('${A}', str(bonus)),
+ ('${B}', ba.Lstr(resource='livesBonusText')),
+ ],
+ ),
+ color=(0.7, 1.0, 0.3, 1),
+ scale=1.3,
+ position=(0, 1, -1),
+ ).autoretain()
self._score += bonus
self._update_scores()
def _award_time_bonus(self, bonus: int) -> None:
ba.playsound(self._cashregistersound)
- PopupText(ba.Lstr(value='+${A} ${B}',
- subs=[('${A}', str(bonus)),
- ('${B}', ba.Lstr(resource='timeBonusText'))]),
- color=(1, 1, 0.5, 1),
- scale=1.0,
- position=(0, 3, -1)).autoretain()
+ PopupText(
+ ba.Lstr(
+ value='+${A} ${B}',
+ subs=[
+ ('${A}', str(bonus)),
+ ('${B}', ba.Lstr(resource='timeBonusText')),
+ ],
+ ),
+ color=(1, 1, 0.5, 1),
+ scale=1.0,
+ position=(0, 3, -1),
+ ).autoretain()
self._score += self._time_bonus
self._update_scores()
def _award_flawless_bonus(self) -> None:
ba.playsound(self._cashregistersound)
- PopupText(ba.Lstr(value='+${A} ${B}',
- subs=[('${A}', str(self._flawless_bonus)),
- ('${B}', ba.Lstr(resource='perfectWaveText'))
- ]),
- color=(1, 1, 0.2, 1),
- scale=1.2,
- position=(0, 2, -1)).autoretain()
+ PopupText(
+ ba.Lstr(
+ value='+${A} ${B}',
+ subs=[
+ ('${A}', str(self._flawless_bonus)),
+ ('${B}', ba.Lstr(resource='perfectWaveText')),
+ ],
+ ),
+ color=(1, 1, 0.2, 1),
+ scale=1.2,
+ position=(0, 2, -1),
+ ).autoretain()
assert self._flawless_bonus is not None
self._score += self._flawless_bonus
self._update_scores()
def _start_time_bonus_timer(self) -> None:
- self._time_bonus_timer = ba.Timer(1.0,
- self._update_time_bonus,
- repeat=True)
+ self._time_bonus_timer = ba.Timer(
+ 1.0, self._update_time_bonus, repeat=True
+ )
def _start_next_wave(self) -> None:
# FIXME: Need to split this up.
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
- self.show_zoom_message(ba.Lstr(value='${A} ${B}',
- subs=[('${A}',
- ba.Lstr(resource='waveText')),
- ('${B}', str(self._wavenum))]),
- scale=1.0,
- duration=1.0,
- trail=True)
+ self.show_zoom_message(
+ ba.Lstr(
+ value='${A} ${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='waveText')),
+ ('${B}', str(self._wavenum)),
+ ],
+ ),
+ scale=1.0,
+ duration=1.0,
+ trail=True,
+ )
ba.timer(0.4, ba.Call(ba.playsound, self._new_wave_sound))
t_sec = 0.0
base_delay = 0.5
@@ -745,16 +888,19 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
if level > 5:
spaz_types += [(TriggerBotPro, 7.5)] * (1 + (level - 5) // 7)
if level > 2:
- spaz_types += [(BomberBotProShielded, 8.0)
- ] * (1 + (level - 2) // 6)
+ spaz_types += [(BomberBotProShielded, 8.0)] * (
+ 1 + (level - 2) // 6
+ )
if level > 6:
- spaz_types += [(TriggerBotProShielded, 12.0)
- ] * (1 + (level - 6) // 5)
+ spaz_types += [(TriggerBotProShielded, 12.0)] * (
+ 1 + (level - 6) // 5
+ )
if level > 1:
- spaz_types += ([(ChargerBot, 10.0)] * (1 + (level - 1) // 4))
+ spaz_types += [(ChargerBot, 10.0)] * (1 + (level - 1) // 4)
if level > 7:
- spaz_types += [(ChargerBotProShielded, 15.0)
- ] * (1 + (level - 7) // 3)
+ spaz_types += [(ChargerBotProShielded, 15.0)] * (
+ 1 + (level - 7) // 3
+ )
# Bot type, their effect on target points.
defender_types: list[tuple[type[SpazBot], float]] = [
@@ -765,16 +911,17 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
if level > 2:
defender_types += [(ChargerBot, 0.75)]
if level > 4:
- defender_types += ([(StickyBot, 0.7)] * (1 + (level - 5) // 6))
+ defender_types += [(StickyBot, 0.7)] * (1 + (level - 5) // 6)
if level > 6:
- defender_types += ([(ExplodeyBot, 0.7)] * (1 +
- (level - 5) // 5))
+ defender_types += [(ExplodeyBot, 0.7)] * (1 + (level - 5) // 5)
if level > 8:
- defender_types += ([(BrawlerBotProShielded, 0.65)] *
- (1 + (level - 5) // 4))
+ defender_types += [(BrawlerBotProShielded, 0.65)] * (
+ 1 + (level - 5) // 4
+ )
if level > 10:
- defender_types += ([(TriggerBotProShielded, 0.6)] *
- (1 + (level - 6) // 3))
+ defender_types += [(TriggerBotProShielded, 0.6)] * (
+ 1 + (level - 6) // 3
+ )
for group in range(group_count):
this_target_point_s = target_points / group_count
@@ -821,31 +968,41 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
elif path == 6:
this_target_point_s *= 0.7
- def _add_defender(defender_type: tuple[type[SpazBot], float],
- pnt: Point) -> tuple[float, Spawn]:
+ def _add_defender(
+ defender_type: tuple[type[SpazBot], float], pnt: Point
+ ) -> tuple[float, Spawn]:
# This is ok because we call it immediately.
# pylint: disable=cell-var-from-loop
return this_target_point_s * defender_type[1], Spawn(
- defender_type[0], point=pnt)
+ defender_type[0], point=pnt
+ )
# Add defenders.
- defender_type1 = defender_types[random.randrange(
- len(defender_types))]
- defender_type2 = defender_types[random.randrange(
- len(defender_types))]
+ defender_type1 = defender_types[
+ random.randrange(len(defender_types))
+ ]
+ defender_type2 = defender_types[
+ random.randrange(len(defender_types))
+ ]
defender1 = defender2 = None
- if ((group == 0) or (group == 1 and level > 3)
- or (group == 2 and level > 5)):
+ if (
+ (group == 0)
+ or (group == 1 and level > 3)
+ or (group == 2 and level > 5)
+ ):
if random.random() < min(0.75, (level - 1) * 0.11):
this_target_point_s, defender1 = _add_defender(
- defender_type1, Point.BOTTOM_LEFT)
+ defender_type1, Point.BOTTOM_LEFT
+ )
if random.random() < min(0.75, (level - 1) * 0.04):
this_target_point_s, defender2 = _add_defender(
- defender_type2, Point.BOTTOM_RIGHT)
+ defender_type2, Point.BOTTOM_RIGHT
+ )
spaz_type = spaz_types[random.randrange(len(spaz_types))]
member_count = max(
- 1, int(round(this_target_point_s / spaz_type[1])))
+ 1, int(round(this_target_point_s / spaz_type[1]))
+ )
for i, _member in enumerate(range(member_count)):
if path == 4:
this_path = i % 3 # Looping forward.
@@ -907,14 +1064,20 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
delay /= self._get_bot_speed(bot_type)
t_sec += delay * 0.5
tcall = ba.Call(
- self.add_bot_at_point, point, bot_type, path,
- 0.1 if point is Point.START else non_runner_spawn_time)
+ self.add_bot_at_point,
+ point,
+ bot_type,
+ path,
+ 0.1 if point is Point.START else non_runner_spawn_time,
+ )
ba.timer(t_sec, tcall)
t_sec += delay * 0.5
# We can end the wave after all the spawning happens.
- ba.timer(t_sec - delay * 0.5 + non_runner_spawn_time + 0.01,
- self._set_can_end_wave)
+ ba.timer(
+ t_sec - delay * 0.5 + non_runner_spawn_time + 0.01,
+ self._set_can_end_wave,
+ )
# Reset our time bonus.
# In this game we use a constant time bonus so it erodes away in
@@ -926,23 +1089,28 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
assert self._time_bonus_mult is not None
txtval = ba.Lstr(
value='${A}: ${B}',
- subs=[('${A}', ba.Lstr(resource='timeBonusText')),
- ('${B}', str(int(self._time_bonus * self._time_bonus_mult)))
- ])
+ subs=[
+ ('${A}', ba.Lstr(resource='timeBonusText')),
+ ('${B}', str(int(self._time_bonus * self._time_bonus_mult))),
+ ],
+ )
self._time_bonus_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'color': (1, 1, 0.0, 1),
- 'shadow': 1.0,
- 'vr_depth': -30,
- 'flatness': 1.0,
- 'position': (0, -60),
- 'scale': 0.8,
- 'text': txtval
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'color': (1, 1, 0.0, 1),
+ 'shadow': 1.0,
+ 'vr_depth': -30,
+ 'flatness': 1.0,
+ 'position': (0, -60),
+ 'scale': 0.8,
+ 'text': txtval,
+ },
+ )
+ )
ba.timer(t_sec, self._start_time_bonus_timer)
@@ -951,26 +1119,39 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
# player could fill the whole map with them)
self._last_wave_end_time = ba.time() + t_sec
totalwaves = str(len(self._waves)) if self._waves is not None else '??'
- txtval = ba.Lstr(value='${A} ${B}',
- subs=[('${A}', ba.Lstr(resource='waveText')),
- ('${B}',
- str(self._wavenum) + ('' if self._preset in {
- Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT
- } else f'/{totalwaves}'))])
+ txtval = ba.Lstr(
+ value='${A} ${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='waveText')),
+ (
+ '${B}',
+ str(self._wavenum)
+ + (
+ ''
+ if self._preset
+ in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}
+ else f'/{totalwaves}'
+ ),
+ ),
+ ],
+ )
self._wave_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'vr_depth': -10,
- 'color': (1, 1, 1, 1),
- 'shadow': 1.0,
- 'flatness': 1.0,
- 'position': (0, -40),
- 'scale': 1.3,
- 'text': txtval
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'vr_depth': -10,
+ 'color': (1, 1, 1, 1),
+ 'shadow': 1.0,
+ 'flatness': 1.0,
+ 'position': (0, -40),
+ 'scale': 1.3,
+ 'text': txtval,
+ },
+ )
+ )
def _on_bot_spawn(self, path: int, spaz: SpazBot) -> None:
@@ -983,21 +1164,25 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
setattr(spaz, 'r_walk_row', path)
setattr(spaz, 'r_walk_speed', self._get_bot_speed(spaz_type))
- def add_bot_at_point(self,
- point: Point,
- spaztype: type[SpazBot],
- path: int,
- spawn_time: float = 0.1) -> None:
+ def add_bot_at_point(
+ self,
+ point: Point,
+ spaztype: type[SpazBot],
+ path: int,
+ spawn_time: float = 0.1,
+ ) -> None:
"""Add the given type bot with the given delay (in seconds)."""
# Don't add if the game has ended.
if self._game_over:
return
pos = self.map.defs.points[point.value][:3]
- self._bots.spawn_bot(spaztype,
- pos=pos,
- spawn_time=spawn_time,
- on_spawn_call=ba.Call(self._on_bot_spawn, path))
+ self._bots.spawn_bot(
+ spaztype,
+ pos=pos,
+ spawn_time=spawn_time,
+ on_spawn_call=ba.Call(self._on_bot_spawn, path),
+ )
def _update_time_bonus(self) -> None:
self._time_bonus = int(self._time_bonus * 0.91)
@@ -1006,16 +1191,19 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
assert self._time_bonus_mult
self._time_bonus_text.node.text = ba.Lstr(
value='${A}: ${B}',
- subs=[('${A}', ba.Lstr(resource='timeBonusText')),
- ('${B}',
- str(int(self._time_bonus * self._time_bonus_mult)))])
+ subs=[
+ ('${A}', ba.Lstr(resource='timeBonusText')),
+ (
+ '${B}',
+ str(int(self._time_bonus * self._time_bonus_mult)),
+ ),
+ ],
+ )
else:
self._time_bonus_text = None
def _start_updating_waves(self) -> None:
- self._wave_update_timer = ba.Timer(2.0,
- self._update_waves,
- repeat=True)
+ self._wave_update_timer = ba.Timer(2.0, self._update_waves, repeat=True)
def _update_scores(self) -> None:
score = self._score
@@ -1089,9 +1277,10 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
bot.node.move_left_right = 0
bot.node.run = 0.0
return True
- if ((ba.is_point_in_box(pos, boxes['b8'])
- and not ba.is_point_in_box(pos, boxes['b9']))
- or pos == (0.0, 0.0, 0.0)):
+ if (
+ ba.is_point_in_box(pos, boxes['b8'])
+ and not ba.is_point_in_box(pos, boxes['b9'])
+ ) or pos == (0.0, 0.0, 0.0):
# Default to walking right if we're still in the walking area.
bot.node.move_left_right = speed
@@ -1118,7 +1307,8 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
assert self.initialplayerinfos is not None
respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
player.respawn_timer = ba.Timer(
- respawn_time, ba.Call(self.spawn_player_if_exists, player))
+ respawn_time, ba.Call(self.spawn_player_if_exists, player)
+ )
player.respawn_icon = RespawnIcon(player, respawn_time)
elif isinstance(msg, SpazBotDiedMessage):
@@ -1136,15 +1326,20 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
target = None
try:
if msg.killerplayer:
- self.stats.player_scored(msg.killerplayer,
- pts,
- target=target,
- kill=True,
- screenmessage=False,
- importance=importance)
- ba.playsound(self._dingsound if importance == 1 else
- self._dingsoundhigh,
- volume=0.6)
+ self.stats.player_scored(
+ msg.killerplayer,
+ pts,
+ target=target,
+ kill=True,
+ screenmessage=False,
+ importance=importance,
+ )
+ ba.playsound(
+ self._dingsound
+ if importance == 1
+ else self._dingsoundhigh,
+ volume=0.6,
+ )
except Exception:
ba.print_exception('Error on SpazBotDiedMessage.')
@@ -1161,8 +1356,9 @@ class RunaroundGame(ba.CoopGameActivity[Player, Team]):
def _get_bot_speed(self, bot_type: type[SpazBot]) -> float:
speed = self._bot_speed_map.get(bot_type)
if speed is None:
- raise TypeError('Invalid bot type to _get_bot_speed(): ' +
- str(bot_type))
+ raise TypeError(
+ 'Invalid bot type to _get_bot_speed(): ' + str(bot_type)
+ )
return speed
def _set_can_end_wave(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/game/targetpractice.py b/assets/src/ba_data/python/bastd/game/targetpractice.py
index 6437aa53..3788f01a 100644
--- a/assets/src/ba_data/python/bastd/game/targetpractice.py
+++ b/assets/src/ba_data/python/bastd/game/targetpractice.py
@@ -44,7 +44,7 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
available_settings = [
ba.IntSetting('Target Count', min_value=1, default=3),
ba.BoolSetting('Enable Impact Bombs', default=True),
- ba.BoolSetting('Enable Triple Bombs', default=True)
+ ba.BoolSetting('Enable Triple Bombs', default=True),
]
default_music = ba.MusicType.FORWARD_MARCH
@@ -55,8 +55,9 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
@classmethod
def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
# We support any teams or versus sessions.
- return (issubclass(sessiontype, ba.CoopSession)
- or issubclass(sessiontype, ba.MultiTeamSession))
+ return issubclass(sessiontype, ba.CoopSession) or issubclass(
+ sessiontype, ba.MultiTeamSession
+ )
def __init__(self, settings: dict):
super().__init__(settings)
@@ -86,8 +87,11 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
def spawn_player(self, player: Player) -> ba.Actor:
spawn_center = (0, 3, -5)
- pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
- spawn_center[2] + random.uniform(-1.5, 1.5))
+ pos = (
+ spawn_center[0] + random.uniform(-1.5, 1.5),
+ spawn_center[1],
+ spawn_center[2] + random.uniform(-1.5, 1.5),
+ )
# Reset their streak.
player.streak = 0
@@ -153,7 +157,8 @@ class TargetPracticeGame(ba.TeamGameActivity[Player, Team]):
bullseye = any(
target.do_hit_at_position(pos, player)
- for target in list(self._targets))
+ for target in list(self._targets)
+ )
if bullseye:
player.streak += 1
else:
@@ -208,39 +213,42 @@ class Target(ba.Actor):
# It can be handy to test with this on to make sure the projection
# isn't too far off from the actual object.
show_in_space = False
- loc1 = ba.newnode('locator',
- attrs={
- 'shape': 'circle',
- 'position': position,
- 'color': (0, 1, 0),
- 'opacity': 0.5,
- 'draw_beauty': show_in_space,
- 'additive': True
- })
- loc2 = ba.newnode('locator',
- attrs={
- 'shape': 'circleOutline',
- 'position': position,
- 'color': (0, 1, 0),
- 'opacity': 0.3,
- 'draw_beauty': False,
- 'additive': True
- })
- loc3 = ba.newnode('locator',
- attrs={
- 'shape': 'circleOutline',
- 'position': position,
- 'color': (0, 1, 0),
- 'opacity': 0.1,
- 'draw_beauty': False,
- 'additive': True
- })
+ loc1 = ba.newnode(
+ 'locator',
+ attrs={
+ 'shape': 'circle',
+ 'position': position,
+ 'color': (0, 1, 0),
+ 'opacity': 0.5,
+ 'draw_beauty': show_in_space,
+ 'additive': True,
+ },
+ )
+ loc2 = ba.newnode(
+ 'locator',
+ attrs={
+ 'shape': 'circleOutline',
+ 'position': position,
+ 'color': (0, 1, 0),
+ 'opacity': 0.3,
+ 'draw_beauty': False,
+ 'additive': True,
+ },
+ )
+ loc3 = ba.newnode(
+ 'locator',
+ attrs={
+ 'shape': 'circleOutline',
+ 'position': position,
+ 'color': (0, 1, 0),
+ 'opacity': 0.1,
+ 'draw_beauty': False,
+ 'additive': True,
+ },
+ )
self._nodes = [loc1, loc2, loc3]
ba.animate_array(loc1, 'size', 1, {0: [0.0], 0.2: [self._r1 * 2.0]})
- ba.animate_array(loc2, 'size', 1, {
- 0.05: [0.0],
- 0.25: [self._r2 * 2.0]
- })
+ ba.animate_array(loc2, 'size', 1, {0.05: [0.0], 0.25: [self._r2 * 2.0]})
ba.animate_array(loc3, 'size', 1, {0.1: [0.0], 0.3: [self._r3 * 2.0]})
ba.playsound(ba.getsound('laserReverse'))
@@ -268,7 +276,7 @@ class Target(ba.Actor):
if activity.has_ended() or self._hit or not self._nodes:
return False
- diff = (ba.Vec3(pos) - self._position)
+ diff = ba.Vec3(pos) - self._position
# Disregard Y difference. Our target point probably isn't exactly
# on the ground anyway.
@@ -284,7 +292,7 @@ class Target(ba.Actor):
0.0: (1.0, 0.0, 0.0),
0.049: (1.0, 0.0, 0.0),
0.05: (1.0, 1.0, 1.0),
- 0.1: (0.0, 1.0, 0.0)
+ 0.1: (0.0, 1.0, 0.0),
}
cdull = (0.3, 0.3, 0.3)
popupcolor: Sequence[float]
@@ -301,9 +309,15 @@ class Target(ba.Actor):
if streak > 0:
ba.playsound(
ba.getsound(
- 'orchestraHit4' if streak > 3 else
- 'orchestraHit3' if streak > 2 else
- 'orchestraHit2' if streak > 1 else 'orchestraHit'))
+ 'orchestraHit4'
+ if streak > 3
+ else 'orchestraHit3'
+ if streak > 2
+ else 'orchestraHit2'
+ if streak > 1
+ else 'orchestraHit'
+ )
+ )
elif dist <= self._r2 + self._rfudge:
self._nodes[0].color = cdull
self._nodes[2].color = cdull
@@ -330,10 +344,12 @@ class Target(ba.Actor):
if len(activity.players) > 1:
popupcolor = ba.safecolor(player.color, target_intensity=0.75)
popupstr += ' ' + player.getname()
- PopupText(popupstr,
- position=self._position,
- color=popupcolor,
- scale=popupscale).autoretain()
+ PopupText(
+ popupstr,
+ position=self._position,
+ color=popupcolor,
+ scale=popupscale,
+ ).autoretain()
# Give this player's team points and update the score-board.
player.team.score += points
@@ -343,23 +359,28 @@ class Target(ba.Actor):
# Also give this individual player points
# (only applies in teams mode).
assert activity.stats is not None
- activity.stats.player_scored(player,
- points,
- showpoints=False,
- screenmessage=False)
+ activity.stats.player_scored(
+ player, points, showpoints=False, screenmessage=False
+ )
- ba.animate_array(self._nodes[0], 'size', 1, {
- 0.8: self._nodes[0].size,
- 1.0: [0.0]
- })
- ba.animate_array(self._nodes[1], 'size', 1, {
- 0.85: self._nodes[1].size,
- 1.05: [0.0]
- })
- ba.animate_array(self._nodes[2], 'size', 1, {
- 0.9: self._nodes[2].size,
- 1.1: [0.0]
- })
+ ba.animate_array(
+ self._nodes[0],
+ 'size',
+ 1,
+ {0.8: self._nodes[0].size, 1.0: [0.0]},
+ )
+ ba.animate_array(
+ self._nodes[1],
+ 'size',
+ 1,
+ {0.85: self._nodes[1].size, 1.05: [0.0]},
+ )
+ ba.animate_array(
+ self._nodes[2],
+ 'size',
+ 1,
+ {0.9: self._nodes[2].size, 1.1: [0.0]},
+ )
ba.timer(1.1, ba.Call(self.handlemessage, ba.DieMessage()))
return bullseye
diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py
index 3cf84ae3..2040e5c2 100644
--- a/assets/src/ba_data/python/bastd/game/thelaststand.py
+++ b/assets/src/ba_data/python/bastd/game/thelaststand.py
@@ -13,12 +13,22 @@ from bastd.actor.playerspaz import PlayerSpaz
from bastd.actor.bomb import TNTSpawner
from bastd.actor.scoreboard import Scoreboard
from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox
-from bastd.actor.spazbot import (SpazBotSet, SpazBotDiedMessage, BomberBot,
- BomberBotPro, BomberBotProShielded,
- BrawlerBot, BrawlerBotPro,
- BrawlerBotProShielded, TriggerBot,
- TriggerBotPro, TriggerBotProShielded,
- ChargerBot, StickyBot, ExplodeyBot)
+from bastd.actor.spazbot import (
+ SpazBotSet,
+ SpazBotDiedMessage,
+ BomberBot,
+ BomberBotPro,
+ BomberBotProShielded,
+ BrawlerBot,
+ BrawlerBotPro,
+ BrawlerBotProShielded,
+ TriggerBot,
+ TriggerBotPro,
+ TriggerBotProShielded,
+ ChargerBot,
+ StickyBot,
+ ExplodeyBot,
+)
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -28,6 +38,7 @@ if TYPE_CHECKING:
@dataclass
class SpawnInfo:
"""Spawning info for a particular bot type."""
+
spawnrate: float
increase: float
dincrease: float
@@ -83,25 +94,26 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
# For each bot type: [spawnrate, increase, d_increase]
self._bot_spawn_types = {
- BomberBot: SpawnInfo(1.00, 0.00, 0.000),
- BomberBotPro: SpawnInfo(0.00, 0.05, 0.001),
- BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
- BrawlerBot: SpawnInfo(1.00, 0.00, 0.000),
- BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001),
- BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
- TriggerBot: SpawnInfo(0.30, 0.00, 0.000),
- TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001),
- TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
- ChargerBot: SpawnInfo(0.30, 0.05, 0.000),
- StickyBot: SpawnInfo(0.10, 0.03, 0.001),
- ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002)
+ BomberBot: SpawnInfo(1.00, 0.00, 0.000),
+ BomberBotPro: SpawnInfo(0.00, 0.05, 0.001),
+ BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
+ BrawlerBot: SpawnInfo(1.00, 0.00, 0.000),
+ BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001),
+ BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
+ TriggerBot: SpawnInfo(0.30, 0.00, 0.000),
+ TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001),
+ TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002),
+ ChargerBot: SpawnInfo(0.30, 0.05, 0.000),
+ StickyBot: SpawnInfo(0.10, 0.03, 0.001),
+ ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002),
} # yapf: disable
def on_transition_in(self) -> None:
super().on_transition_in()
ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound))
- self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
- score_split=0.5)
+ self._scoreboard = Scoreboard(
+ label=ba.Lstr(resource='scoreText'), score_split=0.5
+ )
def on_begin(self) -> None:
super().on_begin()
@@ -112,13 +124,16 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
ba.timer(0.001, ba.WeakCall(self._start_bot_updates))
self.setup_low_life_warning_sound()
self._update_scores()
- self._tntspawner = TNTSpawner(position=self._tntspawnpos,
- respawn_time=10.0)
+ self._tntspawner = TNTSpawner(
+ position=self._tntspawnpos, respawn_time=10.0
+ )
def spawn_player(self, player: Player) -> ba.Actor:
- pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
- self._spawn_center[1],
- self._spawn_center[2] + random.uniform(-1.5, 1.5))
+ pos = (
+ self._spawn_center[0] + random.uniform(-1.5, 1.5),
+ self._spawn_center[1],
+ self._spawn_center[2] + random.uniform(-1.5, 1.5),
+ )
return self.spawn_player_spaz(player, position=pos)
def _start_bot_updates(self) -> None:
@@ -129,67 +144,86 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
self._update_bots()
if len(self.players) > 3:
self._update_bots()
- self._bot_update_timer = ba.Timer(self._bot_update_interval,
- ba.WeakCall(self._update_bots))
+ self._bot_update_timer = ba.Timer(
+ self._bot_update_interval, ba.WeakCall(self._update_bots)
+ )
- def _drop_powerup(self,
- index: int,
- poweruptype: str | None = None) -> None:
+ def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None:
if poweruptype is None:
- poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._excludepowerups))
- PowerupBox(position=self.map.powerup_spawn_points[index],
- poweruptype=poweruptype).autoretain()
+ poweruptype = PowerupBoxFactory.get().get_random_powerup_type(
+ excludetypes=self._excludepowerups
+ )
+ PowerupBox(
+ position=self.map.powerup_spawn_points[index],
+ poweruptype=poweruptype,
+ ).autoretain()
def _start_powerup_drops(self) -> None:
- self._powerup_drop_timer = ba.Timer(3.0,
- ba.WeakCall(self._drop_powerups),
- repeat=True)
+ self._powerup_drop_timer = ba.Timer(
+ 3.0, ba.WeakCall(self._drop_powerups), repeat=True
+ )
- def _drop_powerups(self,
- standard_points: bool = False,
- force_first: str | None = None) -> None:
+ def _drop_powerups(
+ self, standard_points: bool = False, force_first: str | None = None
+ ) -> None:
"""Generic powerup drop."""
from bastd.actor import powerupbox
+
if standard_points:
pts = self.map.powerup_spawn_points
for i in range(len(pts)):
ba.timer(
1.0 + i * 0.5,
- ba.WeakCall(self._drop_powerup, i,
- force_first if i == 0 else None))
+ ba.WeakCall(
+ self._drop_powerup, i, force_first if i == 0 else None
+ ),
+ )
else:
- drop_pt = (self._powerup_center[0] + random.uniform(
- -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
- self._powerup_center[1],
- self._powerup_center[2] + random.uniform(
- -self._powerup_spread[1], self._powerup_spread[1]))
+ drop_pt = (
+ self._powerup_center[0]
+ + random.uniform(
+ -1.0 * self._powerup_spread[0],
+ 1.0 * self._powerup_spread[0],
+ ),
+ self._powerup_center[1],
+ self._powerup_center[2]
+ + random.uniform(
+ -self._powerup_spread[1], self._powerup_spread[1]
+ ),
+ )
# Drop one random one somewhere.
powerupbox.PowerupBox(
position=drop_pt,
poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
- excludetypes=self._excludepowerups)).autoretain()
+ excludetypes=self._excludepowerups
+ ),
+ ).autoretain()
def do_end(self, outcome: str) -> None:
"""End the game."""
if outcome == 'defeat':
self.fade_to_red()
- self.end(delay=2.0,
- results={
- 'outcome': outcome,
- 'score': self._score,
- 'playerinfos': self.initialplayerinfos
- })
+ self.end(
+ delay=2.0,
+ results={
+ 'outcome': outcome,
+ 'score': self._score,
+ 'playerinfos': self.initialplayerinfos,
+ },
+ )
def _update_bots(self) -> None:
assert self._bot_update_interval is not None
self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98)
- self._bot_update_timer = ba.Timer(self._bot_update_interval,
- ba.WeakCall(self._update_bots))
- botspawnpts: list[Sequence[float]] = [[-5.0, 5.5, -4.14],
- [0.0, 5.5, -4.14],
- [5.0, 5.5, -4.14]]
+ self._bot_update_timer = ba.Timer(
+ self._bot_update_interval, ba.WeakCall(self._update_bots)
+ )
+ botspawnpts: list[Sequence[float]] = [
+ [-5.0, 5.5, -4.14],
+ [0.0, 5.5, -4.14],
+ [5.0, 5.5, -4.14],
+ ]
dists = [0.0, 0.0, 0.0]
playerpts: list[Sequence[float]] = []
for player in self.players:
@@ -211,8 +245,11 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
else:
spawnpt = botspawnpts[2]
- spawnpt = (spawnpt[0] + 3.0 * (random.random() - 0.5), spawnpt[1],
- 2.0 * (random.random() - 0.5) + spawnpt[2])
+ spawnpt = (
+ spawnpt[0] + 3.0 * (random.random() - 0.5),
+ spawnpt[1],
+ 2.0 * (random.random() - 0.5) + spawnpt[2],
+ )
# Normalize our bot type total and find a random number within that.
total = 0.0
@@ -268,15 +305,18 @@ class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
if msg.killerplayer:
assert msg.spazbot.node
target = msg.spazbot.node.position
- self.stats.player_scored(msg.killerplayer,
- pts,
- target=target,
- kill=True,
- screenmessage=False,
- importance=importance)
- ba.playsound(self._dingsound
- if importance == 1 else self._dingsoundhigh,
- volume=0.6)
+ self.stats.player_scored(
+ msg.killerplayer,
+ pts,
+ target=target,
+ kill=True,
+ screenmessage=False,
+ importance=importance,
+ )
+ ba.playsound(
+ self._dingsound if importance == 1 else self._dingsoundhigh,
+ volume=0.6,
+ )
# Normally we pull scores from the score-set, but if there's no
# player lets be explicit.
diff --git a/assets/src/ba_data/python/bastd/gameutils.py b/assets/src/ba_data/python/bastd/gameutils.py
index 1d297d79..7dbef7c5 100644
--- a/assets/src/ba_data/python/bastd/gameutils.py
+++ b/assets/src/ba_data/python/bastd/gameutils.py
@@ -27,8 +27,10 @@ class SharedObjects:
def __init__(self) -> None:
activity = ba.getactivity()
if self._STORENAME in activity.customdata:
- raise RuntimeError('Use SharedObjects.get() to fetch the'
- ' shared instance for this activity.')
+ raise RuntimeError(
+ 'Use SharedObjects.get() to fetch the'
+ ' shared instance for this activity.'
+ )
self._object_material: ba.Material | None = None
self._player_material: ba.Material | None = None
self._pickup_material: ba.Material | None = None
@@ -111,7 +113,8 @@ class SharedObjects:
if self._death_material is None:
mat = self._death_material = ba.Material()
mat.add_actions(
- ('message', 'their_node', 'at_connect', ba.DieMessage()))
+ ('message', 'their_node', 'at_connect', ba.DieMessage())
+ )
return self._death_material
@property
diff --git a/assets/src/ba_data/python/bastd/keyboard/englishkeyboard.py b/assets/src/ba_data/python/bastd/keyboard/englishkeyboard.py
index 47162c52..fdf37a0c 100644
--- a/assets/src/ba_data/python/bastd/keyboard/englishkeyboard.py
+++ b/assets/src/ba_data/python/bastd/keyboard/englishkeyboard.py
@@ -43,12 +43,41 @@ def generate_emojis(maxlen: int) -> list[list[str]]:
# ba_meta export keyboard
class EnglishKeyboard(ba.Keyboard):
"""Default English keyboard."""
+
name = 'English'
- chars = [('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'),
- ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'),
- ('z', 'x', 'c', 'v', 'b', 'n', 'm')]
- nums = ('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '/', ':',
- ';', '(', ')', '$', '&', '@', '"', '.', ',', '?', '!', '\'', '_')
+ chars = [
+ ('q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p'),
+ ('a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l'),
+ ('z', 'x', 'c', 'v', 'b', 'n', 'm'),
+ ]
+ nums = (
+ '1',
+ '2',
+ '3',
+ '4',
+ '5',
+ '6',
+ '7',
+ '8',
+ '9',
+ '0',
+ '-',
+ '/',
+ ':',
+ ';',
+ '(',
+ ')',
+ '$',
+ '&',
+ '@',
+ '"',
+ '.',
+ ',',
+ '?',
+ '!',
+ '\'',
+ '_',
+ )
pages: dict[str, tuple[str, ...]] = {
f'emoji{i}': tuple(page)
for i, page in enumerate(generate_emojis(len(nums)))
diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py
index ed695c0b..2c8ce4c8 100644
--- a/assets/src/ba_data/python/bastd/mainmenu.py
+++ b/assets/src/ba_data/python/bastd/mainmenu.py
@@ -1,6 +1,7 @@
# Released under the MIT License. See LICENSE for details.
#
"""Session and Activity for displaying the main menu bg."""
+# pylint: disable=too-many-lines
from __future__ import annotations
@@ -44,42 +45,51 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
vr_mode = ba.app.vr_mode
if not ba.app.toolbar_test:
- color = ((1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6))
+ color = (1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)
# FIXME: Need a node attr for vr-specific-scale.
- scale = (0.9 if
- (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7)
+ scale = (
+ 0.9 if (app.ui.uiscale is ba.UIScale.SMALL or vr_mode) else 0.7
+ )
self.my_name = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'bottom',
- 'h_align': 'center',
- 'color': color,
- 'flatness': 1.0,
- 'shadow': 1.0 if vr_mode else 0.5,
- 'scale': scale,
- 'position': (0, 10),
- 'vr_depth': -10,
- 'text': '\xa9 2011-2022 Eric Froemling'
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'bottom',
+ 'h_align': 'center',
+ 'color': color,
+ 'flatness': 1.0,
+ 'shadow': 1.0 if vr_mode else 0.5,
+ 'scale': scale,
+ 'position': (0, 10),
+ 'vr_depth': -10,
+ 'text': '\xa9 2011-2022 Eric Froemling',
+ },
+ )
+ )
# Throw up some text that only clients can see so they know that the
# host is navigating menus while they're just staring at an
# empty-ish screen.
- tval = ba.Lstr(resource='hostIsNavigatingMenusText',
- subs=[('${HOST}',
- ba.internal.get_v1_account_display_string())])
+ tval = ba.Lstr(
+ resource='hostIsNavigatingMenusText',
+ subs=[('${HOST}', ba.internal.get_v1_account_display_string())],
+ )
self._host_is_navigating_text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'text': tval,
- 'client_only': True,
- 'position': (0, -200),
- 'flatness': 1.0,
- 'h_align': 'center'
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'text': tval,
+ 'client_only': True,
+ 'position': (0, -200),
+ 'flatness': 1.0,
+ 'h_align': 'center',
+ },
+ )
+ )
if not ba.app.main_menu_did_initial_transition and hasattr(
- self, 'my_name'):
+ self, 'my_name'
+ ):
assert self.my_name.node
ba.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0})
@@ -97,18 +107,22 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
if not ba.app.toolbar_test:
if app.debug_build or app.test_build or force_show_build_number:
if app.debug_build:
- text = ba.Lstr(value='${V} (${B}) (${D})',
- subs=[
- ('${V}', app.version),
- ('${B}', str(app.build_number)),
- ('${D}', ba.Lstr(resource='debugText')),
- ])
+ text = ba.Lstr(
+ value='${V} (${B}) (${D})',
+ subs=[
+ ('${V}', app.version),
+ ('${B}', str(app.build_number)),
+ ('${D}', ba.Lstr(resource='debugText')),
+ ],
+ )
else:
- text = ba.Lstr(value='${V} (${B})',
- subs=[
- ('${V}', app.version),
- ('${B}', str(app.build_number)),
- ])
+ text = ba.Lstr(
+ value='${V} (${B})',
+ subs=[
+ ('${V}', app.version),
+ ('${B}', str(app.build_number)),
+ ],
+ )
else:
text = ba.Lstr(value='${V}', subs=[('${V}', app.version)])
scale = 0.9 if (uiscale is ba.UIScale.SMALL or vr_mode) else 0.7
@@ -126,8 +140,10 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
'color': color,
'scale': scale,
'position': (-260, 10) if vr_mode else (-10, 10),
- 'text': text
- }))
+ 'text': text,
+ },
+ )
+ )
if not ba.app.main_menu_did_initial_transition:
assert self.version.node
ba.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0})
@@ -135,39 +151,45 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# Show the iircade logo on our iircade build.
if app.iircade_mode:
img = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': ba.gettexture('iircadeLogo'),
- 'attach': 'center',
- 'scale': (250, 250),
- 'position': (0, 0),
- 'tilt_translate': 0.21,
- 'absolute_scale': True
- })).autoretain()
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': ba.gettexture('iircadeLogo'),
+ 'attach': 'center',
+ 'scale': (250, 250),
+ 'position': (0, 0),
+ 'tilt_translate': 0.21,
+ 'absolute_scale': True,
+ },
+ )
+ ).autoretain()
imgdelay = 0.0 if app.main_menu_did_initial_transition else 1.0
- ba.animate(img.node, 'opacity', {
- imgdelay + 1.5: 0.0,
- imgdelay + 2.5: 1.0
- })
+ ba.animate(
+ img.node, 'opacity', {imgdelay + 1.5: 0.0, imgdelay + 2.5: 1.0}
+ )
# Throw in test build info.
self.beta_info = self.beta_info_2 = None
if app.test_build and not (app.demo_mode or app.arcade_mode):
- pos = ((230, 125) if (app.demo_mode or app.arcade_mode) else
- (230, 35))
+ pos = (
+ (230, 125) if (app.demo_mode or app.arcade_mode) else (230, 35)
+ )
self.beta_info = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'center',
- 'h_align': 'center',
- 'color': (1, 1, 1, 1),
- 'shadow': 0.5,
- 'flatness': 0.5,
- 'scale': 1,
- 'vr_depth': -60,
- 'position': pos,
- 'text': ba.Lstr(resource='testBuildText')
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'center',
+ 'h_align': 'center',
+ 'color': (1, 1, 1, 1),
+ 'shadow': 0.5,
+ 'flatness': 0.5,
+ 'scale': 1,
+ 'vr_depth': -60,
+ 'position': pos,
+ 'text': ba.Lstr(resource='testBuildText'),
+ },
+ )
+ )
if not ba.app.main_menu_did_initial_transition:
assert self.beta_info.node
ba.animate(self.beta_info.node, 'opacity', {1.3: 0, 1.8: 1.0})
@@ -194,56 +216,74 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
gnode.vignette_inner = (0.99, 0.98, 0.98)
self.bottom = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': bottom_model,
- 'lighting': False,
- 'reflection': 'soft',
- 'reflection_scale': [0.45],
- 'color_texture': color_texture
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': bottom_model,
+ 'lighting': False,
+ 'reflection': 'soft',
+ 'reflection_scale': [0.45],
+ 'color_texture': color_texture,
+ },
+ )
+ )
self.vr_bottom_fill = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': vr_bottom_fill_model,
- 'lighting': False,
- 'vr_only': True,
- 'color_texture': color_texture
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': vr_bottom_fill_model,
+ 'lighting': False,
+ 'vr_only': True,
+ 'color_texture': color_texture,
+ },
+ )
+ )
self.vr_top_fill = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': vr_top_fill_model,
- 'vr_only': True,
- 'lighting': False,
- 'color_texture': bgtex
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': vr_top_fill_model,
+ 'vr_only': True,
+ 'lighting': False,
+ 'color_texture': bgtex,
+ },
+ )
+ )
self.terrain = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': model,
- 'color_texture': color_texture,
- 'reflection': 'soft',
- 'reflection_scale': [0.3]
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': model,
+ 'color_texture': color_texture,
+ 'reflection': 'soft',
+ 'reflection_scale': [0.3],
+ },
+ )
+ )
self.trees = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': trees_model,
- 'lighting': False,
- 'reflection': 'char',
- 'reflection_scale': [0.1],
- 'color_texture': trees_texture
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': trees_model,
+ 'lighting': False,
+ 'reflection': 'char',
+ 'reflection_scale': [0.1],
+ 'color_texture': trees_texture,
+ },
+ )
+ )
self.bgterrain = ba.NodeActor(
- ba.newnode('terrain',
- attrs={
- 'model': bgmodel,
- 'color': (0.92, 0.91, 0.9),
- 'lighting': False,
- 'background': True,
- 'color_texture': bgtex
- }))
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': bgmodel,
+ 'color': (0.92, 0.91, 0.9),
+ 'lighting': False,
+ 'background': True,
+ 'color_texture': bgtex,
+ },
+ )
+ )
self._ts = 0.86
@@ -270,7 +310,8 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# If we're signed in, fetch news immediately.
# Otherwise wait until we are signed in.
self._fetch_timer: ba.Timer | None = ba.Timer(
- 1.0, ba.WeakCall(self._try_fetching_news), repeat=True)
+ 1.0, ba.WeakCall(self._try_fetching_news), repeat=True
+ )
self._try_fetching_news()
# We now want to wait until we're signed in before fetching news.
@@ -304,51 +345,60 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
val = self._phrases.pop()
if val == '__ACH__':
vrmode = app.vr_mode
- Text(ba.Lstr(resource='nextAchievementsText'),
- color=((1, 1, 1, 1) if vrmode else
- (0.95, 0.9, 1, 0.4)),
- host_only=True,
- maxwidth=200,
- position=(-300, -35),
- h_align=Text.HAlign.RIGHT,
- transition=Text.Transition.FADE_IN,
- scale=0.9 if vrmode else 0.7,
- flatness=1.0 if vrmode else 0.6,
- shadow=1.0 if vrmode else 0.5,
- h_attach=Text.HAttach.CENTER,
- v_attach=Text.VAttach.TOP,
- transition_delay=1.0,
- transition_out_delay=self._message_duration
- ).autoretain()
+ Text(
+ ba.Lstr(resource='nextAchievementsText'),
+ color=(
+ (1, 1, 1, 1)
+ if vrmode
+ else (0.95, 0.9, 1, 0.4)
+ ),
+ host_only=True,
+ maxwidth=200,
+ position=(-300, -35),
+ h_align=Text.HAlign.RIGHT,
+ transition=Text.Transition.FADE_IN,
+ scale=0.9 if vrmode else 0.7,
+ flatness=1.0 if vrmode else 0.6,
+ shadow=1.0 if vrmode else 0.5,
+ h_attach=Text.HAttach.CENTER,
+ v_attach=Text.VAttach.TOP,
+ transition_delay=1.0,
+ transition_out_delay=self._message_duration,
+ ).autoretain()
achs = [
- a for a in app.ach.achievements
+ a
+ for a in app.ach.achievements
if not a.complete
]
if achs:
ach = achs.pop(
- random.randrange(min(4, len(achs))))
+ random.randrange(min(4, len(achs)))
+ )
ach.create_display(
-180,
-35,
1.0,
outdelay=self._message_duration,
- style='news')
+ style='news',
+ )
if achs:
ach = achs.pop(
- random.randrange(min(8, len(achs))))
+ random.randrange(min(8, len(achs)))
+ )
ach.create_display(
180,
-35,
1.25,
outdelay=self._message_duration,
- style='news')
+ style='news',
+ )
else:
spc = self._message_spacing
keys = {
spc: 0.0,
spc + 1.0: 1.0,
spc + self._message_duration - 1.0: 1.0,
- spc + self._message_duration: 0.0
+ spc + self._message_duration: 0.0,
}
assert self._text.node
ba.animate(self._text.node, 'opacity', keys)
@@ -370,34 +420,47 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# Show upcoming achievements in non-vr versions
# (currently too hard to read in vr).
self._used_phrases = (
- ['__ACH__'] if not ba.app.vr_mode else
- []) + [s for s in news.split('
\n') if s != '']
+ ['__ACH__'] if not ba.app.vr_mode else []
+ ) + [s for s in news.split('
\n') if s != '']
self._phrase_change_timer = ba.Timer(
(self._message_duration + self._message_spacing),
ba.WeakCall(self._change_phrase),
- repeat=True)
+ repeat=True,
+ )
- scl = 1.2 if (ba.app.ui.uiscale is ba.UIScale.SMALL
- or ba.app.vr_mode) else 0.8
+ scl = (
+ 1.2
+ if (
+ ba.app.ui.uiscale is ba.UIScale.SMALL
+ or ba.app.vr_mode
+ )
+ else 0.8
+ )
- color2 = ((1, 1, 1, 1) if ba.app.vr_mode else
- (0.7, 0.65, 0.75, 1.0))
- shadow = (1.0 if ba.app.vr_mode else 0.4)
+ color2 = (
+ (1, 1, 1, 1)
+ if ba.app.vr_mode
+ else (0.7, 0.65, 0.75, 1.0)
+ )
+ shadow = 1.0 if ba.app.vr_mode else 0.4
self._text = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'v_attach': 'top',
- 'h_attach': 'center',
- 'h_align': 'center',
- 'vr_depth': -20,
- 'shadow': shadow,
- 'flatness': 0.8,
- 'v_align': 'top',
- 'color': color2,
- 'scale': scl,
- 'maxwidth': 900.0 / scl,
- 'position': (0, -10)
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'v_attach': 'top',
+ 'h_attach': 'center',
+ 'h_align': 'center',
+ 'vr_depth': -20,
+ 'shadow': shadow,
+ 'flatness': 0.8,
+ 'v_align': 'top',
+ 'color': color2,
+ 'scale': scl,
+ 'maxwidth': 900.0 / scl,
+ 'position': (0, -10),
+ },
+ )
+ )
self._change_phrase()
if not (app.demo_mode or app.arcade_mode) and not app.toolbar_test:
@@ -406,6 +469,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
# Bring up the last place we were, or start at the main menu otherwise.
with ba.Context('ui'):
from bastd.ui import specialoffer
+
if bool(False):
uicontroller = ba.app.ui.controller
assert uicontroller is not None
@@ -418,52 +482,70 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
if ba.app.demo_mode or ba.app.arcade_mode:
# pylint: disable=cyclic-import
from bastd.ui.kiosk import KioskWindow
+
ba.app.ui.set_main_menu_window(
- KioskWindow().get_root_widget())
+ KioskWindow().get_root_widget()
+ )
# ..or in normal cases go back to the main menu
else:
if main_menu_location == 'Gather':
# pylint: disable=cyclic-import
from bastd.ui.gather import GatherWindow
+
ba.app.ui.set_main_menu_window(
- GatherWindow(transition=None).get_root_widget())
+ GatherWindow(transition=None).get_root_widget()
+ )
elif main_menu_location == 'Watch':
# pylint: disable=cyclic-import
from bastd.ui.watch import WatchWindow
+
ba.app.ui.set_main_menu_window(
- WatchWindow(transition=None).get_root_widget())
+ WatchWindow(transition=None).get_root_widget()
+ )
elif main_menu_location == 'Team Game Select':
# pylint: disable=cyclic-import
from bastd.ui.playlist.browser import (
- PlaylistBrowserWindow)
+ PlaylistBrowserWindow,
+ )
+
ba.app.ui.set_main_menu_window(
PlaylistBrowserWindow(
- sessiontype=ba.DualTeamSession,
- transition=None).get_root_widget())
+ sessiontype=ba.DualTeamSession, transition=None
+ ).get_root_widget()
+ )
elif main_menu_location == 'Free-for-All Game Select':
# pylint: disable=cyclic-import
from bastd.ui.playlist.browser import (
- PlaylistBrowserWindow)
+ PlaylistBrowserWindow,
+ )
+
ba.app.ui.set_main_menu_window(
PlaylistBrowserWindow(
sessiontype=ba.FreeForAllSession,
- transition=None).get_root_widget())
+ transition=None,
+ ).get_root_widget()
+ )
elif main_menu_location == 'Coop Select':
# pylint: disable=cyclic-import
from bastd.ui.coop.browser import CoopBrowserWindow
+
ba.app.ui.set_main_menu_window(
- CoopBrowserWindow(
- transition=None).get_root_widget())
+ CoopBrowserWindow(transition=None).get_root_widget()
+ )
elif main_menu_location == 'Benchmarks & Stress Tests':
# pylint: disable=cyclic-import
from bastd.ui.debug import DebugWindow
+
ba.app.ui.set_main_menu_window(
- DebugWindow(transition=None).get_root_widget())
+ DebugWindow(transition=None).get_root_widget()
+ )
else:
# pylint: disable=cyclic-import
from bastd.ui.mainmenu import MainMenuWindow
+
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition=None).get_root_widget())
+ MainMenuWindow(transition=None).get_root_widget()
+ )
# attempt to show any pending offers immediately.
# If that doesn't work, try again in a few seconds
@@ -475,9 +557,11 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
def try_again() -> None:
if not specialoffer.show_offer():
# Try one last time..
- ba.timer(2.0,
- specialoffer.show_offer,
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 2.0,
+ specialoffer.show_offer,
+ timetype=ba.TimeType.REAL,
+ )
ba.timer(2.0, try_again, timetype=ba.TimeType.REAL)
ba.app.main_menu_did_initial_transition = True
@@ -491,13 +575,16 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
if custom_texture != self._custom_logo_tex_name:
self._custom_logo_tex_name = custom_texture
self._logo_node.texture = ba.gettexture(
- custom_texture if custom_texture is not None else 'logo')
- self._logo_node.model_opaque = (None
- if custom_texture is not None
- else ba.getmodel('logo'))
+ custom_texture if custom_texture is not None else 'logo'
+ )
+ self._logo_node.model_opaque = (
+ None if custom_texture is not None else ba.getmodel('logo')
+ )
self._logo_node.model_transparent = (
- None if custom_texture is not None else
- ba.getmodel('logoTransparent'))
+ None
+ if custom_texture is not None
+ else ba.getmodel('logoTransparent')
+ )
# If language has changed, recreate our logo text/graphics.
lang = app.lang.language
@@ -524,54 +611,66 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
x = base_x - 20.0
spacing = 85.0 * base_scale
y_extra = 0.0 if (app.demo_mode or app.arcade_mode) else 0.0
- self._make_logo(x - 110 + 50,
- 113 + y + 1.2 * y_extra,
- 0.34 * base_scale,
- delay=base_delay + 0.1,
- custom_texture='chTitleChar1',
- jitter_scale=2.0,
- vr_depth_offset=-30)
+ self._make_logo(
+ x - 110 + 50,
+ 113 + y + 1.2 * y_extra,
+ 0.34 * base_scale,
+ delay=base_delay + 0.1,
+ custom_texture='chTitleChar1',
+ jitter_scale=2.0,
+ vr_depth_offset=-30,
+ )
x += spacing
delay += delay_inc
- self._make_logo(x - 10 + 50,
- 110 + y + 1.2 * y_extra,
- 0.31 * base_scale,
- delay=base_delay + 0.15,
- custom_texture='chTitleChar2',
- jitter_scale=2.0,
- vr_depth_offset=-30)
+ self._make_logo(
+ x - 10 + 50,
+ 110 + y + 1.2 * y_extra,
+ 0.31 * base_scale,
+ delay=base_delay + 0.15,
+ custom_texture='chTitleChar2',
+ jitter_scale=2.0,
+ vr_depth_offset=-30,
+ )
x += 2.0 * spacing
delay += delay_inc
- self._make_logo(x + 180 - 140,
- 110 + y + 1.2 * y_extra,
- 0.3 * base_scale,
- delay=base_delay + 0.25,
- custom_texture='chTitleChar3',
- jitter_scale=2.0,
- vr_depth_offset=-30)
+ self._make_logo(
+ x + 180 - 140,
+ 110 + y + 1.2 * y_extra,
+ 0.3 * base_scale,
+ delay=base_delay + 0.25,
+ custom_texture='chTitleChar3',
+ jitter_scale=2.0,
+ vr_depth_offset=-30,
+ )
x += spacing
delay += delay_inc
- self._make_logo(x + 241 - 120,
- 110 + y + 1.2 * y_extra,
- 0.31 * base_scale,
- delay=base_delay + 0.3,
- custom_texture='chTitleChar4',
- jitter_scale=2.0,
- vr_depth_offset=-30)
+ self._make_logo(
+ x + 241 - 120,
+ 110 + y + 1.2 * y_extra,
+ 0.31 * base_scale,
+ delay=base_delay + 0.3,
+ custom_texture='chTitleChar4',
+ jitter_scale=2.0,
+ vr_depth_offset=-30,
+ )
x += spacing
delay += delay_inc
- self._make_logo(x + 300 - 90,
- 105 + y + 1.2 * y_extra,
- 0.34 * base_scale,
- delay=base_delay + 0.35,
- custom_texture='chTitleChar5',
- jitter_scale=2.0,
- vr_depth_offset=-30)
- self._make_logo(base_x + 155,
- 146 + y + 1.2 * y_extra,
- 0.28 * base_scale,
- delay=base_delay + 0.2,
- rotate=-7)
+ self._make_logo(
+ x + 300 - 90,
+ 105 + y + 1.2 * y_extra,
+ 0.34 * base_scale,
+ delay=base_delay + 0.35,
+ custom_texture='chTitleChar5',
+ jitter_scale=2.0,
+ vr_depth_offset=-30,
+ )
+ self._make_logo(
+ base_x + 155,
+ 146 + y + 1.2 * y_extra,
+ 0.28 * base_scale,
+ delay=base_delay + 0.2,
+ rotate=-7,
+ )
else:
base_x = -170
x = base_x - 20
@@ -582,118 +681,144 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
for shadow in (True, False):
x = xv1
delay = delay1
- self._make_word('B',
- x - 50,
- y - 23 + 0.8 * y_extra,
- scale=1.3 * base_scale,
- delay=delay,
- vr_depth_offset=3,
- shadow=shadow)
+ self._make_word(
+ 'B',
+ x - 50,
+ y - 23 + 0.8 * y_extra,
+ scale=1.3 * base_scale,
+ delay=delay,
+ vr_depth_offset=3,
+ shadow=shadow,
+ )
x += spacing
delay += delay_inc
- self._make_word('m',
- x,
- y + y_extra,
- delay=delay,
- scale=base_scale,
- shadow=shadow)
+ self._make_word(
+ 'm',
+ x,
+ y + y_extra,
+ delay=delay,
+ scale=base_scale,
+ shadow=shadow,
+ )
x += spacing * 1.25
delay += delay_inc
- self._make_word('b',
- x,
- y + y_extra - 10,
- delay=delay,
- scale=1.1 * base_scale,
- vr_depth_offset=5,
- shadow=shadow)
+ self._make_word(
+ 'b',
+ x,
+ y + y_extra - 10,
+ delay=delay,
+ scale=1.1 * base_scale,
+ vr_depth_offset=5,
+ shadow=shadow,
+ )
x += spacing * 0.85
delay += delay_inc
- self._make_word('S',
- x,
- y - 25 + 0.8 * y_extra,
- scale=1.35 * base_scale,
- delay=delay,
- vr_depth_offset=14,
- shadow=shadow)
+ self._make_word(
+ 'S',
+ x,
+ y - 25 + 0.8 * y_extra,
+ scale=1.35 * base_scale,
+ delay=delay,
+ vr_depth_offset=14,
+ shadow=shadow,
+ )
x += spacing
delay += delay_inc
- self._make_word('q',
- x,
- y + y_extra,
- delay=delay,
- scale=base_scale,
- shadow=shadow)
+ self._make_word(
+ 'q',
+ x,
+ y + y_extra,
+ delay=delay,
+ scale=base_scale,
+ shadow=shadow,
+ )
x += spacing * 0.9
delay += delay_inc
- self._make_word('u',
- x,
- y + y_extra,
- delay=delay,
- scale=base_scale,
- vr_depth_offset=7,
- shadow=shadow)
+ self._make_word(
+ 'u',
+ x,
+ y + y_extra,
+ delay=delay,
+ scale=base_scale,
+ vr_depth_offset=7,
+ shadow=shadow,
+ )
x += spacing * 0.9
delay += delay_inc
- self._make_word('a',
- x,
- y + y_extra,
- delay=delay,
- scale=base_scale,
- shadow=shadow)
+ self._make_word(
+ 'a',
+ x,
+ y + y_extra,
+ delay=delay,
+ scale=base_scale,
+ shadow=shadow,
+ )
x += spacing * 0.64
delay += delay_inc
- self._make_word('d',
- x,
- y + y_extra - 10,
- delay=delay,
- scale=1.1 * base_scale,
- vr_depth_offset=6,
- shadow=shadow)
- self._make_logo(base_x - 28,
- 125 + y + 1.2 * y_extra,
- 0.32 * base_scale,
- delay=base_delay)
+ self._make_word(
+ 'd',
+ x,
+ y + y_extra - 10,
+ delay=delay,
+ scale=1.1 * base_scale,
+ vr_depth_offset=6,
+ shadow=shadow,
+ )
+ self._make_logo(
+ base_x - 28,
+ 125 + y + 1.2 * y_extra,
+ 0.32 * base_scale,
+ delay=base_delay,
+ )
- def _make_word(self,
- word: str,
- x: float,
- y: float,
- scale: float = 1.0,
- delay: float = 0.0,
- vr_depth_offset: float = 0.0,
- shadow: bool = False) -> None:
+ def _make_word(
+ self,
+ word: str,
+ x: float,
+ y: float,
+ scale: float = 1.0,
+ delay: float = 0.0,
+ vr_depth_offset: float = 0.0,
+ shadow: bool = False,
+ ) -> None:
if shadow:
word_obj = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'position': (x, y),
- 'big': True,
- 'color': (0.0, 0.0, 0.2, 0.08),
- 'tilt_translate': 0.09,
- 'opacity_scales_shadow': False,
- 'shadow': 0.2,
- 'vr_depth': -130,
- 'v_align': 'center',
- 'project_scale': 0.97 * scale,
- 'scale': 1.0,
- 'text': word
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'position': (x, y),
+ 'big': True,
+ 'color': (0.0, 0.0, 0.2, 0.08),
+ 'tilt_translate': 0.09,
+ 'opacity_scales_shadow': False,
+ 'shadow': 0.2,
+ 'vr_depth': -130,
+ 'v_align': 'center',
+ 'project_scale': 0.97 * scale,
+ 'scale': 1.0,
+ 'text': word,
+ },
+ )
+ )
self._word_actors.append(word_obj)
else:
word_obj = ba.NodeActor(
- ba.newnode('text',
- attrs={
- 'position': (x, y),
- 'big': True,
- 'color': (1.2, 1.15, 1.15, 1.0),
- 'tilt_translate': 0.11,
- 'shadow': 0.2,
- 'vr_depth': -40 + vr_depth_offset,
- 'v_align': 'center',
- 'project_scale': scale,
- 'scale': 1.0,
- 'text': word
- }))
+ ba.newnode(
+ 'text',
+ attrs={
+ 'position': (x, y),
+ 'big': True,
+ 'color': (1.2, 1.15, 1.15, 1.0),
+ 'tilt_translate': 0.11,
+ 'shadow': 0.2,
+ 'vr_depth': -40 + vr_depth_offset,
+ 'v_align': 'center',
+ 'project_scale': scale,
+ 'scale': 1.0,
+ 'text': word,
+ },
+ )
+ )
self._word_actors.append(word_obj)
# Add a bit of stop-motion-y jitter to the logo
@@ -703,15 +828,15 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
cmb: ba.Node | None
cmb2: ba.Node | None
if not shadow:
- cmb = ba.newnode('combine',
- owner=word_obj.node,
- attrs={'size': 2})
+ cmb = ba.newnode(
+ 'combine', owner=word_obj.node, attrs={'size': 2}
+ )
else:
cmb = None
if shadow:
- cmb2 = ba.newnode('combine',
- owner=word_obj.node,
- attrs={'size': 2})
+ cmb2 = ba.newnode(
+ 'combine', owner=word_obj.node, attrs={'size': 2}
+ )
else:
cmb2 = None
if not shadow:
@@ -749,18 +874,18 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
if not shadow:
assert word_obj.node
- ba.animate(word_obj.node, 'project_scale', {
- delay: 0.0,
- delay + 0.1: scale * 1.1,
- delay + 0.2: scale
- })
+ ba.animate(
+ word_obj.node,
+ 'project_scale',
+ {delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale},
+ )
else:
assert word_obj.node
- ba.animate(word_obj.node, 'project_scale', {
- delay: 0.0,
- delay + 0.1: scale * 1.1,
- delay + 0.2: scale
- })
+ ba.animate(
+ word_obj.node,
+ 'project_scale',
+ {delay: 0.0, delay + 0.1: scale * 1.1, delay + 0.2: scale},
+ )
def _get_custom_logo_tex_name(self) -> str | None:
if ba.internal.get_v1_account_misc_read_val('easter', False):
@@ -768,37 +893,46 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
return None
# Pop the logo and menu in.
- def _make_logo(self,
- x: float,
- y: float,
- scale: float,
- delay: float,
- custom_texture: str | None = None,
- jitter_scale: float = 1.0,
- rotate: float = 0.0,
- vr_depth_offset: float = 0.0) -> None:
+ def _make_logo(
+ self,
+ x: float,
+ y: float,
+ scale: float,
+ delay: float,
+ custom_texture: str | None = None,
+ jitter_scale: float = 1.0,
+ rotate: float = 0.0,
+ vr_depth_offset: float = 0.0,
+ ) -> None:
# Temp easter goodness.
if custom_texture is None:
custom_texture = self._get_custom_logo_tex_name()
self._custom_logo_tex_name = custom_texture
ltex = ba.gettexture(
- custom_texture if custom_texture is not None else 'logo')
- mopaque = (None if custom_texture is not None else ba.getmodel('logo'))
- mtrans = (None if custom_texture is not None else
- ba.getmodel('logoTransparent'))
+ custom_texture if custom_texture is not None else 'logo'
+ )
+ mopaque = None if custom_texture is not None else ba.getmodel('logo')
+ mtrans = (
+ None
+ if custom_texture is not None
+ else ba.getmodel('logoTransparent')
+ )
logo = ba.NodeActor(
- ba.newnode('image',
- attrs={
- 'texture': ltex,
- 'model_opaque': mopaque,
- 'model_transparent': mtrans,
- 'vr_depth': -10 + vr_depth_offset,
- 'rotate': rotate,
- 'attach': 'center',
- 'tilt_translate': 0.21,
- 'absolute_scale': True
- }))
+ ba.newnode(
+ 'image',
+ attrs={
+ 'texture': ltex,
+ 'model_opaque': mopaque,
+ 'model_transparent': mtrans,
+ 'vr_depth': -10 + vr_depth_offset,
+ 'rotate': rotate,
+ 'attach': 'center',
+ 'tilt_translate': 0.21,
+ 'absolute_scale': True,
+ },
+ )
+ )
self._logo_node = logo.node
self._word_actors.append(logo)
@@ -820,8 +954,9 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
keys = {}
time_v = 0.0
for _i in range(10):
- keys[time_v * self._ts] = y + (random.random() -
- 0.5) * 0.7 * jitter_scale
+ keys[time_v * self._ts] = (
+ y + (random.random() - 0.5) * 0.7 * jitter_scale
+ )
time_v += random.random() * 0.1
ba.animate(cmb, 'input1', keys, loop=True)
else:
@@ -832,7 +967,7 @@ class MainMenuActivity(ba.Activity[ba.Player, ba.Team]):
keys = {
delay: 0.0,
delay + 0.1: 700.0 * scale,
- delay + 0.2: 600.0 * scale
+ delay + 0.2: 600.0 * scale,
}
ba.animate(cmb, 'input0', keys)
ba.animate(cmb, 'input1', keys)
@@ -855,21 +990,31 @@ def _preload1() -> None:
Helps avoid hitches later on.
"""
for mname in [
- 'plasticEyesTransparent', 'playerLineup1Transparent',
- 'playerLineup2Transparent', 'playerLineup3Transparent',
- 'playerLineup4Transparent', 'angryComputerTransparent',
- 'scrollWidgetShort', 'windowBGBlotch'
+ 'plasticEyesTransparent',
+ 'playerLineup1Transparent',
+ 'playerLineup2Transparent',
+ 'playerLineup3Transparent',
+ 'playerLineup4Transparent',
+ 'angryComputerTransparent',
+ 'scrollWidgetShort',
+ 'windowBGBlotch',
]:
ba.getmodel(mname)
for tname in ['playerLineup', 'lock']:
ba.gettexture(tname)
for tex in [
- 'iconRunaround', 'iconOnslaught', 'medalComplete', 'medalBronze',
- 'medalSilver', 'medalGold', 'characterIconMask'
+ 'iconRunaround',
+ 'iconOnslaught',
+ 'medalComplete',
+ 'medalBronze',
+ 'medalSilver',
+ 'medalGold',
+ 'characterIconMask',
]:
ba.gettexture(tex)
ba.gettexture('bg')
from bastd.actor.powerupbox import PowerupBoxFactory
+
PowerupBoxFactory.get()
ba.timer(0.1, _preload2)
@@ -881,28 +1026,44 @@ def _preload2() -> None:
for mname in ['powerup', 'powerupSimple']:
ba.getmodel(mname)
for tname in [
- 'powerupBomb', 'powerupSpeed', 'powerupPunch', 'powerupIceBombs',
- 'powerupStickyBombs', 'powerupShield', 'powerupImpactBombs',
- 'powerupHealth'
+ 'powerupBomb',
+ 'powerupSpeed',
+ 'powerupPunch',
+ 'powerupIceBombs',
+ 'powerupStickyBombs',
+ 'powerupShield',
+ 'powerupImpactBombs',
+ 'powerupHealth',
]:
ba.gettexture(tname)
for sname in [
- 'powerup01', 'boxDrop', 'boxingBell', 'scoreHit01', 'scoreHit02',
- 'dripity', 'spawn', 'gong'
+ 'powerup01',
+ 'boxDrop',
+ 'boxingBell',
+ 'scoreHit01',
+ 'scoreHit02',
+ 'dripity',
+ 'spawn',
+ 'gong',
]:
ba.getsound(sname)
from bastd.actor.bomb import BombFactory
+
BombFactory.get()
ba.timer(0.1, _preload3)
def _preload3() -> None:
from bastd.actor.spazfactory import SpazFactory
+
for mname in ['bomb', 'bombSticky', 'impactBomb']:
ba.getmodel(mname)
for tname in [
- 'bombColor', 'bombColorIce', 'bombStickyColor', 'impactBombColor',
- 'impactBombColorLit'
+ 'bombColor',
+ 'bombColorIce',
+ 'bombStickyColor',
+ 'impactBombColor',
+ 'impactBombColorLit',
]:
ba.gettexture(tname)
for sname in ['freeze', 'fuse01', 'activateBeep', 'warnBeep']:
@@ -919,6 +1080,7 @@ def _preload4() -> None:
for sname in ['metalHit', 'metalSkid', 'refWhistle', 'achievement']:
ba.getsound(sname)
from bastd.actor.flag import FlagFactory
+
FlagFactory.get()
diff --git a/assets/src/ba_data/python/bastd/mapdata/big_g.py b/assets/src/ba_data/python/bastd/mapdata/big_g.py
index 207e93e0..8bc82721 100644
--- a/assets/src/ba_data/python/bastd/mapdata/big_g.py
+++ b/assets/src/ba_data/python/bastd/mapdata/big_g.py
@@ -6,26 +6,46 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-0.4011866709, 2.331310176,
- -0.5426286416) + (0.0, 0.0, 0.0) + (
- 19.11746262, 10.19675564, 23.50119277)
-points['ffa_spawn1'] = (3.140826121, 1.16512015,
- 6.172121491) + (4.739204545, 1.0, 1.028864849)
+boxes['area_of_interest_bounds'] = (
+ (-0.4011866709, 2.331310176, -0.5426286416)
+ + (0.0, 0.0, 0.0)
+ + (19.11746262, 10.19675564, 23.50119277)
+)
+points['ffa_spawn1'] = (3.140826121, 1.16512015, 6.172121491) + (
+ 4.739204545,
+ 1.0,
+ 1.028864849,
+)
points['ffa_spawn2'] = (5.416289073, 1.180022599, -0.1696495695) + (
- 2.945888237, 0.621599724, 0.4969830881)
+ 2.945888237,
+ 0.621599724,
+ 0.4969830881,
+)
points['ffa_spawn3'] = (-0.3692088357, 2.88984723, -6.909741615) + (
- 7.575371952, 0.621599724, 0.4969830881)
+ 7.575371952,
+ 0.621599724,
+ 0.4969830881,
+)
points['ffa_spawn4'] = (-2.391932409, 1.123690253, -3.417262271) + (
- 2.933065031, 0.621599724, 0.9796558695)
-points['ffa_spawn5'] = (-7.46052038, 2.863807079,
- 4.936420902) + (0.8707600789, 0.621599724, 2.233577195)
+ 2.933065031,
+ 0.621599724,
+ 0.9796558695,
+)
+points['ffa_spawn5'] = (-7.46052038, 2.863807079, 4.936420902) + (
+ 0.8707600789,
+ 0.621599724,
+ 2.233577195,
+)
points['flag1'] = (7.557928387, 2.889342613, -7.208799596)
points['flag2'] = (7.696183956, 1.095466627, 6.103380446)
points['flag3'] = (-8.122819332, 2.844893069, 6.103380446)
points['flag4'] = (-8.018537918, 2.844893069, -6.202403896)
points['flag_default'] = (-7.563673017, 2.850652319, 0.08844978098)
-boxes['map_bounds'] = (-0.1916036665, 8.764115729, 0.1971423239) + (
- 0.0, 0.0, 0.0) + (27.41996888, 18.47258973, 22.17335735)
+boxes['map_bounds'] = (
+ (-0.1916036665, 8.764115729, 0.1971423239)
+ + (0.0, 0.0, 0.0)
+ + (27.41996888, 18.47258973, 22.17335735)
+)
points['powerup_spawn1'] = (7.830495287, 2.115087683, -0.05452287857)
points['powerup_spawn2'] = (-5.190293739, 1.476317443, -3.80237889)
points['powerup_spawn3'] = (-8.540957726, 3.762979519, -7.27710542)
@@ -42,43 +62,97 @@ points['race_mine7'] = (0.969120762, 2.851484105, -7.892038145)
points['race_mine8'] = (-2.976299166, 2.851484105, -6.241064664)
points['race_mine9'] = (-6.962812986, 2.851484105, -2.120262964)
points['race_point1'] = (2.280447713, 1.16512015, 6.015278429) + (
- 0.7066894139, 4.672784871, 1.322422256)
+ 0.7066894139,
+ 4.672784871,
+ 1.322422256,
+)
points['race_point10'] = (-4.196540687, 2.877461266, -7.106874334) + (
- 0.1057202515, 5.496127671, 1.028552836)
+ 0.1057202515,
+ 5.496127671,
+ 1.028552836,
+)
points['race_point11'] = (-7.634488499, 2.877461266, -3.61728743) + (
- 1.438144134, 5.157457566, 0.06318119808)
+ 1.438144134,
+ 5.157457566,
+ 0.06318119808,
+)
points['race_point12'] = (-7.541251512, 2.877461266, 3.290439202) + (
- 1.668578284, 5.52484043, 0.06318119808)
-points['race_point2'] = (4.853459878, 1.16512015,
- 6.035867283) + (0.3920628436, 4.577066678, 1.34568243)
+ 1.668578284,
+ 5.52484043,
+ 0.06318119808,
+)
+points['race_point2'] = (4.853459878, 1.16512015, 6.035867283) + (
+ 0.3920628436,
+ 4.577066678,
+ 1.34568243,
+)
points['race_point3'] = (6.905234402, 1.16512015, 1.143337503) + (
- 1.611663691, 3.515259775, 0.1135135003)
+ 1.611663691,
+ 3.515259775,
+ 0.1135135003,
+)
points['race_point4'] = (2.681673258, 1.16512015, 0.771967064) + (
- 0.6475414982, 3.602143342, 0.1135135003)
+ 0.6475414982,
+ 3.602143342,
+ 0.1135135003,
+)
points['race_point5'] = (-0.3776550727, 1.225615225, 1.920343787) + (
- 0.1057202515, 4.245024435, 0.5914887576)
+ 0.1057202515,
+ 4.245024435,
+ 0.5914887576,
+)
points['race_point6'] = (-4.365081958, 1.16512015, -0.3565529313) + (
- 1.627090525, 4.549428479, 0.1135135003)
+ 1.627090525,
+ 4.549428479,
+ 0.1135135003,
+)
points['race_point7'] = (0.4149308672, 1.16512015, -3.394316313) + (
- 0.1057202515, 4.945367833, 1.310190117)
+ 0.1057202515,
+ 4.945367833,
+ 1.310190117,
+)
points['race_point8'] = (4.27031635, 2.19747021, -3.335165617) + (
- 0.1057202515, 4.389664492, 1.20413595)
+ 0.1057202515,
+ 4.389664492,
+ 1.20413595,
+)
points['race_point9'] = (2.552998384, 2.877461266, -7.117366939) + (
- 0.1057202515, 5.512312989, 0.9986814472)
+ 0.1057202515,
+ 5.512312989,
+ 0.9986814472,
+)
points['shadow_lower_bottom'] = (-0.2227795102, 0.2903873918, 2.680075641)
points['shadow_lower_top'] = (-0.2227795102, 0.8824975157, 2.680075641)
points['shadow_upper_bottom'] = (-0.2227795102, 6.305086402, 2.680075641)
points['shadow_upper_top'] = (-0.2227795102, 9.470923628, 2.680075641)
-points['spawn1'] = (7.180043217, 2.85596295, -4.407134234) + (0.7629937742,
- 1.0, 1.818908238)
-points['spawn2'] = (5.880548999, 1.142163379, 6.171168951) + (1.817516622, 1.0,
- 0.7724344394)
-points['spawn_by_flag1'] = (7.180043217, 2.85596295,
- -4.407134234) + (0.7629937742, 1.0, 1.818908238)
-points['spawn_by_flag2'] = (5.880548999, 1.142163379,
- 6.171168951) + (1.817516622, 1.0, 0.7724344394)
-points['spawn_by_flag3'] = (-6.66642559, 3.554416948,
- 5.820238985) + (1.097315815, 1.0, 1.285161684)
-points['spawn_by_flag4'] = (-6.842951255, 3.554416948,
- -6.17429905) + (0.8208434737, 1.0, 1.285161684)
+points['spawn1'] = (7.180043217, 2.85596295, -4.407134234) + (
+ 0.7629937742,
+ 1.0,
+ 1.818908238,
+)
+points['spawn2'] = (5.880548999, 1.142163379, 6.171168951) + (
+ 1.817516622,
+ 1.0,
+ 0.7724344394,
+)
+points['spawn_by_flag1'] = (7.180043217, 2.85596295, -4.407134234) + (
+ 0.7629937742,
+ 1.0,
+ 1.818908238,
+)
+points['spawn_by_flag2'] = (5.880548999, 1.142163379, 6.171168951) + (
+ 1.817516622,
+ 1.0,
+ 0.7724344394,
+)
+points['spawn_by_flag3'] = (-6.66642559, 3.554416948, 5.820238985) + (
+ 1.097315815,
+ 1.0,
+ 1.285161684,
+)
+points['spawn_by_flag4'] = (-6.842951255, 3.554416948, -6.17429905) + (
+ 0.8208434737,
+ 1.0,
+ 1.285161684,
+)
points['tnt1'] = (-3.398312776, 2.067056737, -1.90142919)
diff --git a/assets/src/ba_data/python/bastd/mapdata/bridgit.py b/assets/src/ba_data/python/bastd/mapdata/bridgit.py
index 70cb3c78..d6e8994c 100644
--- a/assets/src/ba_data/python/bastd/mapdata/bridgit.py
+++ b/assets/src/ba_data/python/bastd/mapdata/bridgit.py
@@ -6,20 +6,34 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-0.2457963347, 3.828181068,
- -1.528362695) + (0.0, 0.0, 0.0) + (
- 19.14849937, 7.312788846, 8.436232726)
-points['ffa_spawn1'] = (-5.869295124, 3.715437928,
- -1.617274877) + (0.9410329222, 1.0, 1.818908238)
-points['ffa_spawn2'] = (5.160809653, 3.761793434,
- -1.443012115) + (0.7729807005, 1.0, 1.818908238)
-points['ffa_spawn3'] = (-0.4266381164, 3.761793434,
- -1.555562653) + (4.034151421, 1.0, 0.2731725824)
+boxes['area_of_interest_bounds'] = (
+ (-0.2457963347, 3.828181068, -1.528362695)
+ + (0.0, 0.0, 0.0)
+ + (19.14849937, 7.312788846, 8.436232726)
+)
+points['ffa_spawn1'] = (-5.869295124, 3.715437928, -1.617274877) + (
+ 0.9410329222,
+ 1.0,
+ 1.818908238,
+)
+points['ffa_spawn2'] = (5.160809653, 3.761793434, -1.443012115) + (
+ 0.7729807005,
+ 1.0,
+ 1.818908238,
+)
+points['ffa_spawn3'] = (-0.4266381164, 3.761793434, -1.555562653) + (
+ 4.034151421,
+ 1.0,
+ 0.2731725824,
+)
points['flag1'] = (-7.354603923, 3.770769731, -1.617274877)
points['flag2'] = (6.885846926, 3.770685211, -1.443012115)
points['flag_default'] = (-0.2227795102, 3.802429326, -1.562586233)
-boxes['map_bounds'] = (-0.1916036665, 7.481446847, -1.311948055) + (
- 0.0, 0.0, 0.0) + (27.41996888, 18.47258973, 19.52220249)
+boxes['map_bounds'] = (
+ (-0.1916036665, 7.481446847, -1.311948055)
+ + (0.0, 0.0, 0.0)
+ + (27.41996888, 18.47258973, 19.52220249)
+)
points['powerup_spawn1'] = (6.82849491, 4.658454461, 0.1938139802)
points['powerup_spawn2'] = (-7.253381358, 4.728692078, 0.252121017)
points['powerup_spawn3'] = (6.82849491, 4.658454461, -3.461765427)
@@ -28,7 +42,13 @@ points['shadow_lower_bottom'] = (-0.2227795102, 2.83188898, 2.680075641)
points['shadow_lower_top'] = (-0.2227795102, 3.498267184, 2.680075641)
points['shadow_upper_bottom'] = (-0.2227795102, 6.305086402, 2.680075641)
points['shadow_upper_top'] = (-0.2227795102, 9.470923628, 2.680075641)
-points['spawn1'] = (-5.869295124, 3.715437928,
- -1.617274877) + (0.9410329222, 1.0, 1.818908238)
-points['spawn2'] = (5.160809653, 3.761793434,
- -1.443012115) + (0.7729807005, 1.0, 1.818908238)
+points['spawn1'] = (-5.869295124, 3.715437928, -1.617274877) + (
+ 0.9410329222,
+ 1.0,
+ 1.818908238,
+)
+points['spawn2'] = (5.160809653, 3.761793434, -1.443012115) + (
+ 0.7729807005,
+ 1.0,
+ 1.818908238,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/courtyard.py b/assets/src/ba_data/python/bastd/mapdata/courtyard.py
index 634cdb21..9220e055 100644
--- a/assets/src/ba_data/python/bastd/mapdata/courtyard.py
+++ b/assets/src/ba_data/python/bastd/mapdata/courtyard.py
@@ -6,9 +6,11 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.3544110667, 3.958431362,
- -2.175025358) + (0.0, 0.0, 0.0) + (
- 16.37702017, 7.755670126, 13.38680645)
+boxes['area_of_interest_bounds'] = (
+ (0.3544110667, 3.958431362, -2.175025358)
+ + (0.0, 0.0, 0.0)
+ + (16.37702017, 7.755670126, 13.38680645)
+)
points['bot_spawn_bottom'] = (-0.06281376545, 2.814769232, 1.95079953)
points['bot_spawn_bottom_half_left'] = (-2.05017213, 2.814769232, 1.95079953)
points['bot_spawn_bottom_half_right'] = (1.85515704, 2.814769232, 1.95079953)
@@ -16,14 +18,12 @@ points['bot_spawn_bottom_left'] = (-3.680966394, 2.814769232, 1.95079953)
points['bot_spawn_bottom_right'] = (3.586455826, 2.814769232, 1.95079953)
points['bot_spawn_left'] = (-6.447075231, 2.814769232, -2.317996277)
points['bot_spawn_left_lower'] = (-6.447075231, 2.814769232, -1.509957962)
-points['bot_spawn_left_lower_more'] = (-6.447075231, 2.814769232,
- -0.4832205112)
+points['bot_spawn_left_lower_more'] = (-6.447075231, 2.814769232, -0.4832205112)
points['bot_spawn_left_upper'] = (-6.447075231, 2.814769232, -3.183562653)
points['bot_spawn_left_upper_more'] = (-6.447075231, 2.814769232, -4.010007449)
points['bot_spawn_right'] = (6.539735433, 2.814769232, -2.317996277)
points['bot_spawn_right_lower'] = (6.539735433, 2.814769232, -1.396042829)
-points['bot_spawn_right_lower_more'] = (6.539735433, 2.814769232,
- -0.3623501424)
+points['bot_spawn_right_lower_more'] = (6.539735433, 2.814769232, -0.3623501424)
points['bot_spawn_right_upper'] = (6.539735433, 2.814769232, -3.130071083)
points['bot_spawn_right_upper_more'] = (6.539735433, 2.814769232, -3.977427131)
points['bot_spawn_top'] = (-0.06281376545, 2.814769232, -5.833265855)
@@ -34,28 +34,55 @@ points['bot_spawn_top_right'] = (3.396752931, 2.814769232, -5.950680835)
points['bot_spawn_turret_bottom_left'] = (-6.127144702, 3.3275475, 1.911189749)
points['bot_spawn_turret_bottom_right'] = (6.372913618, 3.3275475, 1.79864574)
points['bot_spawn_turret_top_left'] = (-6.127144702, 3.3275475, -6.572879116)
-points['bot_spawn_turret_top_middle'] = (0.08149184008, 4.270281808,
- -8.522292633)
-points['bot_spawn_turret_top_middle_left'] = (-1.271380584, 4.270281808,
- -8.522292633)
-points['bot_spawn_turret_top_middle_right'] = (1.128462393, 4.270281808,
- -8.522292633)
+points['bot_spawn_turret_top_middle'] = (
+ 0.08149184008,
+ 4.270281808,
+ -8.522292633,
+)
+points['bot_spawn_turret_top_middle_left'] = (
+ -1.271380584,
+ 4.270281808,
+ -8.522292633,
+)
+points['bot_spawn_turret_top_middle_right'] = (
+ 1.128462393,
+ 4.270281808,
+ -8.522292633,
+)
points['bot_spawn_turret_top_right'] = (6.372913618, 3.3275475, -6.603689486)
-boxes['edge_box'] = (0.0, 1.036729365, -2.142494752) + (0.0, 0.0, 0.0) + (
- 12.01667356, 11.40580437, 7.808185564)
-points['ffa_spawn1'] = (-6.228613999, 3.765660284,
- -5.15969075) + (1.480100328, 1.0, 0.07121651432)
-points['ffa_spawn2'] = (6.286481065, 3.765660284,
- -4.923207718) + (1.419728931, 1.0, 0.07121651432)
-points['ffa_spawn3'] = (-0.01917923364, 4.39873514,
- -6.964732605) + (1.505953039, 1.0, 0.2494784408)
-points['ffa_spawn4'] = (-0.01917923364, 3.792688047,
- 3.453884398) + (4.987737689, 1.0, 0.1505089956)
+boxes['edge_box'] = (
+ (0.0, 1.036729365, -2.142494752)
+ + (0.0, 0.0, 0.0)
+ + (12.01667356, 11.40580437, 7.808185564)
+)
+points['ffa_spawn1'] = (-6.228613999, 3.765660284, -5.15969075) + (
+ 1.480100328,
+ 1.0,
+ 0.07121651432,
+)
+points['ffa_spawn2'] = (6.286481065, 3.765660284, -4.923207718) + (
+ 1.419728931,
+ 1.0,
+ 0.07121651432,
+)
+points['ffa_spawn3'] = (-0.01917923364, 4.39873514, -6.964732605) + (
+ 1.505953039,
+ 1.0,
+ 0.2494784408,
+)
+points['ffa_spawn4'] = (-0.01917923364, 3.792688047, 3.453884398) + (
+ 4.987737689,
+ 1.0,
+ 0.1505089956,
+)
points['flag1'] = (-5.965661853, 2.820013813, -2.428844806)
points['flag2'] = (5.905546426, 2.800475393, -2.218272564)
points['flag_default'] = (0.2516184246, 2.784213993, -2.644195211)
-boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + (
- 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344)
+boxes['map_bounds'] = (
+ (0.2608783669, 4.899663734, -3.543675157)
+ + (0.0, 0.0, 0.0)
+ + (29.23565494, 14.19991443, 29.92689344)
+)
points['powerup_spawn1'] = (-3.555558641, 3.168458621, 0.3692836925)
points['powerup_spawn2'] = (3.625691848, 3.168458621, 0.4058534671)
points['powerup_spawn3'] = (3.625691848, 3.168458621, -4.987242873)
@@ -64,7 +91,13 @@ points['shadow_lower_bottom'] = (0.5236258282, 0.02085132358, 5.341226521)
points['shadow_lower_top'] = (0.5236258282, 1.206119006, 5.341226521)
points['shadow_upper_bottom'] = (0.5236258282, 6.359015684, 5.341226521)
points['shadow_upper_top'] = (0.5236258282, 10.12385584, 5.341226521)
-points['spawn1'] = (-7.514831403, 3.803639368,
- -2.102145502) + (0.0878727285, 1.0, 2.195980213)
-points['spawn2'] = (7.462102032, 3.772786511,
- -1.835207267) + (0.0288041898, 1.0, 2.221665995)
+points['spawn1'] = (-7.514831403, 3.803639368, -2.102145502) + (
+ 0.0878727285,
+ 1.0,
+ 2.195980213,
+)
+points['spawn2'] = (7.462102032, 3.772786511, -1.835207267) + (
+ 0.0288041898,
+ 1.0,
+ 2.221665995,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/crag_castle.py b/assets/src/ba_data/python/bastd/mapdata/crag_castle.py
index ca5d794d..587002f3 100644
--- a/assets/src/ba_data/python/bastd/mapdata/crag_castle.py
+++ b/assets/src/ba_data/python/bastd/mapdata/crag_castle.py
@@ -6,35 +6,65 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.7033834902, 6.55869393, -3.153439808) + (
- 0.0, 0.0, 0.0) + (16.73648528, 14.94789935, 11.60063102)
+boxes['area_of_interest_bounds'] = (
+ (0.7033834902, 6.55869393, -3.153439808)
+ + (0.0, 0.0, 0.0)
+ + (16.73648528, 14.94789935, 11.60063102)
+)
points['ffa_spawn1'] = (-4.04166076, 7.54589296, -3.542792409) + (
- 2.471508516, 1.156019141, 0.1791707664)
-points['ffa_spawn2'] = (5.429881832, 7.582951102,
- -3.497145747) + (2.415753564, 1.12871694, 0.17898173)
-points['ffa_spawn3'] = (4.8635999, 9.311949436,
- -6.013939259) + (1.61785329, 1.12871694, 0.17898173)
-points['ffa_spawn4'] = (-3.628023052, 9.311949436,
- -6.013939259) + (1.61785329, 1.12871694, 0.17898173)
-points['ffa_spawn5'] = (-2.414363536, 5.930994442,
- 0.03036413701) + (1.61785329, 1.12871694, 0.17898173)
-points['ffa_spawn6'] = (3.520989196, 5.930994442,
- 0.03036413701) + (1.61785329, 1.12871694, 0.17898173)
+ 2.471508516,
+ 1.156019141,
+ 0.1791707664,
+)
+points['ffa_spawn2'] = (5.429881832, 7.582951102, -3.497145747) + (
+ 2.415753564,
+ 1.12871694,
+ 0.17898173,
+)
+points['ffa_spawn3'] = (4.8635999, 9.311949436, -6.013939259) + (
+ 1.61785329,
+ 1.12871694,
+ 0.17898173,
+)
+points['ffa_spawn4'] = (-3.628023052, 9.311949436, -6.013939259) + (
+ 1.61785329,
+ 1.12871694,
+ 0.17898173,
+)
+points['ffa_spawn5'] = (-2.414363536, 5.930994442, 0.03036413701) + (
+ 1.61785329,
+ 1.12871694,
+ 0.17898173,
+)
+points['ffa_spawn6'] = (3.520989196, 5.930994442, 0.03036413701) + (
+ 1.61785329,
+ 1.12871694,
+ 0.17898173,
+)
points['flag1'] = (-1.900164924, 9.363050076, -6.441041548)
points['flag2'] = (3.240019982, 9.319215955, -6.392759924)
points['flag3'] = (-6.883672142, 7.475761129, 0.2098388241)
points['flag4'] = (8.193957063, 7.478129652, 0.1536410508)
points['flag_default'] = (0.6296142785, 6.221901832, -0.0435909658)
-boxes['map_bounds'] = (0.4799042306, 9.085075529, -3.267604531) + (
- 0.0, 0.0, 0.0) + (22.9573075, 9.908550511, 14.17997333)
+boxes['map_bounds'] = (
+ (0.4799042306, 9.085075529, -3.267604531)
+ + (0.0, 0.0, 0.0)
+ + (22.9573075, 9.908550511, 14.17997333)
+)
points['powerup_spawn1'] = (7.916483636, 7.83853949, -5.990841203)
points['powerup_spawn2'] = (-0.6978591232, 7.883836528, -6.066674247)
points['powerup_spawn3'] = (1.858093733, 7.893059862, -6.076932659)
points['powerup_spawn4'] = (-6.671997388, 7.992307645, -6.121432603)
-points['spawn1'] = (-5.169730601, 7.54589296,
- -3.542792409) + (1.057384557, 1.156019141, 0.1791707664)
-points['spawn2'] = (6.203092708, 7.582951102,
- -3.497145747) + (1.009865407, 1.12871694, 0.17898173)
+points['spawn1'] = (-5.169730601, 7.54589296, -3.542792409) + (
+ 1.057384557,
+ 1.156019141,
+ 0.1791707664,
+)
+points['spawn2'] = (6.203092708, 7.582951102, -3.497145747) + (
+ 1.009865407,
+ 1.12871694,
+ 0.17898173,
+)
points['spawn_by_flag1'] = (-2.872146219, 9.363050076, -6.041110823)
points['spawn_by_flag2'] = (4.313355684, 9.363050076, -6.041110823)
points['spawn_by_flag3'] = (-6.634074097, 7.508585058, -0.5918910315)
diff --git a/assets/src/ba_data/python/bastd/mapdata/doom_shroom.py b/assets/src/ba_data/python/bastd/mapdata/doom_shroom.py
index 634a7308..a6a5d498 100644
--- a/assets/src/ba_data/python/bastd/mapdata/doom_shroom.py
+++ b/assets/src/ba_data/python/bastd/mapdata/doom_shroom.py
@@ -6,22 +6,39 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.4687647786, 2.320345088,
- -3.219423694) + (0.0, 0.0, 0.0) + (
- 21.34898078, 10.25529817, 14.67298352)
-points['ffa_spawn1'] = (-5.828122667, 2.301094498,
- -3.445694701) + (1.0, 1.0, 2.682935578)
-points['ffa_spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (1.0, 1.0,
- 2.682935578)
-points['ffa_spawn3'] = (0.8835145921, 2.307217208,
- -0.3552854962) + (4.455517747, 1.0, 0.2723037175)
-points['ffa_spawn4'] = (0.8835145921, 2.307217208,
- -7.124335491) + (4.455517747, 1.0, 0.2723037175)
+boxes['area_of_interest_bounds'] = (
+ (0.4687647786, 2.320345088, -3.219423694)
+ + (0.0, 0.0, 0.0)
+ + (21.34898078, 10.25529817, 14.67298352)
+)
+points['ffa_spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + (
+ 1.0,
+ 1.0,
+ 2.682935578,
+)
+points['ffa_spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (
+ 1.0,
+ 1.0,
+ 2.682935578,
+)
+points['ffa_spawn3'] = (0.8835145921, 2.307217208, -0.3552854962) + (
+ 4.455517747,
+ 1.0,
+ 0.2723037175,
+)
+points['ffa_spawn4'] = (0.8835145921, 2.307217208, -7.124335491) + (
+ 4.455517747,
+ 1.0,
+ 0.2723037175,
+)
points['flag1'] = (-7.153737138, 2.251993091, -3.427368878)
points['flag2'] = (8.103769491, 2.320591215, -3.548878069)
points['flag_default'] = (0.5964565429, 2.373456481, -4.241969517)
-boxes['map_bounds'] = (0.4566560559, 1.332051421, -3.80651373) + (
- 0.0, 0.0, 0.0) + (27.75073129, 14.44528216, 22.9896617)
+boxes['map_bounds'] = (
+ (0.4566560559, 1.332051421, -3.80651373)
+ + (0.0, 0.0, 0.0)
+ + (27.75073129, 14.44528216, 22.9896617)
+)
points['powerup_spawn1'] = (5.180858712, 4.278900266, -7.282758712)
points['powerup_spawn2'] = (-3.236908759, 4.159702067, -0.3232556512)
points['powerup_spawn3'] = (5.082843398, 4.159702067, -0.3232556512)
@@ -30,7 +47,13 @@ points['shadow_lower_bottom'] = (0.5964565429, -0.2279530265, 3.368035253)
points['shadow_lower_top'] = (0.5964565429, 0.6982784189, 3.368035253)
points['shadow_upper_bottom'] = (0.5964565429, 5.413250948, 3.368035253)
points['shadow_upper_top'] = (0.5964565429, 7.891484473, 3.368035253)
-points['spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + (1.0, 1.0,
- 2.682935578)
-points['spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (1.0, 1.0,
- 2.682935578)
+points['spawn1'] = (-5.828122667, 2.301094498, -3.445694701) + (
+ 1.0,
+ 1.0,
+ 2.682935578,
+)
+points['spawn2'] = (6.496252674, 2.397778847, -3.573241388) + (
+ 1.0,
+ 1.0,
+ 2.682935578,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/football_stadium.py b/assets/src/ba_data/python/bastd/mapdata/football_stadium.py
index b650238e..6e490c44 100644
--- a/assets/src/ba_data/python/bastd/mapdata/football_stadium.py
+++ b/assets/src/ba_data/python/bastd/mapdata/football_stadium.py
@@ -6,23 +6,42 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.0, 1.185751251, 0.4326226188) + (
- 0.0, 0.0, 0.0) + (29.8180273, 11.57249038, 18.89134176)
-boxes['edge_box'] = (-0.103873591, 0.4133341891, 0.4294651013) + (
- 0.0, 0.0, 0.0) + (22.48295719, 1.290242794, 8.990252454)
-points['ffa_spawn1'] = (-0.08015551329, 0.02275111462,
- -4.373674593) + (8.895057015, 1.0, 0.444350722)
-points['ffa_spawn2'] = (-0.08015551329, 0.02275111462,
- 4.076288941) + (8.895057015, 1.0, 0.444350722)
+boxes['area_of_interest_bounds'] = (
+ (0.0, 1.185751251, 0.4326226188)
+ + (0.0, 0.0, 0.0)
+ + (29.8180273, 11.57249038, 18.89134176)
+)
+boxes['edge_box'] = (
+ (-0.103873591, 0.4133341891, 0.4294651013)
+ + (0.0, 0.0, 0.0)
+ + (22.48295719, 1.290242794, 8.990252454)
+)
+points['ffa_spawn1'] = (-0.08015551329, 0.02275111462, -4.373674593) + (
+ 8.895057015,
+ 1.0,
+ 0.444350722,
+)
+points['ffa_spawn2'] = (-0.08015551329, 0.02275111462, 4.076288941) + (
+ 8.895057015,
+ 1.0,
+ 0.444350722,
+)
points['flag1'] = (-10.99027878, 0.05744967453, 0.1095578275)
points['flag2'] = (11.01486398, 0.03986567039, 0.1095578275)
points['flag_default'] = (-0.1001374046, 0.04180340146, 0.1095578275)
-boxes['goal1'] = (12.22454533, 1.0,
- 0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313)
-boxes['goal2'] = (-12.15961605, 1.0,
- 0.1097860203) + (0.0, 0.0, 0.0) + (2.0, 2.0, 13.11856424)
-boxes['map_bounds'] = (0.0, 1.185751251, 0.4326226188) + (0.0, 0.0, 0.0) + (
- 42.09506485, 22.81173179, 29.76723155)
+boxes['goal1'] = (
+ (12.22454533, 1.0, 0.1087926362) + (0.0, 0.0, 0.0) + (2.0, 2.0, 12.97466313)
+)
+boxes['goal2'] = (
+ (-12.15961605, 1.0, 0.1097860203)
+ + (0.0, 0.0, 0.0)
+ + (2.0, 2.0, 13.11856424)
+)
+boxes['map_bounds'] = (
+ (0.0, 1.185751251, 0.4326226188)
+ + (0.0, 0.0, 0.0)
+ + (42.09506485, 22.81173179, 29.76723155)
+)
points['powerup_spawn1'] = (5.414681236, 0.9515026107, -5.037912441)
points['powerup_spawn2'] = (-5.555402285, 0.9515026107, -5.037912441)
points['powerup_spawn3'] = (5.414681236, 0.9515026107, 5.148223181)
diff --git a/assets/src/ba_data/python/bastd/mapdata/happy_thoughts.py b/assets/src/ba_data/python/bastd/mapdata/happy_thoughts.py
index c3e03ab3..164bdb2a 100644
--- a/assets/src/ba_data/python/bastd/mapdata/happy_thoughts.py
+++ b/assets/src/ba_data/python/bastd/mapdata/happy_thoughts.py
@@ -6,41 +6,82 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-1.045859963, 12.67722855,
- -5.401537075) + (0.0, 0.0, 0.0) + (
- 34.46156851, 20.94044653, 0.6931564611)
-points['ffa_spawn1'] = (-9.295167711, 8.010664315,
- -5.44451005) + (1.555840357, 1.453808816, 0.1165648888)
+boxes['area_of_interest_bounds'] = (
+ (-1.045859963, 12.67722855, -5.401537075)
+ + (0.0, 0.0, 0.0)
+ + (34.46156851, 20.94044653, 0.6931564611)
+)
+points['ffa_spawn1'] = (-9.295167711, 8.010664315, -5.44451005) + (
+ 1.555840357,
+ 1.453808816,
+ 0.1165648888,
+)
points['ffa_spawn2'] = (7.484707127, 8.172681752, -5.614479365) + (
- 1.553861796, 1.453808816, 0.04419853907)
+ 1.553861796,
+ 1.453808816,
+ 0.04419853907,
+)
points['ffa_spawn3'] = (9.55724115, 11.30789446, -5.614479365) + (
- 1.337925849, 1.453808816, 0.04419853907)
+ 1.337925849,
+ 1.453808816,
+ 0.04419853907,
+)
points['ffa_spawn4'] = (-11.55747023, 10.99170684, -5.614479365) + (
- 1.337925849, 1.453808816, 0.04419853907)
+ 1.337925849,
+ 1.453808816,
+ 0.04419853907,
+)
points['ffa_spawn5'] = (-1.878892369, 9.46490571, -5.614479365) + (
- 1.337925849, 1.453808816, 0.04419853907)
+ 1.337925849,
+ 1.453808816,
+ 0.04419853907,
+)
points['ffa_spawn6'] = (-0.4912812943, 5.077006397, -5.521672101) + (
- 1.878332089, 1.453808816, 0.007578097856)
+ 1.878332089,
+ 1.453808816,
+ 0.007578097856,
+)
points['flag1'] = (-11.75152479, 8.057427485, -5.52)
points['flag2'] = (9.840909039, 8.188634282, -5.52)
points['flag3'] = (-0.2195258696, 5.010273907, -5.52)
points['flag4'] = (-0.04605809154, 12.73369108, -5.52)
points['flag_default'] = (-0.04201942896, 12.72374492, -5.52)
-boxes['map_bounds'] = (-0.8748348681, 9.212941713, -5.729538885) + (
- 0.0, 0.0, 0.0) + (36.09666006, 26.19950145, 7.89541168)
+boxes['map_bounds'] = (
+ (-0.8748348681, 9.212941713, -5.729538885)
+ + (0.0, 0.0, 0.0)
+ + (36.09666006, 26.19950145, 7.89541168)
+)
points['powerup_spawn1'] = (1.160232442, 6.745963662, -5.469115985)
points['powerup_spawn2'] = (-1.899700206, 10.56447241, -5.505721177)
points['powerup_spawn3'] = (10.56098871, 12.25165669, -5.576232453)
points['powerup_spawn4'] = (-12.33530337, 12.25165669, -5.576232453)
-points['spawn1'] = (-9.295167711, 8.010664315,
- -5.44451005) + (1.555840357, 1.453808816, 0.1165648888)
-points['spawn2'] = (7.484707127, 8.172681752,
- -5.614479365) + (1.553861796, 1.453808816, 0.04419853907)
+points['spawn1'] = (-9.295167711, 8.010664315, -5.44451005) + (
+ 1.555840357,
+ 1.453808816,
+ 0.1165648888,
+)
+points['spawn2'] = (7.484707127, 8.172681752, -5.614479365) + (
+ 1.553861796,
+ 1.453808816,
+ 0.04419853907,
+)
points['spawn_by_flag1'] = (-9.295167711, 8.010664315, -5.44451005) + (
- 1.555840357, 1.453808816, 0.1165648888)
+ 1.555840357,
+ 1.453808816,
+ 0.1165648888,
+)
points['spawn_by_flag2'] = (7.484707127, 8.172681752, -5.614479365) + (
- 1.553861796, 1.453808816, 0.04419853907)
+ 1.553861796,
+ 1.453808816,
+ 0.04419853907,
+)
points['spawn_by_flag3'] = (-1.45994593, 5.038762459, -5.535288724) + (
- 0.9516389866, 0.6666414677, 0.08607244075)
+ 0.9516389866,
+ 0.6666414677,
+ 0.08607244075,
+)
points['spawn_by_flag4'] = (0.4932087091, 12.74493212, -5.598987003) + (
- 0.5245740665, 0.5245740665, 0.01941146064)
+ 0.5245740665,
+ 0.5245740665,
+ 0.01941146064,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/hockey_stadium.py b/assets/src/ba_data/python/bastd/mapdata/hockey_stadium.py
index 722797f4..9f00e49c 100644
--- a/assets/src/ba_data/python/bastd/mapdata/hockey_stadium.py
+++ b/assets/src/ba_data/python/bastd/mapdata/hockey_stadium.py
@@ -6,19 +6,31 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.0, 0.7956858119, 0.0) + (
- 0.0, 0.0, 0.0) + (30.80223883, 0.5961646365, 13.88431707)
-points['ffa_spawn1'] = (-0.001925625146, 0.02305323209,
- -3.81971842) + (7.828121539, 1.0, 0.1588021252)
-points['ffa_spawn2'] = (-0.001925625146, 0.02305323209,
- 3.560115735) + (7.828121539, 1.0, 0.05859841271)
+boxes['area_of_interest_bounds'] = (
+ (0.0, 0.7956858119, 0.0)
+ + (0.0, 0.0, 0.0)
+ + (30.80223883, 0.5961646365, 13.88431707)
+)
+points['ffa_spawn1'] = (-0.001925625146, 0.02305323209, -3.81971842) + (
+ 7.828121539,
+ 1.0,
+ 0.1588021252,
+)
+points['ffa_spawn2'] = (-0.001925625146, 0.02305323209, 3.560115735) + (
+ 7.828121539,
+ 1.0,
+ 0.05859841271,
+)
points['flag1'] = (-11.21689747, 0.09527878981, -0.07659307272)
points['flag2'] = (11.08204909, 0.04119542459, -0.07659307272)
points['flag_default'] = (-0.01690735171, 0.06139940044, -0.07659307272)
boxes['goal1'] = (8.45, 1.0, 0.0) + (0.0, 0.0, 0.0) + (0.4334079123, 1.6, 3.0)
boxes['goal2'] = (-8.45, 1.0, 0.0) + (0.0, 0.0, 0.0) + (0.4334079123, 1.6, 3.0)
-boxes['map_bounds'] = (0.0, 0.7956858119, -0.4689020853) + (0.0, 0.0, 0.0) + (
- 35.16182389, 12.18696164, 21.52869693)
+boxes['map_bounds'] = (
+ (0.0, 0.7956858119, -0.4689020853)
+ + (0.0, 0.0, 0.0)
+ + (35.16182389, 12.18696164, 21.52869693)
+)
points['powerup_spawn1'] = (-3.654355317, 1.080990833, -4.765886164)
points['powerup_spawn2'] = (-3.654355317, 1.080990833, 4.599802158)
points['powerup_spawn3'] = (2.881071011, 1.080990833, -4.765886164)
diff --git a/assets/src/ba_data/python/bastd/mapdata/lake_frigid.py b/assets/src/ba_data/python/bastd/mapdata/lake_frigid.py
index a56b174f..4f5fbe78 100644
--- a/assets/src/ba_data/python/bastd/mapdata/lake_frigid.py
+++ b/assets/src/ba_data/python/bastd/mapdata/lake_frigid.py
@@ -6,21 +6,39 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.622753268, 3.958431362, -2.48708008) + (
- 0.0, 0.0, 0.0) + (20.62310543, 7.755670126, 12.33155049)
-points['ffa_spawn1'] = (-5.782222813, 2.601256429,
- -2.116763055) + (0.4872664751, 1.0, 2.99296869)
-points['ffa_spawn2'] = (8.331810793, 2.563661107,
- -2.362712466) + (0.4929678379, 1.0, 2.590481339)
-points['ffa_spawn3'] = (-0.01917923364, 2.623757527,
- -6.518902459) + (4.450854686, 1.0, 0.2494784408)
-points['ffa_spawn4'] = (-0.01917923364, 2.620884201,
- 2.154362669) + (4.987737689, 1.0, 0.1505089956)
+boxes['area_of_interest_bounds'] = (
+ (0.622753268, 3.958431362, -2.48708008)
+ + (0.0, 0.0, 0.0)
+ + (20.62310543, 7.755670126, 12.33155049)
+)
+points['ffa_spawn1'] = (-5.782222813, 2.601256429, -2.116763055) + (
+ 0.4872664751,
+ 1.0,
+ 2.99296869,
+)
+points['ffa_spawn2'] = (8.331810793, 2.563661107, -2.362712466) + (
+ 0.4929678379,
+ 1.0,
+ 2.590481339,
+)
+points['ffa_spawn3'] = (-0.01917923364, 2.623757527, -6.518902459) + (
+ 4.450854686,
+ 1.0,
+ 0.2494784408,
+)
+points['ffa_spawn4'] = (-0.01917923364, 2.620884201, 2.154362669) + (
+ 4.987737689,
+ 1.0,
+ 0.1505089956,
+)
points['flag1'] = (-5.965661853, 2.60868975, -2.428844806)
points['flag2'] = (7.469054879, 2.600634569, -2.218272564)
points['flag_default'] = (0.5814687904, 2.593249132, -6.083520531)
-boxes['map_bounds'] = (0.6679698457, 6.090222998, -2.478650859) + (
- 0.0, 0.0, 0.0) + (26.78420476, 12.49722958, 19.09355242)
+boxes['map_bounds'] = (
+ (0.6679698457, 6.090222998, -2.478650859)
+ + (0.0, 0.0, 0.0)
+ + (26.78420476, 12.49722958, 19.09355242)
+)
points['powerup_spawn1'] = (-3.178773331, 3.168458621, 1.526824762)
points['powerup_spawn2'] = (3.625691848, 3.168458621, 1.563394537)
points['powerup_spawn3'] = (3.625691848, 3.168458621, -5.768903171)
@@ -52,26 +70,56 @@ points['race_mine7'] = (-4.356878305, 2.523837916, -2.04510117)
points['race_mine8'] = (-0.713193354, 2.523837916, -0.1340958729)
points['race_mine9'] = (-0.713193354, 2.523837916, 1.275675237)
points['race_point1'] = (0.5901776337, 2.544287937, 1.543598704) + (
- 0.2824957007, 3.950514538, 2.292534365)
-points['race_point2'] = (4.7526567, 2.489758467,
- 1.09551316) + (0.2824957007, 3.950514538, 2.392880724)
+ 0.2824957007,
+ 3.950514538,
+ 2.292534365,
+)
+points['race_point2'] = (4.7526567, 2.489758467, 1.09551316) + (
+ 0.2824957007,
+ 3.950514538,
+ 2.392880724,
+)
points['race_point3'] = (7.450800117, 2.601570758, -2.248040576) + (
- 2.167067932, 3.950514538, 0.2574992262)
+ 2.167067932,
+ 3.950514538,
+ 0.2574992262,
+)
points['race_point4'] = (5.064768438, 2.489758467, -5.820463576) + (
- 0.2824957007, 3.950514538, 2.392880724)
+ 0.2824957007,
+ 3.950514538,
+ 2.392880724,
+)
points['race_point5'] = (0.5901776337, 2.67667329, -6.165424036) + (
- 0.2824957007, 3.950514538, 2.156382533)
+ 0.2824957007,
+ 3.950514538,
+ 2.156382533,
+)
points['race_point6'] = (-3.057459058, 2.489758467, -6.114179652) + (
- 0.2824957007, 3.950514538, 2.323773344)
-points['race_point7'] = (-5.814316926, 2.57969886,
- -2.248040576) + (2.0364457, 3.950514538, 0.2574992262)
+ 0.2824957007,
+ 3.950514538,
+ 2.323773344,
+)
+points['race_point7'] = (-5.814316926, 2.57969886, -2.248040576) + (
+ 2.0364457,
+ 3.950514538,
+ 0.2574992262,
+)
points['race_point8'] = (-2.958397223, 2.489758467, 1.360005754) + (
- 0.2824957007, 3.950514538, 2.529692681)
+ 0.2824957007,
+ 3.950514538,
+ 2.529692681,
+)
points['shadow_lower_bottom'] = (0.5236258282, 1.516338013, 5.341226521)
points['shadow_lower_top'] = (0.5236258282, 2.516776651, 5.341226521)
points['shadow_upper_bottom'] = (0.5236258282, 4.543246769, 5.341226521)
points['shadow_upper_top'] = (0.5236258282, 5.917963067, 5.341226521)
-points['spawn1'] = (-5.945079307, 2.524666031,
- -2.102145502) + (0.0878727285, 1.0, 2.195980213)
-points['spawn2'] = (8.079733391, 2.506883995,
- -2.364598145) + (0.0288041898, 1.0, 2.221665995)
+points['spawn1'] = (-5.945079307, 2.524666031, -2.102145502) + (
+ 0.0878727285,
+ 1.0,
+ 2.195980213,
+)
+points['spawn2'] = (8.079733391, 2.506883995, -2.364598145) + (
+ 0.0288041898,
+ 1.0,
+ 2.221665995,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/monkey_face.py b/assets/src/ba_data/python/bastd/mapdata/monkey_face.py
index b0415091..274f58e2 100644
--- a/assets/src/ba_data/python/bastd/mapdata/monkey_face.py
+++ b/assets/src/ba_data/python/bastd/mapdata/monkey_face.py
@@ -6,22 +6,39 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-1.657177611, 4.132574186,
- -1.580485661) + (0.0, 0.0, 0.0) + (
- 17.36258946, 10.49020453, 12.31460338)
+boxes['area_of_interest_bounds'] = (
+ (-1.657177611, 4.132574186, -1.580485661)
+ + (0.0, 0.0, 0.0)
+ + (17.36258946, 10.49020453, 12.31460338)
+)
points['ffa_spawn1'] = (-8.026373566, 3.349937889, -2.542088202) + (
- 0.9450583628, 0.9450583628, 1.181509268)
-points['ffa_spawn2'] = (4.73470012, 3.308679998,
- -2.757871588) + (0.9335931003, 1.0, 1.217352295)
-points['ffa_spawn3'] = (-1.907161509, 3.326830784,
- -6.572223028) + (4.080767643, 1.0, 0.2880331593)
-points['ffa_spawn4'] = (-1.672823345, 3.326830784,
- 2.405442985) + (3.870724402, 1.0, 0.2880331593)
+ 0.9450583628,
+ 0.9450583628,
+ 1.181509268,
+)
+points['ffa_spawn2'] = (4.73470012, 3.308679998, -2.757871588) + (
+ 0.9335931003,
+ 1.0,
+ 1.217352295,
+)
+points['ffa_spawn3'] = (-1.907161509, 3.326830784, -6.572223028) + (
+ 4.080767643,
+ 1.0,
+ 0.2880331593,
+)
+points['ffa_spawn4'] = (-1.672823345, 3.326830784, 2.405442985) + (
+ 3.870724402,
+ 1.0,
+ 0.2880331593,
+)
points['flag1'] = (-8.968414135, 3.35709348, -2.804123917)
points['flag2'] = (5.945128279, 3.354825248, -2.663635497)
points['flag_default'] = (-1.688166134, 3.392387172, -2.238613943)
-boxes['map_bounds'] = (-1.615296127, 6.825502312, -2.200965435) + (
- 0.0, 0.0, 0.0) + (22.51905077, 12.21074608, 15.9079565)
+boxes['map_bounds'] = (
+ (-1.615296127, 6.825502312, -2.200965435)
+ + (0.0, 0.0, 0.0)
+ + (22.51905077, 12.21074608, 15.9079565)
+)
points['powerup_spawn1'] = (-6.859406739, 4.429165244, -6.588618549)
points['powerup_spawn2'] = (-5.422572086, 4.228850685, 2.803988636)
points['powerup_spawn3'] = (3.148493267, 4.429165244, -6.588618549)
@@ -30,7 +47,13 @@ points['shadow_lower_bottom'] = (-1.877364768, 0.9878677276, 5.50201662)
points['shadow_lower_top'] = (-1.877364768, 2.881511768, 5.50201662)
points['shadow_upper_bottom'] = (-1.877364768, 6.169020542, 5.50201662)
points['shadow_upper_top'] = (-1.877364768, 10.2492777, 5.50201662)
-points['spawn1'] = (-8.026373566, 3.349937889,
- -2.542088202) + (0.9450583628, 0.9450583628, 1.181509268)
-points['spawn2'] = (4.73470012, 3.308679998, -2.757871588) + (0.9335931003,
- 1.0, 1.217352295)
+points['spawn1'] = (-8.026373566, 3.349937889, -2.542088202) + (
+ 0.9450583628,
+ 0.9450583628,
+ 1.181509268,
+)
+points['spawn2'] = (4.73470012, 3.308679998, -2.757871588) + (
+ 0.9335931003,
+ 1.0,
+ 1.217352295,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/rampage.py b/assets/src/ba_data/python/bastd/mapdata/rampage.py
index 1daff2d7..f1e96a24 100644
--- a/assets/src/ba_data/python/bastd/mapdata/rampage.py
+++ b/assets/src/ba_data/python/bastd/mapdata/rampage.py
@@ -6,27 +6,47 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.3544110667, 5.616383286,
- -4.066055072) + (0.0, 0.0, 0.0) + (
- 19.90053969, 10.34051135, 8.16221072)
-boxes['edge_box'] = (0.3544110667, 5.438284793, -4.100357672) + (
- 0.0, 0.0, 0.0) + (12.57718032, 4.645176013, 3.605557343)
-points['ffa_spawn1'] = (0.5006944438, 5.051501304,
- -5.79356326) + (6.626174027, 1.0, 0.3402012662)
-points['ffa_spawn2'] = (0.5006944438, 5.051501304,
- -2.435321368) + (6.626174027, 1.0, 0.3402012662)
+boxes['area_of_interest_bounds'] = (
+ (0.3544110667, 5.616383286, -4.066055072)
+ + (0.0, 0.0, 0.0)
+ + (19.90053969, 10.34051135, 8.16221072)
+)
+boxes['edge_box'] = (
+ (0.3544110667, 5.438284793, -4.100357672)
+ + (0.0, 0.0, 0.0)
+ + (12.57718032, 4.645176013, 3.605557343)
+)
+points['ffa_spawn1'] = (0.5006944438, 5.051501304, -5.79356326) + (
+ 6.626174027,
+ 1.0,
+ 0.3402012662,
+)
+points['ffa_spawn2'] = (0.5006944438, 5.051501304, -2.435321368) + (
+ 6.626174027,
+ 1.0,
+ 0.3402012662,
+)
points['flag1'] = (-5.885814199, 5.112162255, -4.251754911)
points['flag2'] = (6.700855451, 5.10270501, -4.259912982)
points['flag_default'] = (0.3196701116, 5.110914413, -4.292515158)
-boxes['map_bounds'] = (0.4528955042, 4.899663734, -3.543675157) + (
- 0.0, 0.0, 0.0) + (23.54502348, 14.19991443, 12.08017448)
+boxes['map_bounds'] = (
+ (0.4528955042, 4.899663734, -3.543675157)
+ + (0.0, 0.0, 0.0)
+ + (23.54502348, 14.19991443, 12.08017448)
+)
points['powerup_spawn1'] = (-2.645358507, 6.426340583, -4.226597191)
points['powerup_spawn2'] = (3.540102796, 6.549722855, -4.198476335)
points['shadow_lower_bottom'] = (5.580073911, 3.136491026, 5.341226521)
points['shadow_lower_top'] = (5.580073911, 4.321758709, 5.341226521)
points['shadow_upper_bottom'] = (5.274539479, 8.425373402, 5.341226521)
points['shadow_upper_top'] = (5.274539479, 11.93458162, 5.341226521)
-points['spawn1'] = (-4.745706238, 5.051501304,
- -4.247934288) + (0.9186962739, 1.0, 0.5153189341)
-points['spawn2'] = (5.838590388, 5.051501304,
- -4.259627405) + (0.9186962739, 1.0, 0.5153189341)
+points['spawn1'] = (-4.745706238, 5.051501304, -4.247934288) + (
+ 0.9186962739,
+ 1.0,
+ 0.5153189341,
+)
+points['spawn2'] = (5.838590388, 5.051501304, -4.259627405) + (
+ 0.9186962739,
+ 1.0,
+ 0.5153189341,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/roundabout.py b/assets/src/ba_data/python/bastd/mapdata/roundabout.py
index e9f7bdbd..084cb702 100644
--- a/assets/src/ba_data/python/bastd/mapdata/roundabout.py
+++ b/assets/src/ba_data/python/bastd/mapdata/roundabout.py
@@ -6,27 +6,48 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-1.552280404, 3.189001207, -2.40908495) + (
- 0.0, 0.0, 0.0) + (11.96255385, 8.857531648, 9.531689995)
-points['ffa_spawn1'] = (-4.056288044, 3.85970651,
- -4.6096757) + (0.9393824595, 1.0, 1.422669346)
-points['ffa_spawn2'] = (0.9091263403, 3.849381394,
- -4.673201431) + (0.9179219809, 1.0, 1.422669346)
-points['ffa_spawn3'] = (-1.50312174, 1.498336991,
- -0.7271163774) + (5.733928927, 1.0, 0.1877531607)
+boxes['area_of_interest_bounds'] = (
+ (-1.552280404, 3.189001207, -2.40908495)
+ + (0.0, 0.0, 0.0)
+ + (11.96255385, 8.857531648, 9.531689995)
+)
+points['ffa_spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + (
+ 0.9393824595,
+ 1.0,
+ 1.422669346,
+)
+points['ffa_spawn2'] = (0.9091263403, 3.849381394, -4.673201431) + (
+ 0.9179219809,
+ 1.0,
+ 1.422669346,
+)
+points['ffa_spawn3'] = (-1.50312174, 1.498336991, -0.7271163774) + (
+ 5.733928927,
+ 1.0,
+ 0.1877531607,
+)
points['flag1'] = (-3.01567985, 3.846779683, -6.702828912)
points['flag2'] = (-0.01282460768, 3.828492613, -6.684991743)
points['flag_default'] = (-1.509110449, 1.447854976, -1.440324146)
-boxes['map_bounds'] = (-1.615296127, 8.764115729, -2.663738363) + (
- 0.0, 0.0, 0.0) + (20.48886392, 18.92340529, 13.79786814)
+boxes['map_bounds'] = (
+ (-1.615296127, 8.764115729, -2.663738363)
+ + (0.0, 0.0, 0.0)
+ + (20.48886392, 18.92340529, 13.79786814)
+)
points['powerup_spawn1'] = (-6.794510156, 2.660340814, 0.01205780317)
points['powerup_spawn2'] = (3.611953494, 2.660340814, 0.01205780317)
points['shadow_lower_bottom'] = (-1.848173322, 0.6339980822, 2.267036343)
points['shadow_lower_top'] = (-1.848173322, 1.077175164, 2.267036343)
points['shadow_upper_bottom'] = (-1.848173322, 6.04794944, 2.267036343)
points['shadow_upper_top'] = (-1.848173322, 9.186681264, 2.267036343)
-points['spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + (0.9393824595, 1.0,
- 1.422669346)
-points['spawn2'] = (0.9091263403, 3.849381394,
- -4.673201431) + (0.9179219809, 1.0, 1.422669346)
+points['spawn1'] = (-4.056288044, 3.85970651, -4.6096757) + (
+ 0.9393824595,
+ 1.0,
+ 1.422669346,
+)
+points['spawn2'] = (0.9091263403, 3.849381394, -4.673201431) + (
+ 0.9179219809,
+ 1.0,
+ 1.422669346,
+)
points['tnt1'] = (-1.509110449, 2.457517361, 0.2340271555)
diff --git a/assets/src/ba_data/python/bastd/mapdata/step_right_up.py b/assets/src/ba_data/python/bastd/mapdata/step_right_up.py
index 5d709bdd..b214840a 100644
--- a/assets/src/ba_data/python/bastd/mapdata/step_right_up.py
+++ b/assets/src/ba_data/python/bastd/mapdata/step_right_up.py
@@ -6,23 +6,41 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.3544110667, 6.07676405, -2.271833016) + (
- 0.0, 0.0, 0.0) + (22.55121262, 10.14644532, 14.66087273)
-points['ffa_spawn1'] = (-6.989214197, 5.824902099,
- -4.003708602) + (0.4063164993, 1.0, 3.6294637)
-points['ffa_spawn2'] = (7.305179278, 5.866583139,
- -4.003708602) + (0.4063164993, 1.0, 3.6294637)
-points['ffa_spawn3'] = (2.641427041, 4.793721175,
- -4.003708602) + (0.4063164993, 1.0, 3.6294637)
-points['ffa_spawn4'] = (-2.36228023, 4.793721175,
- -4.003708602) + (0.4063164993, 1.0, 3.6294637)
+boxes['area_of_interest_bounds'] = (
+ (0.3544110667, 6.07676405, -2.271833016)
+ + (0.0, 0.0, 0.0)
+ + (22.55121262, 10.14644532, 14.66087273)
+)
+points['ffa_spawn1'] = (-6.989214197, 5.824902099, -4.003708602) + (
+ 0.4063164993,
+ 1.0,
+ 3.6294637,
+)
+points['ffa_spawn2'] = (7.305179278, 5.866583139, -4.003708602) + (
+ 0.4063164993,
+ 1.0,
+ 3.6294637,
+)
+points['ffa_spawn3'] = (2.641427041, 4.793721175, -4.003708602) + (
+ 0.4063164993,
+ 1.0,
+ 3.6294637,
+)
+points['ffa_spawn4'] = (-2.36228023, 4.793721175, -4.003708602) + (
+ 0.4063164993,
+ 1.0,
+ 3.6294637,
+)
points['flag1'] = (-6.005199892, 5.824953504, -8.182477108)
points['flag2'] = (6.671556926, 5.819873617, -0.3196373496)
points['flag3'] = (-2.105198862, 4.785722143, -3.938339596)
points['flag4'] = (2.693244393, 4.785722143, -3.938339596)
points['flag_default'] = (0.2516184246, 4.163099318, -3.691279318)
-boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + (
- 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344)
+boxes['map_bounds'] = (
+ (0.2608783669, 4.899663734, -3.543675157)
+ + (0.0, 0.0, 0.0)
+ + (29.23565494, 14.19991443, 29.92689344)
+)
points['powerup_spawn1'] = (-5.250579743, 4.725015518, 2.81876755)
points['powerup_spawn2'] = (5.694682017, 4.725015518, 2.81876755)
points['powerup_spawn3'] = (7.897510583, 6.314188898, -0.7152878923)
@@ -33,16 +51,34 @@ points['shadow_lower_bottom'] = (0.5236258282, 2.599698775, 5.341226521)
points['shadow_lower_top'] = (0.5236258282, 3.784966458, 5.341226521)
points['shadow_upper_bottom'] = (0.5236258282, 7.323868662, 5.341226521)
points['shadow_upper_top'] = (0.5236258282, 11.08870881, 5.341226521)
-points['spawn1'] = (-4.265431979, 5.461124528,
- -4.003708602) + (0.4063164993, 1.0, 2.195980213)
-points['spawn2'] = (5.073366552, 5.444726373,
- -4.063095718) + (0.3465292314, 1.0, 2.221665995)
-points['spawn_by_flag1'] = (-6.663464996, 5.978624661,
- -6.165495294) + (0.7518730647, 1.0, 0.8453811633)
-points['spawn_by_flag2'] = (7.389476227, 5.978624661,
- -1.709266011) + (0.7518730647, 1.0, 0.8453811633)
-points['spawn_by_flag3'] = (-2.112456453, 4.802744618,
- -3.947702091) + (0.7518730647, 1.0, 0.8453811633)
-points['spawn_by_flag4'] = (2.701146355, 4.802744618,
- -3.947702091) + (0.7518730647, 1.0, 0.8453811633)
+points['spawn1'] = (-4.265431979, 5.461124528, -4.003708602) + (
+ 0.4063164993,
+ 1.0,
+ 2.195980213,
+)
+points['spawn2'] = (5.073366552, 5.444726373, -4.063095718) + (
+ 0.3465292314,
+ 1.0,
+ 2.221665995,
+)
+points['spawn_by_flag1'] = (-6.663464996, 5.978624661, -6.165495294) + (
+ 0.7518730647,
+ 1.0,
+ 0.8453811633,
+)
+points['spawn_by_flag2'] = (7.389476227, 5.978624661, -1.709266011) + (
+ 0.7518730647,
+ 1.0,
+ 0.8453811633,
+)
+points['spawn_by_flag3'] = (-2.112456453, 4.802744618, -3.947702091) + (
+ 0.7518730647,
+ 1.0,
+ 0.8453811633,
+)
+points['spawn_by_flag4'] = (2.701146355, 4.802744618, -3.947702091) + (
+ 0.7518730647,
+ 1.0,
+ 0.8453811633,
+)
points['tnt1'] = (0.258764453, 4.834253071, -4.306874943)
diff --git a/assets/src/ba_data/python/bastd/mapdata/the_pad.py b/assets/src/ba_data/python/bastd/mapdata/the_pad.py
index 7afe6298..cfdc835d 100644
--- a/assets/src/ba_data/python/bastd/mapdata/the_pad.py
+++ b/assets/src/ba_data/python/bastd/mapdata/the_pad.py
@@ -6,22 +6,39 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.3544110667, 4.493562578,
- -2.518391331) + (0.0, 0.0, 0.0) + (
- 16.64754831, 8.06138989, 18.5029888)
-points['ffa_spawn1'] = (-3.812275836, 4.380655495,
- -8.962074979) + (2.371946621, 1.0, 0.8737798622)
-points['ffa_spawn2'] = (4.472503025, 4.406820459,
- -9.007239732) + (2.708525168, 1.0, 0.8737798622)
-points['ffa_spawn3'] = (6.972673935, 4.380775486,
- -7.424407061) + (0.4850648533, 1.0, 1.597018665)
-points['ffa_spawn4'] = (-6.36978974, 4.380775486,
- -7.424407061) + (0.4850648533, 1.0, 1.597018665)
+boxes['area_of_interest_bounds'] = (
+ (0.3544110667, 4.493562578, -2.518391331)
+ + (0.0, 0.0, 0.0)
+ + (16.64754831, 8.06138989, 18.5029888)
+)
+points['ffa_spawn1'] = (-3.812275836, 4.380655495, -8.962074979) + (
+ 2.371946621,
+ 1.0,
+ 0.8737798622,
+)
+points['ffa_spawn2'] = (4.472503025, 4.406820459, -9.007239732) + (
+ 2.708525168,
+ 1.0,
+ 0.8737798622,
+)
+points['ffa_spawn3'] = (6.972673935, 4.380775486, -7.424407061) + (
+ 0.4850648533,
+ 1.0,
+ 1.597018665,
+)
+points['ffa_spawn4'] = (-6.36978974, 4.380775486, -7.424407061) + (
+ 0.4850648533,
+ 1.0,
+ 1.597018665,
+)
points['flag1'] = (-7.026110145, 4.308759233, -6.302807727)
points['flag2'] = (7.632557137, 4.366002373, -6.287969342)
points['flag_default'] = (0.4611826686, 4.382076338, 3.680881802)
-boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + (
- 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344)
+boxes['map_bounds'] = (
+ (0.2608783669, 4.899663734, -3.543675157)
+ + (0.0, 0.0, 0.0)
+ + (29.23565494, 14.19991443, 29.92689344)
+)
points['powerup_spawn1'] = (-4.166594349, 5.281834349, -6.427493781)
points['powerup_spawn2'] = (4.426873526, 5.342460464, -6.329745237)
points['powerup_spawn3'] = (-4.201686731, 5.123385835, 0.4400721376)
@@ -30,8 +47,14 @@ points['shadow_lower_bottom'] = (-0.2912522507, 2.020798381, 5.341226521)
points['shadow_lower_top'] = (-0.2912522507, 3.206066063, 5.341226521)
points['shadow_upper_bottom'] = (-0.2912522507, 6.062361813, 5.341226521)
points['shadow_upper_top'] = (-0.2912522507, 9.827201965, 5.341226521)
-points['spawn1'] = (-3.902942148, 4.380655495,
- -8.962074979) + (1.66339533, 1.0, 0.8737798622)
-points['spawn2'] = (4.775040345, 4.406820459, -9.007239732) + (1.66339533, 1.0,
- 0.8737798622)
+points['spawn1'] = (-3.902942148, 4.380655495, -8.962074979) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
+points['spawn2'] = (4.775040345, 4.406820459, -9.007239732) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
points['tnt1'] = (0.4599593402, 4.044276501, -6.573537395)
diff --git a/assets/src/ba_data/python/bastd/mapdata/tip_top.py b/assets/src/ba_data/python/bastd/mapdata/tip_top.py
index 7e8a17d6..19c9326d 100644
--- a/assets/src/ba_data/python/bastd/mapdata/tip_top.py
+++ b/assets/src/ba_data/python/bastd/mapdata/tip_top.py
@@ -6,28 +6,54 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (0.004375512593, 7.141135803,
- -0.01745294675) + (0.0, 0.0, 0.0) + (
- 21.12506141, 4.959977313, 16.6885592)
+boxes['area_of_interest_bounds'] = (
+ (0.004375512593, 7.141135803, -0.01745294675)
+ + (0.0, 0.0, 0.0)
+ + (21.12506141, 4.959977313, 16.6885592)
+)
points['ffa_spawn1'] = (-4.211611443, 6.96684623, -3.792009469) + (
- 0.3862608373, 1.155037873, 0.301362335)
-points['ffa_spawn2'] = (7.384331793, 5.212769921,
- -2.788130319) + (1.155037873, 1.155037873, 1.155037873)
-points['ffa_spawn3'] = (-7.264053816, 5.461241477,
- -3.095884089) + (1.155037873, 1.155037873, 1.155037873)
-points['ffa_spawn4'] = (0.02413253541, 5.367227206,
- 4.075190968) + (1.875050182, 1.155037873, 0.2019443553)
+ 0.3862608373,
+ 1.155037873,
+ 0.301362335,
+)
+points['ffa_spawn2'] = (7.384331793, 5.212769921, -2.788130319) + (
+ 1.155037873,
+ 1.155037873,
+ 1.155037873,
+)
+points['ffa_spawn3'] = (-7.264053816, 5.461241477, -3.095884089) + (
+ 1.155037873,
+ 1.155037873,
+ 1.155037873,
+)
+points['ffa_spawn4'] = (0.02413253541, 5.367227206, 4.075190968) + (
+ 1.875050182,
+ 1.155037873,
+ 0.2019443553,
+)
points['ffa_spawn5'] = (-1.571185756, 7.042332385, -0.4760548825) + (
- 0.3862608373, 1.155037873, 0.301362335)
+ 0.3862608373,
+ 1.155037873,
+ 0.301362335,
+)
points['ffa_spawn6'] = (1.693597207, 7.042332385, -0.4760548825) + (
- 0.3862608373, 1.155037873, 0.301362335)
+ 0.3862608373,
+ 1.155037873,
+ 0.301362335,
+)
points['ffa_spawn7'] = (4.398059102, 6.96684623, -3.802802846) + (
- 0.3862608373, 1.155037873, 0.301362335)
+ 0.3862608373,
+ 1.155037873,
+ 0.301362335,
+)
points['flag1'] = (-7.006685836, 5.420897881, -2.717154638)
points['flag2'] = (7.166003893, 5.166226103, -2.651234621)
points['flag_default'] = (0.07287520555, 8.865234972, -4.988876512)
-boxes['map_bounds'] = (-0.2103025678, 7.746661892, -0.3767425594) + (
- 0.0, 0.0, 0.0) + (23.8148841, 13.86473252, 16.37749544)
+boxes['map_bounds'] = (
+ (-0.2103025678, 7.746661892, -0.3767425594)
+ + (0.0, 0.0, 0.0)
+ + (23.8148841, 13.86473252, 16.37749544)
+)
points['powerup_spawn1'] = (1.660037213, 8.050002248, -1.221221367)
points['powerup_spawn2'] = (-1.486576666, 7.912313704, -1.233393956)
points['powerup_spawn3'] = (2.629546191, 6.361794487, 1.399066775)
@@ -36,7 +62,13 @@ points['shadow_lower_bottom'] = (0.07287520555, 4.000760229, 6.31658856)
points['shadow_lower_top'] = (0.07287520555, 4.751182548, 6.31658856)
points['shadow_upper_bottom'] = (0.07287520555, 9.154987885, 6.31658856)
points['shadow_upper_top'] = (0.07287520555, 13.8166857, 6.31658856)
-points['spawn1'] = (-7.264053816, 5.461241477,
- -3.095884089) + (1.155037873, 1.155037873, 1.155037873)
-points['spawn2'] = (7.384331793, 5.212769921,
- -2.788130319) + (1.155037873, 1.155037873, 1.155037873)
+points['spawn1'] = (-7.264053816, 5.461241477, -3.095884089) + (
+ 1.155037873,
+ 1.155037873,
+ 1.155037873,
+)
+points['spawn2'] = (7.384331793, 5.212769921, -2.788130319) + (
+ 1.155037873,
+ 1.155037873,
+ 1.155037873,
+)
diff --git a/assets/src/ba_data/python/bastd/mapdata/tower_d.py b/assets/src/ba_data/python/bastd/mapdata/tower_d.py
index bc7b4350..0816c2b5 100644
--- a/assets/src/ba_data/python/bastd/mapdata/tower_d.py
+++ b/assets/src/ba_data/python/bastd/mapdata/tower_d.py
@@ -6,58 +6,120 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-0.4714933293, 2.887077774,
- -1.505479919) + (0.0, 0.0, 0.0) + (
- 17.90145968, 6.188484831, 15.96149117)
-boxes['b1'] = (-4.832680224, 2.581977222, -2.345017289) + (0.0, 0.0, 0.0) + (
- 0.9362069954, 2.750877211, 7.267441751)
-boxes['b2'] = (4.74117666, 2.581977222, -3.341802598) + (0.0, 0.0, 0.0) + (
- 0.8162971446, 2.750877211, 7.222259865)
-boxes['b3'] = (6.423991249, 2.581977222, -4.423628975) + (0.0, 0.0, 0.0) + (
- 0.8263776825, 4.534503966, 9.174814862)
-boxes['b4'] = (-6.647568724, 3.229920028, -3.057507544) + (0.0, 0.0, 0.0) + (
- 0.9362069954, 2.750877211, 8.925082868)
-boxes['b5'] = (3.537030094, 2.581977222, -2.366599524) + (0.0, 0.0, 0.0) + (
- 0.6331341496, 2.750877211, 5.327279595)
-boxes['b6'] = (5.758481164, 2.581977222, 1.147676334) + (0.0, 0.0, 0.0) + (
- 2.300443227, 2.182113263, 0.5026852656)
-boxes['b7'] = (-2.872805708, 2.581977222, -1.563390778) + (0.0, 0.0, 0.0) + (
- 0.9362069954, 2.750877211, 5.916439874)
-boxes['b8'] = (-0.6884909563, 4.756572255, -5.317308535) + (0.0, 0.0, 0.0) + (
- 24.86081756, 9.001418153, 13.43159632)
-boxes['b9'] = (-0.01526615369, 2.860860149, -0.9527080851) + (
- 0.0, 0.0, 0.0) + (4.899031047, 3.041987448, 6.142004808)
+boxes['area_of_interest_bounds'] = (
+ (-0.4714933293, 2.887077774, -1.505479919)
+ + (0.0, 0.0, 0.0)
+ + (17.90145968, 6.188484831, 15.96149117)
+)
+boxes['b1'] = (
+ (-4.832680224, 2.581977222, -2.345017289)
+ + (0.0, 0.0, 0.0)
+ + (0.9362069954, 2.750877211, 7.267441751)
+)
+boxes['b2'] = (
+ (4.74117666, 2.581977222, -3.341802598)
+ + (0.0, 0.0, 0.0)
+ + (0.8162971446, 2.750877211, 7.222259865)
+)
+boxes['b3'] = (
+ (6.423991249, 2.581977222, -4.423628975)
+ + (0.0, 0.0, 0.0)
+ + (0.8263776825, 4.534503966, 9.174814862)
+)
+boxes['b4'] = (
+ (-6.647568724, 3.229920028, -3.057507544)
+ + (0.0, 0.0, 0.0)
+ + (0.9362069954, 2.750877211, 8.925082868)
+)
+boxes['b5'] = (
+ (3.537030094, 2.581977222, -2.366599524)
+ + (0.0, 0.0, 0.0)
+ + (0.6331341496, 2.750877211, 5.327279595)
+)
+boxes['b6'] = (
+ (5.758481164, 2.581977222, 1.147676334)
+ + (0.0, 0.0, 0.0)
+ + (2.300443227, 2.182113263, 0.5026852656)
+)
+boxes['b7'] = (
+ (-2.872805708, 2.581977222, -1.563390778)
+ + (0.0, 0.0, 0.0)
+ + (0.9362069954, 2.750877211, 5.916439874)
+)
+boxes['b8'] = (
+ (-0.6884909563, 4.756572255, -5.317308535)
+ + (0.0, 0.0, 0.0)
+ + (24.86081756, 9.001418153, 13.43159632)
+)
+boxes['b9'] = (
+ (-0.01526615369, 2.860860149, -0.9527080851)
+ + (0.0, 0.0, 0.0)
+ + (4.899031047, 3.041987448, 6.142004808)
+)
points['bot_spawn_bottom_left'] = (-7.400801881, 1.617640411, 5.384327397) + (
- 1.66339533, 1.0, 0.8737798622)
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
points['bot_spawn_bottom_right'] = (6.492270514, 1.617640411, 5.384327397) + (
- 1.66339533, 1.0, 0.8737798622)
-points['bot_spawn_start'] = (-9.000552706, 3.1524,
- 0.3095359717) + (1.66339533, 1.0, 0.8737798622)
-boxes['edge_box'] = (-0.6502137344, 3.189969807, 4.099657551) + (
- 0.0, 0.0, 0.0) + (14.24474785, 1.469696777, 2.898807504)
-boxes['edge_box2'] = (-0.144434237, 2.297109431, 0.3535332953) + (
- 0.0, 0.0, 0.0) + (1.744002325, 1.060637489, 4.905560105)
-points['ffa_spawn1'] = (0.1024602894, 2.713599022,
- -0.3639688715) + (1.66339533, 1.0, 0.8737798622)
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
+points['bot_spawn_start'] = (-9.000552706, 3.1524, 0.3095359717) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
+boxes['edge_box'] = (
+ (-0.6502137344, 3.189969807, 4.099657551)
+ + (0.0, 0.0, 0.0)
+ + (14.24474785, 1.469696777, 2.898807504)
+)
+boxes['edge_box2'] = (
+ (-0.144434237, 2.297109431, 0.3535332953)
+ + (0.0, 0.0, 0.0)
+ + (1.744002325, 1.060637489, 4.905560105)
+)
+points['ffa_spawn1'] = (0.1024602894, 2.713599022, -0.3639688715) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
points['flag1'] = (-7.751267587, 3.143417127, 0.266009523)
points['flag2'] = (6.851493725, 2.251048381, 0.3832888796)
points['flag_default'] = (0.01935110284, 2.227757733, -6.312658764)
-boxes['map_bounds'] = (0.2608783669, 4.899663734, -3.543675157) + (
- 0.0, 0.0, 0.0) + (29.23565494, 14.19991443, 29.92689344)
-boxes['powerup_region'] = (0.3544110667, 4.036899334, 3.413130338) + (
- 0.0, 0.0, 0.0) + (12.48542377, 0.3837028864, 1.622880843)
+boxes['map_bounds'] = (
+ (0.2608783669, 4.899663734, -3.543675157)
+ + (0.0, 0.0, 0.0)
+ + (29.23565494, 14.19991443, 29.92689344)
+)
+boxes['powerup_region'] = (
+ (0.3544110667, 4.036899334, 3.413130338)
+ + (0.0, 0.0, 0.0)
+ + (12.48542377, 0.3837028864, 1.622880843)
+)
points['powerup_spawn1'] = (-4.926642821, 2.397645612, 2.876782366)
points['powerup_spawn2'] = (-1.964335546, 2.397645612, 3.751374716)
points['powerup_spawn3'] = (1.64883201, 2.397645612, 3.751374716)
points['powerup_spawn4'] = (4.398865293, 2.397645612, 2.877618924)
-boxes['score_region'] = (8.378672831, 3.049210364, 0.5079393073) + (
- 0.0, 0.0, 0.0) + (1.276586051, 1.368127109, 0.6915290191)
+boxes['score_region'] = (
+ (8.378672831, 3.049210364, 0.5079393073)
+ + (0.0, 0.0, 0.0)
+ + (1.276586051, 1.368127109, 0.6915290191)
+)
points['shadow_lower_bottom'] = (-0.2912522507, 0.9466821599, 5.341226521)
points['shadow_lower_top'] = (-0.2912522507, 2.131949842, 5.341226521)
points['shadow_upper_bottom'] = (-0.4529958189, 6.062361813, 5.341226521)
points['shadow_upper_top'] = (-0.2912522507, 9.827201965, 5.341226521)
-points['spawn1'] = (0.1024602894, 2.713599022,
- -0.3639688715) + (1.66339533, 1.0, 0.8737798622)
-points['spawn2'] = (0.1449413466, 2.739700702,
- -0.3431945526) + (1.66339533, 1.0, 0.8737798622)
+points['spawn1'] = (0.1024602894, 2.713599022, -0.3639688715) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
+points['spawn2'] = (0.1449413466, 2.739700702, -0.3431945526) + (
+ 1.66339533,
+ 1.0,
+ 0.8737798622,
+)
points['tnt_loc'] = (0.008297003631, 2.777570118, 3.894548697)
diff --git a/assets/src/ba_data/python/bastd/mapdata/zig_zag.py b/assets/src/ba_data/python/bastd/mapdata/zig_zag.py
index 701d9f82..51fec1a8 100644
--- a/assets/src/ba_data/python/bastd/mapdata/zig_zag.py
+++ b/assets/src/ba_data/python/bastd/mapdata/zig_zag.py
@@ -6,23 +6,41 @@
points = {}
# noinspection PyDictCreation
boxes = {}
-boxes['area_of_interest_bounds'] = (-1.807378035, 3.943412768, -1.61304303) + (
- 0.0, 0.0, 0.0) + (23.01413538, 13.27980464, 10.0098376)
-points['ffa_spawn1'] = (-9.523537347, 4.645005984,
- -3.193868606) + (0.8511546708, 1.0, 1.303629055)
-points['ffa_spawn2'] = (6.217011718, 4.632396767,
- -3.190279407) + (0.8844870264, 1.0, 1.303629055)
-points['ffa_spawn3'] = (-4.433947713, 3.005988298,
- -4.908942678) + (1.531254794, 1.0, 0.7550370795)
-points['ffa_spawn4'] = (1.457496709, 3.01461103,
- -4.907036892) + (1.531254794, 1.0, 0.7550370795)
+boxes['area_of_interest_bounds'] = (
+ (-1.807378035, 3.943412768, -1.61304303)
+ + (0.0, 0.0, 0.0)
+ + (23.01413538, 13.27980464, 10.0098376)
+)
+points['ffa_spawn1'] = (-9.523537347, 4.645005984, -3.193868606) + (
+ 0.8511546708,
+ 1.0,
+ 1.303629055,
+)
+points['ffa_spawn2'] = (6.217011718, 4.632396767, -3.190279407) + (
+ 0.8844870264,
+ 1.0,
+ 1.303629055,
+)
+points['ffa_spawn3'] = (-4.433947713, 3.005988298, -4.908942678) + (
+ 1.531254794,
+ 1.0,
+ 0.7550370795,
+)
+points['ffa_spawn4'] = (1.457496709, 3.01461103, -4.907036892) + (
+ 1.531254794,
+ 1.0,
+ 0.7550370795,
+)
points['flag1'] = (-9.969465388, 4.651689505, -4.965994534)
points['flag2'] = (6.844972512, 4.652142027, -4.951156148)
points['flag3'] = (1.155692239, 2.973774873, -4.890524808)
points['flag4'] = (-4.224099354, 3.006208239, -4.890524808)
points['flag_default'] = (-1.42651413, 3.018804771, 0.8808626995)
-boxes['map_bounds'] = (-1.567840276, 8.764115729, -1.311948055) + (
- 0.0, 0.0, 0.0) + (28.76792809, 17.64963076, 19.52220249)
+boxes['map_bounds'] = (
+ (-1.567840276, 8.764115729, -1.311948055)
+ + (0.0, 0.0, 0.0)
+ + (28.76792809, 17.64963076, 19.52220249)
+)
points['powerup_spawn1'] = (2.55551802, 4.369175386, -4.798598276)
points['powerup_spawn2'] = (-6.02484656, 4.369175386, -4.798598276)
points['powerup_spawn3'] = (5.557525662, 5.378865401, -4.798598276)
@@ -31,16 +49,34 @@ points['shadow_lower_bottom'] = (-1.42651413, 1.681630068, 4.790029929)
points['shadow_lower_top'] = (-1.42651413, 2.54918356, 4.790029929)
points['shadow_upper_bottom'] = (-1.42651413, 6.802574329, 4.790029929)
points['shadow_upper_top'] = (-1.42651413, 8.779767257, 4.790029929)
-points['spawn1'] = (-9.523537347, 4.645005984,
- -3.193868606) + (0.8511546708, 1.0, 1.303629055)
-points['spawn2'] = (6.217011718, 4.632396767,
- -3.190279407) + (0.8844870264, 1.0, 1.303629055)
-points['spawn_by_flag1'] = (-9.523537347, 4.645005984,
- -3.193868606) + (0.8511546708, 1.0, 1.303629055)
-points['spawn_by_flag2'] = (6.217011718, 4.632396767,
- -3.190279407) + (0.8844870264, 1.0, 1.303629055)
-points['spawn_by_flag3'] = (1.457496709, 3.01461103,
- -4.907036892) + (1.531254794, 1.0, 0.7550370795)
-points['spawn_by_flag4'] = (-4.433947713, 3.005988298,
- -4.908942678) + (1.531254794, 1.0, 0.7550370795)
+points['spawn1'] = (-9.523537347, 4.645005984, -3.193868606) + (
+ 0.8511546708,
+ 1.0,
+ 1.303629055,
+)
+points['spawn2'] = (6.217011718, 4.632396767, -3.190279407) + (
+ 0.8844870264,
+ 1.0,
+ 1.303629055,
+)
+points['spawn_by_flag1'] = (-9.523537347, 4.645005984, -3.193868606) + (
+ 0.8511546708,
+ 1.0,
+ 1.303629055,
+)
+points['spawn_by_flag2'] = (6.217011718, 4.632396767, -3.190279407) + (
+ 0.8844870264,
+ 1.0,
+ 1.303629055,
+)
+points['spawn_by_flag3'] = (1.457496709, 3.01461103, -4.907036892) + (
+ 1.531254794,
+ 1.0,
+ 0.7550370795,
+)
+points['spawn_by_flag4'] = (-4.433947713, 3.005988298, -4.908942678) + (
+ 1.531254794,
+ 1.0,
+ 0.7550370795,
+)
points['tnt1'] = (-1.42651413, 4.045239665, 0.04094631341)
diff --git a/assets/src/ba_data/python/bastd/maps.py b/assets/src/ba_data/python/bastd/maps.py
index 7d3779e4..c20c82a1 100644
--- a/assets/src/ba_data/python/bastd/maps.py
+++ b/assets/src/ba_data/python/bastd/maps.py
@@ -18,6 +18,7 @@ class HockeyStadium(ba.Map):
"""Stadium map used for ice hockey games."""
from bastd.mapdata import hockey_stadium as defs
+
name = 'Hockey Stadium'
@classmethod
@@ -32,13 +33,15 @@ class HockeyStadium(ba.Map):
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
- 'models': (ba.getmodel('hockeyStadiumOuter'),
- ba.getmodel('hockeyStadiumInner'),
- ba.getmodel('hockeyStadiumStands')),
+ 'models': (
+ ba.getmodel('hockeyStadiumOuter'),
+ ba.getmodel('hockeyStadiumInner'),
+ ba.getmodel('hockeyStadiumStands'),
+ ),
'vr_fill_model': ba.getmodel('footballStadiumVRFill'),
'collide_model': ba.getcollidemodel('hockeyStadiumCollide'),
'tex': ba.gettexture('hockeyStadium'),
- 'stands_tex': ba.gettexture('footballStadium')
+ 'stands_tex': ba.gettexture('footballStadium'),
}
mat = ba.Material()
mat.add_actions(actions=('modify_part_collision', 'friction', 0.01))
@@ -48,44 +51,48 @@ class HockeyStadium(ba.Map):
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
- self.node = ba.newnode('terrain',
- delegate=self,
- attrs={
- 'model':
- self.preloaddata['models'][0],
- 'collide_model':
- self.preloaddata['collide_model'],
- 'color_texture':
- self.preloaddata['tex'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['ice_material']
- ]
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_model'],
- 'vr_only': True,
- 'lighting': False,
- 'background': True,
- 'color_texture': self.preloaddata['stands_tex']
- })
+ self.node = ba.newnode(
+ 'terrain',
+ delegate=self,
+ attrs={
+ 'model': self.preloaddata['models'][0],
+ 'collide_model': self.preloaddata['collide_model'],
+ 'color_texture': self.preloaddata['tex'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['ice_material'],
+ ],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_model'],
+ 'vr_only': True,
+ 'lighting': False,
+ 'background': True,
+ 'color_texture': self.preloaddata['stands_tex'],
+ },
+ )
mats = [shared.footing_material, self.preloaddata['ice_material']]
- self.floor = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['models'][1],
- 'color_texture': self.preloaddata['tex'],
- 'opacity': 0.92,
- 'opacity_in_low_or_medium_quality': 1.0,
- 'materials': mats
- })
+ self.floor = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['models'][1],
+ 'color_texture': self.preloaddata['tex'],
+ 'opacity': 0.92,
+ 'opacity_in_low_or_medium_quality': 1.0,
+ 'materials': mats,
+ },
+ )
self.stands = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['models'][2],
'visible_in_reflections': False,
- 'color_texture': self.preloaddata['stands_tex']
- })
+ 'color_texture': self.preloaddata['stands_tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.floor_reflection = True
gnode.debris_friction = 0.3
@@ -101,6 +108,7 @@ class HockeyStadium(ba.Map):
class FootballStadium(ba.Map):
"""Stadium map for football games."""
+
from bastd.mapdata import football_stadium as defs
name = 'Football Stadium'
@@ -120,7 +128,7 @@ class FootballStadium(ba.Map):
'model': ba.getmodel('footballStadium'),
'vr_fill_model': ba.getmodel('footballStadiumVRFill'),
'collide_model': ba.getcollidemodel('footballStadiumCollide'),
- 'tex': ba.gettexture('footballStadium')
+ 'tex': ba.gettexture('footballStadium'),
}
return data
@@ -134,16 +142,19 @@ class FootballStadium(ba.Map):
'model': self.preloaddata['model'],
'collide_model': self.preloaddata['collide_model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.3, 1.2, 1.0)
gnode.ambient_color = (1.3, 1.2, 1.0)
@@ -152,9 +163,7 @@ class FootballStadium(ba.Map):
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
- 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:
box_position = self.defs.boxes['edge_box'][0:3]
box_scale = self.defs.boxes['edge_box'][6:9]
xpos = (point.x - box_position[0]) / box_scale[0]
@@ -164,6 +173,7 @@ class FootballStadium(ba.Map):
class Bridgit(ba.Map):
"""Map with a narrow bridge in the middle."""
+
from bastd.mapdata import bridgit as defs
name = 'Bridgit'
@@ -190,12 +200,14 @@ class Bridgit(ba.Map):
'tex': ba.gettexture('bridgitLevelColor'),
'model_bg_tex': ba.gettexture('natureBackgroundColor'),
'collide_bg': ba.getcollidemodel('natureBackgroundCollide'),
- 'railing_collide_model':
- (ba.getcollidemodel('bridgitLevelRailingCollide')),
- 'bg_material': ba.Material()
+ 'railing_collide_model': (
+ ba.getcollidemodel('bridgitLevelRailingCollide')
+ ),
+ 'bg_material': ba.Material(),
}
- data['bg_material'].add_actions(actions=('modify_part_collision',
- 'friction', 10.0))
+ data['bg_material'].add_actions(
+ actions=('modify_part_collision', 'friction', 10.0)
+ )
return data
def __init__(self) -> None:
@@ -208,47 +220,55 @@ class Bridgit(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model_top'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_bottom'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_bottom'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['model_bg'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bg_vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bg_vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
- self.bg_collide = ba.newnode('terrain',
- attrs={
- 'collide_model':
- self.preloaddata['collide_bg'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['bg_material'],
- shared.death_material
- ]
- })
+ 'bumper': True,
+ },
+ )
+ self.bg_collide = ba.newnode(
+ 'terrain',
+ attrs={
+ 'collide_model': self.preloaddata['collide_bg'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['bg_material'],
+ shared.death_material,
+ ],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.1, 1.2, 1.3)
gnode.ambient_color = (1.1, 1.2, 1.3)
@@ -267,8 +287,12 @@ class BigG(ba.Map):
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return [
- 'race', 'melee', 'keep_away', 'team_flag', 'king_of_the_hill',
- 'conquest'
+ 'race',
+ 'melee',
+ 'keep_away',
+ 'team_flag',
+ 'king_of_the_hill',
+ 'conquest',
]
@classmethod
@@ -287,10 +311,11 @@ class BigG(ba.Map):
'model_bg_tex': ba.gettexture('natureBackgroundColor'),
'collide_bg': ba.getcollidemodel('natureBackgroundCollide'),
'bumper_collide_model': ba.getcollidemodel('bigGBumper'),
- 'bg_material': ba.Material()
+ 'bg_material': ba.Material(),
}
- data['bg_material'].add_actions(actions=('modify_part_collision',
- 'friction', 10.0))
+ data['bg_material'].add_actions(
+ actions=('modify_part_collision', 'friction', 10.0)
+ )
return data
def __init__(self) -> None:
@@ -304,48 +329,56 @@ class BigG(ba.Map):
'color': (0.7, 0.7, 0.7),
'model': self.preloaddata['model_top'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_bottom'],
- 'color': (0.7, 0.7, 0.7),
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_bottom'],
+ 'color': (0.7, 0.7, 0.7),
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['model_bg'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bg_vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bg_vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['bumper_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
- self.bg_collide = ba.newnode('terrain',
- attrs={
- 'collide_model':
- self.preloaddata['collide_bg'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['bg_material'],
- shared.death_material
- ]
- })
+ 'bumper': True,
+ },
+ )
+ self.bg_collide = ba.newnode(
+ 'terrain',
+ attrs={
+ 'collide_model': self.preloaddata['collide_bg'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['bg_material'],
+ shared.death_material,
+ ],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.1, 1.2, 1.3)
gnode.ambient_color = (1.1, 1.2, 1.3)
@@ -380,12 +413,14 @@ class Roundabout(ba.Map):
'tex': ba.gettexture('roundaboutLevelColor'),
'model_bg_tex': ba.gettexture('natureBackgroundColor'),
'collide_bg': ba.getcollidemodel('natureBackgroundCollide'),
- 'railing_collide_model':
- (ba.getcollidemodel('roundaboutLevelBumper')),
- 'bg_material': ba.Material()
+ 'railing_collide_model': (
+ ba.getcollidemodel('roundaboutLevelBumper')
+ ),
+ 'bg_material': ba.Material(),
}
- data['bg_material'].add_actions(actions=('modify_part_collision',
- 'friction', 10.0))
+ data['bg_material'].add_actions(
+ actions=('modify_part_collision', 'friction', 10.0)
+ )
return data
def __init__(self) -> None:
@@ -398,47 +433,55 @@ class Roundabout(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_bottom'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_bottom'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['model_bg'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bg_vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- self.bg_collide = ba.newnode('terrain',
- attrs={
- 'collide_model':
- self.preloaddata['collide_bg'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['bg_material'],
- shared.death_material
- ]
- })
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bg_vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ self.bg_collide = ba.newnode(
+ 'terrain',
+ attrs={
+ 'collide_model': self.preloaddata['collide_bg'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['bg_material'],
+ shared.death_material,
+ ],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
+ 'bumper': True,
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.0, 1.05, 1.1)
gnode.ambient_color = (1.0, 1.05, 1.1)
@@ -474,12 +517,14 @@ class MonkeyFace(ba.Map):
'tex': ba.gettexture('monkeyFaceLevelColor'),
'model_bg_tex': ba.gettexture('natureBackgroundColor'),
'collide_bg': ba.getcollidemodel('natureBackgroundCollide'),
- 'railing_collide_model':
- (ba.getcollidemodel('monkeyFaceLevelBumper')),
- 'bg_material': ba.Material()
+ 'railing_collide_model': (
+ ba.getcollidemodel('monkeyFaceLevelBumper')
+ ),
+ 'bg_material': ba.Material(),
}
- data['bg_material'].add_actions(actions=('modify_part_collision',
- 'friction', 10.0))
+ data['bg_material'].add_actions(
+ actions=('modify_part_collision', 'friction', 10.0)
+ )
return data
def __init__(self) -> None:
@@ -492,47 +537,55 @@ class MonkeyFace(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['model_bg'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bg_vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- self.bg_collide = ba.newnode('terrain',
- attrs={
- 'collide_model':
- self.preloaddata['collide_bg'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['bg_material'],
- shared.death_material
- ]
- })
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bg_vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ self.bg_collide = ba.newnode(
+ 'terrain',
+ attrs={
+ 'collide_model': self.preloaddata['collide_bg'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['bg_material'],
+ shared.death_material,
+ ],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
+ 'bumper': True,
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.1, 1.2, 1.2)
gnode.ambient_color = (1.2, 1.3, 1.3)
@@ -552,7 +605,11 @@ class ZigZag(ba.Map):
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return [
- 'melee', 'keep_away', 'team_flag', 'conquest', 'king_of_the_hill'
+ 'melee',
+ 'keep_away',
+ 'team_flag',
+ 'conquest',
+ 'king_of_the_hill',
]
@classmethod
@@ -571,10 +628,11 @@ class ZigZag(ba.Map):
'model_bg_tex': ba.gettexture('natureBackgroundColor'),
'collide_bg': ba.getcollidemodel('natureBackgroundCollide'),
'railing_collide_model': ba.getcollidemodel('zigZagLevelBumper'),
- 'bg_material': ba.Material()
+ 'bg_material': ba.Material(),
}
- data['bg_material'].add_actions(actions=('modify_part_collision',
- 'friction', 10.0))
+ data['bg_material'].add_actions(
+ actions=('modify_part_collision', 'friction', 10.0)
+ )
return data
def __init__(self) -> None:
@@ -587,46 +645,54 @@ class ZigZag(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['model_bg'],
'lighting': False,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_bottom'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bg_vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['model_bg_tex']
- })
- self.bg_collide = ba.newnode('terrain',
- attrs={
- 'collide_model':
- self.preloaddata['collide_bg'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['bg_material'],
- shared.death_material
- ]
- })
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_bottom'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bg_vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['model_bg_tex'],
+ },
+ )
+ self.bg_collide = ba.newnode(
+ 'terrain',
+ attrs={
+ 'collide_model': self.preloaddata['collide_bg'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['bg_material'],
+ shared.death_material,
+ ],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
+ 'bumper': True,
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.0, 1.15, 1.15)
gnode.ambient_color = (1.0, 1.15, 1.15)
@@ -662,7 +728,7 @@ class ThePad(ba.Map):
'bgmodel': ba.getmodel('thePadBG'),
'railing_collide_model': ba.getcollidemodel('thePadLevelBumper'),
'vr_fill_mound_model': ba.getmodel('thePadVRFillMound'),
- 'vr_fill_mound_tex': ba.gettexture('vrFillMound')
+ 'vr_fill_mound_tex': ba.gettexture('vrFillMound'),
}
# fixme should chop this into vr/non-vr sections for efficiency
return data
@@ -677,38 +743,45 @@ class ThePad(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.56, 0.55, 0.47),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'bumper': True,
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.56, 0.55, 0.47),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.1, 1.1, 1.0)
gnode.ambient_color = (1.1, 1.1, 1.0)
@@ -742,7 +815,7 @@ class DoomShroom(ba.Map):
'bgmodel': ba.getmodel('doomShroomBG'),
'vr_fill_model': ba.getmodel('doomShroomVRFill'),
'stem_model': ba.getmodel('doomShroomStem'),
- 'collide_bg': ba.getcollidemodel('doomShroomStemCollide')
+ 'collide_bg': ba.getcollidemodel('doomShroomStemCollide'),
}
return data
@@ -756,36 +829,43 @@ class DoomShroom(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
- self.stem = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['stem_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
+ self.stem = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['stem_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.bg_collide = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['collide_bg'],
- 'materials': [shared.footing_material, shared.death_material]
- })
+ 'materials': [shared.footing_material, shared.death_material],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (0.82, 1.10, 1.15)
gnode.ambient_color = (0.9, 1.3, 1.1)
@@ -793,9 +873,7 @@ class DoomShroom(ba.Map):
gnode.vignette_outer = (0.76, 0.76, 0.76)
gnode.vignette_inner = (0.95, 0.95, 0.99)
- 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:
xpos = point.x
zpos = point.z
x_adj = xpos * 0.125
@@ -831,7 +909,7 @@ class LakeFrigid(ba.Map):
'collide_model': ba.getcollidemodel('lakeFrigidCollide'),
'tex': ba.gettexture('lakeFrigid'),
'tex_reflections': ba.gettexture('lakeFrigidReflections'),
- 'vr_fill_model': ba.getmodel('lakeFrigidVRFill')
+ 'vr_fill_model': ba.getmodel('lakeFrigidVRFill'),
}
mat = ba.Material()
mat.add_actions(actions=('modify_part_collision', 'friction', 0.01))
@@ -841,42 +919,47 @@ class LakeFrigid(ba.Map):
def __init__(self) -> None:
super().__init__()
shared = SharedObjects.get()
- self.node = ba.newnode('terrain',
- delegate=self,
- attrs={
- 'collide_model':
- self.preloaddata['collide_model'],
- 'model':
- self.preloaddata['model'],
- 'color_texture':
- self.preloaddata['tex'],
- 'materials': [
- shared.footing_material,
- self.preloaddata['ice_material']
- ]
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_top'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_reflections'],
- 'lighting': False,
- 'overlay': True,
- 'opacity': 0.15,
- 'color_texture': self.preloaddata['tex_reflections']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['tex']
- })
+ self.node = ba.newnode(
+ 'terrain',
+ delegate=self,
+ attrs={
+ 'collide_model': self.preloaddata['collide_model'],
+ 'model': self.preloaddata['model'],
+ 'color_texture': self.preloaddata['tex'],
+ 'materials': [
+ shared.footing_material,
+ self.preloaddata['ice_material'],
+ ],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_top'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_reflections'],
+ 'lighting': False,
+ 'overlay': True,
+ 'opacity': 0.15,
+ 'color_texture': self.preloaddata['tex_reflections'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1, 1, 1)
gnode.ambient_color = (1, 1, 1)
@@ -912,7 +995,7 @@ class TipTop(ba.Map):
'tex': ba.gettexture('tipTopLevelColor'),
'bgtex': ba.gettexture('tipTopBGColor'),
'bgmodel': ba.getmodel('tipTopBG'),
- 'railing_collide_model': ba.getcollidemodel('tipTopLevelBumper')
+ 'railing_collide_model': ba.getcollidemodel('tipTopLevelBumper'),
}
return data
@@ -927,15 +1010,18 @@ class TipTop(ba.Map):
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
'color': (0.7, 0.7, 0.7),
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color': (0.7, 0.7, 0.7),
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color': (0.7, 0.7, 0.7),
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
@@ -943,15 +1029,17 @@ class TipTop(ba.Map):
'lighting': False,
'color': (0.4, 0.4, 0.4),
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
+ 'bumper': True,
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (0.8, 0.9, 1.3)
gnode.ambient_color = (0.8, 0.9, 1.3)
@@ -984,10 +1072,11 @@ class CragCastle(ba.Map):
'tex': ba.gettexture('cragCastleLevelColor'),
'bgtex': ba.gettexture('menuBG'),
'bgmodel': ba.getmodel('thePadBG'),
- 'railing_collide_model':
- (ba.getcollidemodel('cragCastleLevelBumper')),
+ 'railing_collide_model': (
+ ba.getcollidemodel('cragCastleLevelBumper')
+ ),
'vr_fill_mound_model': ba.getmodel('cragCastleVRFillMound'),
- 'vr_fill_mound_tex': ba.gettexture('vrFillMound')
+ 'vr_fill_mound_tex': ba.gettexture('vrFillMound'),
}
# fixme should chop this into vr/non-vr sections
return data
@@ -1002,38 +1091,45 @@ class CragCastle(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.2, 0.25, 0.2),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'bumper': True,
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.2, 0.25, 0.2),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.shadow_ortho = True
gnode.shadow_offset = (0, 0, -5.0)
@@ -1063,32 +1159,28 @@ class TowerD(ba.Map):
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
- 'model':
- ba.getmodel('towerDLevel'),
- 'model_bottom':
- ba.getmodel('towerDLevelBottom'),
- 'collide_model':
- ba.getcollidemodel('towerDLevelCollide'),
- 'tex':
- ba.gettexture('towerDLevelColor'),
- 'bgtex':
- ba.gettexture('menuBG'),
- 'bgmodel':
- ba.getmodel('thePadBG'),
- 'player_wall_collide_model':
- ba.getcollidemodel('towerDPlayerWall'),
- 'player_wall_material':
- ba.Material()
+ 'model': ba.getmodel('towerDLevel'),
+ 'model_bottom': ba.getmodel('towerDLevelBottom'),
+ 'collide_model': ba.getcollidemodel('towerDLevelCollide'),
+ 'tex': ba.gettexture('towerDLevelColor'),
+ 'bgtex': ba.gettexture('menuBG'),
+ 'bgmodel': ba.getmodel('thePadBG'),
+ 'player_wall_collide_model': ba.getcollidemodel('towerDPlayerWall'),
+ 'player_wall_material': ba.Material(),
}
# fixme should chop this into vr/non-vr sections
data['player_wall_material'].add_actions(
- actions=('modify_part_collision', 'friction', 0.0))
+ actions=('modify_part_collision', 'friction', 0.0)
+ )
# anything that needs to hit the wall can apply this material
data['collide_with_wall_material'] = ba.Material()
data['player_wall_material'].add_actions(
- conditions=('they_dont_have_material',
- data['collide_with_wall_material']),
- actions=('modify_part_collision', 'collide', False))
+ conditions=(
+ 'they_dont_have_material',
+ data['collide_with_wall_material'],
+ ),
+ actions=('modify_part_collision', 'collide', False),
+ )
data['vr_fill_mound_model'] = ba.getmodel('stepRightUpVRFillMound')
data['vr_fill_mound_tex'] = ba.gettexture('vrFillMound')
return data
@@ -1103,49 +1195,53 @@ class TowerD(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.node_bottom = ba.newnode(
'terrain',
delegate=self,
attrs={
'model': self.preloaddata['model_bottom'],
'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.53, 0.57, 0.5),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.53, 0.57, 0.5),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
self.player_wall = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['player_wall_collide_model'],
'affect_bg_dynamics': False,
- 'materials': [self.preloaddata['player_wall_material']]
- })
+ 'materials': [self.preloaddata['player_wall_material']],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.15, 1.11, 1.03)
gnode.ambient_color = (1.2, 1.1, 1.0)
gnode.vignette_outer = (0.7, 0.73, 0.7)
gnode.vignette_inner = (0.95, 0.95, 0.95)
- 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:
# see if we're within edge_box
boxes = self.defs.boxes
box_position = boxes['edge_box'][0:3]
@@ -1157,8 +1253,9 @@ class TowerD(ba.Map):
xpos2 = (point.x - box_position2[0]) / box_scale2[0]
zpos2 = (point.z - box_position2[2]) / box_scale2[2]
# if we're outside of *both* boxes we're near the edge
- return ((xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5) and
- (xpos2 < -0.5 or xpos2 > 0.5 or zpos2 < -0.5 or zpos2 > 0.5))
+ return (xpos < -0.5 or xpos > 0.5 or zpos < -0.5 or zpos > 0.5) and (
+ xpos2 < -0.5 or xpos2 > 0.5 or zpos2 < -0.5 or zpos2 > 0.5
+ )
class HappyThoughts(ba.Map):
@@ -1172,7 +1269,11 @@ class HappyThoughts(ba.Map):
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return [
- 'melee', 'keep_away', 'team_flag', 'conquest', 'king_of_the_hill'
+ 'melee',
+ 'keep_away',
+ 'team_flag',
+ 'conquest',
+ 'king_of_the_hill',
]
@classmethod
@@ -1189,7 +1290,7 @@ class HappyThoughts(ba.Map):
'tex': ba.gettexture('alwaysLandLevelColor'),
'bgtex': ba.gettexture('alwaysLandBGColor'),
'vr_fill_mound_model': ba.getmodel('alwaysLandVRFillMound'),
- 'vr_fill_mound_tex': ba.gettexture('vrFillMound')
+ 'vr_fill_mound_tex': ba.gettexture('vrFillMound'),
}
return data
@@ -1207,31 +1308,37 @@ class HappyThoughts(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
+ 'materials': [shared.footing_material],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.2, 0.25, 0.2),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.2, 0.25, 0.2),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.happy_thoughts_mode = True
gnode.shadow_offset = (0.0, 8.0, 5.0)
@@ -1243,25 +1350,24 @@ class HappyThoughts(ba.Map):
self.is_flying = True
# throw out some tips on flying
- txt = ba.newnode('text',
- attrs={
- 'text': ba.Lstr(resource='pressJumpToFlyText'),
- 'scale': 1.2,
- 'maxwidth': 800,
- 'position': (0, 200),
- 'shadow': 0.5,
- 'flatness': 0.5,
- 'h_align': 'center',
- 'v_attach': 'bottom'
- })
- cmb = ba.newnode('combine',
- owner=txt,
- attrs={
- 'size': 4,
- 'input0': 0.3,
- 'input1': 0.9,
- 'input2': 0.0
- })
+ txt = ba.newnode(
+ 'text',
+ attrs={
+ 'text': ba.Lstr(resource='pressJumpToFlyText'),
+ 'scale': 1.2,
+ 'maxwidth': 800,
+ 'position': (0, 200),
+ 'shadow': 0.5,
+ 'flatness': 0.5,
+ 'h_align': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
+ cmb = ba.newnode(
+ 'combine',
+ owner=txt,
+ attrs={'size': 4, 'input0': 0.3, 'input1': 0.9, 'input2': 0.0},
+ )
ba.animate(cmb, 'input3', {3.0: 0, 4.0: 1, 9.0: 1, 10.0: 0})
cmb.connectattr('output', txt, 'color')
ba.timer(10.0, txt.delete)
@@ -1293,7 +1399,7 @@ class StepRightUp(ba.Map):
'bgtex': ba.gettexture('menuBG'),
'bgmodel': ba.getmodel('thePadBG'),
'vr_fill_mound_model': ba.getmodel('stepRightUpVRFillMound'),
- 'vr_fill_mound_tex': ba.gettexture('vrFillMound')
+ 'vr_fill_mound_tex': ba.gettexture('vrFillMound'),
}
# fixme should chop this into vr/non-vr chunks
return data
@@ -1308,33 +1414,38 @@ class StepRightUp(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.node_bottom = ba.newnode(
'terrain',
delegate=self,
attrs={
'model': self.preloaddata['model_bottom'],
'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.53, 0.57, 0.5),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.53, 0.57, 0.5),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.2, 1.1, 1.0)
gnode.ambient_color = (1.2, 1.1, 1.0)
@@ -1367,19 +1478,24 @@ class Courtyard(ba.Map):
'tex': ba.gettexture('courtyardLevelColor'),
'bgtex': ba.gettexture('menuBG'),
'bgmodel': ba.getmodel('thePadBG'),
- 'player_wall_collide_model':
- (ba.getcollidemodel('courtyardPlayerWall')),
- 'player_wall_material': ba.Material()
+ 'player_wall_collide_model': (
+ ba.getcollidemodel('courtyardPlayerWall')
+ ),
+ 'player_wall_material': ba.Material(),
}
# FIXME: Chop this into vr and non-vr chunks.
data['player_wall_material'].add_actions(
- actions=('modify_part_collision', 'friction', 0.0))
+ actions=('modify_part_collision', 'friction', 0.0)
+ )
# anything that needs to hit the wall should apply this.
data['collide_with_wall_material'] = ba.Material()
data['player_wall_material'].add_actions(
- conditions=('they_dont_have_material',
- data['collide_with_wall_material']),
- actions=('modify_part_collision', 'collide', False))
+ conditions=(
+ 'they_dont_have_material',
+ data['collide_with_wall_material'],
+ ),
+ actions=('modify_part_collision', 'collide', False),
+ )
data['vr_fill_mound_model'] = ba.getmodel('stepRightUpVRFillMound')
data['vr_fill_mound_tex'] = ba.gettexture('vrFillMound')
return data
@@ -1394,31 +1510,37 @@ class Courtyard(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['model_bottom'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_mound_model'],
- 'lighting': False,
- 'vr_only': True,
- 'color': (0.53, 0.57, 0.5),
- 'background': True,
- 'color_texture': self.preloaddata['vr_fill_mound_tex']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['model_bottom'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_mound_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'color': (0.53, 0.57, 0.5),
+ 'background': True,
+ 'color_texture': self.preloaddata['vr_fill_mound_tex'],
+ },
+ )
# in co-op mode games, put up a wall to prevent players
# from getting in the turrets (that would foil our brilliant AI)
if isinstance(ba.getsession(), ba.CoopSession):
@@ -1428,17 +1550,16 @@ class Courtyard(ba.Map):
attrs={
'collide_model': cmodel,
'affect_bg_dynamics': False,
- 'materials': [self.preloaddata['player_wall_material']]
- })
+ 'materials': [self.preloaddata['player_wall_material']],
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.2, 1.17, 1.1)
gnode.ambient_color = (1.2, 1.17, 1.1)
gnode.vignette_outer = (0.6, 0.6, 0.64)
gnode.vignette_inner = (0.95, 0.95, 0.93)
- 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:
# count anything off our ground level as safe (for our platforms)
# see if we're within edge_box
box_position = self.defs.boxes['edge_box'][0:3]
@@ -1476,7 +1597,7 @@ class Rampage(ba.Map):
'bgmodel': ba.getmodel('rampageBG'),
'bgmodel2': ba.getmodel('rampageBG2'),
'vr_fill_model': ba.getmodel('rampageVRFill'),
- 'railing_collide_model': ba.getcollidemodel('rampageBumper')
+ 'railing_collide_model': ba.getcollidemodel('rampageBumper'),
}
return data
@@ -1490,53 +1611,60 @@ class Rampage(ba.Map):
'collide_model': self.preloaddata['collide_model'],
'model': self.preloaddata['model'],
'color_texture': self.preloaddata['tex'],
- 'materials': [shared.footing_material]
- })
+ 'materials': [shared.footing_material],
+ },
+ )
self.background = ba.newnode(
'terrain',
attrs={
'model': self.preloaddata['bgmodel'],
'lighting': False,
'background': True,
- 'color_texture': self.preloaddata['bgtex']
- })
- self.bottom = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bottom_model'],
- 'lighting': False,
- 'color_texture': self.preloaddata['tex']
- })
- self.bg2 = ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['bgmodel2'],
- 'lighting': False,
- 'background': True,
- 'color_texture': self.preloaddata['bgtex2']
- })
- ba.newnode('terrain',
- attrs={
- 'model': self.preloaddata['vr_fill_model'],
- 'lighting': False,
- 'vr_only': True,
- 'background': True,
- 'color_texture': self.preloaddata['bgtex2']
- })
+ 'color_texture': self.preloaddata['bgtex'],
+ },
+ )
+ self.bottom = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bottom_model'],
+ 'lighting': False,
+ 'color_texture': self.preloaddata['tex'],
+ },
+ )
+ self.bg2 = ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['bgmodel2'],
+ 'lighting': False,
+ 'background': True,
+ 'color_texture': self.preloaddata['bgtex2'],
+ },
+ )
+ ba.newnode(
+ 'terrain',
+ attrs={
+ 'model': self.preloaddata['vr_fill_model'],
+ 'lighting': False,
+ 'vr_only': True,
+ 'background': True,
+ 'color_texture': self.preloaddata['bgtex2'],
+ },
+ )
self.railing = ba.newnode(
'terrain',
attrs={
'collide_model': self.preloaddata['railing_collide_model'],
'materials': [shared.railing_material],
- 'bumper': True
- })
+ 'bumper': True,
+ },
+ )
gnode = ba.getactivity().globalsnode
gnode.tint = (1.2, 1.1, 0.97)
gnode.ambient_color = (1.3, 1.2, 1.03)
gnode.vignette_outer = (0.62, 0.64, 0.69)
gnode.vignette_inner = (0.97, 0.95, 0.93)
- 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:
box_position = self.defs.boxes['edge_box'][0:3]
box_scale = self.defs.boxes['edge_box'][6:9]
xpos = (point.x - box_position[0]) / box_scale[0]
diff --git a/assets/src/ba_data/python/bastd/stdmap.py b/assets/src/ba_data/python/bastd/stdmap.py
index 82101d16..505705ac 100644
--- a/assets/src/ba_data/python/bastd/stdmap.py
+++ b/assets/src/ba_data/python/bastd/stdmap.py
@@ -14,18 +14,19 @@ if TYPE_CHECKING:
def _get_map_data(name: str) -> dict[str, Any]:
import json
+
print('Would get map data', name)
- with open('ba_data/data/maps/' + name + '.json',
- encoding='utf-8') as infile:
+ with open(
+ 'ba_data/data/maps/' + name + '.json', encoding='utf-8'
+ ) as infile:
mapdata = json.loads(infile.read())
assert isinstance(mapdata, dict)
return mapdata
class StdMap(ba.Map):
- """A map completely defined by asset data.
+ """A map completely defined by asset data."""
- """
_data: dict[str, Any] | None = None
@classmethod
diff --git a/assets/src/ba_data/python/bastd/tutorial.py b/assets/src/ba_data/python/bastd/tutorial.py
index c92aed86..d2cd7b03 100644
--- a/assets/src/ba_data/python/bastd/tutorial.py
+++ b/assets/src/ba_data/python/bastd/tutorial.py
@@ -32,12 +32,13 @@ def _safesetattr(node: ba.Node | None, attr: str, value: Any) -> None:
class ButtonPress:
-
- def __init__(self,
- button: str,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 0):
+ def __init__(
+ self,
+ button: str,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 0,
+ ):
self._button = button
self._delay = delay
self._release = release
@@ -94,29 +95,37 @@ class ButtonPress:
else:
ba.timer(self._delay, call, timeformat=ba.TimeFormat.MILLISECONDS)
if img is not None:
- ba.timer(self._delay,
- ba.Call(_safesetattr, img, 'color', c_bright),
- timeformat=ba.TimeFormat.MILLISECONDS)
- ba.timer(self._delay,
- ba.Call(_safesetattr, img, 'vr_depth', -30),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ self._delay,
+ ba.Call(_safesetattr, img, 'color', c_bright),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ ba.timer(
+ self._delay,
+ ba.Call(_safesetattr, img, 'vr_depth', -30),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
if self._release:
if self._delay == 0 and self._release_delay == 0:
release_call()
else:
- ba.timer(0.001 * (self._delay + self._release_delay),
- release_call)
+ ba.timer(
+ 0.001 * (self._delay + self._release_delay), release_call
+ )
if img is not None:
- ba.timer(self._delay + self._release_delay + 100,
- ba.Call(_safesetattr, img, 'color', color),
- timeformat=ba.TimeFormat.MILLISECONDS)
- ba.timer(self._delay + self._release_delay + 100,
- ba.Call(_safesetattr, img, 'vr_depth', -20),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ self._delay + self._release_delay + 100,
+ ba.Call(_safesetattr, img, 'color', color),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ ba.timer(
+ self._delay + self._release_delay + 100,
+ ba.Call(_safesetattr, img, 'vr_depth', -20),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
class ButtonRelease:
-
def __init__(self, button: str, delay: int = 0):
self._button = button
self._delay = delay
@@ -154,12 +163,16 @@ class ButtonRelease:
else:
ba.timer(self._delay, call, timeformat=ba.TimeFormat.MILLISECONDS)
if img is not None:
- ba.timer(self._delay + 100,
- ba.Call(_safesetattr, img, 'color', color),
- timeformat=ba.TimeFormat.MILLISECONDS)
- ba.timer(self._delay + 100,
- ba.Call(_safesetattr, img, 'vr_depth', -20),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ self._delay + 100,
+ ba.Call(_safesetattr, img, 'color', color),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ ba.timer(
+ self._delay + 100,
+ ba.Call(_safesetattr, img, 'vr_depth', -20),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
class Player(ba.Player['Team']):
@@ -177,9 +190,9 @@ class Team(ba.Team[Player]):
class TutorialActivity(ba.Activity[Player, Team]):
-
def __init__(self, settings: dict | None = None):
from bastd.maps import Rampage
+
if settings is None:
settings = {}
super().__init__(settings)
@@ -247,46 +260,53 @@ class TutorialActivity(ba.Activity[Player, Team]):
buttons_y = 160
# Need different versions of this: taps/buttons/keys.
- self.text = ba.newnode('text',
- attrs={
- 'text': '',
- 'scale': 1.9,
- 'position': (0, text_y),
- 'maxwidth': 500,
- 'flatness': 0.0,
- 'shadow': 0.5,
- 'h_align': 'center',
- 'v_align': 'center',
- 'v_attach': 'center'
- })
+ self.text = ba.newnode(
+ 'text',
+ attrs={
+ 'text': '',
+ 'scale': 1.9,
+ 'position': (0, text_y),
+ 'maxwidth': 500,
+ 'flatness': 0.0,
+ 'shadow': 0.5,
+ 'h_align': 'center',
+ 'v_align': 'center',
+ 'v_attach': 'center',
+ },
+ )
# Need different versions of this: taps/buttons/keys.
- txt = ba.Lstr(
- resource=self._r +
- '.cpuBenchmarkText') if self._benchmark_type == 'cpu' else ba.Lstr(
- resource=self._r + '.toSkipPressAnythingText')
- t = self._skip_text = ba.newnode('text',
- attrs={
- 'text': txt,
- 'maxwidth': 900,
- 'scale': 1.1,
- 'vr_depth': 100,
- 'position': (0, 30),
- 'h_align': 'center',
- 'v_align': 'center',
- 'v_attach': 'bottom'
- })
+ txt = (
+ ba.Lstr(resource=self._r + '.cpuBenchmarkText')
+ if self._benchmark_type == 'cpu'
+ else ba.Lstr(resource=self._r + '.toSkipPressAnythingText')
+ )
+ t = self._skip_text = ba.newnode(
+ 'text',
+ attrs={
+ 'text': txt,
+ 'maxwidth': 900,
+ 'scale': 1.1,
+ 'vr_depth': 100,
+ 'position': (0, 30),
+ 'h_align': 'center',
+ 'v_align': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
ba.animate(t, 'opacity', {1.0: 0.0, 2.0: 0.7})
- self._skip_count_text = ba.newnode('text',
- attrs={
- 'text': '',
- 'scale': 1.4,
- 'vr_depth': 90,
- 'position': (0, 70),
- 'h_align': 'center',
- 'v_align': 'center',
- 'v_attach': 'bottom'
- })
+ self._skip_count_text = ba.newnode(
+ 'text',
+ attrs={
+ 'text': '',
+ 'scale': 1.4,
+ 'vr_depth': 90,
+ 'position': (0, 70),
+ 'h_align': 'center',
+ 'v_align': 'center',
+ 'v_attach': 'bottom',
+ },
+ )
ouya = False
@@ -303,18 +323,21 @@ class TutorialActivity(ba.Activity[Player, Team]):
return 0.6 * r, 0.6 * g, 0.6 * b
self.jump_image_color = c = _sc(0.4, 1, 0.4)
- self.jump_image = ba.newnode('image',
- attrs={
- 'texture': self._jump_button_tex,
- 'absolute_scale': True,
- 'vr_depth': -20,
- 'position': p,
- 'scale': (image_size, image_size),
- 'color': c
- })
+ self.jump_image = ba.newnode(
+ 'image',
+ attrs={
+ 'texture': self._jump_button_tex,
+ 'absolute_scale': True,
+ 'vr_depth': -20,
+ 'position': p,
+ 'scale': (image_size, image_size),
+ 'color': c,
+ },
+ )
p = (position[0] + center_offs - offs, position[1])
- self.punch_image_color = c = _sc(0.2, 0.6, 1) if ouya else _sc(
- 1, 0.7, 0.3)
+ self.punch_image_color = c = (
+ _sc(0.2, 0.6, 1) if ouya else _sc(1, 0.7, 0.3)
+ )
self.punch_image = ba.newnode(
'image',
attrs={
@@ -323,8 +346,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
'vr_depth': -20,
'position': p,
'scale': (image_size, image_size),
- 'color': c
- })
+ 'color': c,
+ },
+ )
p = (position[0] + center_offs + offs, position[1])
self.bomb_image_color = c = _sc(1, 0.3, 0.3)
self.bomb_image = ba.newnode(
@@ -335,11 +359,13 @@ class TutorialActivity(ba.Activity[Player, Team]):
'vr_depth': -20,
'position': p,
'scale': (image_size, image_size),
- 'color': c
- })
+ 'color': c,
+ },
+ )
p = (position[0] + center_offs, position[1] + offs)
- self.pickup_image_color = c = _sc(1, 0.8, 0.3) if ouya else _sc(
- 0.5, 0.5, 1)
+ self.pickup_image_color = c = (
+ _sc(1, 0.8, 0.3) if ouya else _sc(0.5, 0.5, 1)
+ )
self.pickup_image = ba.newnode(
'image',
attrs={
@@ -348,11 +374,11 @@ class TutorialActivity(ba.Activity[Player, Team]):
'vr_depth': -20,
'position': p,
'scale': (image_size, image_size),
- 'color': c
- })
+ 'color': c,
+ },
+ )
- self._stick_base_position = p = (position[0] - center_offs,
- position[1])
+ self._stick_base_position = p = (position[0] - center_offs, position[1])
self._stick_base_image_color = c2 = (0.25, 0.25, 0.25, 1.0)
self._stick_base_image = ba.newnode(
'image',
@@ -362,21 +388,28 @@ class TutorialActivity(ba.Activity[Player, Team]):
'vr_depth': -40,
'position': p,
'scale': (image_size_2, image_size_2),
- 'color': c2
- })
+ 'color': c2,
+ },
+ )
self._stick_nub_position = p = (position[0] - center_offs, position[1])
self._stick_nub_image_color = c3 = (0.4, 0.4, 0.4, 1.0)
- self._stick_nub_image = ba.newnode('image',
- attrs={
- 'texture': ba.gettexture('nub'),
- 'absolute_scale': True,
- 'position': p,
- 'scale': (nub_size, nub_size),
- 'color': c3
- })
+ self._stick_nub_image = ba.newnode(
+ 'image',
+ attrs={
+ 'texture': ba.gettexture('nub'),
+ 'absolute_scale': True,
+ 'position': p,
+ 'scale': (nub_size, nub_size),
+ 'color': c3,
+ },
+ )
self.control_ui_nodes = [
- self.jump_image, self.punch_image, self.bomb_image,
- self.pickup_image, self._stick_base_image, self._stick_nub_image
+ self.jump_image,
+ self.punch_image,
+ self.bomb_image,
+ self.pickup_image,
+ self._stick_base_image,
+ self._stick_nub_image,
]
for n in self.control_ui_nodes:
n.opacity = 0.0
@@ -398,7 +431,7 @@ class TutorialActivity(ba.Activity[Player, Team]):
assert self._scale is not None
p = [
self._stick_nub_position[0] + x * offs * self._scale,
- self._stick_nub_position[1] + y * offs * self._scale
+ self._stick_nub_position[1] + y * offs * self._scale,
]
c = list(self._stick_nub_image_color)
if abs(x) > 0.1 or abs(y) > 0.1:
@@ -420,7 +453,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
try:
class Reset:
-
def __init__(self) -> None:
pass
@@ -429,17 +461,23 @@ class TutorialActivity(ba.Activity[Player, Team]):
# if we're looping, print out how long each cycle took
# print out how long each cycle took..
if a.last_start_time is not None:
- tval = ba.time(
- ba.TimeType.REAL,
- ba.TimeFormat.MILLISECONDS) - a.last_start_time
+ tval = (
+ ba.time(
+ ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS
+ )
+ - a.last_start_time
+ )
assert isinstance(tval, int)
diff = tval
a.cycle_times.append(diff)
ba.screenmessage(
- 'cycle time: ' + str(diff) + ' (average: ' +
- str(sum(a.cycle_times) / len(a.cycle_times)) + ')')
- tval = ba.time(ba.TimeType.REAL,
- ba.TimeFormat.MILLISECONDS)
+ 'cycle time: '
+ + str(diff)
+ + ' (average: '
+ + str(sum(a.cycle_times) / len(a.cycle_times))
+ + ')'
+ )
+ tval = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
assert isinstance(tval, int)
a.last_start_time = tval
@@ -455,7 +493,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
# Can be used for debugging.
class SetSpeed:
-
def __init__(self, speed: int):
self._speed = speed
@@ -464,7 +501,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
ba.internal.set_debug_speed_exponent(self._speed)
class RemoveGloves:
-
def __init__(self) -> None:
pass
@@ -475,7 +511,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
a.current_spaz._gloves_wear_off()
class KillSpaz:
-
def __init__(self, num: int, explode: bool = False):
self._num = num
self._explode = explode
@@ -486,16 +521,17 @@ class TutorialActivity(ba.Activity[Player, Team]):
del a.spazzes[self._num]
class SpawnSpaz:
-
- def __init__(self,
- num: int,
- position: Sequence[float],
- color: Sequence[float] = (1.0, 1.0, 1.0),
- make_current: bool = False,
- relative_to: int | None = None,
- name: str | ba.Lstr = '',
- flash: bool = True,
- angle: float = 0.0):
+ def __init__(
+ self,
+ num: int,
+ position: Sequence[float],
+ color: Sequence[float] = (1.0, 1.0, 1.0),
+ make_current: bool = False,
+ relative_to: int | None = None,
+ name: str | ba.Lstr = '',
+ flash: bool = True,
+ angle: float = 0.0,
+ ):
self._num = num
self._position = position
self._make_current = make_current
@@ -514,21 +550,25 @@ class TutorialActivity(ba.Activity[Player, Team]):
snode = a.spazzes[self._relative_to].node
assert snode
their_pos = snode.position
- pos = (their_pos[0] + self._position[0],
- their_pos[1] + self._position[1],
- their_pos[2] + self._position[2])
+ pos = (
+ their_pos[0] + self._position[0],
+ their_pos[1] + self._position[1],
+ their_pos[2] + self._position[2],
+ )
else:
pos = self._position
# if there's already a spaz at this spot, insta-kill it
if self._num in a.spazzes:
a.spazzes[self._num].handlemessage(
- ba.DieMessage(immediate=True))
+ ba.DieMessage(immediate=True)
+ )
s = a.spazzes[self._num] = basespaz.Spaz(
color=self._color,
start_invincible=self._flash,
- demo_mode=True)
+ demo_mode=True,
+ )
# FIXME: Should extend spaz to support Lstr names.
assert s.node
@@ -544,13 +584,14 @@ class TutorialActivity(ba.Activity[Player, Team]):
ba.playsound(a.spawn_sound, position=pos)
class Powerup:
-
- def __init__(self,
- num: int,
- position: Sequence[float],
- color: Sequence[float] = (1.0, 1.0, 1.0),
- make_current: bool = False,
- relative_to: int | None = None):
+ def __init__(
+ self,
+ num: int,
+ position: Sequence[float],
+ color: Sequence[float] = (1.0, 1.0, 1.0),
+ make_current: bool = False,
+ relative_to: int | None = None,
+ ):
self._position = position
self._relative_to = relative_to
@@ -562,17 +603,20 @@ class TutorialActivity(ba.Activity[Player, Team]):
snode = a.spazzes[self._relative_to].node
assert snode
their_pos = snode.position
- pos = (their_pos[0] + self._position[0],
- their_pos[1] + self._position[1],
- their_pos[2] + self._position[2])
+ pos = (
+ their_pos[0] + self._position[0],
+ their_pos[1] + self._position[1],
+ their_pos[2] + self._position[2],
+ )
else:
pos = self._position
from bastd.actor import powerupbox
- powerupbox.PowerupBox(position=pos,
- poweruptype='punch').autoretain()
+
+ powerupbox.PowerupBox(
+ position=pos, poweruptype='punch'
+ ).autoretain()
class Delay:
-
def __init__(self, time: int) -> None:
self._time = time
@@ -580,7 +624,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
return self._time
class AnalyticsScreen:
-
def __init__(self, screen: str) -> None:
self._screen = screen
@@ -588,7 +631,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
ba.set_analytics_screen(self._screen)
class DelayOld:
-
def __init__(self, time: int) -> None:
self._time = time
@@ -596,7 +638,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
return int(0.9 * self._time)
class DelayOld2:
-
def __init__(self, time: int) -> None:
self._time = time
@@ -604,7 +645,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
return int(0.8 * self._time)
class End:
-
def __init__(self) -> None:
pass
@@ -613,7 +653,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
a.end()
class Move:
-
def __init__(self, x: float, y: float):
self._x = float(x)
self._y = float(y)
@@ -629,7 +668,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
a.set_stick_image_position(self._x, self._y)
class MoveLR:
-
def __init__(self, x: float):
self._x = float(x)
@@ -639,11 +677,11 @@ class TutorialActivity(ba.Activity[Player, Team]):
# FIXME: Game should take floats for this.
x_clamped = self._x
s.on_move_left_right(x_clamped)
- a.set_stick_image_position(self._x,
- a.stick_image_position_y)
+ a.set_stick_image_position(
+ self._x, a.stick_image_position_y
+ )
class MoveUD:
-
def __init__(self, y: float):
self._y = float(y)
@@ -653,96 +691,106 @@ class TutorialActivity(ba.Activity[Player, Team]):
# FIXME: Game should take floats for this.
y_clamped = self._y
s.on_move_up_down(y_clamped)
- a.set_stick_image_position(a.stick_image_position_x,
- self._y)
+ a.set_stick_image_position(
+ a.stick_image_position_x, self._y
+ )
class Bomb(ButtonPress):
-
- def __init__(self,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 500):
- ButtonPress.__init__(self,
- 'bomb',
- delay=delay,
- release=release,
- release_delay=release_delay)
+ def __init__(
+ self,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 500,
+ ):
+ ButtonPress.__init__(
+ self,
+ 'bomb',
+ delay=delay,
+ release=release,
+ release_delay=release_delay,
+ )
class Jump(ButtonPress):
-
- def __init__(self,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 500):
- ButtonPress.__init__(self,
- 'jump',
- delay=delay,
- release=release,
- release_delay=release_delay)
+ def __init__(
+ self,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 500,
+ ):
+ ButtonPress.__init__(
+ self,
+ 'jump',
+ delay=delay,
+ release=release,
+ release_delay=release_delay,
+ )
class Punch(ButtonPress):
-
- def __init__(self,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 500):
- ButtonPress.__init__(self,
- 'punch',
- delay=delay,
- release=release,
- release_delay=release_delay)
+ def __init__(
+ self,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 500,
+ ):
+ ButtonPress.__init__(
+ self,
+ 'punch',
+ delay=delay,
+ release=release,
+ release_delay=release_delay,
+ )
class PickUp(ButtonPress):
-
- def __init__(self,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 500):
- ButtonPress.__init__(self,
- 'pickUp',
- delay=delay,
- release=release,
- release_delay=release_delay)
+ def __init__(
+ self,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 500,
+ ):
+ ButtonPress.__init__(
+ self,
+ 'pickUp',
+ delay=delay,
+ release=release,
+ release_delay=release_delay,
+ )
class Run(ButtonPress):
-
- def __init__(self,
- delay: int = 0,
- release: bool = True,
- release_delay: int = 500):
- ButtonPress.__init__(self,
- 'run',
- delay=delay,
- release=release,
- release_delay=release_delay)
+ def __init__(
+ self,
+ delay: int = 0,
+ release: bool = True,
+ release_delay: int = 500,
+ ):
+ ButtonPress.__init__(
+ self,
+ 'run',
+ delay=delay,
+ release=release,
+ release_delay=release_delay,
+ )
class BombRelease(ButtonRelease):
-
def __init__(self, delay: int = 0):
super().__init__('bomb', delay=delay)
class JumpRelease(ButtonRelease):
-
def __init__(self, delay: int = 0):
super().__init__('jump', delay=delay)
class PunchRelease(ButtonRelease):
-
def __init__(self, delay: int = 0):
super().__init__('punch', delay=delay)
class PickUpRelease(ButtonRelease):
-
def __init__(self, delay: int = 0):
super().__init__('pickUp', delay=delay)
class RunRelease(ButtonRelease):
-
def __init__(self, delay: int = 0):
super().__init__('run', delay=delay)
class ShowControls:
-
def __init__(self) -> None:
pass
@@ -751,7 +799,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
ba.animate(n, 'opacity', {0.0: 0.0, 1.0: 1.0})
class Text:
-
def __init__(self, text: str | ba.Lstr):
self.text = text
@@ -760,7 +807,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
a.text.text = self.text
class PrintPos:
-
def __init__(self, spaz_num: int | None = None):
self._spaz_num = spaz_num
@@ -774,7 +820,6 @@ class TutorialActivity(ba.Activity[Player, Team]):
print('RestorePos(' + str((t[0], t[1] - 1.0, t[2])) + '),')
class RestorePos:
-
def __init__(self, pos: Sequence[float]) -> None:
self._pos = pos
@@ -784,11 +829,12 @@ class TutorialActivity(ba.Activity[Player, Team]):
s.handlemessage(ba.StandMessage(self._pos, 0))
class Celebrate:
-
- def __init__(self,
- celebrate_type: str = 'both',
- spaz_num: int | None = None,
- duration: int = 1000):
+ def __init__(
+ self,
+ celebrate_type: str = 'both',
+ spaz_num: int | None = None,
+ duration: int = 1000,
+ ):
self._spaz_num = spaz_num
self._celebrate_type = celebrate_type
self._duration = duration
@@ -806,8 +852,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
elif self._celebrate_type == 'both':
s.node.handlemessage('celebrate', self._duration)
else:
- raise Exception('invalid celebrate type ' +
- self._celebrate_type)
+ raise Exception(
+ 'invalid celebrate type ' + self._celebrate_type
+ )
self._entries = [
Reset(),
@@ -818,10 +865,11 @@ class TutorialActivity(ba.Activity[Player, Team]):
Celebrate('left'),
DelayOld(2000),
Text(
- ba.Lstr(resource=self._r + '.phrase02Text',
- subs=[
- ('${APP_NAME}', ba.Lstr(resource='titleText'))
- ])), # welcome to
+ ba.Lstr(
+ resource=self._r + '.phrase02Text',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ )
+ ), # welcome to
DelayOld(80),
Run(release=False),
Jump(release=False),
@@ -845,8 +893,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(80),
MoveUD(0),
DelayOld(1500),
- Text(ba.Lstr(resource=self._r +
- '.phrase03Text')), # here's a few tips
+ Text(
+ ba.Lstr(resource=self._r + '.phrase03Text')
+ ), # here's a few tips
DelayOld(1000),
ShowControls(),
DelayOld(1000),
@@ -856,10 +905,11 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(1000),
AnalyticsScreen('Tutorial Section 2'),
Text(
- ba.Lstr(resource=self._r + '.phrase04Text',
- subs=[
- ('${APP_NAME}', ba.Lstr(resource='titleText'))
- ])), # many things are based on physics
+ ba.Lstr(
+ resource=self._r + '.phrase04Text',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ )
+ ), # many things are based on physics
DelayOld(20),
MoveUD(0),
DelayOld(60),
@@ -1214,28 +1264,36 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(100),
Move(0, 0),
DelayOld(1000),
- Text(ba.Lstr(resource=self._r +
- '.phrase05Text')), # for example when you punch..
+ Text(
+ ba.Lstr(resource=self._r + '.phrase05Text')
+ ), # for example when you punch..
DelayOld(510),
Move(0, -0.01),
DelayOld(100),
Move(0, 0),
DelayOld(500),
- SpawnSpaz(0, (-0.09249162673950195, 4.337906360626221, -2.3),
- make_current=True,
- flash=False),
- SpawnSpaz(1, (-3.1, 4.3, -2.0),
- make_current=False,
- color=(1, 1, 0.4),
- name=ba.Lstr(resource=self._r + '.randomName1Text')),
+ SpawnSpaz(
+ 0,
+ (-0.09249162673950195, 4.337906360626221, -2.3),
+ make_current=True,
+ flash=False,
+ ),
+ SpawnSpaz(
+ 1,
+ (-3.1, 4.3, -2.0),
+ make_current=False,
+ color=(1, 1, 0.4),
+ name=ba.Lstr(resource=self._r + '.randomName1Text'),
+ ),
Move(-1.0, 0),
DelayOld(1050),
Move(0, -0.01),
DelayOld(100),
Move(0, 0),
DelayOld(1000),
- Text(ba.Lstr(resource=self._r +
- '.phrase06Text')), # your damage is based
+ Text(
+ ba.Lstr(resource=self._r + '.phrase06Text')
+ ), # your damage is based
DelayOld(1200),
Move(-0.05, 0),
DelayOld(200),
@@ -1249,17 +1307,22 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(100),
Move(0, 0),
Text(
- ba.Lstr(resource=self._r + '.phrase07Text',
- subs=[('${NAME}',
- ba.Lstr(resource=self._r +
- '.randomName1Text'))
- ])), # see that didn't hurt fred
+ ba.Lstr(
+ resource=self._r + '.phrase07Text',
+ subs=[
+ (
+ '${NAME}',
+ ba.Lstr(resource=self._r + '.randomName1Text'),
+ )
+ ],
+ )
+ ), # see that didn't hurt fred
DelayOld(2000),
Celebrate('right', spaz_num=1),
DelayOld(1400),
- Text(ba.Lstr(
- resource=self._r +
- '.phrase08Text')), # lets jump and spin to get more speed
+ Text(
+ ba.Lstr(resource=self._r + '.phrase08Text')
+ ), # lets jump and spin to get more speed
DelayOld(30),
MoveLR(0),
DelayOld(40),
@@ -1458,22 +1521,27 @@ class TutorialActivity(ba.Activity[Player, Team]):
MoveLR(0.00390637),
Move(0, 0),
DelayOld(1000),
- Text(ba.Lstr(resource=self._r +
- '.phrase09Text')), # ah that's better
+ Text(
+ ba.Lstr(resource=self._r + '.phrase09Text')
+ ), # ah that's better
DelayOld(1900),
AnalyticsScreen('Tutorial Section 3'),
- Text(ba.Lstr(resource=self._r +
- '.phrase10Text')), # running also helps
+ Text(
+ ba.Lstr(resource=self._r + '.phrase10Text')
+ ), # running also helps
DelayOld(100),
- SpawnSpaz(0, (-3.2, 4.3, -4.4), make_current=True,
- flash=False),
- SpawnSpaz(1, (3.3, 4.2, -5.8),
- make_current=False,
- color=(0.9, 0.5, 1.0),
- name=ba.Lstr(resource=self._r + '.randomName2Text')),
+ SpawnSpaz(0, (-3.2, 4.3, -4.4), make_current=True, flash=False),
+ SpawnSpaz(
+ 1,
+ (3.3, 4.2, -5.8),
+ make_current=False,
+ color=(0.9, 0.5, 1.0),
+ name=ba.Lstr(resource=self._r + '.randomName2Text'),
+ ),
DelayOld(1800),
- Text(ba.Lstr(resource=self._r +
- '.phrase11Text')), # hold ANY button to run
+ Text(
+ ba.Lstr(resource=self._r + '.phrase11Text')
+ ), # hold ANY button to run
DelayOld(300),
MoveUD(0),
DelayOld(20),
@@ -1729,21 +1797,23 @@ class TutorialActivity(ba.Activity[Player, Team]):
MoveUD(0),
AnalyticsScreen('Tutorial Section 4'),
Text(
- ba.Lstr(resource=self._r +
- '.phrase12Text')), # for extra-awesome punches,...
+ ba.Lstr(resource=self._r + '.phrase12Text')
+ ), # for extra-awesome punches,...
DelayOld(200),
SpawnSpaz(
0,
(2.368781805038452, 4.337533950805664, -4.360159873962402),
make_current=True,
- flash=False),
+ flash=False,
+ ),
SpawnSpaz(
1,
(-3.2, 4.3, -4.5),
make_current=False,
color=(1.0, 0.7, 0.3),
# name=R.randomName3Text),
- name=ba.Lstr(resource=self._r + '.randomName3Text')),
+ name=ba.Lstr(resource=self._r + '.randomName3Text'),
+ ),
DelayOld(100),
Powerup(1, (2.5, 0.0, 0), relative_to=0),
Move(1, 0),
@@ -1940,29 +2010,45 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(750),
MoveLR(0),
Text(
- ba.Lstr(resource=self._r + '.phrase13Text',
- subs=[('${NAME}',
- ba.Lstr(resource=self._r +
- '.randomName3Text'))
- ])), # whoops sorry bill
+ ba.Lstr(
+ resource=self._r + '.phrase13Text',
+ subs=[
+ (
+ '${NAME}',
+ ba.Lstr(resource=self._r + '.randomName3Text'),
+ )
+ ],
+ )
+ ), # whoops sorry bill
RemoveGloves(),
DelayOld(2000),
AnalyticsScreen('Tutorial Section 5'),
Text(
- ba.Lstr(resource=self._r + '.phrase14Text',
- subs=[('${NAME}',
- ba.Lstr(resource=self._r +
- '.randomName4Text'))])
+ ba.Lstr(
+ resource=self._r + '.phrase14Text',
+ subs=[
+ (
+ '${NAME}',
+ ba.Lstr(resource=self._r + '.randomName4Text'),
+ )
+ ],
+ )
), # you can pick up and throw things such as chuck here
- SpawnSpaz(0, (-4.0, 4.3, -2.5),
- make_current=True,
- flash=False,
- angle=90),
- SpawnSpaz(1, (5, 0, -1.0),
- relative_to=0,
- make_current=False,
- color=(0.4, 1.0, 0.7),
- name=ba.Lstr(resource=self._r + '.randomName4Text')),
+ SpawnSpaz(
+ 0,
+ (-4.0, 4.3, -2.5),
+ make_current=True,
+ flash=False,
+ angle=90,
+ ),
+ SpawnSpaz(
+ 1,
+ (5, 0, -1.0),
+ relative_to=0,
+ make_current=False,
+ color=(0.4, 1.0, 0.7),
+ name=ba.Lstr(resource=self._r + '.randomName4Text'),
+ ),
DelayOld(1000),
Celebrate('left', 1, duration=1000),
Move(1, 0.2),
@@ -1980,17 +2066,17 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(800),
Move(0, 0),
DelayOld(800),
- SpawnSpaz(0, (1.5, 4.3, -4.0),
- make_current=True,
- flash=False,
- angle=0),
+ SpawnSpaz(
+ 0, (1.5, 4.3, -4.0), make_current=True, flash=False, angle=0
+ ),
AnalyticsScreen('Tutorial Section 6'),
- Text(ba.Lstr(resource=self._r +
- '.phrase15Text')), # lastly there's bombs
+ Text(
+ ba.Lstr(resource=self._r + '.phrase15Text')
+ ), # lastly there's bombs
DelayOld(1900),
Text(
- ba.Lstr(resource=self._r +
- '.phrase16Text')), # throwing bombs takes practice
+ ba.Lstr(resource=self._r + '.phrase16Text')
+ ), # throwing bombs takes practice
DelayOld(2000),
Bomb(),
Move(-0.1, -0.1),
@@ -2000,12 +2086,13 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(1000),
Bomb(),
DelayOld(2000),
- Text(ba.Lstr(resource=self._r +
- '.phrase17Text')), # not a very good throw
+ Text(
+ ba.Lstr(resource=self._r + '.phrase17Text')
+ ), # not a very good throw
DelayOld(3000),
Text(
- ba.Lstr(resource=self._r +
- '.phrase18Text')), # moving helps you get distance
+ ba.Lstr(resource=self._r + '.phrase18Text')
+ ), # moving helps you get distance
DelayOld(1000),
Bomb(),
DelayOld(500),
@@ -2021,8 +2108,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(100),
Move(0, 0),
DelayOld(2500),
- Text(ba.Lstr(resource=self._r +
- '.phrase19Text')), # jumping helps you get height
+ Text(
+ ba.Lstr(resource=self._r + '.phrase19Text')
+ ), # jumping helps you get height
DelayOld(2000),
Bomb(),
DelayOld(500),
@@ -2040,8 +2128,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
DelayOld(100),
Move(0, 0),
DelayOld(2000),
- Text(ba.Lstr(resource=self._r +
- '.phrase20Text')), # whiplash your bombs
+ Text(
+ ba.Lstr(resource=self._r + '.phrase20Text')
+ ), # whiplash your bombs
DelayOld(1000),
Bomb(release=False),
DelayOld2(80),
@@ -2201,23 +2290,29 @@ class TutorialActivity(ba.Activity[Player, Team]):
Move(0, 0),
DelayOld(2000),
AnalyticsScreen('Tutorial Section 7'),
- Text(ba.Lstr(
- resource=self._r +
- '.phrase21Text')), # timing your bombs can be tricky
+ Text(
+ ba.Lstr(resource=self._r + '.phrase21Text')
+ ), # timing your bombs can be tricky
Move(-1, 0),
DelayOld(1000),
Move(0, -0.1),
DelayOld(100),
Move(0, 0),
- SpawnSpaz(0, (-0.7, 4.3, -3.9),
- make_current=True,
- flash=False,
- angle=-30),
- SpawnSpaz(1, (6.5, 0, -0.75),
- relative_to=0,
- make_current=False,
- color=(0.3, 0.8, 1.0),
- name=ba.Lstr(resource=self._r + '.randomName5Text')),
+ SpawnSpaz(
+ 0,
+ (-0.7, 4.3, -3.9),
+ make_current=True,
+ flash=False,
+ angle=-30,
+ ),
+ SpawnSpaz(
+ 1,
+ (6.5, 0, -0.75),
+ relative_to=0,
+ make_current=False,
+ color=(0.3, 0.8, 1.0),
+ name=ba.Lstr(resource=self._r + '.randomName5Text'),
+ ),
DelayOld2(1000),
Move(-1, 0),
DelayOld2(1800),
@@ -2238,8 +2333,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
Delay(1500),
Text(''),
Delay(200),
- Text(ba.Lstr(resource=self._r +
- '.phrase23Text')), # try cooking off
+ Text(
+ ba.Lstr(resource=self._r + '.phrase23Text')
+ ), # try cooking off
Delay(1500),
Bomb(),
Delay(800),
@@ -2253,8 +2349,9 @@ class TutorialActivity(ba.Activity[Player, Team]):
Delay(100),
Move(0, 0),
Delay(2000),
- Text(ba.Lstr(resource=self._r +
- '.phrase24Text')), # hooray nicely cooked
+ Text(
+ ba.Lstr(resource=self._r + '.phrase24Text')
+ ), # hooray nicely cooked
Celebrate(),
DelayOld(2000),
KillSpaz(1),
@@ -2266,17 +2363,19 @@ class TutorialActivity(ba.Activity[Player, Team]):
Move(0, 0),
DelayOld(1000),
AnalyticsScreen('Tutorial Section 8'),
- Text(ba.Lstr(resource=self._r +
- '.phrase25Text')), # well that's just about it
+ Text(
+ ba.Lstr(resource=self._r + '.phrase25Text')
+ ), # well that's just about it
DelayOld(2000),
- Text(ba.Lstr(resource=self._r +
- '.phrase26Text')), # go get em tiger
+ Text(
+ ba.Lstr(resource=self._r + '.phrase26Text')
+ ), # go get em tiger
DelayOld(2000),
- Text(ba.Lstr(resource=self._r +
- '.phrase27Text')), # remember you training
+ Text(
+ ba.Lstr(resource=self._r + '.phrase27Text')
+ ), # remember you training
DelayOld(3000),
- Text(ba.Lstr(resource=self._r +
- '.phrase28Text')), # well maybe
+ Text(ba.Lstr(resource=self._r + '.phrase28Text')), # well maybe
DelayOld(1600),
Text(ba.Lstr(resource=self._r + '.phrase29Text')), # good luck
Celebrate('right', duration=10000),
@@ -2294,7 +2393,8 @@ class TutorialActivity(ba.Activity[Player, Team]):
# Otherwise try again in a few seconds.
else:
self._read_entries_timer = ba.Timer(
- 3.0, ba.WeakCall(self._read_entries))
+ 3.0, ba.WeakCall(self._read_entries)
+ )
def _run_next_entry(self) -> None:
@@ -2312,29 +2412,42 @@ class TutorialActivity(ba.Activity[Player, Team]):
self._entry_timer = ba.Timer(
result,
ba.WeakCall(self._run_next_entry),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
return
# Done with these entries.. start over soon.
- self._read_entries_timer = ba.Timer(1.0,
- ba.WeakCall(self._read_entries))
+ self._read_entries_timer = ba.Timer(
+ 1.0, ba.WeakCall(self._read_entries)
+ )
def _update_skip_votes(self) -> None:
count = sum(1 for player in self.players if player.pressed)
assert self._skip_count_text
- self._skip_count_text.text = ba.Lstr(
- resource=self._r + '.skipVoteCountText',
- subs=[('${COUNT}', str(count)),
- ('${TOTAL}', str(len(self.players)))]) if count > 0 else ''
- if (count >= len(self.players) and self.players
- and not self._have_skipped):
+ self._skip_count_text.text = (
+ ba.Lstr(
+ resource=self._r + '.skipVoteCountText',
+ subs=[
+ ('${COUNT}', str(count)),
+ ('${TOTAL}', str(len(self.players))),
+ ],
+ )
+ if count > 0
+ else ''
+ )
+ if (
+ count >= len(self.players)
+ and self.players
+ and not self._have_skipped
+ ):
ba.internal.increment_analytics_count('Tutorial skip')
ba.set_analytics_screen('Tutorial Skip')
self._have_skipped = True
ba.playsound(ba.getsound('swish'))
# self._skip_count_text.text = self._r.skippingText
- self._skip_count_text.text = ba.Lstr(resource=self._r +
- '.skippingText')
+ self._skip_count_text.text = ba.Lstr(
+ resource=self._r + '.skippingText'
+ )
assert self._skip_text
self._skip_text.text = ''
self.end()
@@ -2347,21 +2460,25 @@ class TutorialActivity(ba.Activity[Player, Team]):
if len(self.players) == 1 and not self._issued_warning:
self._issued_warning = True
assert self._skip_text
- self._skip_text.text = ba.Lstr(resource=self._r +
- '.skipConfirmText')
+ self._skip_text.text = ba.Lstr(
+ resource=self._r + '.skipConfirmText'
+ )
self._skip_text.color = (1, 1, 1)
self._skip_text.scale = 1.3
incr = 50
t = incr
for _i in range(6):
- ba.timer(t,
- ba.Call(setattr, self._skip_text, 'color',
- (1, 0.5, 0.1)),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ t,
+ ba.Call(setattr, self._skip_text, 'color', (1, 0.5, 0.1)),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
t += incr
- ba.timer(t,
- ba.Call(setattr, self._skip_text, 'color', (1, 1, 0)),
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ t,
+ ba.Call(setattr, self._skip_text, 'color', (1, 1, 0)),
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
t += incr
ba.timer(6.0, ba.WeakCall(self._revert_confirm))
return
@@ -2370,16 +2487,20 @@ class TutorialActivity(ba.Activity[Player, Team]):
# test...
if not all(self.players):
- ba.print_error('Nonexistent player in _player_pressed_button: ' +
- str([str(p) for p in self.players]) + ': we are ' +
- str(player))
+ ba.print_error(
+ 'Nonexistent player in _player_pressed_button: '
+ + str([str(p) for p in self.players])
+ + ': we are '
+ + str(player)
+ )
self._update_skip_votes()
def _revert_confirm(self) -> None:
assert self._skip_text
- self._skip_text.text = ba.Lstr(resource=self._r +
- '.toSkipPressAnythingText')
+ self._skip_text.text = ba.Lstr(
+ resource=self._r + '.toSkipPressAnythingText'
+ )
self._skip_text.color = (1, 1, 1)
self._issued_warning = False
@@ -2388,15 +2509,23 @@ class TutorialActivity(ba.Activity[Player, Team]):
# We just wanna know if this player presses anything.
player.assigninput(
- (ba.InputType.JUMP_PRESS, ba.InputType.PUNCH_PRESS,
- ba.InputType.BOMB_PRESS, ba.InputType.PICK_UP_PRESS),
- ba.Call(self._player_pressed_button, player))
+ (
+ ba.InputType.JUMP_PRESS,
+ ba.InputType.PUNCH_PRESS,
+ ba.InputType.BOMB_PRESS,
+ ba.InputType.PICK_UP_PRESS,
+ ),
+ ba.Call(self._player_pressed_button, player),
+ )
def on_player_leave(self, player: Player) -> None:
if not all(self.players):
- ba.print_error('Nonexistent player in on_player_leave: ' +
- str([str(p) for p in self.players]) + ': we are ' +
- str(player))
+ ba.print_error(
+ 'Nonexistent player in on_player_leave: '
+ + str([str(p) for p in self.players])
+ + ': we are '
+ + str(player)
+ )
super().on_player_leave(player)
# our leaving may influence the vote total needed/etc
self._update_skip_votes()
diff --git a/assets/src/ba_data/python/bastd/ui/account/__init__.py b/assets/src/ba_data/python/bastd/ui/account/__init__.py
index 24e4007e..b4ea7e2b 100644
--- a/assets/src/ba_data/python/bastd/ui/account/__init__.py
+++ b/assets/src/ba_data/python/bastd/ui/account/__init__.py
@@ -12,18 +12,22 @@ def show_sign_in_prompt(account_type: str | None = None) -> None:
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.account import settings
from ba.internal import sign_in_v1
+
if account_type == 'Google Play':
ConfirmWindow(
ba.Lstr(resource='notSignedInGooglePlayErrorText'),
lambda: sign_in_v1('Google Play'),
ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'),
width=460,
- height=130)
+ height=130,
+ )
else:
ConfirmWindow(
ba.Lstr(resource='notSignedInErrorText'),
- lambda: settings.AccountSettingsWindow(modal=True,
- close_once_signed_in=True),
+ lambda: settings.AccountSettingsWindow(
+ modal=True, close_once_signed_in=True
+ ),
ok_text=ba.Lstr(resource='accountSettingsWindow.signInText'),
width=460,
- height=130)
+ height=130,
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/account/link.py b/assets/src/ba_data/python/bastd/ui/account/link.py
index b16f8392..7309a797 100644
--- a/assets/src/ba_data/python/bastd/ui/account/link.py
+++ b/assets/src/ba_data/python/bastd/ui/account/link.py
@@ -32,81 +32,111 @@ class AccountLinkWindow(ba.Window):
self._width = 560
self._height = 420
uiscale = ba.app.ui.uiscale
- base_scale = (1.65 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.1)
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=base_scale,
- scale_origin_stack_offset=scale_origin,
- stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- position=(40, self._height - 45),
- size=(50, 50),
- scale=0.7,
- label='',
- color=bg_color,
- on_activate_call=self._cancel,
- autoselect=True,
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ base_scale = (
+ 1.65
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.1
+ )
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=base_scale,
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(0, -10)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(40, self._height - 45),
+ size=(50, 50),
+ scale=0.7,
+ label='',
+ color=bg_color,
+ on_activate_call=self._cancel,
+ autoselect=True,
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
maxlinks = ba.internal.get_v1_account_misc_read_val(
- 'maxLinkAccounts', 5)
+ 'maxLinkAccounts', 5
+ )
ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.56),
size=(0, 0),
- text=ba.Lstr(resource=(
- 'accountSettingsWindow.linkAccountsInstructionsNewText'),
- subs=[('${COUNT}', str(maxlinks))]),
+ text=ba.Lstr(
+ resource=(
+ 'accountSettingsWindow.linkAccountsInstructionsNewText'
+ ),
+ subs=[('${COUNT}', str(maxlinks))],
+ ),
maxwidth=self._width * 0.9,
color=ba.app.ui.infotextcolor,
max_height=self._height * 0.6,
h_align='center',
- v_align='center')
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ v_align='center',
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
ba.buttonwidget(
parent=self._root_widget,
position=(40, 30),
size=(200, 60),
label=ba.Lstr(
- resource='accountSettingsWindow.linkAccountsGenerateCodeText'),
+ resource='accountSettingsWindow.linkAccountsGenerateCodeText'
+ ),
autoselect=True,
- on_activate_call=self._generate_press)
+ on_activate_call=self._generate_press,
+ )
self._enter_code_button = ba.buttonwidget(
parent=self._root_widget,
position=(self._width - 240, 30),
size=(200, 60),
label=ba.Lstr(
- resource='accountSettingsWindow.linkAccountsEnterCodeText'),
+ resource='accountSettingsWindow.linkAccountsEnterCodeText'
+ ),
autoselect=True,
- on_activate_call=self._enter_code_press)
+ on_activate_call=self._enter_code_press,
+ )
def _generate_press(self) -> None:
from bastd.ui import account
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
ba.screenmessage(
ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'),
- color=(0, 1, 0))
- ba.internal.add_transaction({
- 'type': 'ACCOUNT_LINK_CODE_REQUEST',
- 'expire_time': time.time() + 5
- })
+ color=(0, 1, 0),
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'ACCOUNT_LINK_CODE_REQUEST',
+ 'expire_time': time.time() + 5,
+ }
+ )
ba.internal.run_transactions()
def _enter_code_press(self) -> None:
from bastd.ui import promocode
- promocode.PromoCodeWindow(modal=True,
- origin_widget=self._enter_code_button)
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ promocode.PromoCodeWindow(
+ modal=True, origin_widget=self._enter_code_button
+ )
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
def _cancel(self) -> None:
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
class AccountLinkCodeWindow(ba.Window):
@@ -116,36 +146,49 @@ class AccountLinkCodeWindow(ba.Window):
self._width = 350
self._height = 200
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- color=(0.45, 0.63, 0.15),
- transition='in_scale',
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ color=(0.45, 0.63, 0.15),
+ transition='in_scale',
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
self._data = copy.deepcopy(data)
ba.playsound(ba.getsound('cashRegister'))
ba.playsound(ba.getsound('swish'))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.5,
- position=(40, self._height - 40),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.45, 0.63, 0.15),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.5),
- size=(0, 0),
- color=(1.0, 3.0, 1.0),
- scale=2.0,
- h_align='center',
- v_align='center',
- text=data['code'],
- maxwidth=self._width * 0.85)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.5,
+ position=(40, self._height - 40),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.45, 0.63, 0.15),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.5),
+ size=(0, 0),
+ color=(1.0, 3.0, 1.0),
+ scale=2.0,
+ h_align='center',
+ v_align='center',
+ text=data['code'],
+ maxwidth=self._width * 0.85,
+ )
def close(self) -> None:
"""close the window"""
diff --git a/assets/src/ba_data/python/bastd/ui/account/settings.py b/assets/src/ba_data/python/bastd/ui/account/settings.py
index 1fdc3b7f..3aae9917 100644
--- a/assets/src/ba_data/python/bastd/ui/account/settings.py
+++ b/assets/src/ba_data/python/bastd/ui/account/settings.py
@@ -18,11 +18,13 @@ if TYPE_CHECKING:
class AccountSettingsWindow(ba.Window):
"""Window for account related functionality."""
- def __init__(self,
- transition: str = 'in_right',
- modal: bool = False,
- origin_widget: ba.Widget | None = None,
- close_once_signed_in: bool = False):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ modal: bool = False,
+ origin_widget: ba.Widget | None = None,
+ close_once_signed_in: bool = False,
+ ):
# pylint: disable=too-many-statements
self._sign_in_v2_button: ba.Widget | None = None
@@ -44,15 +46,20 @@ class AccountSettingsWindow(ba.Window):
self._r = 'accountSettingsWindow'
self._modal = modal
self._needs_refresh = False
- self._signed_in = (ba.internal.get_v1_account_state() == 'signed_in')
+ self._signed_in = ba.internal.get_v1_account_state() == 'signed_in'
self._account_state_num = ba.internal.get_v1_account_state_num()
- self._show_linked = (self._signed_in
- and ba.internal.get_v1_account_misc_read_val(
- 'allowAccountLinking2', False))
- self._check_sign_in_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._show_linked = (
+ self._signed_in
+ and ba.internal.get_v1_account_misc_read_val(
+ 'allowAccountLinking2', False
+ )
+ )
+ self._check_sign_in_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
# Currently we can only reset achievements on game-center.
account_type: str | None
@@ -60,15 +67,20 @@ class AccountSettingsWindow(ba.Window):
account_type = ba.internal.get_v1_account_type()
else:
account_type = None
- self._can_reset_achievements = (account_type == 'Game Center')
+ self._can_reset_achievements = account_type == 'Game Center'
app = ba.app
uiscale = app.ui.uiscale
self._width = 760 if uiscale is ba.UIScale.SMALL else 660
x_offs = 50 if uiscale is ba.UIScale.SMALL else 0
- self._height = (390 if uiscale is ba.UIScale.SMALL else
- 430 if uiscale is ba.UIScale.MEDIUM else 490)
+ self._height = (
+ 390
+ if uiscale is ba.UIScale.SMALL
+ else 430
+ if uiscale is ba.UIScale.MEDIUM
+ else 490
+ )
self._sign_in_button = None
self._sign_in_text = None
@@ -92,18 +104,29 @@ class AccountSettingsWindow(ba.Window):
self._show_sign_in_buttons.append('V2')
top_extra = 15 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(2.09 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -19) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.09
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -19)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
self._back_button = None
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
else:
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -113,34 +136,43 @@ class AccountSettingsWindow(ba.Window):
text_scale=1.2,
autoselect=True,
label=ba.Lstr(
- resource='doneText' if self._modal else 'backText'),
+ resource='doneText' if self._modal else 'backText'
+ ),
button_type='regular' if self._modal else 'back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
if not self._modal:
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 56),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 56),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 41),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- maxwidth=self._width - 340,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 41),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ maxwidth=self._width - 340,
+ h_align='center',
+ v_align='center',
+ )
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - 65 - self._scroll_height),
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - 65 - self._scroll_height,
+ ),
size=(self._scroll_width, self._scroll_height),
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
self._subcontainer: ba.Widget | None = None
self._refresh()
self._restore_state()
@@ -158,15 +190,21 @@ class AccountSettingsWindow(ba.Window):
account_state_num = ba.internal.get_v1_account_state_num()
account_state = ba.internal.get_v1_account_state()
- show_linked = (self._signed_in
- and ba.internal.get_v1_account_misc_read_val(
- 'allowAccountLinking2', False))
+ show_linked = (
+ self._signed_in
+ and ba.internal.get_v1_account_misc_read_val(
+ 'allowAccountLinking2', False
+ )
+ )
- if (account_state_num != self._account_state_num
- or self._show_linked != show_linked or self._needs_refresh):
+ if (
+ account_state_num != self._account_state_num
+ or self._show_linked != show_linked
+ or self._needs_refresh
+ ):
self._show_linked = show_linked
self._account_state_num = account_state_num
- self._signed_in = (account_state == 'signed_in')
+ self._signed_in = account_state == 'signed_in'
self._refresh()
# Go ahead and refresh some individual things
@@ -189,8 +227,11 @@ class AccountSettingsWindow(ba.Window):
from bastd.ui import confirm
account_state = ba.internal.get_v1_account_state()
- account_type = (ba.internal.get_v1_account_type()
- if account_state == 'signed_in' else 'unknown')
+ account_type = (
+ ba.internal.get_v1_account_type()
+ if account_state == 'signed_in'
+ else 'unknown'
+ )
is_google = account_type == 'Google Play'
@@ -206,34 +247,47 @@ class AccountSettingsWindow(ba.Window):
show_signing_in_text = account_state == 'signing_in'
signing_in_text_space = 80.0
- show_google_play_sign_in_button = (account_state == 'signed_out'
- and 'Google Play'
- in self._show_sign_in_buttons)
- show_device_sign_in_button = (account_state == 'signed_out' and 'Local'
- in self._show_sign_in_buttons)
- show_v2_sign_in_button = (account_state == 'signed_out'
- and 'V2' in self._show_sign_in_buttons)
+ show_google_play_sign_in_button = (
+ account_state == 'signed_out'
+ and 'Google Play' in self._show_sign_in_buttons
+ )
+ show_device_sign_in_button = (
+ account_state == 'signed_out'
+ and 'Local' in self._show_sign_in_buttons
+ )
+ show_v2_sign_in_button = (
+ account_state == 'signed_out' and 'V2' in self._show_sign_in_buttons
+ )
sign_in_button_space = 70.0
- show_game_service_button = (self._signed_in
- and account_type in ['Game Center'])
+ show_game_service_button = self._signed_in and account_type in [
+ 'Game Center'
+ ]
game_service_button_space = 60.0
- show_linked_accounts_text = (self._signed_in and
- ba.internal.get_v1_account_misc_read_val(
- 'allowAccountLinking2', False))
+ show_linked_accounts_text = (
+ self._signed_in
+ and ba.internal.get_v1_account_misc_read_val(
+ 'allowAccountLinking2', False
+ )
+ )
linked_accounts_text_space = 60.0
- show_achievements_button = (self._signed_in and account_type
- in ('Google Play', 'Alibaba', 'Local',
- 'OUYA', 'V2'))
+ show_achievements_button = self._signed_in and account_type in (
+ 'Google Play',
+ 'Alibaba',
+ 'Local',
+ 'OUYA',
+ 'V2',
+ )
achievements_button_space = 60.0
- show_achievements_text = (self._signed_in
- and not show_achievements_button)
+ show_achievements_text = (
+ self._signed_in and not show_achievements_button
+ )
achievements_text_space = 27.0
- show_leaderboards_button = (self._signed_in and is_google)
+ show_leaderboards_button = self._signed_in and is_google
leaderboards_button_space = 60.0
show_campaign_progress = self._signed_in
@@ -245,30 +299,38 @@ class AccountSettingsWindow(ba.Window):
show_reset_progress_button = False
reset_progress_button_space = 70.0
- show_manage_v2_account_button = (self._signed_in
- and account_type == 'V2'
- and bool(False)) # Disabled for now.
+ show_manage_v2_account_button = (
+ self._signed_in and account_type == 'V2' and bool(False)
+ ) # Disabled for now.
manage_v2_account_button_space = 100.0
show_player_profiles_button = self._signed_in
- player_profiles_button_space = (70.0 if show_manage_v2_account_button
- else 100.0)
+ player_profiles_button_space = (
+ 70.0 if show_manage_v2_account_button else 100.0
+ )
- show_link_accounts_button = (self._signed_in and
- ba.internal.get_v1_account_misc_read_val(
- 'allowAccountLinking2', False))
+ show_link_accounts_button = (
+ self._signed_in
+ and ba.internal.get_v1_account_misc_read_val(
+ 'allowAccountLinking2', False
+ )
+ )
link_accounts_button_space = 70.0
show_unlink_accounts_button = show_link_accounts_button
unlink_accounts_button_space = 90.0
- show_sign_out_button = (self._signed_in and account_type
- in ['Local', 'Google Play', 'V2'])
+ show_sign_out_button = self._signed_in and account_type in [
+ 'Local',
+ 'Google Play',
+ 'V2',
+ ]
sign_out_button_space = 70.0
show_cancel_v2_sign_in_button = (
account_state == 'signing_in'
- and ba.app.accounts_v2.have_primary_credentials())
+ and ba.app.accounts_v2.have_primary_credentials()
+ )
cancel_v2_sign_in_button_space = 70.0
if self._subcontainer is not None:
@@ -316,13 +378,14 @@ class AccountSettingsWindow(ba.Window):
self._sub_height += sign_out_button_space
if show_cancel_v2_sign_in_button:
self._sub_height += cancel_v2_sign_in_button_space
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
first_selectable = None
v = self._sub_height - 10.0
@@ -335,14 +398,17 @@ class AccountSettingsWindow(ba.Window):
size=(0, 0),
text=ba.Lstr(
resource='accountSettingsWindow.deviceSpecificAccountText',
- subs=[('${NAME}',
- ba.internal.get_v1_account_display_string())]),
+ subs=[
+ ('${NAME}', ba.internal.get_v1_account_display_string())
+ ],
+ ),
scale=0.7,
color=(0.5, 0.5, 0.6),
maxwidth=self._sub_width * 0.9,
flatness=1.0,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
v -= local_signed_in_as_space * 0.4
self._account_name_text: ba.Widget | None
@@ -350,16 +416,19 @@ class AccountSettingsWindow(ba.Window):
v -= signed_in_as_space * 0.2
txt = ba.Lstr(
resource='accountSettingsWindow.youAreSignedInAsText',
- fallback_resource='accountSettingsWindow.youAreLoggedInAsText')
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v),
- size=(0, 0),
- text=txt,
- scale=0.9,
- color=ba.app.ui.title_color,
- maxwidth=self._sub_width * 0.9,
- h_align='center',
- v_align='center')
+ fallback_resource='accountSettingsWindow.youAreLoggedInAsText',
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v),
+ size=(0, 0),
+ text=txt,
+ scale=0.9,
+ color=ba.app.ui.title_color,
+ maxwidth=self._sub_width * 0.9,
+ h_align='center',
+ v_align='center',
+ )
v -= signed_in_as_space * 0.4
self._account_name_text = ba.textwidget(
parent=self._subcontainer,
@@ -370,7 +439,8 @@ class AccountSettingsWindow(ba.Window):
res_scale=1.5,
color=(1, 1, 1, 1),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
self._refresh_account_name_text()
v -= signed_in_as_space * 0.4
else:
@@ -385,45 +455,55 @@ class AccountSettingsWindow(ba.Window):
v -= sign_in_benefits_space
app = ba.app
extra: str | ba.Lstr | None
- if (app.platform in ['mac', 'ios']
- and app.subplatform == 'appstore'):
+ if app.platform in ['mac', 'ios'] and app.subplatform == 'appstore':
extra = ba.Lstr(
value='\n${S}',
- subs=[('${S}',
- ba.Lstr(resource='signInWithGameCenterText'))])
+ subs=[
+ ('${S}', ba.Lstr(resource='signInWithGameCenterText'))
+ ],
+ )
else:
extra = ''
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5,
- v + sign_in_benefits_space * 0.4),
- size=(0, 0),
- text=ba.Lstr(value='${A}${B}',
- subs=[('${A}',
- ba.Lstr(resource=self._r +
- '.signInInfoText')),
- ('${B}', extra)]),
- max_height=sign_in_benefits_space * 0.9,
- scale=0.9,
- color=(0.75, 0.7, 0.8),
- maxwidth=self._sub_width * 0.8,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(
+ self._sub_width * 0.5,
+ v + sign_in_benefits_space * 0.4,
+ ),
+ size=(0, 0),
+ text=ba.Lstr(
+ value='${A}${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource=self._r + '.signInInfoText')),
+ ('${B}', extra),
+ ],
+ ),
+ max_height=sign_in_benefits_space * 0.9,
+ scale=0.9,
+ color=(0.75, 0.7, 0.8),
+ maxwidth=self._sub_width * 0.8,
+ h_align='center',
+ v_align='center',
+ )
if show_signing_in_text:
v -= signing_in_text_space
ba.textwidget(
parent=self._subcontainer,
- position=(self._sub_width * 0.5,
- v + signing_in_text_space * 0.5),
+ position=(
+ self._sub_width * 0.5,
+ v + signing_in_text_space * 0.5,
+ ),
size=(0, 0),
text=ba.Lstr(resource='accountSettingsWindow.signingInText'),
scale=0.9,
color=(0, 1, 0),
maxwidth=self._sub_width * 0.8,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
if show_google_play_sign_in_button:
button_width = 350
@@ -435,18 +515,28 @@ class AccountSettingsWindow(ba.Window):
size=(button_width, 60),
label=ba.Lstr(
value='${A}${B}',
- subs=[('${A}',
- ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO)),
- ('${B}',
- ba.Lstr(resource=self._r +
- '.signInWithGooglePlayText'))]),
- on_activate_call=lambda: self._sign_in_press('Google Play'))
+ subs=[
+ (
+ '${A}',
+ ba.charstr(ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO),
+ ),
+ (
+ '${B}',
+ ba.Lstr(
+ resource=self._r + '.signInWithGooglePlayText'
+ ),
+ ),
+ ],
+ ),
+ on_activate_call=lambda: self._sign_in_press('Google Play'),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -460,7 +550,8 @@ class AccountSettingsWindow(ba.Window):
autoselect=True,
size=(button_width, 60),
label='',
- on_activate_call=self._v2_sign_in_press)
+ on_activate_call=self._v2_sign_in_press,
+ )
ba.textwidget(
parent=self._subcontainer,
draw_controller=btn,
@@ -470,29 +561,37 @@ class AccountSettingsWindow(ba.Window):
position=(self._sub_width * 0.5, v + 17),
text=ba.Lstr(
value='${A}${B}',
- subs=[('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)),
- ('${B}',
- ba.Lstr(resource=self._r + '.signInWithV2Text'))]),
+ subs=[
+ ('${A}', ba.charstr(ba.SpecialChar.V2_LOGO)),
+ (
+ '${B}',
+ ba.Lstr(resource=self._r + '.signInWithV2Text'),
+ ),
+ ],
+ ),
maxwidth=button_width * 0.8,
- color=(0.75, 1.0, 0.7))
- ba.textwidget(parent=self._subcontainer,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(self._sub_width * 0.5, v - 4),
- text=ba.Lstr(resource=self._r +
- '.signInWithV2InfoText'),
- flatness=1.0,
- scale=0.57,
- maxwidth=button_width * 0.9,
- color=(0.55, 0.8, 0.5))
+ color=(0.75, 1.0, 0.7),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._sub_width * 0.5, v - 4),
+ text=ba.Lstr(resource=self._r + '.signInWithV2InfoText'),
+ flatness=1.0,
+ scale=0.57,
+ maxwidth=button_width * 0.9,
+ color=(0.55, 0.8, 0.5),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -506,40 +605,48 @@ class AccountSettingsWindow(ba.Window):
autoselect=True,
size=(button_width, 60),
label='',
- on_activate_call=lambda: self._sign_in_press('Local'))
- ba.textwidget(parent=self._subcontainer,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(self._sub_width * 0.5, v + 17),
- text=ba.Lstr(
- value='${A}${B}',
- subs=[('${A}',
- ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)),
- ('${B}',
- ba.Lstr(resource=self._r +
- '.signInWithDeviceText'))]),
- maxwidth=button_width * 0.8,
- color=(0.75, 1.0, 0.7))
- ba.textwidget(parent=self._subcontainer,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(self._sub_width * 0.5, v - 4),
- text=ba.Lstr(resource=self._r +
- '.signInWithDeviceInfoText'),
- flatness=1.0,
- scale=0.57,
- maxwidth=button_width * 0.9,
- color=(0.55, 0.8, 0.5))
+ on_activate_call=lambda: self._sign_in_press('Local'),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._sub_width * 0.5, v + 17),
+ text=ba.Lstr(
+ value='${A}${B}',
+ subs=[
+ ('${A}', ba.charstr(ba.SpecialChar.LOCAL_ACCOUNT)),
+ (
+ '${B}',
+ ba.Lstr(resource=self._r + '.signInWithDeviceText'),
+ ),
+ ],
+ ),
+ maxwidth=button_width * 0.8,
+ color=(0.75, 1.0, 0.7),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._sub_width * 0.5, v - 4),
+ text=ba.Lstr(resource=self._r + '.signInWithDeviceInfoText'),
+ flatness=1.0,
+ scale=0.57,
+ maxwidth=button_width * 0.9,
+ color=(0.55, 0.8, 0.5),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
ba.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -557,13 +664,16 @@ class AccountSettingsWindow(ba.Window):
icon=ba.gettexture('settingsIcon'),
textcolor=(0.75, 0.7, 0.8),
on_activate_call=lambda: ba.open_url(
- 'https://ballistica.net/accountsettings'))
+ 'https://ballistica.net/accountsettings'
+ ),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
if show_player_profiles_button:
@@ -578,13 +688,15 @@ class AccountSettingsWindow(ba.Window):
color=(0.55, 0.5, 0.6),
icon=ba.gettexture('cuteSpaz'),
textcolor=(0.75, 0.7, 0.8),
- on_activate_call=self._player_profiles_press)
+ on_activate_call=self._player_profiles_press,
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0)
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
@@ -595,8 +707,9 @@ class AccountSettingsWindow(ba.Window):
if account_type == 'Game Center':
account_type_name = ba.Lstr(resource='gameCenterText')
else:
- raise ValueError("unknown account type: '" +
- str(account_type) + "'")
+ raise ValueError(
+ "unknown account type: '" + str(account_type) + "'"
+ )
self._game_service_button = btn = ba.buttonwidget(
parent=self._subcontainer,
position=((self._sub_width - button_width) * 0.5, v),
@@ -605,13 +718,15 @@ class AccountSettingsWindow(ba.Window):
autoselect=True,
on_activate_call=ba.internal.show_online_score_ui,
size=(button_width, 50),
- label=account_type_name)
+ label=account_type_name,
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
v -= game_service_button_space * 0.15
else:
@@ -628,7 +743,8 @@ class AccountSettingsWindow(ba.Window):
color=(0.75, 0.7, 0.8),
maxwidth=self._sub_width * 0.8,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
v -= achievements_text_space * 0.5
else:
self._achievements_text = None
@@ -643,18 +759,23 @@ class AccountSettingsWindow(ba.Window):
color=(0.55, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
- icon=ba.gettexture('googlePlayAchievementsIcon'
- if is_google else 'achievementsIcon'),
+ icon=ba.gettexture(
+ 'googlePlayAchievementsIcon'
+ if is_google
+ else 'achievementsIcon'
+ ),
icon_color=(0.8, 0.95, 0.7) if is_google else (0.85, 0.8, 0.9),
on_activate_call=self._on_achievements_press,
size=(button_width, 50),
- label='')
+ label='',
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
v -= achievements_button_space * 0.15
else:
@@ -677,13 +798,15 @@ class AccountSettingsWindow(ba.Window):
icon_color=(0.8, 0.95, 0.7),
on_activate_call=self._on_leaderboards_press,
size=(button_width, 50),
- label=ba.Lstr(resource='leaderboardsText'))
+ label=ba.Lstr(resource='leaderboardsText'),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
v -= leaderboards_button_space * 0.15
else:
@@ -700,7 +823,8 @@ class AccountSettingsWindow(ba.Window):
color=(0.75, 0.7, 0.8),
maxwidth=self._sub_width * 0.8,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
v -= campaign_progress_space * 0.5
self._refresh_campaign_progress_text()
else:
@@ -709,16 +833,17 @@ class AccountSettingsWindow(ba.Window):
self._tickets_text: ba.Widget | None
if show_tickets:
v -= tickets_space * 0.5
- self._tickets_text = ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5,
- v),
- size=(0, 0),
- scale=0.9,
- color=(0.75, 0.7, 0.8),
- maxwidth=self._sub_width * 0.8,
- flatness=1.0,
- h_align='center',
- v_align='center')
+ self._tickets_text = ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v),
+ size=(0, 0),
+ scale=0.9,
+ color=(0.75, 0.7, 0.8),
+ maxwidth=self._sub_width * 0.8,
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ )
v -= tickets_space * 0.5
self._refresh_tickets_text()
@@ -730,11 +855,13 @@ class AccountSettingsWindow(ba.Window):
button_width = 250
if show_reset_progress_button:
- confirm_text = (ba.Lstr(resource=self._r +
- '.resetProgressConfirmText')
- if self._can_reset_achievements else ba.Lstr(
- resource=self._r +
- '.resetProgressConfirmNoAchievementsText'))
+ confirm_text = (
+ ba.Lstr(resource=self._r + '.resetProgressConfirmText')
+ if self._can_reset_achievements
+ else ba.Lstr(
+ resource=self._r + '.resetProgressConfirmNoAchievementsText'
+ )
+ )
v -= reset_progress_button_space
self._reset_progress_button = btn = ba.buttonwidget(
parent=self._subcontainer,
@@ -748,13 +875,16 @@ class AccountSettingsWindow(ba.Window):
text=confirm_text,
width=500,
height=200,
- action=self._reset_progress))
+ action=self._reset_progress,
+ ),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn)
self._linked_accounts_text: ba.Widget | None
@@ -768,7 +898,8 @@ class AccountSettingsWindow(ba.Window):
color=(0.75, 0.7, 0.8),
maxwidth=self._sub_width * 0.95,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
v -= linked_accounts_text_space * 0.2
self._update_linked_accounts_text()
else:
@@ -783,34 +914,39 @@ class AccountSettingsWindow(ba.Window):
size=(button_width, 60),
label='',
color=(0.55, 0.5, 0.6),
- on_activate_call=self._link_accounts_press)
- ba.textwidget(parent=self._subcontainer,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(self._sub_width * 0.5, v + 17 + 20),
- text=ba.Lstr(resource=self._r + '.linkAccountsText'),
- maxwidth=button_width * 0.8,
- color=(0.75, 0.7, 0.8))
- ba.textwidget(parent=self._subcontainer,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(self._sub_width * 0.5, v - 4 + 20),
- text=ba.Lstr(resource=self._r +
- '.linkAccountsInfoText'),
- flatness=1.0,
- scale=0.5,
- maxwidth=button_width * 0.8,
- color=(0.75, 0.7, 0.8))
+ on_activate_call=self._link_accounts_press,
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._sub_width * 0.5, v + 17 + 20),
+ text=ba.Lstr(resource=self._r + '.linkAccountsText'),
+ maxwidth=button_width * 0.8,
+ color=(0.75, 0.7, 0.8),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._sub_width * 0.5, v - 4 + 20),
+ text=ba.Lstr(resource=self._r + '.linkAccountsInfoText'),
+ flatness=1.0,
+ scale=0.5,
+ maxwidth=button_width * 0.8,
+ color=(0.75, 0.7, 0.8),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._unlink_accounts_button: ba.Widget | None
@@ -823,7 +959,8 @@ class AccountSettingsWindow(ba.Window):
size=(button_width, 60),
label='',
color=(0.55, 0.5, 0.6),
- on_activate_call=self._unlink_accounts_press)
+ on_activate_call=self._unlink_accounts_press,
+ )
self._unlink_accounts_button_label = ba.textwidget(
parent=self._subcontainer,
draw_controller=btn,
@@ -833,13 +970,15 @@ class AccountSettingsWindow(ba.Window):
position=(self._sub_width * 0.5, v + 55),
text=ba.Lstr(resource=self._r + '.unlinkAccountsText'),
maxwidth=button_width * 0.8,
- color=(0.75, 0.7, 0.8))
+ color=(0.75, 0.7, 0.8),
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._update_unlink_accounts_button()
else:
@@ -855,13 +994,15 @@ class AccountSettingsWindow(ba.Window):
color=(0.55, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
- on_activate_call=self._sign_out_press)
+ on_activate_call=self._sign_out_press,
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
if show_cancel_v2_sign_in_button:
@@ -874,49 +1015,63 @@ class AccountSettingsWindow(ba.Window):
color=(0.55, 0.5, 0.6),
textcolor=(0.75, 0.7, 0.8),
autoselect=True,
- on_activate_call=self._cancel_v2_sign_in_press)
+ on_activate_call=self._cancel_v2_sign_in_press,
+ )
if first_selectable is None:
first_selectable = btn
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
ba.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
# Whatever the topmost selectable thing is, we want it to scroll all
# the way up when we select it.
if first_selectable is not None:
- ba.widget(edit=first_selectable,
- up_widget=bbtn,
- show_buffer_top=400)
+ ba.widget(
+ edit=first_selectable, up_widget=bbtn, show_buffer_top=400
+ )
# (this should re-scroll us to the top..)
- ba.containerwidget(edit=self._subcontainer,
- visible_child=first_selectable)
+ ba.containerwidget(
+ edit=self._subcontainer, visible_child=first_selectable
+ )
self._needs_refresh = False
def _on_achievements_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui import achievements
+
account_state = ba.internal.get_v1_account_state()
- account_type = (ba.internal.get_v1_account_type()
- if account_state == 'signed_in' else 'unknown')
+ account_type = (
+ ba.internal.get_v1_account_type()
+ if account_state == 'signed_in'
+ else 'unknown'
+ )
# for google play we use the built-in UI; otherwise pop up our own
if account_type == 'Google Play':
- ba.timer(0.15,
- ba.Call(ba.internal.show_online_score_ui, 'achievements'),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.15,
+ ba.Call(ba.internal.show_online_score_ui, 'achievements'),
+ timetype=ba.TimeType.REAL,
+ )
elif account_type != 'unknown':
assert self._achievements_button is not None
achievements.AchievementsWindow(
- position=self._achievements_button.get_screen_space_center())
+ position=self._achievements_button.get_screen_space_center()
+ )
else:
- print('ERROR: unknown account type in on_achievements_press:',
- account_type)
+ print(
+ 'ERROR: unknown account type in on_achievements_press:',
+ account_type,
+ )
def _on_leaderboards_press(self) -> None:
- ba.timer(0.15,
- ba.Call(ba.internal.show_online_score_ui, 'leaderboards'),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.15,
+ ba.Call(ba.internal.show_online_score_ui, 'leaderboards'),
+ timetype=ba.TimeType.REAL,
+ )
def _have_unlinkable_accounts(self) -> bool:
# if this is not present, we haven't had contact from the server so
@@ -924,7 +1079,8 @@ class AccountSettingsWindow(ba.Window):
if ba.internal.get_public_login_id() is None:
return False
accounts = ba.internal.get_v1_account_misc_read_val_2(
- 'linkedAccounts', [])
+ 'linkedAccounts', []
+ )
return len(accounts) > 1
def _update_unlink_accounts_button(self) -> None:
@@ -947,7 +1103,8 @@ class AccountSettingsWindow(ba.Window):
accounts_str = num * '.' + (4 - num) * ' '
else:
accounts = ba.internal.get_v1_account_misc_read_val_2(
- 'linkedAccounts', [])
+ 'linkedAccounts', []
+ )
# our_account = _bs.get_v1_account_display_string()
# accounts = [a for a in accounts if a != our_account]
# accounts_str = u', '.join(accounts) if accounts else
@@ -956,15 +1113,20 @@ class AccountSettingsWindow(ba.Window):
# accounts
# (they can see that in the unlink section if they're curious)
accounts_str = str(max(0, len(accounts) - 1))
- ba.textwidget(edit=self._linked_accounts_text,
- text=ba.Lstr(value='${L} ${A}',
- subs=[('${L}',
- ba.Lstr(resource=self._r +
- '.linkedAccountsText')),
- ('${A}', accounts_str)]))
+ ba.textwidget(
+ edit=self._linked_accounts_text,
+ text=ba.Lstr(
+ value='${L} ${A}',
+ subs=[
+ ('${L}', ba.Lstr(resource=self._r + '.linkedAccountsText')),
+ ('${A}', accounts_str),
+ ],
+ ),
+ )
def _refresh_campaign_progress_text(self) -> None:
from ba.internal import getcampaign
+
if self._campaign_progress_text is None:
return
p_str: str | ba.Lstr
@@ -975,9 +1137,10 @@ class AccountSettingsWindow(ba.Window):
# Last level cant be completed; hence the -1;
progress = min(1.0, float(levels_complete) / (len(levels) - 1))
- p_str = ba.Lstr(resource=self._r + '.campaignProgressText',
- subs=[('${PROGRESS}',
- str(int(progress * 100.0)) + '%')])
+ p_str = ba.Lstr(
+ resource=self._r + '.campaignProgressText',
+ subs=[('${PROGRESS}', str(int(progress * 100.0)) + '%')],
+ )
except Exception:
p_str = '?'
ba.print_exception('Error calculating co-op campaign progress.')
@@ -991,9 +1154,12 @@ class AccountSettingsWindow(ba.Window):
except Exception:
ba.print_exception()
tc_str = '-'
- ba.textwidget(edit=self._tickets_text,
- text=ba.Lstr(resource=self._r + '.ticketsText',
- subs=[('${COUNT}', tc_str)]))
+ ba.textwidget(
+ edit=self._tickets_text,
+ text=ba.Lstr(
+ resource=self._r + '.ticketsText', subs=[('${COUNT}', tc_str)]
+ ),
+ )
def _refresh_account_name_text(self) -> None:
if self._account_name_text is None:
@@ -1006,14 +1172,17 @@ class AccountSettingsWindow(ba.Window):
ba.textwidget(edit=self._account_name_text, text=name_str)
def _refresh_achievements(self) -> None:
- if (self._achievements_text is None
- and self._achievements_button is None):
+ if (
+ self._achievements_text is None
+ and self._achievements_button is None
+ ):
return
complete = sum(1 if a.complete else 0 for a in ba.app.ach.achievements)
total = len(ba.app.ach.achievements)
- txt_final = ba.Lstr(resource=self._r + '.achievementProgressText',
- subs=[('${COUNT}', str(complete)),
- ('${TOTAL}', str(total))])
+ txt_final = ba.Lstr(
+ resource=self._r + '.achievementProgressText',
+ subs=[('${COUNT}', str(complete)), ('${TOTAL}', str(total))],
+ )
if self._achievements_text is not None:
ba.textwidget(edit=self._achievements_text, text=txt_final)
@@ -1023,11 +1192,13 @@ class AccountSettingsWindow(ba.Window):
def _link_accounts_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import link
+
link.AccountLinkWindow(origin_widget=self._link_accounts_button)
def _unlink_accounts_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import unlink
+
if not self._have_unlinkable_accounts():
ba.playsound(ba.getsound('error'))
return
@@ -1036,10 +1207,12 @@ class AccountSettingsWindow(ba.Window):
def _player_profiles_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.profile import browser as pbrowser
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
pbrowser.ProfileBrowserWindow(
- origin_widget=self._player_profiles_button)
+ origin_widget=self._player_profiles_button
+ )
def _cancel_v2_sign_in_press(self) -> None:
# Just say we don't wanna be signed in anymore.
@@ -1061,15 +1234,17 @@ class AccountSettingsWindow(ba.Window):
# signed in at this point (affects v1 accounts).
cfg['Auto Account State'] = 'signed_out'
cfg.commit()
- ba.buttonwidget(edit=self._sign_out_button,
- label=ba.Lstr(resource=self._r + '.signingOutText'))
+ ba.buttonwidget(
+ edit=self._sign_out_button,
+ label=ba.Lstr(resource=self._r + '.signingOutText'),
+ )
# Speed UI updates along.
ba.timer(0.1, ba.WeakCall(self._update), timetype=ba.TimeType.REAL)
- def _sign_in_press(self,
- account_type: str,
- show_test_warning: bool = True) -> None:
+ def _sign_in_press(
+ self, account_type: str, show_test_warning: bool = True
+ ) -> None:
del show_test_warning # unused
ba.internal.sign_in_v1(account_type)
@@ -1083,12 +1258,14 @@ class AccountSettingsWindow(ba.Window):
def _v2_sign_in_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.v2 import V2SignInWindow
+
assert self._sign_in_v2_button is not None
V2SignInWindow(origin_widget=self._sign_in_v2_button)
def _reset_progress(self) -> None:
try:
from ba.internal import getcampaign
+
# FIXME: This would need to happen server-side these days.
if self._can_reset_achievements:
ba.app.config['Achievements'] = {}
@@ -1106,13 +1283,16 @@ class AccountSettingsWindow(ba.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
def _save_state(self) -> None:
try:
diff --git a/assets/src/ba_data/python/bastd/ui/account/unlink.py b/assets/src/ba_data/python/bastd/ui/account/unlink.py
index 84dffb90..ffbce7db 100644
--- a/assets/src/ba_data/python/bastd/ui/account/unlink.py
+++ b/assets/src/ba_data/python/bastd/ui/account/unlink.py
@@ -33,24 +33,36 @@ class AccountUnlinkWindow(ba.Window):
self._scroll_width = 400
self._scroll_height = 200
uiscale = ba.app.ui.uiscale
- base_scale = (2.0 if uiscale is ba.UIScale.SMALL else
- 1.6 if uiscale is ba.UIScale.MEDIUM else 1.1)
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=base_scale,
- scale_origin_stack_offset=scale_origin,
- stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- position=(30, self._height - 50),
- size=(50, 50),
- scale=0.7,
- label='',
- color=bg_color,
- on_activate_call=self._cancel,
- autoselect=True,
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ base_scale = (
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.6
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.1
+ )
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=base_scale,
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(0, -10)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(30, self._height - 50),
+ size=(50, 50),
+ scale=0.7,
+ label='',
+ color=bg_color,
+ on_activate_call=self._cancel,
+ autoselect=True,
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height * 0.88),
@@ -61,62 +73,76 @@ class AccountUnlinkWindow(ba.Window):
maxwidth=self._width * 0.7,
color=ba.app.ui.infotextcolor,
h_align='center',
- v_align='center')
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ v_align='center',
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - 85 - self._scroll_height),
- size=(self._scroll_width, self._scroll_height))
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - 85 - self._scroll_height,
+ ),
+ size=(self._scroll_width, self._scroll_height),
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
- self._columnwidget = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0,
- left_border=10)
+ self._columnwidget = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0, left_border=10
+ )
our_login_id = ba.internal.get_public_login_id()
if our_login_id is None:
entries = []
else:
account_infos = ba.internal.get_v1_account_misc_read_val_2(
- 'linkedAccounts2', [])
- entries = [{
- 'name': ai['d'],
- 'id': ai['id']
- } for ai in account_infos if ai['id'] != our_login_id]
+ 'linkedAccounts2', []
+ )
+ entries = [
+ {'name': ai['d'], 'id': ai['id']}
+ for ai in account_infos
+ if ai['id'] != our_login_id
+ ]
# (avoid getting our selection stuck on an empty column widget)
if not entries:
ba.containerwidget(edit=self._scrollwidget, selectable=False)
for i, entry in enumerate(entries):
- txt = ba.textwidget(parent=self._columnwidget,
- selectable=True,
- text=entry['name'],
- size=(self._scroll_width - 30, 30),
- autoselect=True,
- click_activate=True,
- on_activate_call=ba.Call(
- self._on_entry_selected, entry))
+ txt = ba.textwidget(
+ parent=self._columnwidget,
+ selectable=True,
+ text=entry['name'],
+ size=(self._scroll_width - 30, 30),
+ autoselect=True,
+ click_activate=True,
+ on_activate_call=ba.Call(self._on_entry_selected, entry),
+ )
ba.widget(edit=txt, left_widget=self._cancel_button)
if i == 0:
ba.widget(edit=txt, up_widget=self._cancel_button)
def _on_entry_selected(self, entry: dict[str, Any]) -> None:
- ba.screenmessage(ba.Lstr(resource='pleaseWaitText',
- fallback_resource='requestingText'),
- color=(0, 1, 0))
- ba.internal.add_transaction({
- 'type': 'ACCOUNT_UNLINK_REQUEST',
- 'accountID': entry['id'],
- 'expire_time': time.time() + 5
- })
+ ba.screenmessage(
+ ba.Lstr(
+ resource='pleaseWaitText', fallback_resource='requestingText'
+ ),
+ color=(0, 1, 0),
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'ACCOUNT_UNLINK_REQUEST',
+ 'accountID': entry['id'],
+ 'expire_time': time.time() + 5,
+ }
+ )
ba.internal.run_transactions()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
def _cancel(self) -> None:
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/account/v2.py b/assets/src/ba_data/python/bastd/ui/account/v2.py
index 38047c23..7f155e8a 100644
--- a/assets/src/ba_data/python/bastd/ui/account/v2.py
+++ b/assets/src/ba_data/python/bastd/ui/account/v2.py
@@ -29,12 +29,22 @@ class V2SignInWindow(ba.Window):
self._proxykey: str | None = None
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_scale',
- scale_origin_stack_offset=origin_widget.get_screen_space_center(),
- scale=(1.25 if uiscale is ba.UIScale.SMALL else
- 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_scale',
+ scale_origin_stack_offset=(
+ origin_widget.get_screen_space_center()
+ ),
+ scale=(
+ 1.25
+ if uiscale is ba.UIScale.SMALL
+ else 1.05
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.9
+ ),
+ )
+ )
self._loading_text = ba.textwidget(
parent=self._root_widget,
@@ -43,8 +53,10 @@ class V2SignInWindow(ba.Window):
v_align='center',
size=(0, 0),
maxwidth=0.9 * self._width,
- text=ba.Lstr(value='${A}...',
- subs=[('${A}', ba.Lstr(resource='loadingText'))]),
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
)
self._cancel_button = ba.buttonwidget(
@@ -57,15 +69,17 @@ class V2SignInWindow(ba.Window):
autoselect=True,
textcolor=(0.75, 0.7, 0.8),
)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
self._update_timer: ba.Timer | None = None
# Ask the cloud for a proxy login id.
- ba.app.cloud.send_message_cb(bacommon.cloud.LoginProxyRequestMessage(),
- on_response=ba.WeakCall(
- self._on_proxy_request_response))
+ ba.app.cloud.send_message_cb(
+ bacommon.cloud.LoginProxyRequestMessage(),
+ on_response=ba.WeakCall(self._on_proxy_request_response),
+ )
def _on_proxy_request_response(
self, response: bacommon.cloud.LoginProxyRequestResponse | Exception
@@ -77,12 +91,14 @@ class V2SignInWindow(ba.Window):
ba.textwidget(
edit=self._loading_text,
text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
return
# Show link(s) the user can use to log in.
- address = ba.internal.get_master_server_address(
- version=2) + response.url
+ address = (
+ ba.internal.get_master_server_address(version=2) + response.url
+ )
address_pretty = address.removeprefix('https://')
ba.textwidget(
@@ -90,55 +106,70 @@ class V2SignInWindow(ba.Window):
position=(self._width * 0.5, self._height - 95),
size=(0, 0),
text=ba.Lstr(
- resource='accountSettingsWindow.v2LinkInstructionsText'),
+ resource='accountSettingsWindow.v2LinkInstructionsText'
+ ),
color=ba.app.ui.title_color,
maxwidth=self._width * 0.9,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
button_width = 450
if is_browser_likely_available():
- ba.buttonwidget(parent=self._root_widget,
- position=((self._width * 0.5 - button_width * 0.5),
- self._height - 185),
- autoselect=True,
- size=(button_width, 60),
- label=ba.Lstr(value=address_pretty),
- color=(0.55, 0.5, 0.6),
- textcolor=(0.75, 0.7, 0.8),
- on_activate_call=lambda: ba.open_url(address))
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ (self._width * 0.5 - button_width * 0.5),
+ self._height - 185,
+ ),
+ autoselect=True,
+ size=(button_width, 60),
+ label=ba.Lstr(value=address_pretty),
+ color=(0.55, 0.5, 0.6),
+ textcolor=(0.75, 0.7, 0.8),
+ on_activate_call=lambda: ba.open_url(address),
+ )
qroffs = 0.0
else:
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 145),
- size=(0, 0),
- text=ba.Lstr(value=address_pretty),
- flatness=1.0,
- maxwidth=self._width,
- scale=0.75,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 145),
+ size=(0, 0),
+ text=ba.Lstr(value=address_pretty),
+ flatness=1.0,
+ maxwidth=self._width,
+ scale=0.75,
+ h_align='center',
+ v_align='center',
+ )
qroffs = 20.0
qr_size = 270
- ba.imagewidget(parent=self._root_widget,
- position=(self._width * 0.5 - qr_size * 0.5,
- self._height * 0.36 + qroffs - qr_size * 0.5),
- size=(qr_size, qr_size),
- texture=ba.internal.get_qrcode_texture(address))
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ self._width * 0.5 - qr_size * 0.5,
+ self._height * 0.36 + qroffs - qr_size * 0.5,
+ ),
+ size=(qr_size, qr_size),
+ texture=ba.internal.get_qrcode_texture(address),
+ )
# Start querying for results.
self._proxyid = response.proxyid
self._proxykey = response.proxykey
- ba.timer(STATUS_CHECK_INTERVAL_SECONDS,
- ba.WeakCall(self._ask_for_status))
+ ba.timer(
+ STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
+ )
def _ask_for_status(self) -> None:
assert self._proxyid is not None
assert self._proxykey is not None
ba.app.cloud.send_message_cb(
bacommon.cloud.LoginProxyStateQueryMessage(
- proxyid=self._proxyid, proxykey=self._proxykey),
- on_response=ba.WeakCall(self._got_status))
+ proxyid=self._proxyid, proxykey=self._proxykey
+ ),
+ on_response=ba.WeakCall(self._got_status),
+ )
def _got_status(
self, response: bacommon.cloud.LoginProxyStateQueryResponse | Exception
@@ -146,16 +177,20 @@ class V2SignInWindow(ba.Window):
# For now, if anything goes wrong on the server-side, just abort
# with a vague error message. Can be more verbose later if need be.
- if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
- and response.state is response.State.FAIL):
+ if (
+ isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
+ and response.state is response.State.FAIL
+ ):
ba.playsound(ba.getsound('error'))
ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
self._done()
return
# If we got a token, set ourself as signed in. Hooray!
- if (isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
- and response.state is response.State.SUCCESS):
+ if (
+ isinstance(response, bacommon.cloud.LoginProxyStateQueryResponse)
+ and response.state is response.State.SUCCESS
+ ):
assert response.credentials is not None
ba.app.accounts_v2.set_primary_credentials(response.credentials)
@@ -165,23 +200,29 @@ class V2SignInWindow(ba.Window):
try:
ba.app.cloud.send_message_cb(
bacommon.cloud.LoginProxyCompleteMessage(
- proxyid=self._proxyid),
- on_response=ba.WeakCall(self._proxy_complete_response))
+ proxyid=self._proxyid
+ ),
+ on_response=ba.WeakCall(self._proxy_complete_response),
+ )
except CommunicationError:
pass
except Exception:
logging.warning(
'Unexpected error sending login-proxy-complete message',
- exc_info=True)
+ exc_info=True,
+ )
self._done()
return
# If we're still waiting, ask again soon.
- if (isinstance(response, Exception)
- or response.state is response.State.WAITING):
- ba.timer(STATUS_CHECK_INTERVAL_SECONDS,
- ba.WeakCall(self._ask_for_status))
+ if (
+ isinstance(response, Exception)
+ or response.state is response.State.WAITING
+ ):
+ ba.timer(
+ STATUS_CHECK_INTERVAL_SECONDS, ba.WeakCall(self._ask_for_status)
+ )
def _proxy_complete_response(self, response: None | Exception) -> None:
del response # Not used.
diff --git a/assets/src/ba_data/python/bastd/ui/account/viewer.py b/assets/src/ba_data/python/bastd/ui/account/viewer.py
index d62848f6..d8cdfbc9 100644
--- a/assets/src/ba_data/python/bastd/ui/account/viewer.py
+++ b/assets/src/ba_data/python/bastd/ui/account/viewer.py
@@ -17,12 +17,14 @@ if TYPE_CHECKING:
class AccountViewerWindow(popup.PopupWindow):
"""Popup window that displays info for an account."""
- def __init__(self,
- account_id: str,
- profile_id: str | None = None,
- position: tuple[float, float] = (0.0, 0.0),
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0)):
+ def __init__(
+ self,
+ account_id: str,
+ profile_id: str | None = None,
+ position: tuple[float, float] = (0.0, 0.0),
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ ):
from ba.internal import is_browser_likely_available, master_server_get
self._account_id = account_id
@@ -30,24 +32,36 @@ class AccountViewerWindow(popup.PopupWindow):
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.6 if uiscale is ba.UIScale.SMALL else
- 1.8 if uiscale is ba.UIScale.MEDIUM else 1.4)
+ scale = (
+ 2.6
+ if uiscale is ba.UIScale.SMALL
+ else 1.8
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.4
+ )
self._transitioning_out = False
self._width = 400
- self._height = (300 if uiscale is ba.UIScale.SMALL else
- 400 if uiscale is ba.UIScale.MEDIUM else 450)
+ self._height = (
+ 300
+ if uiscale is ba.UIScale.SMALL
+ else 400
+ if uiscale is ba.UIScale.MEDIUM
+ else 450
+ )
self._subcontainer: ba.Widget | None = None
bg_color = (0.5, 0.4, 0.6)
# Creates our _root_widget.
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color,
- offset=offset)
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ offset=offset,
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
@@ -59,7 +73,8 @@ class AccountViewerWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
self._title_text = ba.textwidget(
parent=self.root_widget,
@@ -70,30 +85,38 @@ class AccountViewerWindow(popup.PopupWindow):
scale=0.6,
text=ba.Lstr(resource='playerInfoText'),
maxwidth=200,
- color=(0.7, 0.7, 0.7, 0.7))
+ color=(0.7, 0.7, 0.7, 0.7),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._width - 60,
- self._height - 70),
- position=(30, 30),
- capture_arrows=True,
- simple_culling_v=10)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._width - 60, self._height - 70),
+ position=(30, 30),
+ capture_arrows=True,
+ simple_culling_v=10,
+ )
ba.widget(edit=self._scrollwidget, autoselect=True)
self._loading_text = ba.textwidget(
parent=self._scrollwidget,
scale=0.5,
- text=ba.Lstr(value='${A}...',
- subs=[('${A}', ba.Lstr(resource='loadingText'))]),
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
size=(self._width - 60, 100),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
# In cases where the user most likely has a browser/email, lets
# offer a 'report this user' button.
- if (is_browser_likely_available()
- and ba.internal.get_v1_account_misc_read_val(
- 'showAccountExtrasMenu', False)):
+ if (
+ is_browser_likely_available()
+ and ba.internal.get_v1_account_misc_read_val(
+ 'showAccountExtrasMenu', False
+ )
+ ):
self._extras_menu_button = ba.buttonwidget(
parent=self.root_widget,
@@ -104,20 +127,26 @@ class AccountViewerWindow(popup.PopupWindow):
button_type='square',
color=(0.64, 0.52, 0.69),
textcolor=(0.57, 0.47, 0.57),
- on_activate_call=self._on_extras_menu_press)
+ on_activate_call=self._on_extras_menu_press,
+ )
- ba.containerwidget(edit=self.root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
- master_server_get('bsAccountInfo', {
- 'buildNumber': ba.app.build_number,
- 'accountID': self._account_id,
- 'profileID': self._profile_id
- },
- callback=ba.WeakCall(self._on_query_response))
+ master_server_get(
+ 'bsAccountInfo',
+ {
+ 'buildNumber': ba.app.build_number,
+ 'accountID': self._account_id,
+ 'profileID': self._profile_id,
+ },
+ callback=ba.WeakCall(self._on_query_response),
+ )
- def popup_menu_selected_choice(self, window: popup.PopupMenu,
- choice: str) -> None:
+ def popup_menu_selected_choice(
+ self, window: popup.PopupMenu, choice: str
+ ) -> None:
"""Called when a menu entry is selected."""
del window # Unused arg.
if choice == 'more':
@@ -136,7 +165,7 @@ class AccountViewerWindow(popup.PopupWindow):
choices = ['more', 'report']
choices_display = [
ba.Lstr(resource='coopSelectWindow.seeMoreText'),
- ba.Lstr(resource='reportThisPlayerText')
+ ba.Lstr(resource='reportThisPlayerText'),
]
is_admin = False
if is_admin:
@@ -147,28 +176,38 @@ class AccountViewerWindow(popup.PopupWindow):
uiscale = ba.app.ui.uiscale
popup.PopupMenuWindow(
position=self._extras_menu_button.get_screen_space_center(),
- scale=(2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23),
+ scale=(
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ ),
choices=choices,
choices_display=choices_display,
current_choice='more',
- delegate=self)
+ delegate=self,
+ )
def _on_ban_press(self) -> None:
- ba.internal.add_transaction({
- 'type': 'BAN_ACCOUNT',
- 'account': self._account_id
- })
+ ba.internal.add_transaction(
+ {'type': 'BAN_ACCOUNT', 'account': self._account_id}
+ )
ba.internal.run_transactions()
def _on_report_press(self) -> None:
from bastd.ui import report
- report.ReportPlayerWindow(self._account_id,
- origin_widget=self._extras_menu_button)
+
+ report.ReportPlayerWindow(
+ self._account_id, origin_widget=self._extras_menu_button
+ )
def _on_more_press(self) -> None:
- ba.open_url(ba.internal.get_master_server_address() +
- '/highscores?profile=' + self._account_id)
+ ba.open_url(
+ ba.internal.get_master_server_address()
+ + '/highscores?profile='
+ + self._account_id
+ )
def _on_query_response(self, data: dict[str, Any] | None) -> None:
# FIXME: Tidy this up.
@@ -179,7 +218,8 @@ class AccountViewerWindow(popup.PopupWindow):
if data is None:
ba.textwidget(
edit=self._loading_text,
- text=ba.Lstr(resource='internal.unavailableNoConnectionText'))
+ text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
+ )
else:
try:
self._loading_text.delete()
@@ -188,10 +228,10 @@ class AccountViewerWindow(popup.PopupWindow):
trophystr = data['trophies']
num = 10
chunks = [
- trophystr[i:i + num]
+ trophystr[i : i + num]
for i in range(0, len(trophystr), num)
]
- trophystr = ('\n\n'.join(chunks))
+ trophystr = '\n\n'.join(chunks)
if trophystr == '':
trophystr = '-'
except Exception:
@@ -199,14 +239,19 @@ class AccountViewerWindow(popup.PopupWindow):
account_name_spacing = 15
tscale = 0.65
ts_height = ba.internal.get_string_height(
- trophystr, suppress_warning=True)
+ trophystr, suppress_warning=True
+ )
sub_width = self._width - 80
- sub_height = 200 + ts_height * tscale + \
- account_name_spacing * len(data['accountDisplayStrings'])
+ sub_height = (
+ 200
+ + ts_height * tscale
+ + account_name_spacing * len(data['accountDisplayStrings'])
+ )
self._subcontainer = ba.containerwidget(
parent=self._scrollwidget,
size=(sub_width, sub_height),
- background=False)
+ background=False,
+ )
v = sub_height - 20
title_scale = 0.37
@@ -219,17 +264,24 @@ class AccountViewerWindow(popup.PopupWindow):
if data['profile'] is not None:
profile = data['profile']
character = ba.app.spaz_appearances.get(
- profile['character'], None)
+ profile['character'], None
+ )
if character is not None:
- tint_color = (profile['color'] if 'color'
- in profile else (1, 1, 1))
- tint2_color = (profile['highlight']
- if 'highlight' in profile else
- (1, 1, 1))
+ tint_color = (
+ profile['color']
+ if 'color' in profile
+ else (1, 1, 1)
+ )
+ tint2_color = (
+ profile['highlight']
+ if 'highlight' in profile
+ else (1, 1, 1)
+ )
icon_tex = character.icon_texture
tint_tex = character.icon_mask_texture
mask_texture = ba.gettexture(
- 'characterIconMask')
+ 'characterIconMask'
+ )
ba.imagewidget(
parent=self._subcontainer,
position=(sub_width * center - 40, v - 80),
@@ -239,7 +291,8 @@ class AccountViewerWindow(popup.PopupWindow):
texture=ba.gettexture(icon_tex),
tint_texture=ba.gettexture(tint_tex),
tint_color=tint_color,
- tint2_color=tint2_color)
+ tint2_color=tint2_color,
+ )
v -= 95
except Exception:
ba.print_exception('Error displaying character.')
@@ -253,7 +306,8 @@ class AccountViewerWindow(popup.PopupWindow):
color=ba.safecolor(tint_color, 0.7),
shadow=1.0,
text=ba.Lstr(value=data['profileDisplayString']),
- maxwidth=sub_width * maxwidth_scale * 0.75)
+ maxwidth=sub_width * maxwidth_scale * 0.75,
+ )
showing_character = True
v -= 33
@@ -263,172 +317,228 @@ class AccountViewerWindow(popup.PopupWindow):
v = sub_height - 20
if len(data['accountDisplayStrings']) <= 1:
account_title = ba.Lstr(
- resource='settingsWindow.accountText')
+ resource='settingsWindow.accountText'
+ )
else:
account_title = ba.Lstr(
resource='accountSettingsWindow.accountsText',
- fallback_resource='settingsWindow.accountText')
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- flatness=1.0,
- h_align='center',
- v_align='center',
- scale=title_scale,
- color=ba.app.ui.infotextcolor,
- text=account_title,
- maxwidth=sub_width * maxwidth_scale)
- draw_small = (showing_character
- or len(data['accountDisplayStrings']) > 1)
+ fallback_resource='settingsWindow.accountText',
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ scale=title_scale,
+ color=ba.app.ui.infotextcolor,
+ text=account_title,
+ maxwidth=sub_width * maxwidth_scale,
+ )
+ draw_small = (
+ showing_character or len(data['accountDisplayStrings']) > 1
+ )
v -= 14 if draw_small else 20
for account_string in data['accountDisplayStrings']:
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- h_align='center',
- v_align='center',
- scale=0.55 if draw_small else 0.8,
- text=account_string,
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ h_align='center',
+ v_align='center',
+ scale=0.55 if draw_small else 0.8,
+ text=account_string,
+ maxwidth=sub_width * maxwidth_scale,
+ )
v -= account_name_spacing
v += account_name_spacing
v -= 25 if showing_character else 29
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- flatness=1.0,
- h_align='center',
- v_align='center',
- scale=title_scale,
- color=ba.app.ui.infotextcolor,
- text=ba.Lstr(resource='rankText'),
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ scale=title_scale,
+ color=ba.app.ui.infotextcolor,
+ text=ba.Lstr(resource='rankText'),
+ maxwidth=sub_width * maxwidth_scale,
+ )
v -= 14
if data['rank'] is None:
rank_str = '-'
suffix_offset = None
else:
str_raw = ba.Lstr(
- resource='league.rankInLeagueText').evaluate()
+ resource='league.rankInLeagueText'
+ ).evaluate()
# FIXME: Would be nice to not have to eval this.
rank_str = ba.Lstr(
resource='league.rankInLeagueText',
- subs=[('${RANK}', str(data['rank'][2])),
- ('${NAME}',
- ba.Lstr(translate=('leagueNames',
- data['rank'][0]))),
- ('${SUFFIX}', '')]).evaluate()
+ subs=[
+ ('${RANK}', str(data['rank'][2])),
+ (
+ '${NAME}',
+ ba.Lstr(
+ translate=('leagueNames', data['rank'][0])
+ ),
+ ),
+ ('${SUFFIX}', ''),
+ ],
+ ).evaluate()
rank_str_width = min(
sub_width * maxwidth_scale,
ba.internal.get_string_width(
- rank_str, suppress_warning=True) * 0.55)
+ rank_str, suppress_warning=True
+ )
+ * 0.55,
+ )
# Only tack our suffix on if its at the end and only for
# non-diamond leagues.
- if (str_raw.endswith('${SUFFIX}')
- and data['rank'][0] != 'Diamond'):
+ if (
+ str_raw.endswith('${SUFFIX}')
+ and data['rank'][0] != 'Diamond'
+ ):
suffix_offset = rank_str_width * 0.5 + 2
else:
suffix_offset = None
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- h_align='center',
- v_align='center',
- scale=0.55,
- text=rank_str,
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ h_align='center',
+ v_align='center',
+ scale=0.55,
+ text=rank_str,
+ maxwidth=sub_width * maxwidth_scale,
+ )
if suffix_offset is not None:
assert data['rank'] is not None
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center + suffix_offset,
- v + 3),
- h_align='left',
- v_align='center',
- scale=0.29,
- flatness=1.0,
- text='[' + str(data['rank'][1]) + ']')
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center + suffix_offset, v + 3),
+ h_align='left',
+ v_align='center',
+ scale=0.29,
+ flatness=1.0,
+ text='[' + str(data['rank'][1]) + ']',
+ )
v -= 14
- str_raw = ba.Lstr(
- resource='league.rankInLeagueText').evaluate()
+ str_raw = ba.Lstr(resource='league.rankInLeagueText').evaluate()
old_offs = -50
prev_ranks_shown = 0
for prev_rank in data['prevRanks']:
rank_str = ba.Lstr(
value='${S}: ${I}',
subs=[
- ('${S}',
- ba.Lstr(resource='league.seasonText',
- subs=[('${NUMBER}', str(prev_rank[0]))])),
- ('${I}',
- ba.Lstr(resource='league.rankInLeagueText',
- subs=[('${RANK}', str(prev_rank[3])),
- ('${NAME}',
- ba.Lstr(translate=('leagueNames',
- prev_rank[1]))),
- ('${SUFFIX}', '')]))
- ]).evaluate()
+ (
+ '${S}',
+ ba.Lstr(
+ resource='league.seasonText',
+ subs=[('${NUMBER}', str(prev_rank[0]))],
+ ),
+ ),
+ (
+ '${I}',
+ ba.Lstr(
+ resource='league.rankInLeagueText',
+ subs=[
+ ('${RANK}', str(prev_rank[3])),
+ (
+ '${NAME}',
+ ba.Lstr(
+ translate=(
+ 'leagueNames',
+ prev_rank[1],
+ )
+ ),
+ ),
+ ('${SUFFIX}', ''),
+ ],
+ ),
+ ),
+ ],
+ ).evaluate()
rank_str_width = min(
sub_width * maxwidth_scale,
ba.internal.get_string_width(
- rank_str, suppress_warning=True) * 0.3)
+ rank_str, suppress_warning=True
+ )
+ * 0.3,
+ )
# Only tack our suffix on if its at the end and only for
# non-diamond leagues.
- if (str_raw.endswith('${SUFFIX}')
- and prev_rank[1] != 'Diamond'):
+ if (
+ str_raw.endswith('${SUFFIX}')
+ and prev_rank[1] != 'Diamond'
+ ):
suffix_offset = rank_str_width + 2
else:
suffix_offset = None
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center + old_offs, v),
- h_align='left',
- v_align='center',
- scale=0.3,
- text=rank_str,
- flatness=1.0,
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center + old_offs, v),
+ h_align='left',
+ v_align='center',
+ scale=0.3,
+ text=rank_str,
+ flatness=1.0,
+ maxwidth=sub_width * maxwidth_scale,
+ )
if suffix_offset is not None:
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center + old_offs +
- suffix_offset, v + 1),
- h_align='left',
- v_align='center',
- scale=0.20,
- flatness=1.0,
- text='[' + str(prev_rank[2]) + ']')
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(
+ sub_width * center + old_offs + suffix_offset,
+ v + 1,
+ ),
+ h_align='left',
+ v_align='center',
+ scale=0.20,
+ flatness=1.0,
+ text='[' + str(prev_rank[2]) + ']',
+ )
prev_ranks_shown += 1
v -= 10
v -= 13
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- flatness=1.0,
- h_align='center',
- v_align='center',
- scale=title_scale,
- color=ba.app.ui.infotextcolor,
- text=ba.Lstr(resource='achievementsText'),
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ scale=title_scale,
+ color=ba.app.ui.infotextcolor,
+ text=ba.Lstr(resource='achievementsText'),
+ maxwidth=sub_width * maxwidth_scale,
+ )
v -= 14
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- h_align='center',
- v_align='center',
- scale=0.55,
- text=str(data['achievementsCompleted']) + ' / ' +
- str(len(ba.app.ach.achievements)),
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ h_align='center',
+ v_align='center',
+ scale=0.55,
+ text=str(data['achievementsCompleted'])
+ + ' / '
+ + str(len(ba.app.ach.achievements)),
+ maxwidth=sub_width * maxwidth_scale,
+ )
v -= 25
if prev_ranks_shown == 0 and showing_character:
@@ -439,26 +549,31 @@ class AccountViewerWindow(popup.PopupWindow):
center = 0.5
maxwidth_scale = 0.9
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- position=(sub_width * center, v),
- h_align='center',
- v_align='center',
- scale=title_scale,
- color=ba.app.ui.infotextcolor,
- flatness=1.0,
- text=ba.Lstr(resource='trophiesThisSeasonText',
- fallback_resource='trophiesText'),
- maxwidth=sub_width * maxwidth_scale)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ position=(sub_width * center, v),
+ h_align='center',
+ v_align='center',
+ scale=title_scale,
+ color=ba.app.ui.infotextcolor,
+ flatness=1.0,
+ text=ba.Lstr(
+ resource='trophiesThisSeasonText',
+ fallback_resource='trophiesText',
+ ),
+ maxwidth=sub_width * maxwidth_scale,
+ )
v -= 19
- ba.textwidget(parent=self._subcontainer,
- size=(0, ts_height),
- position=(sub_width * 0.5,
- v - ts_height * tscale),
- h_align='center',
- v_align='top',
- corner_scale=tscale,
- text=trophystr)
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, ts_height),
+ position=(sub_width * 0.5, v - ts_height * tscale),
+ h_align='center',
+ v_align='top',
+ corner_scale=tscale,
+ text=trophystr,
+ )
except Exception:
ba.print_exception('Error displaying account info.')
diff --git a/assets/src/ba_data/python/bastd/ui/achievements.py b/assets/src/ba_data/python/bastd/ui/achievements.py
index c5bc5d24..cc862a95 100644
--- a/assets/src/ba_data/python/bastd/ui/achievements.py
+++ b/assets/src/ba_data/python/bastd/ui/achievements.py
@@ -16,26 +16,38 @@ if TYPE_CHECKING:
class AchievementsWindow(popup.PopupWindow):
"""Popup window to view achievements."""
- def __init__(self,
- position: tuple[float, float],
- scale: float | None = None):
+ def __init__(
+ self, position: tuple[float, float], scale: float | None = None
+ ):
# pylint: disable=too-many-locals
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 450
- self._height = (300 if uiscale is ba.UIScale.SMALL else
- 370 if uiscale is ba.UIScale.MEDIUM else 450)
+ self._height = (
+ 300
+ if uiscale is ba.UIScale.SMALL
+ else 370
+ if uiscale is ba.UIScale.MEDIUM
+ else 450
+ )
bg_color = (0.5, 0.4, 0.6)
# creates our _root_widget
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color)
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
@@ -47,36 +59,43 @@ class AchievementsWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
achievements = ba.app.ach.achievements
num_complete = len([a for a in achievements if a.complete])
txt_final = ba.Lstr(
resource='accountSettingsWindow.achievementProgressText',
- subs=[('${COUNT}', str(num_complete)),
- ('${TOTAL}', str(len(achievements)))])
- self._title_text = ba.textwidget(parent=self.root_widget,
- position=(self._width * 0.5,
- self._height - 20),
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=0.6,
- text=txt_final,
- maxwidth=200,
- color=(1, 1, 1, 0.4))
+ subs=[
+ ('${COUNT}', str(num_complete)),
+ ('${TOTAL}', str(len(achievements))),
+ ],
+ )
+ self._title_text = ba.textwidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5, self._height - 20),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=0.6,
+ text=txt_final,
+ maxwidth=200,
+ color=(1, 1, 1, 0.4),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._width - 60,
- self._height - 70),
- position=(30, 30),
- capture_arrows=True,
- simple_culling_v=10)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._width - 60, self._height - 70),
+ position=(30, 30),
+ capture_arrows=True,
+ simple_culling_v=10,
+ )
ba.widget(edit=self._scrollwidget, autoselect=True)
- ba.containerwidget(edit=self.root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
incr = 36
sub_width = self._width - 90
@@ -85,109 +104,125 @@ class AchievementsWindow(popup.PopupWindow):
eq_rsrc = 'coopSelectWindow.powerRankingPointsEqualsText'
pts_rsrc = 'coopSelectWindow.powerRankingPointsText'
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(sub_width, sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(sub_width, sub_height),
+ background=False,
+ )
total_pts = 0
for i, ach in enumerate(achievements):
complete = ach.complete
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.08 - 5,
- sub_height - 20 - incr * i),
- maxwidth=20,
- scale=0.5,
- color=(0.6, 0.6, 0.7) if complete else
- (0.6, 0.6, 0.7, 0.2),
- flatness=1.0,
- shadow=0.0,
- text=str(i + 1),
- size=(0, 0),
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.08 - 5, sub_height - 20 - incr * i),
+ maxwidth=20,
+ scale=0.5,
+ color=(0.6, 0.6, 0.7) if complete else (0.6, 0.6, 0.7, 0.2),
+ flatness=1.0,
+ shadow=0.0,
+ text=str(i + 1),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ )
- ba.imagewidget(parent=self._subcontainer,
- position=(sub_width * 0.10 + 1, sub_height - 20 -
- incr * i - 9) if complete else
- (sub_width * 0.10 - 4,
- sub_height - 20 - incr * i - 14),
- size=(18, 18) if complete else (27, 27),
- opacity=1.0 if complete else 0.3,
- color=ach.get_icon_color(complete)[:3],
- texture=ach.get_icon_texture(complete))
+ ba.imagewidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9)
+ if complete
+ else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14),
+ size=(18, 18) if complete else (27, 27),
+ opacity=1.0 if complete else 0.3,
+ color=ach.get_icon_color(complete)[:3],
+ texture=ach.get_icon_texture(complete),
+ )
if complete:
- ba.imagewidget(parent=self._subcontainer,
- position=(sub_width * 0.10 - 4,
- sub_height - 25 - incr * i - 9),
- size=(28, 28),
- color=(2, 1.4, 0),
- texture=ba.gettexture('achievementOutline'))
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.19,
- sub_height - 19 - incr * i + 3),
- maxwidth=sub_width * 0.62,
- scale=0.6,
- flatness=1.0,
- shadow=0.0,
- color=(1, 1, 1) if complete else (1, 1, 1, 0.2),
- text=ach.display_name,
- size=(0, 0),
- h_align='left',
- v_align='center')
+ ba.imagewidget(
+ parent=self._subcontainer,
+ position=(
+ sub_width * 0.10 - 4,
+ sub_height - 25 - incr * i - 9,
+ ),
+ size=(28, 28),
+ color=(2, 1.4, 0),
+ texture=ba.gettexture('achievementOutline'),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.19, sub_height - 19 - incr * i + 3),
+ maxwidth=sub_width * 0.62,
+ scale=0.6,
+ flatness=1.0,
+ shadow=0.0,
+ color=(1, 1, 1) if complete else (1, 1, 1, 0.2),
+ text=ach.display_name,
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ )
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.19,
- sub_height - 19 - incr * i - 10),
- maxwidth=sub_width * 0.62,
- scale=0.4,
- flatness=1.0,
- shadow=0.0,
- color=(0.83, 0.8, 0.85) if complete else
- (0.8, 0.8, 0.8, 0.2),
- text=ach.description_full_complete
- if complete else ach.description_full,
- size=(0, 0),
- h_align='left',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.19, sub_height - 19 - incr * i - 10),
+ maxwidth=sub_width * 0.62,
+ scale=0.4,
+ flatness=1.0,
+ shadow=0.0,
+ color=(0.83, 0.8, 0.85) if complete else (0.8, 0.8, 0.8, 0.2),
+ text=ach.description_full_complete
+ if complete
+ else ach.description_full,
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ )
pts = ach.power_ranking_value
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.92,
- sub_height - 20 - incr * i),
- maxwidth=sub_width * 0.15,
- color=(0.7, 0.8, 1.0) if complete else
- (0.9, 0.9, 1.0, 0.3),
- flatness=1.0,
- shadow=0.0,
- scale=0.6,
- text=ba.Lstr(resource=pts_rsrc,
- subs=[('${NUMBER}', str(pts))]),
- size=(0, 0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.92, sub_height - 20 - incr * i),
+ maxwidth=sub_width * 0.15,
+ color=(0.7, 0.8, 1.0) if complete else (0.9, 0.9, 1.0, 0.3),
+ flatness=1.0,
+ shadow=0.0,
+ scale=0.6,
+ text=ba.Lstr(resource=pts_rsrc, subs=[('${NUMBER}', str(pts))]),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
if complete:
total_pts += pts
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 1.0,
- sub_height - 20 - incr * len(achievements)),
- maxwidth=sub_width * 0.5,
- scale=0.7,
- color=(0.7, 0.8, 1.0),
- flatness=1.0,
- shadow=0.0,
- text=ba.Lstr(
- value='${A} ${B}',
- subs=[
- ('${A}',
- ba.Lstr(resource='coopSelectWindow.totalText')),
- ('${B}',
- ba.Lstr(resource=eq_rsrc,
- subs=[('${NUMBER}', str(total_pts))]))
- ]),
- size=(0, 0),
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(
+ sub_width * 1.0,
+ sub_height - 20 - incr * len(achievements),
+ ),
+ maxwidth=sub_width * 0.5,
+ scale=0.7,
+ color=(0.7, 0.8, 1.0),
+ flatness=1.0,
+ shadow=0.0,
+ text=ba.Lstr(
+ value='${A} ${B}',
+ subs=[
+ ('${A}', ba.Lstr(resource='coopSelectWindow.totalText')),
+ (
+ '${B}',
+ ba.Lstr(
+ resource=eq_rsrc,
+ subs=[('${NUMBER}', str(total_pts))],
+ ),
+ ),
+ ],
+ ),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ )
def _on_cancel_press(self) -> None:
self._transition_out()
diff --git a/assets/src/ba_data/python/bastd/ui/appinvite.py b/assets/src/ba_data/python/bastd/ui/appinvite.py
index 176be36c..bd1fd41e 100644
--- a/assets/src/ba_data/python/bastd/ui/appinvite.py
+++ b/assets/src/ba_data/python/bastd/ui/appinvite.py
@@ -25,25 +25,36 @@ class AppInviteWindow(ba.Window):
self._height = 400
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_scale',
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_scale',
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.8,
- position=(60, self._height - 50),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.4, 0.4, 0.6),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.8,
+ position=(60, self._height - 50),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.4, 0.4, 0.6),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
ba.textwidget(
parent=self._root_widget,
@@ -59,19 +70,34 @@ class AppInviteWindow(ba.Window):
text=ba.Lstr(
resource='gatherWindow.earnTicketsForRecommendingAmountText',
fallback_resource=(
- 'gatherWindow.earnTicketsForRecommendingText'),
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'friendTryTickets', 300))),
- ('${YOU_COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'friendTryAwardTickets', 100)))]))
+ 'gatherWindow.earnTicketsForRecommendingText'
+ ),
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'friendTryTickets', 300
+ )
+ ),
+ ),
+ (
+ '${YOU_COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'friendTryAwardTickets', 100
+ )
+ ),
+ ),
+ ],
+ ),
+ )
- or_text = ba.Lstr(resource='orText',
- subs=[('${A}', ''),
- ('${B}', '')]).evaluate().strip()
+ or_text = (
+ ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')])
+ .evaluate()
+ .strip()
+ )
ba.buttonwidget(
parent=self._root_widget,
size=(250, 150),
@@ -79,18 +105,21 @@ class AppInviteWindow(ba.Window):
autoselect=True,
button_type='square',
label=ba.Lstr(resource='gatherWindow.inviteFriendsText'),
- on_activate_call=ba.WeakCall(self._google_invites))
+ on_activate_call=ba.WeakCall(self._google_invites),
+ )
- ba.textwidget(parent=self._root_widget,
- size=(0, 0),
- position=(self._width * 0.5, self._height * 0.5 - 94),
- autoselect=True,
- scale=0.9,
- h_align='center',
- v_align='center',
- color=(0.5, 0.5, 0.5),
- flatness=1.0,
- text=or_text)
+ ba.textwidget(
+ parent=self._root_widget,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height * 0.5 - 94),
+ autoselect=True,
+ scale=0.9,
+ h_align='center',
+ v_align='center',
+ color=(0.5, 0.5, 0.5),
+ flatness=1.0,
+ text=or_text,
+ )
ba.buttonwidget(
parent=self._root_widget,
@@ -101,16 +130,18 @@ class AppInviteWindow(ba.Window):
textcolor=(0.7, 0.7, 0.8),
text_scale=0.8,
label=ba.Lstr(resource='gatherWindow.appInviteSendACodeText'),
- on_activate_call=ba.WeakCall(self._send_code))
+ on_activate_call=ba.WeakCall(self._send_code),
+ )
# kick off a transaction to get our code
ba.internal.add_transaction(
{
'type': 'FRIEND_PROMO_CODE_REQUEST',
'ali': False,
- 'expire_time': time.time() + 20
+ 'expire_time': time.time() + 20,
},
- callback=ba.WeakCall(self._on_code_result))
+ callback=ba.WeakCall(self._on_code_result),
+ )
ba.internal.run_transactions()
def _on_code_result(self, result: dict[str, Any] | None) -> None:
@@ -122,24 +153,33 @@ class AppInviteWindow(ba.Window):
def _google_invites(self) -> None:
if self._data is None:
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.unavailableTemporarilyText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='getTicketsWindow.unavailableTemporarilyText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
if ba.internal.get_v1_account_state() == 'signed_in':
ba.set_analytics_screen('App Invite UI')
ba.internal.show_app_invite(
- ba.Lstr(resource='gatherWindow.appInviteTitleText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate(),
- ba.Lstr(resource='gatherWindow.appInviteMessageText',
- subs=[('${COUNT}', str(self._data['tickets'])),
- ('${NAME}',
- ba.internal.get_v1_account_name().split()[0]),
- ('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate(), self._data['code'])
+ ba.Lstr(
+ resource='gatherWindow.appInviteTitleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate(),
+ ba.Lstr(
+ resource='gatherWindow.appInviteMessageText',
+ subs=[
+ ('${COUNT}', str(self._data['tickets'])),
+ (
+ '${NAME}',
+ ba.internal.get_v1_account_name().split()[0],
+ ),
+ ('${APP_NAME}', ba.Lstr(resource='titleText')),
+ ],
+ ).evaluate(),
+ self._data['code'],
+ )
else:
ba.playsound(ba.getsound('error'))
@@ -153,32 +193,44 @@ class ShowFriendCodeWindow(ba.Window):
def __init__(self, data: dict[str, Any]):
from ba.internal import is_browser_likely_available
+
ba.set_analytics_screen('Friend Promo Code')
self._width = 650
self._height = 400
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- color=(0.45, 0.63, 0.15),
- transition='in_scale',
- scale=(1.7 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ color=(0.45, 0.63, 0.15),
+ transition='in_scale',
+ scale=(
+ 1.7
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
self._data = copy.deepcopy(data)
ba.playsound(ba.getsound('cashRegister'))
ba.playsound(ba.getsound('swish'))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.7,
- position=(50, self._height - 50),
- size=(60, 60),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.45, 0.63, 0.15),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.7,
+ position=(50, self._height - 50),
+ size=(60, 60),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.45, 0.63, 0.15),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
ba.textwidget(
parent=self._root_widget,
@@ -190,23 +242,27 @@ class ShowFriendCodeWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource='gatherWindow.shareThisCodeWithFriendsText'),
- maxwidth=self._width * 0.85)
+ maxwidth=self._width * 0.85,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.645),
- size=(0, 0),
- color=(1.0, 3.0, 1.0),
- scale=2.0,
- h_align='center',
- v_align='center',
- text=data['code'],
- maxwidth=self._width * 0.85)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.645),
+ size=(0, 0),
+ color=(1.0, 3.0, 1.0),
+ scale=2.0,
+ h_align='center',
+ v_align='center',
+ text=data['code'],
+ maxwidth=self._width * 0.85,
+ )
award_str: str | ba.Lstr | None
if self._data['awardTickets'] != 0:
award_str = ba.Lstr(
resource='gatherWindow.friendPromoCodeAwardText',
- subs=[('${COUNT}', str(self._data['awardTickets']))])
+ subs=[('${COUNT}', str(self._data['awardTickets']))],
+ )
else:
award_str = ''
ba.textwidget(
@@ -221,80 +277,127 @@ class ShowFriendCodeWindow(ba.Window):
text=ba.Lstr(
value='${A}\n${B}\n${C}\n${D}',
subs=[
- ('${A}',
- ba.Lstr(
- resource='gatherWindow.friendPromoCodeRedeemLongText',
- subs=[('${COUNT}', str(self._data['tickets'])),
- ('${MAX_USES}',
- str(self._data['usesRemaining']))])),
- ('${B}',
- ba.Lstr(resource=(
- 'gatherWindow.friendPromoCodeWhereToEnterText'))),
+ (
+ '${A}',
+ ba.Lstr(
+ resource=(
+ 'gatherWindow.friendPromoCodeRedeemLongText'
+ ),
+ subs=[
+ ('${COUNT}', str(self._data['tickets'])),
+ (
+ '${MAX_USES}',
+ str(self._data['usesRemaining']),
+ ),
+ ],
+ ),
+ ),
+ (
+ '${B}',
+ ba.Lstr(
+ resource=(
+ 'gatherWindow.friendPromoCodeWhereToEnterText'
+ )
+ ),
+ ),
('${C}', award_str),
- ('${D}',
- ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText',
- subs=[('${EXPIRE_HOURS}',
- str(self._data['expireHours']))]))
- ]),
+ (
+ '${D}',
+ ba.Lstr(
+ resource='gatherWindow.friendPromoCodeExpireText',
+ subs=[
+ (
+ '${EXPIRE_HOURS}',
+ str(self._data['expireHours']),
+ )
+ ],
+ ),
+ ),
+ ],
+ ),
maxwidth=self._width * 0.9,
- max_height=self._height * 0.35)
+ max_height=self._height * 0.35,
+ )
if is_browser_likely_available():
xoffs = 0
- ba.buttonwidget(parent=self._root_widget,
- size=(200, 40),
- position=(self._width * 0.5 - 100 + xoffs, 39),
- autoselect=True,
- label=ba.Lstr(resource='gatherWindow.emailItText'),
- on_activate_call=ba.WeakCall(self._email))
+ ba.buttonwidget(
+ parent=self._root_widget,
+ size=(200, 40),
+ position=(self._width * 0.5 - 100 + xoffs, 39),
+ autoselect=True,
+ label=ba.Lstr(resource='gatherWindow.emailItText'),
+ on_activate_call=ba.WeakCall(self._email),
+ )
def _google_invites(self) -> None:
ba.set_analytics_screen('App Invite UI')
ba.internal.show_app_invite(
- ba.Lstr(resource='gatherWindow.appInviteTitleText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate(),
- ba.Lstr(resource='gatherWindow.appInviteMessageText',
- subs=[('${COUNT}', str(self._data['tickets'])),
- ('${NAME}',
- ba.internal.get_v1_account_name().split()[0]),
- ('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate(), self._data['code'])
+ ba.Lstr(
+ resource='gatherWindow.appInviteTitleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate(),
+ ba.Lstr(
+ resource='gatherWindow.appInviteMessageText',
+ subs=[
+ ('${COUNT}', str(self._data['tickets'])),
+ ('${NAME}', ba.internal.get_v1_account_name().split()[0]),
+ ('${APP_NAME}', ba.Lstr(resource='titleText')),
+ ],
+ ).evaluate(),
+ self._data['code'],
+ )
def _email(self) -> None:
import urllib.parse
# If somehow we got signed out.
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.screenmessage(ba.Lstr(resource='notSignedInText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notSignedInText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
ba.set_analytics_screen('Email Friend Code')
- subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText').
- evaluate().replace(
- '${NAME}', ba.internal.get_v1_account_name()).replace(
- '${APP_NAME}',
- ba.Lstr(resource='titleText').evaluate()).replace(
- '${COUNT}', str(self._data['tickets'])))
- body = (ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText').
- evaluate().replace('${APP_NAME}',
- ba.Lstr(resource='titleText').evaluate()) +
- '\n\n' + str(self._data['code']) + '\n\n')
+ subject = (
+ ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText')
+ .evaluate()
+ .replace('${NAME}', ba.internal.get_v1_account_name())
+ .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate())
+ .replace('${COUNT}', str(self._data['tickets']))
+ )
+ body = (
+ ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText')
+ .evaluate()
+ .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate())
+ + '\n\n'
+ + str(self._data['code'])
+ + '\n\n'
+ )
body += (
- (ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText').
- evaluate().replace('${COUNT}', str(self._data['tickets']))) +
- '\n\n' +
- ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText').
- evaluate().replace('${APP_NAME}',
- ba.Lstr(resource='titleText').evaluate()) +
- '\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText').
- evaluate().replace('${EXPIRE_HOURS}', str(
- self._data['expireHours'])) + '\n' +
- ba.Lstr(resource='enjoyText').evaluate())
- ba.open_url('mailto:?subject=' + urllib.parse.quote(subject) +
- '&body=' + urllib.parse.quote(body))
+ (
+ ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText')
+ .evaluate()
+ .replace('${COUNT}', str(self._data['tickets']))
+ )
+ + '\n\n'
+ + ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText')
+ .evaluate()
+ .replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate())
+ + '\n'
+ + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText')
+ .evaluate()
+ .replace('${EXPIRE_HOURS}', str(self._data['expireHours']))
+ + '\n'
+ + ba.Lstr(resource='enjoyText').evaluate()
+ )
+ ba.open_url(
+ 'mailto:?subject='
+ + urllib.parse.quote(subject)
+ + '&body='
+ + urllib.parse.quote(body)
+ )
def close(self) -> None:
"""Close the window."""
@@ -304,9 +407,12 @@ class ShowFriendCodeWindow(ba.Window):
def handle_app_invites_press(force_code: bool = False) -> None:
"""(internal)"""
app = ba.app
- do_app_invites = (app.platform == 'android' and app.subplatform == 'google'
- and ba.internal.get_v1_account_misc_read_val(
- 'enableAppInvites', False) and not app.on_tv)
+ do_app_invites = (
+ app.platform == 'android'
+ and app.subplatform == 'google'
+ and ba.internal.get_v1_account_misc_read_val('enableAppInvites', False)
+ and not app.on_tv
+ )
if force_code:
do_app_invites = False
@@ -316,13 +422,15 @@ def handle_app_invites_press(force_code: bool = False) -> None:
else:
ba.screenmessage(
ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'),
- color=(0, 1, 0))
+ color=(0, 1, 0),
+ )
def handle_result(result: dict[str, Any] | None) -> None:
with ba.Context('ui'):
if result is None:
- ba.screenmessage(ba.Lstr(resource='errorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='errorText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
else:
ShowFriendCodeWindow(result)
@@ -331,7 +439,8 @@ def handle_app_invites_press(force_code: bool = False) -> None:
{
'type': 'FRIEND_PROMO_CODE_REQUEST',
'ali': False,
- 'expire_time': time.time() + 10
+ 'expire_time': time.time() + 10,
},
- callback=handle_result)
+ callback=handle_result,
+ )
ba.internal.run_transactions()
diff --git a/assets/src/ba_data/python/bastd/ui/characterpicker.py b/assets/src/ba_data/python/bastd/ui/characterpicker.py
index 62f78bce..490c0626 100644
--- a/assets/src/ba_data/python/bastd/ui/characterpicker.py
+++ b/assets/src/ba_data/python/bastd/ui/characterpicker.py
@@ -18,22 +18,30 @@ if TYPE_CHECKING:
class CharacterPicker(popup.PopupWindow):
"""Popup window for selecting characters."""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float] = (0.0, 0.0),
- delegate: Any = None,
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- tint_color: Sequence[float] = (1.0, 1.0, 1.0),
- tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
- selected_character: str | None = None):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float] = (0.0, 0.0),
+ delegate: Any = None,
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ tint_color: Sequence[float] = (1.0, 1.0, 1.0),
+ tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
+ selected_character: str | None = None,
+ ):
# pylint: disable=too-many-locals
from bastd.actor import spazappearance
+
del parent # unused here
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (1.85 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 1.85
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._delegate = delegate
self._transitioning_out = False
@@ -60,49 +68,60 @@ class CharacterPicker(popup.PopupWindow):
button_buffer_h = 10
button_buffer_v = 15
- self._width = (10 + columns * (button_width + 2 * button_buffer_h) *
- (1.0 / 0.95) * (1.0 / 0.8))
- self._height = self._width * (0.8
- if uiscale is ba.UIScale.SMALL else 1.06)
+ self._width = 10 + columns * (button_width + 2 * button_buffer_h) * (
+ 1.0 / 0.95
+ ) * (1.0 / 0.8)
+ self._height = self._width * (
+ 0.8 if uiscale is ba.UIScale.SMALL else 1.06
+ )
self._scroll_width = self._width * 0.8
self._scroll_height = self._height * 0.8
- self._scroll_position = ((self._width - self._scroll_width) * 0.5,
- (self._height - self._scroll_height) * 0.5)
+ self._scroll_position = (
+ (self._width - self._scroll_width) * 0.5,
+ (self._height - self._scroll_height) * 0.5,
+ )
# creates our _root_widget
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=(0.5, 0.5, 0.5),
- offset=offset,
- focus_position=self._scroll_position,
- focus_size=(self._scroll_width,
- self._scroll_height))
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=(0.5, 0.5, 0.5),
+ offset=offset,
+ focus_position=self._scroll_position,
+ focus_size=(self._scroll_width, self._scroll_height),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._scroll_width,
- self._scroll_height),
- color=(0.55, 0.55, 0.55),
- highlight=False,
- position=self._scroll_position)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._scroll_width, self._scroll_height),
+ color=(0.55, 0.55, 0.55),
+ highlight=False,
+ position=self._scroll_position,
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._sub_width = self._scroll_width * 0.95
- self._sub_height = 5 + rows * (button_height +
- 2 * button_buffer_v) + 100
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._sub_height = (
+ 5 + rows * (button_height + 2 * button_buffer_v) + 100
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
index = 0
mask_texture = ba.gettexture('characterIconMask')
for y in range(rows):
for x in range(columns):
- pos = (x * (button_width + 2 * button_buffer_h) +
- button_buffer_h, self._sub_height - (y + 1) *
- (button_height + 2 * button_buffer_v) + 12)
+ pos = (
+ x * (button_width + 2 * button_buffer_h) + button_buffer_h,
+ self._sub_height
+ - (y + 1) * (button_height + 2 * button_buffer_v)
+ + 12,
+ )
btn = ba.buttonwidget(
parent=self._subcontainer,
button_type='square',
@@ -115,27 +134,33 @@ class CharacterPicker(popup.PopupWindow):
color=(1, 1, 1),
tint_color=tint_color,
tint2_color=tint2_color,
- on_activate_call=ba.Call(self._select_character,
- self._spazzes[index]),
- position=pos)
+ on_activate_call=ba.Call(
+ self._select_character, self._spazzes[index]
+ ),
+ position=pos,
+ )
ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
if self._spazzes[index] == selected_character:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=btn,
- visible_child=btn)
- name = ba.Lstr(translate=('characterNames',
- self._spazzes[index]))
- ba.textwidget(parent=self._subcontainer,
- text=name,
- position=(pos[0] + button_width * 0.5,
- pos[1] - 12),
- size=(0, 0),
- scale=0.5,
- maxwidth=button_width,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- color=(0.8, 0.8, 0.8, 0.8))
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=btn,
+ visible_child=btn,
+ )
+ name = ba.Lstr(
+ translate=('characterNames', self._spazzes[index])
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ text=name,
+ position=(pos[0] + button_width * 0.5, pos[1] - 12),
+ size=(0, 0),
+ scale=0.5,
+ maxwidth=button_width,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ color=(0.8, 0.8, 0.8, 0.8),
+ )
index += 1
if index >= count:
@@ -150,19 +175,23 @@ class CharacterPicker(popup.PopupWindow):
on_activate_call=self._on_store_press,
color=(0.6, 0.6, 0.6),
textcolor=(0.8, 0.8, 0.8),
- autoselect=True)
+ autoselect=True,
+ )
ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
def _on_store_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._transition_out()
- StoreBrowserWindow(modal=True,
- show_tab=StoreBrowserWindow.TabID.CHARACTERS,
- origin_widget=self._get_more_characters_button)
+ StoreBrowserWindow(
+ modal=True,
+ show_tab=StoreBrowserWindow.TabID.CHARACTERS,
+ origin_widget=self._get_more_characters_button,
+ )
def _select_character(self, character: str) -> None:
if self._delegate is not None:
diff --git a/assets/src/ba_data/python/bastd/ui/colorpicker.py b/assets/src/ba_data/python/bastd/ui/colorpicker.py
index 13384e8f..378f56f1 100644
--- a/assets/src/ba_data/python/bastd/ui/colorpicker.py
+++ b/assets/src/ba_data/python/bastd/ui/colorpicker.py
@@ -19,14 +19,16 @@ class ColorPicker(PopupWindow):
Passes the color to the delegate's color_picker_selected_color() method.
"""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float],
- initial_color: Sequence[float] = (1.0, 1.0, 1.0),
- delegate: Any = None,
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- tag: Any = ''):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float],
+ initial_color: Sequence[float] = (1.0, 1.0, 1.0),
+ delegate: Any = None,
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ tag: Any = '',
+ ):
# pylint: disable=too-many-locals
from ba.internal import get_player_colors
@@ -36,8 +38,13 @@ class ColorPicker(PopupWindow):
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._parent = parent
self._position = position
self._scale = scale
@@ -48,14 +55,16 @@ class ColorPicker(PopupWindow):
self._initial_color = initial_color
# Create our _root_widget.
- PopupWindow.__init__(self,
- position=position,
- size=(210, 240),
- scale=scale,
- focus_position=(10, 10),
- focus_size=(190, 220),
- bg_color=(0.5, 0.5, 0.5),
- offset=offset)
+ PopupWindow.__init__(
+ self,
+ position=position,
+ size=(210, 240),
+ scale=scale,
+ focus_position=(10, 10),
+ focus_size=(190, 220),
+ bg_color=(0.5, 0.5, 0.5),
+ offset=offset,
+ )
rows: list[list[ba.Widget]] = []
closest_dist = 9999.0
closest = (0, 0)
@@ -64,22 +73,25 @@ class ColorPicker(PopupWindow):
rows.append(row)
for x in range(4):
color = self.colors[y][x]
- dist = (abs(color[0] - initial_color[0]) +
- abs(color[1] - initial_color[1]) +
- abs(color[2] - initial_color[2]))
+ dist = (
+ abs(color[0] - initial_color[0])
+ + abs(color[1] - initial_color[1])
+ + abs(color[2] - initial_color[2])
+ )
if dist < closest_dist:
closest = (x, y)
closest_dist = dist
- btn = ba.buttonwidget(parent=self.root_widget,
- position=(22 + 45 * x, 185 - 45 * y),
- size=(35, 40),
- label='',
- button_type='square',
- on_activate_call=ba.WeakCall(
- self._select, x, y),
- autoselect=True,
- color=color,
- extra_touch_border_scale=0.0)
+ btn = ba.buttonwidget(
+ parent=self.root_widget,
+ position=(22 + 45 * x, 185 - 45 * y),
+ size=(35, 40),
+ label='',
+ button_type='square',
+ on_activate_call=ba.WeakCall(self._select, x, y),
+ autoselect=True,
+ color=color,
+ extra_touch_border_scale=0.0,
+ )
row.append(btn)
other_button = ba.buttonwidget(
parent=self.root_widget,
@@ -88,27 +100,35 @@ class ColorPicker(PopupWindow):
text_scale=0.5,
textcolor=(0.8, 0.8, 0.8),
size=(120, 30),
- label=ba.Lstr(resource='otherText',
- fallback_resource='coopSelectWindow.customText'),
+ label=ba.Lstr(
+ resource='otherText',
+ fallback_resource='coopSelectWindow.customText',
+ ),
autoselect=True,
- on_activate_call=ba.WeakCall(self._select_other))
+ on_activate_call=ba.WeakCall(self._select_other),
+ )
# Custom colors are limited to pro currently.
if not ba.app.accounts_v1.have_pro():
- ba.imagewidget(parent=self.root_widget,
- position=(50, 12),
- size=(30, 30),
- texture=ba.gettexture('lock'),
- draw_controller=other_button)
+ ba.imagewidget(
+ parent=self.root_widget,
+ position=(50, 12),
+ size=(30, 30),
+ texture=ba.gettexture('lock'),
+ draw_controller=other_button,
+ )
# If their color is close to one of our swatches, select it.
# Otherwise select 'other'.
if closest_dist < 0.03:
- ba.containerwidget(edit=self.root_widget,
- selected_child=rows[closest[1]][closest[0]])
+ ba.containerwidget(
+ edit=self.root_widget,
+ selected_child=rows[closest[1]][closest[0]],
+ )
else:
- ba.containerwidget(edit=self.root_widget,
- selected_child=other_button)
+ ba.containerwidget(
+ edit=self.root_widget, selected_child=other_button
+ )
def get_tag(self) -> Any:
"""Return this popup's tag."""
@@ -122,13 +142,15 @@ class ColorPicker(PopupWindow):
purchase.PurchaseWindow(items=['pro'])
self._transition_out()
return
- ColorPickerExact(parent=self._parent,
- position=self._position,
- initial_color=self._initial_color,
- delegate=self._delegate,
- scale=self._scale,
- offset=self._offset,
- tag=self._tag)
+ ColorPickerExact(
+ parent=self._parent,
+ position=self._position,
+ initial_color=self._initial_color,
+ delegate=self._delegate,
+ scale=self._scale,
+ offset=self._offset,
+ tag=self._tag,
+ )
# New picker now 'owns' the delegate; we shouldn't send it any
# more messages.
@@ -154,34 +176,43 @@ class ColorPicker(PopupWindow):
class ColorPickerExact(PopupWindow):
- """ pops up a ui to select from a set of colors.
- passes the color to the delegate's color_picker_selected_color() method """
+ """pops up a ui to select from a set of colors.
+ passes the color to the delegate's color_picker_selected_color() method"""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float],
- initial_color: Sequence[float] = (1.0, 1.0, 1.0),
- delegate: Any = None,
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- tag: Any = ''):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float],
+ initial_color: Sequence[float] = (1.0, 1.0, 1.0),
+ delegate: Any = None,
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ tag: Any = '',
+ ):
# pylint: disable=too-many-locals
del parent # Unused var.
from ba.internal import get_player_colors
+
c_raw = get_player_colors()
assert len(c_raw) == 16
self.colors = [c_raw[0:4], c_raw[4:8], c_raw[8:12], c_raw[12:16]]
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._delegate = delegate
self._transitioning_out = False
self._tag = tag
self._color = list(initial_color)
- self._last_press_time = ba.time(ba.TimeType.REAL,
- ba.TimeFormat.MILLISECONDS)
+ self._last_press_time = ba.time(
+ ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS
+ )
self._last_press_color_name: str | None = None
self._last_press_increasing: bool | None = None
self._change_speed = 1.0
@@ -189,60 +220,71 @@ class ColorPickerExact(PopupWindow):
height = 240.0
# Creates our _root_widget.
- PopupWindow.__init__(self,
- position=position,
- size=(width, height),
- scale=scale,
- focus_position=(10, 10),
- focus_size=(width - 20, height - 20),
- bg_color=(0.5, 0.5, 0.5),
- offset=offset)
- self._swatch = ba.imagewidget(parent=self.root_widget,
- position=(width * 0.5 - 50, height - 70),
- size=(100, 70),
- texture=ba.gettexture('buttonSquare'),
- color=(1, 0, 0))
+ PopupWindow.__init__(
+ self,
+ position=position,
+ size=(width, height),
+ scale=scale,
+ focus_position=(10, 10),
+ focus_size=(width - 20, height - 20),
+ bg_color=(0.5, 0.5, 0.5),
+ offset=offset,
+ )
+ self._swatch = ba.imagewidget(
+ parent=self.root_widget,
+ position=(width * 0.5 - 50, height - 70),
+ size=(100, 70),
+ texture=ba.gettexture('buttonSquare'),
+ color=(1, 0, 0),
+ )
x = 50
y = height - 90
self._label_r: ba.Widget
self._label_g: ba.Widget
self._label_b: ba.Widget
- for color_name, color_val in [('r', (1, 0.15, 0.15)),
- ('g', (0.15, 1, 0.15)),
- ('b', (0.15, 0.15, 1))]:
- txt = ba.textwidget(parent=self.root_widget,
- position=(x - 10, y),
- size=(0, 0),
- h_align='center',
- color=color_val,
- v_align='center',
- text='0.12')
+ for color_name, color_val in [
+ ('r', (1, 0.15, 0.15)),
+ ('g', (0.15, 1, 0.15)),
+ ('b', (0.15, 0.15, 1)),
+ ]:
+ txt = ba.textwidget(
+ parent=self.root_widget,
+ position=(x - 10, y),
+ size=(0, 0),
+ h_align='center',
+ color=color_val,
+ v_align='center',
+ text='0.12',
+ )
setattr(self, '_label_' + color_name, txt)
for b_label, bhval, binc in [('-', 30, False), ('+', 75, True)]:
- ba.buttonwidget(parent=self.root_widget,
- position=(x + bhval, y - 15),
- scale=0.8,
- repeat=True,
- text_scale=1.3,
- size=(40, 40),
- label=b_label,
- autoselect=True,
- enable_sound=False,
- on_activate_call=ba.WeakCall(
- self._color_change_press, color_name,
- binc))
+ ba.buttonwidget(
+ parent=self.root_widget,
+ position=(x + bhval, y - 15),
+ scale=0.8,
+ repeat=True,
+ text_scale=1.3,
+ size=(40, 40),
+ label=b_label,
+ autoselect=True,
+ enable_sound=False,
+ on_activate_call=ba.WeakCall(
+ self._color_change_press, color_name, binc
+ ),
+ )
y -= 42
- btn = ba.buttonwidget(parent=self.root_widget,
- position=(width * 0.5 - 40, 10),
- size=(80, 30),
- text_scale=0.6,
- color=(0.6, 0.6, 0.6),
- textcolor=(0.7, 0.7, 0.7),
- label=ba.Lstr(resource='doneText'),
- on_activate_call=ba.WeakCall(
- self._transition_out),
- autoselect=True)
+ btn = ba.buttonwidget(
+ parent=self.root_widget,
+ position=(width * 0.5 - 40, 10),
+ size=(80, 30),
+ text_scale=0.6,
+ color=(0.6, 0.6, 0.6),
+ textcolor=(0.7, 0.7, 0.7),
+ label=ba.Lstr(resource='doneText'),
+ on_activate_call=ba.WeakCall(self._transition_out),
+ autoselect=True,
+ )
ba.containerwidget(edit=self.root_widget, start_button=btn)
# Unlike the swatch picker, we stay open and constantly push our
@@ -268,8 +310,11 @@ class ColorPickerExact(PopupWindow):
# If we get rapid-fire presses, eventually start moving faster.
current_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
since_last = current_time - self._last_press_time
- if (since_last < 200 and self._last_press_color_name == color_name
- and self._last_press_increasing == increasing):
+ if (
+ since_last < 200
+ and self._last_press_color_name == color_name
+ and self._last_press_increasing == increasing
+ ):
self._change_speed += 0.25
else:
self._change_speed = 1.0
@@ -280,7 +325,8 @@ class ColorPickerExact(PopupWindow):
color_index = ('r', 'g', 'b').index(color_name)
offs = int(self._change_speed) * (0.01 if increasing else -0.01)
self._color[color_index] = max(
- 0.0, min(1.0, self._color[color_index] + offs))
+ 0.0, min(1.0, self._color[color_index] + offs)
+ )
self._update_for_color()
def get_tag(self) -> Any:
diff --git a/assets/src/ba_data/python/bastd/ui/config.py b/assets/src/ba_data/python/bastd/ui/config.py
index e8a6cc1d..b2008665 100644
--- a/assets/src/ba_data/python/bastd/ui/config.py
+++ b/assets/src/ba_data/python/bastd/ui/config.py
@@ -22,16 +22,18 @@ class ConfigCheckBox:
widget: ba.Widget
"""The underlying ba.Widget instance."""
- def __init__(self,
- parent: ba.Widget,
- configkey: str,
- position: tuple[float, float],
- size: tuple[float, float],
- displayname: str | ba.Lstr | None = None,
- scale: float | None = None,
- maxwidth: float | None = None,
- autoselect: bool = True,
- value_change_call: Callable[[Any], Any] | None = None):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ configkey: str,
+ position: tuple[float, float],
+ size: tuple[float, float],
+ displayname: str | ba.Lstr | None = None,
+ scale: float | None = None,
+ maxwidth: float | None = None,
+ autoselect: bool = True,
+ value_change_call: Callable[[Any], Any] | None = None,
+ ):
if displayname is None:
displayname = configkey
self._value_change_call = value_change_call
@@ -46,7 +48,8 @@ class ConfigCheckBox:
value=ba.app.config.resolve(configkey),
on_value_change_call=self._value_changed,
scale=scale,
- maxwidth=maxwidth)
+ maxwidth=maxwidth,
+ )
# complain if we outlive our checkbox
ba.uicleanupcheck(self, self.widget)
@@ -77,18 +80,20 @@ class ConfigNumberEdit:
plusbutton: ba.Widget
"""The button widget used to increase the value."""
- def __init__(self,
- parent: ba.Widget,
- configkey: str,
- position: tuple[float, float],
- minval: float = 0.0,
- maxval: float = 100.0,
- increment: float = 1.0,
- callback: Callable[[float], Any] | None = None,
- xoffset: float = 0.0,
- displayname: str | ba.Lstr | None = None,
- changesound: bool = True,
- textscale: float = 1.0):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ configkey: str,
+ position: tuple[float, float],
+ minval: float = 0.0,
+ maxval: float = 100.0,
+ increment: float = 1.0,
+ callback: Callable[[float], Any] | None = None,
+ xoffset: float = 0.0,
+ displayname: str | ba.Lstr | None = None,
+ changesound: bool = True,
+ textscale: float = 1.0,
+ ):
if displayname is None:
displayname = configkey
@@ -99,24 +104,28 @@ class ConfigNumberEdit:
self._callback = callback
self._value = ba.app.config.resolve(configkey)
- self.nametext = ba.textwidget(parent=parent,
- position=position,
- size=(100, 30),
- text=displayname,
- maxwidth=160 + xoffset,
- color=(0.8, 0.8, 0.8, 1.0),
- h_align='left',
- v_align='center',
- scale=textscale)
- self.valuetext = ba.textwidget(parent=parent,
- position=(246 + xoffset, position[1]),
- size=(60, 28),
- editable=False,
- color=(0.3, 1.0, 0.3, 1.0),
- h_align='right',
- v_align='center',
- text=str(self._value),
- padding=2)
+ self.nametext = ba.textwidget(
+ parent=parent,
+ position=position,
+ size=(100, 30),
+ text=displayname,
+ maxwidth=160 + xoffset,
+ color=(0.8, 0.8, 0.8, 1.0),
+ h_align='left',
+ v_align='center',
+ scale=textscale,
+ )
+ self.valuetext = ba.textwidget(
+ parent=parent,
+ position=(246 + xoffset, position[1]),
+ size=(60, 28),
+ editable=False,
+ color=(0.3, 1.0, 0.3, 1.0),
+ h_align='right',
+ v_align='center',
+ text=str(self._value),
+ padding=2,
+ )
self.minusbutton = ba.buttonwidget(
parent=parent,
position=(330 + xoffset, position[1]),
@@ -125,16 +134,18 @@ class ConfigNumberEdit:
autoselect=True,
on_activate_call=ba.Call(self._down),
repeat=True,
- enable_sound=changesound)
- self.plusbutton = ba.buttonwidget(parent=parent,
- position=(380 + xoffset,
- position[1]),
- size=(28, 28),
- label='+',
- autoselect=True,
- on_activate_call=ba.Call(self._up),
- repeat=True,
- enable_sound=changesound)
+ enable_sound=changesound,
+ )
+ self.plusbutton = ba.buttonwidget(
+ parent=parent,
+ position=(380 + xoffset, position[1]),
+ size=(28, 28),
+ label='+',
+ autoselect=True,
+ on_activate_call=ba.Call(self._up),
+ repeat=True,
+ enable_sound=changesound,
+ )
# Complain if we outlive our widgets.
ba.uicleanupcheck(self, self.nametext)
self._update_display()
diff --git a/assets/src/ba_data/python/bastd/ui/configerror.py b/assets/src/ba_data/python/bastd/ui/configerror.py
index 26ffdf6f..129e5eaa 100644
--- a/assets/src/ba_data/python/bastd/ui/configerror.py
+++ b/assets/src/ba_data/python/bastd/ui/configerror.py
@@ -20,7 +20,8 @@ class ConfigErrorWindow(ba.Window):
self._config_file_path = ba.app.config_file_path
width = 800
super().__init__(
- ba.containerwidget(size=(width, 300), transition='in_right'))
+ ba.containerwidget(size=(width, 300), transition='in_right')
+ )
padding = 20
ba.textwidget(
parent=self._root_widget,
@@ -29,32 +30,43 @@ class ConfigErrorWindow(ba.Window):
h_align='center',
v_align='top',
scale=0.73,
- text=(f'Error reading {ba.internal.appnameupper()} config file'
- ':\n\n\nCheck the console'
- ' (press ~ twice) for details.\n\nWould you like to quit and'
- ' try to fix it by hand\nor overwrite it with defaults?\n\n'
- '(high scores, player profiles, etc will be lost if you'
- ' overwrite)'))
- ba.textwidget(parent=self._root_widget,
- position=(padding, 198),
- size=(width - 2 * padding, 100 - 2 * padding),
- h_align='center',
- v_align='top',
- scale=0.5,
- text=self._config_file_path)
- quit_button = ba.buttonwidget(parent=self._root_widget,
- position=(35, 30),
- size=(240, 54),
- label='Quit and Edit',
- on_activate_call=self._quit)
- ba.buttonwidget(parent=self._root_widget,
- position=(width - 370, 30),
- size=(330, 54),
- label='Overwrite with Defaults',
- on_activate_call=self._defaults)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=quit_button,
- selected_child=quit_button)
+ text=(
+ f'Error reading {ba.internal.appnameupper()} config file'
+ ':\n\n\nCheck the console'
+ ' (press ~ twice) for details.\n\nWould you like to quit and'
+ ' try to fix it by hand\nor overwrite it with defaults?\n\n'
+ '(high scores, player profiles, etc will be lost if you'
+ ' overwrite)'
+ ),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(padding, 198),
+ size=(width - 2 * padding, 100 - 2 * padding),
+ h_align='center',
+ v_align='top',
+ scale=0.5,
+ text=self._config_file_path,
+ )
+ quit_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35, 30),
+ size=(240, 54),
+ label='Quit and Edit',
+ on_activate_call=self._quit,
+ )
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(width - 370, 30),
+ size=(330, 54),
+ label='Overwrite with Defaults',
+ on_activate_call=self._defaults,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=quit_button,
+ selected_child=quit_button,
+ )
def _quit(self) -> None:
ba.timer(0.001, self._edit_and_quit, timetype=ba.TimeType.REAL)
@@ -66,6 +78,7 @@ class ConfigErrorWindow(ba.Window):
def _defaults(self) -> None:
from ba.internal import commit_app_config
+
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.playsound(ba.getsound('gunCocking'))
ba.screenmessage('settings reset.', color=(1, 1, 0))
diff --git a/assets/src/ba_data/python/bastd/ui/confirm.py b/assets/src/ba_data/python/bastd/ui/confirm.py
index 46bb90bf..74e32dca 100644
--- a/assets/src/ba_data/python/bastd/ui/confirm.py
+++ b/assets/src/ba_data/python/bastd/ui/confirm.py
@@ -16,18 +16,20 @@ if TYPE_CHECKING:
class ConfirmWindow:
"""Window for answering simple yes/no questions."""
- def __init__(self,
- text: str | ba.Lstr = 'Are you sure?',
- action: Callable[[], Any] | None = None,
- width: float = 360.0,
- height: float = 100.0,
- cancel_button: bool = True,
- cancel_is_selected: bool = False,
- color: tuple[float, float, float] = (1, 1, 1),
- text_scale: float = 1.0,
- ok_text: str | ba.Lstr | None = None,
- cancel_text: str | ba.Lstr | None = None,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ text: str | ba.Lstr = 'Are you sure?',
+ action: Callable[[], Any] | None = None,
+ width: float = 360.0,
+ height: float = 100.0,
+ cancel_button: bool = True,
+ cancel_is_selected: bool = False,
+ color: tuple[float, float, float] = (1, 1, 1),
+ text_scale: float = 1.0,
+ ok_text: str | ba.Lstr | None = None,
+ cancel_text: str | ba.Lstr | None = None,
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
if ok_text is None:
ok_text = ba.Lstr(resource='okText')
@@ -55,29 +57,39 @@ class ConfirmWindow:
transition=transition,
toolbar_visibility='menu_minimal_no_back',
parent=ba.internal.get_special_widget('overlay_stack'),
- scale=(2.1 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- scale_origin_stack_offset=scale_origin)
+ scale=(
+ 2.1
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ scale_origin_stack_offset=scale_origin,
+ )
- ba.textwidget(parent=self.root_widget,
- position=(width * 0.5, height - 5 - (height - 75) * 0.5),
- size=(0, 0),
- h_align='center',
- v_align='center',
- text=text,
- scale=text_scale,
- color=color,
- maxwidth=width * 0.9,
- max_height=height - 75)
+ ba.textwidget(
+ parent=self.root_widget,
+ position=(width * 0.5, height - 5 - (height - 75) * 0.5),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ text=text,
+ scale=text_scale,
+ color=color,
+ maxwidth=width * 0.9,
+ max_height=height - 75,
+ )
cbtn: ba.Widget | None
if cancel_button:
- cbtn = btn = ba.buttonwidget(parent=self.root_widget,
- autoselect=True,
- position=(20, 20),
- size=(150, 50),
- label=cancel_text,
- on_activate_call=self._cancel)
+ cbtn = btn = ba.buttonwidget(
+ parent=self.root_widget,
+ autoselect=True,
+ position=(20, 20),
+ size=(150, 50),
+ label=cancel_text,
+ on_activate_call=self._cancel,
+ )
ba.containerwidget(edit=self.root_widget, cancel_button=btn)
ok_button_h = width - 175
else:
@@ -86,37 +98,51 @@ class ConfirmWindow:
# button
ok_button_h = width * 0.5 - 75
cbtn = None
- btn = ba.buttonwidget(parent=self.root_widget,
- autoselect=True,
- position=(ok_button_h, 20),
- size=(150, 50),
- label=ok_text,
- on_activate_call=self._ok)
+ btn = ba.buttonwidget(
+ parent=self.root_widget,
+ autoselect=True,
+ position=(ok_button_h, 20),
+ size=(150, 50),
+ label=ok_text,
+ on_activate_call=self._ok,
+ )
# if they didn't want a cancel button, we still want to be able to hit
# cancel/back/etc to dismiss the window
if not cancel_button:
- ba.containerwidget(edit=self.root_widget,
- on_cancel_call=btn.activate)
+ ba.containerwidget(
+ edit=self.root_widget, on_cancel_call=btn.activate
+ )
- ba.containerwidget(edit=self.root_widget,
- selected_child=(cbtn if cbtn is not None
- and cancel_is_selected else btn),
- start_button=btn)
+ ba.containerwidget(
+ edit=self.root_widget,
+ selected_child=(
+ cbtn if cbtn is not None and cancel_is_selected else btn
+ ),
+ start_button=btn,
+ )
def _cancel(self) -> None:
ba.containerwidget(
edit=self.root_widget,
- transition=('out_right' if self._transition_out is None else
- self._transition_out))
+ transition=(
+ 'out_right'
+ if self._transition_out is None
+ else self._transition_out
+ ),
+ )
def _ok(self) -> None:
if not self.root_widget:
return
ba.containerwidget(
edit=self.root_widget,
- transition=('out_left' if self._transition_out is None else
- self._transition_out))
+ transition=(
+ 'out_left'
+ if self._transition_out is None
+ else self._transition_out
+ ),
+ )
if self._action is not None:
self._action()
@@ -124,10 +150,12 @@ class ConfirmWindow:
class QuitWindow:
"""Popup window to confirm quitting."""
- def __init__(self,
- swish: bool = False,
- back: bool = False,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ swish: bool = False,
+ back: bool = False,
+ origin_widget: ba.Widget | None = None,
+ ):
ui = ba.app.ui
app = ba.app
self._back = back
@@ -138,19 +166,22 @@ class QuitWindow:
ui.quit_window = None
if swish:
ba.playsound(ba.getsound('swish'))
- quit_resource = ('quitGameText'
- if app.platform == 'mac' else 'exitGameText')
- self._root_widget = ui.quit_window = (ConfirmWindow(
- ba.Lstr(resource=quit_resource,
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))]),
+ quit_resource = (
+ 'quitGameText' if app.platform == 'mac' else 'exitGameText'
+ )
+ self._root_widget = ui.quit_window = ConfirmWindow(
+ ba.Lstr(
+ resource=quit_resource,
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
self._fade_and_quit,
- origin_widget=origin_widget).root_widget)
+ origin_widget=origin_widget,
+ ).root_widget
def _fade_and_quit(self) -> None:
ba.internal.fade_screen(
- False,
- time=0.2,
- endcall=lambda: ba.quit(soft=True, back=self._back))
+ False, time=0.2, endcall=lambda: ba.quit(soft=True, back=self._back)
+ )
ba.internal.lock_all_input()
# Unlock and fade back in shortly.. just in case something goes wrong
diff --git a/assets/src/ba_data/python/bastd/ui/continues.py b/assets/src/ba_data/python/bastd/ui/continues.py
index 85e9b7ba..11bdeedb 100644
--- a/assets/src/ba_data/python/bastd/ui/continues.py
+++ b/assets/src/ba_data/python/bastd/ui/continues.py
@@ -17,9 +17,13 @@ if TYPE_CHECKING:
class ContinuesWindow(ba.Window):
"""A window to continue a game."""
- def __init__(self, activity: ba.Activity, cost: int,
- continue_call: Callable[[], Any], cancel_call: Callable[[],
- Any]):
+ def __init__(
+ self,
+ activity: ba.Activity,
+ cost: int,
+ continue_call: Callable[[], Any],
+ cancel_call: Callable[[], Any],
+ ):
self._activity = weakref.ref(activity)
self._cost = cost
self._continue_call = continue_call
@@ -29,88 +33,114 @@ class ContinuesWindow(ba.Window):
self._height = 200
self._transitioning_out = False
super().__init__(
- ba.containerwidget(size=(self._width, self._height),
- background=False,
- toolbar_visibility='menu_currency',
- transition='in_scale',
- scale=1.5))
- txt = (ba.Lstr(
- resource='continuePurchaseText').evaluate().split('${PRICE}'))
+ ba.containerwidget(
+ size=(self._width, self._height),
+ background=False,
+ toolbar_visibility='menu_currency',
+ transition='in_scale',
+ scale=1.5,
+ )
+ )
+ txt = (
+ ba.Lstr(resource='continuePurchaseText')
+ .evaluate()
+ .split('${PRICE}')
+ )
t_left = txt[0]
- t_left_width = ba.internal.get_string_width(t_left,
- suppress_warning=True)
+ t_left_width = ba.internal.get_string_width(
+ t_left, suppress_warning=True
+ )
t_price = ba.charstr(ba.SpecialChar.TICKET) + str(self._cost)
- t_price_width = ba.internal.get_string_width(t_price,
- suppress_warning=True)
+ t_price_width = ba.internal.get_string_width(
+ t_price, suppress_warning=True
+ )
t_right = txt[-1]
- t_right_width = ba.internal.get_string_width(t_right,
- suppress_warning=True)
+ t_right_width = ba.internal.get_string_width(
+ t_right, suppress_warning=True
+ )
width_total_half = (t_left_width + t_price_width + t_right_width) * 0.5
- ba.textwidget(parent=self._root_widget,
- text=t_left,
- flatness=1.0,
- shadow=1.0,
- size=(0, 0),
- h_align='left',
- v_align='center',
- position=(self._width * 0.5 - width_total_half,
- self._height - 30))
- ba.textwidget(parent=self._root_widget,
- text=t_price,
- flatness=1.0,
- shadow=1.0,
- color=(0.2, 1.0, 0.2),
- size=(0, 0),
- position=(self._width * 0.5 - width_total_half +
- t_left_width, self._height - 30),
- h_align='left',
- v_align='center')
- ba.textwidget(parent=self._root_widget,
- text=t_right,
- flatness=1.0,
- shadow=1.0,
- size=(0, 0),
- h_align='left',
- v_align='center',
- position=(self._width * 0.5 - width_total_half +
- t_left_width + t_price_width + 5,
- self._height - 30))
+ ba.textwidget(
+ parent=self._root_widget,
+ text=t_left,
+ flatness=1.0,
+ shadow=1.0,
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ position=(self._width * 0.5 - width_total_half, self._height - 30),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ text=t_price,
+ flatness=1.0,
+ shadow=1.0,
+ color=(0.2, 1.0, 0.2),
+ size=(0, 0),
+ position=(
+ self._width * 0.5 - width_total_half + t_left_width,
+ self._height - 30,
+ ),
+ h_align='left',
+ v_align='center',
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ text=t_right,
+ flatness=1.0,
+ shadow=1.0,
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ position=(
+ self._width * 0.5
+ - width_total_half
+ + t_left_width
+ + t_price_width
+ + 5,
+ self._height - 30,
+ ),
+ )
self._tickets_text_base: str | None
self._tickets_text: ba.Widget | None
if not ba.app.ui.use_toolbars:
self._tickets_text_base = ba.Lstr(
resource='getTicketsWindow.youHaveShortText',
- fallback_resource='getTicketsWindow.youHaveText').evaluate()
+ fallback_resource='getTicketsWindow.youHaveText',
+ ).evaluate()
self._tickets_text = ba.textwidget(
parent=self._root_widget,
text='',
flatness=1.0,
color=(0.2, 1.0, 0.2),
shadow=1.0,
- position=(self._width * 0.5 + width_total_half,
- self._height - 50),
+ position=(
+ self._width * 0.5 + width_total_half,
+ self._height - 50,
+ ),
size=(0, 0),
scale=0.35,
h_align='right',
- v_align='center')
+ v_align='center',
+ )
else:
self._tickets_text_base = None
self._tickets_text = None
- self._counter_text = ba.textwidget(parent=self._root_widget,
- text=str(self._count),
- color=(0.7, 0.7, 0.7),
- scale=1.2,
- size=(0, 0),
- big=True,
- position=(self._width * 0.5,
- self._height - 80),
- flatness=1.0,
- shadow=1.0,
- h_align='center',
- v_align='center')
+ self._counter_text = ba.textwidget(
+ parent=self._root_widget,
+ text=str(self._count),
+ color=(0.7, 0.7, 0.7),
+ scale=1.2,
+ size=(0, 0),
+ big=True,
+ position=(self._width * 0.5, self._height - 80),
+ flatness=1.0,
+ shadow=1.0,
+ h_align='center',
+ v_align='center',
+ )
self._cancel_button = ba.buttonwidget(
parent=self._root_widget,
position=(30, 30),
@@ -118,24 +148,27 @@ class ContinuesWindow(ba.Window):
label=ba.Lstr(resource='endText', fallback_resource='cancelText'),
autoselect=True,
enable_sound=False,
- on_activate_call=self._on_cancel_press)
+ on_activate_call=self._on_cancel_press,
+ )
self._continue_button = ba.buttonwidget(
parent=self._root_widget,
label=ba.Lstr(resource='continueText'),
autoselect=True,
position=(self._width - 130, 30),
size=(120, 50),
- on_activate_call=self._on_continue_press)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button,
- start_button=self._continue_button,
- selected_child=self._cancel_button)
+ on_activate_call=self._on_continue_press,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._cancel_button,
+ start_button=self._continue_button,
+ selected_child=self._cancel_button,
+ )
self._counting_down = True
- self._countdown_timer = ba.Timer(1.0,
- ba.WeakCall(self._tick),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._countdown_timer = ba.Timer(
+ 1.0, ba.WeakCall(self._tick), repeat=True, timetype=ba.TimeType.REAL
+ )
# If there is foreground activity, suspend it.
ba.app.pause()
@@ -153,15 +186,17 @@ class ContinuesWindow(ba.Window):
return
if ba.internal.get_v1_account_state() == 'signed_in':
- sval = (ba.charstr(ba.SpecialChar.TICKET) +
- str(ba.internal.get_v1_account_ticket_count()))
+ sval = ba.charstr(ba.SpecialChar.TICKET) + str(
+ ba.internal.get_v1_account_ticket_count()
+ )
else:
sval = '?'
if self._tickets_text is not None:
assert self._tickets_text_base is not None
- ba.textwidget(edit=self._tickets_text,
- text=self._tickets_text_base.replace(
- '${COUNT}', sval))
+ ba.textwidget(
+ edit=self._tickets_text,
+ text=self._tickets_text_base.replace('${COUNT}', sval),
+ )
if self._counting_down:
self._count -= 1
@@ -187,8 +222,9 @@ class ContinuesWindow(ba.Window):
else:
# If somehow we got signed out...
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.screenmessage(ba.Lstr(resource='notSignedInText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notSignedInText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
@@ -204,8 +240,9 @@ class ContinuesWindow(ba.Window):
if not self._transitioning_out:
ba.playsound(ba.getsound('swish'))
self._transitioning_out = True
- ba.containerwidget(edit=self._root_widget,
- transition='out_scale')
+ ba.containerwidget(
+ edit=self._root_widget, transition='out_scale'
+ )
self._continue_call()
def _on_cancel(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/coop/browser.py b/assets/src/ba_data/python/bastd/ui/coop/browser.py
index b6d978d3..f96e0a55 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/browser.py
@@ -25,20 +25,36 @@ class CoopBrowserWindow(ba.Window):
def _update_corner_button_positions(self) -> None:
uiscale = ba.app.ui.uiscale
- offs = (-55 if uiscale is ba.UIScale.SMALL
- and ba.internal.is_party_icon_visible() else 0)
+ offs = (
+ -55
+ if uiscale is ba.UIScale.SMALL
+ and ba.internal.is_party_icon_visible()
+ else 0
+ )
if self._league_rank_button is not None:
self._league_rank_button.set_position(
- (self._width - 282 + offs - self._x_inset, self._height - 85 -
- (4 if uiscale is ba.UIScale.SMALL else 0)))
+ (
+ self._width - 282 + offs - self._x_inset,
+ self._height
+ - 85
+ - (4 if uiscale is ba.UIScale.SMALL else 0),
+ )
+ )
if self._store_button is not None:
self._store_button.set_position(
- (self._width - 170 + offs - self._x_inset, self._height - 85 -
- (4 if uiscale is ba.UIScale.SMALL else 0)))
+ (
+ self._width - 170 + offs - self._x_inset,
+ self._height
+ - 85
+ - (4 if uiscale is ba.UIScale.SMALL else 0),
+ )
+ )
- def __init__(self,
- transition: str | None = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
import threading
@@ -55,12 +71,14 @@ class CoopBrowserWindow(ba.Window):
# Quick note to players that tourneys won't work in ballistica
# core builds. (need to split the word so it won't get subbed out)
if 'ballistica' + 'core' == ba.internal.appname():
- ba.timer(1.0,
- lambda: ba.screenmessage(
- ba.Lstr(resource='noTournamentsInTestBuildText'),
- color=(1, 1, 0),
- ),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 1.0,
+ lambda: ba.screenmessage(
+ ba.Lstr(resource='noTournamentsInTestBuildText'),
+ color=(1, 1, 0),
+ ),
+ timetype=ba.TimeType.REAL,
+ )
# If they provided an origin-widget, scale up from that.
scale_origin: tuple[float, float] | None
@@ -85,8 +103,13 @@ class CoopBrowserWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120
self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (657 if uiscale is ba.UIScale.SMALL else
- 730 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 657
+ if uiscale is ba.UIScale.SMALL
+ else 730
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
app.ui.set_main_menu_location('Coop Select')
self._r = 'coopSelectWindow'
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
@@ -94,30 +117,49 @@ class CoopBrowserWindow(ba.Window):
self._tourney_data_up_to_date = False
self._campaign_difficulty = ba.internal.get_v1_account_misc_val(
- 'campaignDifficulty', 'easy')
+ 'campaignDifficulty', 'easy'
+ )
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
- stack_offset=((0, -15) if uiscale is ba.UIScale.SMALL else (
- 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)),
- transition=transition,
- scale=(1.2 if uiscale is ba.UIScale.SMALL else
- 0.8 if uiscale is ba.UIScale.MEDIUM else 0.75)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ toolbar_visibility='menu_full',
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(
+ (0, -15)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0)
+ ),
+ transition=transition,
+ scale=(
+ 1.2
+ if uiscale is ba.UIScale.SMALL
+ else 0.8
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.75
+ ),
+ )
+ )
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
self._back_button = None
else:
self._back_button = ba.buttonwidget(
parent=self._root_widget,
- position=(75 + x_inset, self._height - 87 -
- (4 if uiscale is ba.UIScale.SMALL else 0)),
+ position=(
+ 75 + x_inset,
+ self._height
+ - 87
+ - (4 if uiscale is ba.UIScale.SMALL else 0),
+ ),
size=(120, 60),
scale=1.2,
autoselect=True,
label=ba.Lstr(resource='backText'),
- button_type='back')
+ button_type='back',
+ )
self._league_rank_button: LeagueRankButton | None
self._store_button: StoreButton | None
@@ -127,19 +169,28 @@ class CoopBrowserWindow(ba.Window):
if not app.ui.use_toolbars:
prb = self._league_rank_button = LeagueRankButton(
parent=self._root_widget,
- position=(self._width - (282 + x_inset), self._height - 85 -
- (4 if uiscale is ba.UIScale.SMALL else 0)),
+ position=(
+ self._width - (282 + x_inset),
+ self._height
+ - 85
+ - (4 if uiscale is ba.UIScale.SMALL else 0),
+ ),
size=(100, 60),
color=(0.4, 0.4, 0.9),
textcolor=(0.9, 0.9, 2.0),
scale=1.05,
- on_activate_call=ba.WeakCall(self._switch_to_league_rankings))
+ on_activate_call=ba.WeakCall(self._switch_to_league_rankings),
+ )
self._league_rank_button_widget = prb.get_button()
sbtn = self._store_button = StoreButton(
parent=self._root_widget,
- position=(self._width - (170 + x_inset), self._height - 85 -
- (4 if uiscale is ba.UIScale.SMALL else 0)),
+ position=(
+ self._width - (170 + x_inset),
+ self._height
+ - 85
+ - (4 if uiscale is ba.UIScale.SMALL else 0),
+ ),
size=(100, 60),
color=(0.6, 0.4, 0.7),
show_tickets=True,
@@ -147,12 +198,17 @@ class CoopBrowserWindow(ba.Window):
sale_scale=0.85,
textcolor=(0.9, 0.7, 1.0),
scale=1.05,
- on_activate_call=ba.WeakCall(self._switch_to_score, None))
+ on_activate_call=ba.WeakCall(self._switch_to_score, None),
+ )
self._store_button_widget = sbtn.get_button()
- ba.widget(edit=self._back_button,
- right_widget=self._league_rank_button_widget)
- ba.widget(edit=self._league_rank_button_widget,
- left_widget=self._back_button)
+ ba.widget(
+ edit=self._back_button,
+ right_widget=self._league_rank_button_widget,
+ )
+ ba.widget(
+ edit=self._league_rank_button_widget,
+ left_widget=self._back_button,
+ )
else:
self._league_rank_button = None
self._store_button = None
@@ -166,32 +222,40 @@ class CoopBrowserWindow(ba.Window):
1.0,
ba.WeakCall(self._update_corner_button_positions),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
self._last_tournament_query_time: float | None = None
self._last_tournament_query_response_time: float | None = None
self._doing_tournament_query = False
- self._selected_campaign_level = (cfg.get(
- 'Selected Coop Campaign Level', None))
- self._selected_custom_level = (cfg.get('Selected Coop Custom Level',
- None))
+ self._selected_campaign_level = cfg.get(
+ 'Selected Coop Campaign Level', None
+ )
+ self._selected_custom_level = cfg.get(
+ 'Selected Coop Custom Level', None
+ )
# Don't want initial construction affecting our last-selected.
self._do_selection_callbacks = False
v = self._height - 95
txt = ba.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5,
- v + 40 - (0 if uiscale is ba.UIScale.SMALL else 0)),
+ position=(
+ self._width * 0.5,
+ v + 40 - (0 if uiscale is ba.UIScale.SMALL else 0),
+ ),
size=(0, 0),
- text=ba.Lstr(resource='playModes.singlePlayerCoopText',
- fallback_resource='playModes.coopText'),
+ text=ba.Lstr(
+ resource='playModes.singlePlayerCoopText',
+ fallback_resource='playModes.coopText',
+ ),
h_align='center',
color=app.ui.title_color,
scale=1.5,
maxwidth=500,
- v_align='center')
+ v_align='center',
+ )
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
ba.textwidget(edit=txt, text='')
@@ -201,9 +265,15 @@ class CoopBrowserWindow(ba.Window):
edit=self._back_button,
button_type='backSmall',
size=(60, 50),
- position=(75 + x_inset, self._height - 87 -
- (4 if uiscale is ba.UIScale.SMALL else 0) + 6),
- label=ba.charstr(ba.SpecialChar.BACK))
+ position=(
+ 75 + x_inset,
+ self._height
+ - 87
+ - (4 if uiscale is ba.UIScale.SMALL else 0)
+ + 6,
+ ),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._selected_row = cfg.get('Selected Coop Row', None)
@@ -214,9 +284,9 @@ class CoopBrowserWindow(ba.Window):
self.a_outline_model = ba.getmodel('achievementOutline')
self._scroll_width = self._width - (130 + 2 * x_inset)
- self._scroll_height = (self._height -
- (190 if uiscale is ba.UIScale.SMALL
- and app.ui.use_toolbars else 160))
+ self._scroll_height = self._height - (
+ 190 if uiscale is ba.UIScale.SMALL and app.ui.use_toolbars else 160
+ )
self._subcontainerwidth = 800.0
self._subcontainerheight = 1400.0
@@ -224,13 +294,15 @@ class CoopBrowserWindow(ba.Window):
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
- position=(65 + x_inset, 120) if uiscale is ba.UIScale.SMALL
- and app.ui.use_toolbars else (65 + x_inset, 70),
+ position=(65 + x_inset, 120)
+ if uiscale is ba.UIScale.SMALL and app.ui.use_toolbars
+ else (65 + x_inset, 70),
size=(self._scroll_width, self._scroll_height),
simple_culling_v=10.0,
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
self._subcontainer: ba.Widget | None = None
# Take note of our account state; we'll refresh later if this changes.
@@ -250,11 +322,15 @@ class CoopBrowserWindow(ba.Window):
# If we've got a cached tournament list for our account and info for
# each one of those tournaments, go ahead and display it as a
# starting point.
- if (app.accounts_v1.account_tournament_list is not None
- and app.accounts_v1.account_tournament_list[0]
- == ba.internal.get_v1_account_state_num() and all(
- t_id in app.accounts_v1.tournament_info
- for t_id in app.accounts_v1.account_tournament_list[1])):
+ if (
+ app.accounts_v1.account_tournament_list is not None
+ and app.accounts_v1.account_tournament_list[0]
+ == ba.internal.get_v1_account_state_num()
+ and all(
+ t_id in app.accounts_v1.tournament_info
+ for t_id in app.accounts_v1.account_tournament_list[1]
+ )
+ ):
tourney_data = [
app.accounts_v1.tournament_info[t_id]
for t_id in app.accounts_v1.account_tournament_list[1]
@@ -262,10 +338,12 @@ class CoopBrowserWindow(ba.Window):
self._update_for_data(tourney_data)
# This will pull new data periodically, update timers, etc.
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
# noinspection PyUnresolvedReferences
@@ -294,9 +372,12 @@ class CoopBrowserWindow(ba.Window):
# If its been a while since we got a tournament update, consider the
# data invalid (prevents us from joining tournaments if our internet
# connection goes down for a while).
- if (self._last_tournament_query_response_time is None
- or ba.time(ba.TimeType.REAL) -
- self._last_tournament_query_response_time > 60.0 * 2):
+ if (
+ self._last_tournament_query_response_time is None
+ or ba.time(ba.TimeType.REAL)
+ - self._last_tournament_query_response_time
+ > 60.0 * 2
+ ):
self._tourney_data_up_to_date = False
# If our account state has changed, do a full request.
@@ -318,17 +399,15 @@ class CoopBrowserWindow(ba.Window):
# Send off a new tournament query if its been long enough or whatnot.
if not self._doing_tournament_query and (
- self._last_tournament_query_time is None
- or cur_time - self._last_tournament_query_time > 30.0
- or self._fg_state != ba.app.fg_state):
+ self._last_tournament_query_time is None
+ or cur_time - self._last_tournament_query_time > 30.0
+ or self._fg_state != ba.app.fg_state
+ ):
self._fg_state = ba.app.fg_state
self._last_tournament_query_time = cur_time
self._doing_tournament_query = True
ba.internal.tournament_query(
- args={
- 'source': 'coop window refresh',
- 'numScores': 1
- },
+ args={'source': 'coop window refresh', 'numScores': 1},
callback=ba.WeakCall(self._on_tournament_query_response),
)
@@ -339,18 +418,28 @@ class CoopBrowserWindow(ba.Window):
if tbtn.time_remaining_value_text is not None:
ba.textwidget(
edit=tbtn.time_remaining_value_text,
- text=ba.timestring(tbtn.time_remaining,
- centi=False,
- suppress_format_warning=True) if
- (tbtn.has_time_remaining
- and self._tourney_data_up_to_date) else '-')
+ text=ba.timestring(
+ tbtn.time_remaining,
+ centi=False,
+ suppress_format_warning=True,
+ )
+ if (
+ tbtn.has_time_remaining
+ and self._tourney_data_up_to_date
+ )
+ else '-',
+ )
# Also adjust the ad icon visibility.
if tbtn.allow_ads and ba.internal.has_video_ads():
- ba.imagewidget(edit=tbtn.entry_fee_ad_image,
- opacity=1.0 if ads_enabled else 0.25)
- ba.textwidget(edit=tbtn.entry_fee_text_remaining,
- color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
+ ba.imagewidget(
+ edit=tbtn.entry_fee_ad_image,
+ opacity=1.0 if ads_enabled else 0.25,
+ )
+ ba.textwidget(
+ edit=tbtn.entry_fee_text_remaining,
+ color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2),
+ )
self._update_hard_mode_lock_image()
@@ -358,7 +447,8 @@ class CoopBrowserWindow(ba.Window):
try:
ba.imagewidget(
edit=self._hard_button_lock_image,
- opacity=0.0 if ba.app.accounts_v1.have_pro_options() else 1.0)
+ opacity=0.0 if ba.app.accounts_v1.have_pro_options() else 1.0,
+ )
except Exception:
ba.print_exception('Error updating campaign lock.')
@@ -366,11 +456,10 @@ class CoopBrowserWindow(ba.Window):
# If the number of tournaments or challenges in the data differs from
# our current arrangement, refresh with the new number.
- if ((data is None and self._tournament_button_count != 0)
- or (data is not None and
- (len(data) != self._tournament_button_count))):
- self._tournament_button_count = (len(data)
- if data is not None else 0)
+ if (data is None and self._tournament_button_count != 0) or (
+ data is not None and (len(data) != self._tournament_button_count)
+ ):
+ self._tournament_button_count = len(data) if data is not None else 0
ba.app.config['Tournament Rows'] = self._tournament_button_count
self._refresh()
@@ -379,13 +468,15 @@ class CoopBrowserWindow(ba.Window):
assert data is not None
tbtn.update_for_data(data[i])
- def _on_tournament_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_tournament_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
accounts = ba.app.accounts_v1
if data is not None:
tournament_data = data['t'] # This used to be the whole payload.
self._last_tournament_query_response_time = ba.time(
- ba.TimeType.REAL)
+ ba.TimeType.REAL
+ )
else:
tournament_data = None
@@ -397,7 +488,8 @@ class CoopBrowserWindow(ba.Window):
# Also cache the current tourney list/order for this account.
accounts.account_tournament_list = (
ba.internal.get_v1_account_state_num(),
- [e['tournamentID'] for e in tournament_data])
+ [e['tournamentID'] for e in tournament_data],
+ )
self._doing_tournament_query = False
self._update_for_data(tournament_data)
@@ -405,9 +497,12 @@ class CoopBrowserWindow(ba.Window):
def _set_campaign_difficulty(self, difficulty: str) -> None:
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
+
if difficulty != self._campaign_difficulty:
- if (difficulty == 'hard'
- and not ba.app.accounts_v1.have_pro_options()):
+ if (
+ difficulty == 'hard'
+ and not ba.app.accounts_v1.have_pro_options()
+ ):
PurchaseWindow(items=['pro'])
return
ba.playsound(ba.getsound('gunCocking'))
@@ -415,11 +510,13 @@ class CoopBrowserWindow(ba.Window):
print('ERROR: invalid campaign difficulty:', difficulty)
difficulty = 'easy'
self._campaign_difficulty = difficulty
- ba.internal.add_transaction({
- 'type': 'SET_MISC_VAL',
- 'name': 'campaignDifficulty',
- 'value': difficulty
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'SET_MISC_VAL',
+ 'name': 'campaignDifficulty',
+ 'value': difficulty,
+ }
+ )
self._refresh_campaign_row()
else:
ba.playsound(ba.getsound('click01'))
@@ -429,6 +526,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from ba.internal import getcampaign
from bastd.ui.coop.gamebutton import GameButton
+
parent_widget = self._campaign_sub_container
# Clear out anything in the parent widget already.
@@ -455,14 +553,19 @@ class CoopBrowserWindow(ba.Window):
on_activate_call=ba.Call(self._set_campaign_difficulty, 'easy'),
on_select_call=ba.Call(self.sel_change, 'campaign', 'easyButton'),
color=sel_color
- if self._campaign_difficulty == 'easy' else un_sel_color,
+ if self._campaign_difficulty == 'easy'
+ else un_sel_color,
textcolor=sel_textcolor
- if self._campaign_difficulty == 'easy' else un_sel_textcolor)
+ if self._campaign_difficulty == 'easy'
+ else un_sel_textcolor,
+ )
ba.widget(edit=self._easy_button, show_buffer_left=100)
if self._selected_campaign_level == 'easyButton':
- ba.containerwidget(edit=parent_widget,
- selected_child=self._easy_button,
- visible_child=self._easy_button)
+ ba.containerwidget(
+ edit=parent_widget,
+ selected_child=self._easy_button,
+ visible_child=self._easy_button,
+ )
lock_tex = ba.gettexture('lock')
self._hard_button = ba.buttonwidget(
@@ -476,21 +579,27 @@ class CoopBrowserWindow(ba.Window):
on_activate_call=ba.Call(self._set_campaign_difficulty, 'hard'),
on_select_call=ba.Call(self.sel_change, 'campaign', 'hardButton'),
color=sel_color_hard
- if self._campaign_difficulty == 'hard' else un_sel_color,
+ if self._campaign_difficulty == 'hard'
+ else un_sel_color,
textcolor=sel_textcolor
- if self._campaign_difficulty == 'hard' else un_sel_textcolor)
+ if self._campaign_difficulty == 'hard'
+ else un_sel_textcolor,
+ )
self._hard_button_lock_image = ba.imagewidget(
parent=parent_widget,
size=(30, 30),
draw_controller=self._hard_button,
position=(h + 30 - 10, v2 + 32 + 70 - 35),
- texture=lock_tex)
+ texture=lock_tex,
+ )
self._update_hard_mode_lock_image()
ba.widget(edit=self._hard_button, show_buffer_left=100)
if self._selected_campaign_level == 'hardButton':
- ba.containerwidget(edit=parent_widget,
- selected_child=self._hard_button,
- visible_child=self._hard_button)
+ ba.containerwidget(
+ edit=parent_widget,
+ selected_child=self._hard_button,
+ visible_child=self._hard_button,
+ )
ba.widget(edit=self._hard_button, down_widget=next_widget_down)
h_spacing = 200
@@ -515,10 +624,12 @@ class CoopBrowserWindow(ba.Window):
self._selected_campaign_level = items[0]
h = 150
for i in items:
- is_last_sel = (i == self._selected_campaign_level)
+ is_last_sel = i == self._selected_campaign_level
campaign_buttons.append(
- GameButton(self, parent_widget, i, h, v2, is_last_sel,
- 'campaign').get_button())
+ GameButton(
+ self, parent_widget, i, h, v2, is_last_sel, 'campaign'
+ ).get_button()
+ )
h += h_spacing
ba.widget(edit=campaign_buttons[0], left_widget=self._easy_button)
@@ -526,9 +637,11 @@ class CoopBrowserWindow(ba.Window):
if self._back_button is not None:
ba.widget(edit=self._easy_button, up_widget=self._back_button)
for btn in campaign_buttons:
- ba.widget(edit=btn,
- up_widget=self._back_button,
- down_widget=next_widget_down)
+ ba.widget(
+ edit=btn,
+ up_widget=self._back_button,
+ down_widget=next_widget_down,
+ )
# Update our existing percent-complete text.
campaign = getcampaign(campaignname)
@@ -541,20 +654,27 @@ class CoopBrowserWindow(ba.Window):
self._campaign_percent_text = ba.textwidget(
edit=self._campaign_percent_text,
- text=ba.Lstr(value='${C} (${P})',
- subs=[('${C}',
- ba.Lstr(resource=self._r + '.campaignText')),
- ('${P}', p_str)]))
+ text=ba.Lstr(
+ value='${C} (${P})',
+ subs=[
+ ('${C}', ba.Lstr(resource=self._r + '.campaignText')),
+ ('${P}', p_str),
+ ],
+ ),
+ )
def _on_tournament_info_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.confirm import ConfirmWindow
+
txt = ba.Lstr(resource=self._r + '.tournamentInfoText')
- ConfirmWindow(txt,
- cancel_button=False,
- width=550,
- height=260,
- origin_widget=self._tournament_info_button)
+ ConfirmWindow(
+ txt,
+ cancel_button=False,
+ width=550,
+ height=260,
+ origin_widget=self._tournament_info_button,
+ )
def _refresh(self) -> None:
# pylint: disable=too-many-statements
@@ -570,7 +690,8 @@ class CoopBrowserWindow(ba.Window):
tourney_row_height = 200
self._subcontainerheight = (
- 620 + self._tournament_button_count * tourney_row_height)
+ 620 + self._tournament_button_count * tourney_row_height
+ )
self._subcontainer = ba.containerwidget(
parent=self._scrollwidget,
@@ -578,13 +699,16 @@ class CoopBrowserWindow(ba.Window):
background=False,
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
if self._back_button is not None:
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
w_parent = self._subcontainer
h_base = 6
@@ -599,7 +723,8 @@ class CoopBrowserWindow(ba.Window):
h_align='left',
v_align='center',
color=ba.app.ui.title_color,
- scale=1.1)
+ scale=1.1,
+ )
row_v_show_buffer = 100
v -= 198
@@ -612,21 +737,23 @@ class CoopBrowserWindow(ba.Window):
highlight=False,
border_opacity=0.0,
color=(0.45, 0.4, 0.5),
- on_select_call=lambda: self._on_row_selected('campaign'))
+ on_select_call=lambda: self._on_row_selected('campaign'),
+ )
self._campaign_h_scroll = h_scroll
- ba.widget(edit=h_scroll,
- show_buffer_top=row_v_show_buffer,
- show_buffer_bottom=row_v_show_buffer,
- autoselect=True)
+ ba.widget(
+ edit=h_scroll,
+ show_buffer_top=row_v_show_buffer,
+ show_buffer_bottom=row_v_show_buffer,
+ autoselect=True,
+ )
if self._selected_row == 'campaign':
- ba.containerwidget(edit=w_parent,
- selected_child=h_scroll,
- visible_child=h_scroll)
+ ba.containerwidget(
+ edit=w_parent, selected_child=h_scroll, visible_child=h_scroll
+ )
ba.containerwidget(edit=h_scroll, claims_left_right=True)
- self._campaign_sub_container = ba.containerwidget(parent=h_scroll,
- size=(180 + 200 * 10,
- 200),
- background=False)
+ self._campaign_sub_container = ba.containerwidget(
+ parent=h_scroll, size=(180 + 200 * 10, 200), background=False
+ )
# Tournaments
@@ -634,17 +761,20 @@ class CoopBrowserWindow(ba.Window):
v -= 53
# FIXME shouldn't use hard-coded strings here.
- txt = ba.Lstr(resource='tournamentsText',
- fallback_resource='tournamentText').evaluate()
+ txt = ba.Lstr(
+ resource='tournamentsText', fallback_resource='tournamentText'
+ ).evaluate()
t_width = ba.internal.get_string_width(txt, suppress_warning=True)
- ba.textwidget(parent=w_parent,
- position=(h_base + 27, v + 30),
- size=(0, 0),
- text=txt,
- h_align='left',
- v_align='center',
- color=ba.app.ui.title_color,
- scale=1.1)
+ ba.textwidget(
+ parent=w_parent,
+ position=(h_base + 27, v + 30),
+ size=(0, 0),
+ text=txt,
+ h_align='left',
+ v_align='center',
+ color=ba.app.ui.title_color,
+ scale=1.1,
+ )
self._tournament_info_button = ba.buttonwidget(
parent=w_parent,
label='?',
@@ -656,10 +786,13 @@ class CoopBrowserWindow(ba.Window):
textcolor=(0.7, 0.6, 0.75),
autoselect=True,
up_widget=self._campaign_h_scroll,
- on_activate_call=self._on_tournament_info_press)
- ba.widget(edit=self._tournament_info_button,
- left_widget=self._tournament_info_button,
- right_widget=self._tournament_info_button)
+ on_activate_call=self._on_tournament_info_press,
+ )
+ ba.widget(
+ edit=self._tournament_info_button,
+ left_widget=self._tournament_info_button,
+ right_widget=self._tournament_info_button,
+ )
# Say 'unavailable' if there are zero tournaments, and if we're not
# signed in add that as well (that's probably why we see
@@ -669,16 +802,21 @@ class CoopBrowserWindow(ba.Window):
if ba.internal.get_v1_account_state() != 'signed_in':
unavailable_text = ba.Lstr(
value='${A} (${B})',
- subs=[('${A}', unavailable_text),
- ('${B}', ba.Lstr(resource='notSignedInText'))])
- ba.textwidget(parent=w_parent,
- position=(h_base + 47, v),
- size=(0, 0),
- text=unavailable_text,
- h_align='left',
- v_align='center',
- color=ba.app.ui.title_color,
- scale=0.9)
+ subs=[
+ ('${A}', unavailable_text),
+ ('${B}', ba.Lstr(resource='notSignedInText')),
+ ],
+ )
+ ba.textwidget(
+ parent=w_parent,
+ position=(h_base + 47, v),
+ size=(0, 0),
+ text=unavailable_text,
+ h_align='left',
+ v_align='center',
+ color=ba.app.ui.title_color,
+ scale=0.9,
+ )
v -= 40
v -= 198
@@ -692,44 +830,57 @@ class CoopBrowserWindow(ba.Window):
highlight=False,
border_opacity=0.0,
color=(0.45, 0.4, 0.5),
- on_select_call=ba.Call(self._on_row_selected,
- 'tournament' + str(i + 1)))
- ba.widget(edit=h_scroll,
- show_buffer_top=row_v_show_buffer,
- show_buffer_bottom=row_v_show_buffer,
- autoselect=True)
+ on_select_call=ba.Call(
+ self._on_row_selected, 'tournament' + str(i + 1)
+ ),
+ )
+ ba.widget(
+ edit=h_scroll,
+ show_buffer_top=row_v_show_buffer,
+ show_buffer_bottom=row_v_show_buffer,
+ autoselect=True,
+ )
if self._selected_row == 'tournament' + str(i + 1):
- ba.containerwidget(edit=w_parent,
- selected_child=h_scroll,
- visible_child=h_scroll)
+ ba.containerwidget(
+ edit=w_parent,
+ selected_child=h_scroll,
+ visible_child=h_scroll,
+ )
ba.containerwidget(edit=h_scroll, claims_left_right=True)
- sc2 = ba.containerwidget(parent=h_scroll,
- size=(self._scroll_width - 24, 200),
- background=False)
+ sc2 = ba.containerwidget(
+ parent=h_scroll,
+ size=(self._scroll_width - 24, 200),
+ background=False,
+ )
h = 0
v2 = -2
is_last_sel = True
self._tournament_buttons.append(
- TournamentButton(sc2,
- h,
- v2,
- is_last_sel,
- on_pressed=ba.WeakCall(
- self.run_tournament)))
+ TournamentButton(
+ sc2,
+ h,
+ v2,
+ is_last_sel,
+ on_pressed=ba.WeakCall(self.run_tournament),
+ )
+ )
v -= 200
# Custom Games. (called 'Practice' in UI these days).
v -= 50
- ba.textwidget(parent=w_parent,
- position=(h_base + 27, v + 30 + 198),
- size=(0, 0),
- text=ba.Lstr(
- resource='practiceText',
- fallback_resource='coopSelectWindow.customText'),
- h_align='left',
- v_align='center',
- color=ba.app.ui.title_color,
- scale=1.1)
+ ba.textwidget(
+ parent=w_parent,
+ position=(h_base + 27, v + 30 + 198),
+ size=(0, 0),
+ text=ba.Lstr(
+ resource='practiceText',
+ fallback_resource='coopSelectWindow.customText',
+ ),
+ h_align='left',
+ v_align='center',
+ color=ba.app.ui.title_color,
+ scale=1.1,
+ )
items = [
'Challenges:Infinite Onslaught',
@@ -743,8 +894,8 @@ class CoopBrowserWindow(ba.Window):
# Show easter-egg-hunt either if its easter or we own it.
if ba.internal.get_v1_account_misc_read_val(
- 'easter',
- False) or ba.internal.get_purchased('games.easter_egg_hunt'):
+ 'easter', False
+ ) or ba.internal.get_purchased('games.easter_egg_hunt'):
items = [
'Challenges:Easter Egg Hunt',
'Challenges:Pro Easter Egg Hunt',
@@ -761,28 +912,33 @@ class CoopBrowserWindow(ba.Window):
highlight=False,
border_opacity=0.0,
color=(0.45, 0.4, 0.5),
- on_select_call=ba.Call(self._on_row_selected, 'custom'))
- ba.widget(edit=h_scroll,
- show_buffer_top=row_v_show_buffer,
- show_buffer_bottom=1.5 * row_v_show_buffer,
- autoselect=True)
+ on_select_call=ba.Call(self._on_row_selected, 'custom'),
+ )
+ ba.widget(
+ edit=h_scroll,
+ show_buffer_top=row_v_show_buffer,
+ show_buffer_bottom=1.5 * row_v_show_buffer,
+ autoselect=True,
+ )
if self._selected_row == 'custom':
- ba.containerwidget(edit=w_parent,
- selected_child=h_scroll,
- visible_child=h_scroll)
+ ba.containerwidget(
+ edit=w_parent, selected_child=h_scroll, visible_child=h_scroll
+ )
ba.containerwidget(edit=h_scroll, claims_left_right=True)
- sc2 = ba.containerwidget(parent=h_scroll,
- size=(max(self._scroll_width - 24,
- 30 + 200 * len(items)), 200),
- background=False)
+ sc2 = ba.containerwidget(
+ parent=h_scroll,
+ size=(max(self._scroll_width - 24, 30 + 200 * len(items)), 200),
+ background=False,
+ )
h_spacing = 200
self._custom_buttons: list[GameButton] = []
h = 0
v2 = -2
for item in items:
- is_last_sel = (item == self._selected_custom_level)
+ is_last_sel = item == self._selected_custom_level
self._custom_buttons.append(
- GameButton(self, sc2, item, h, v2, is_last_sel, 'custom'))
+ GameButton(self, sc2, item, h, v2, is_last_sel, 'custom')
+ )
h += h_spacing
# We can't fill in our campaign row until tourney buttons are in place.
@@ -793,33 +949,44 @@ class CoopBrowserWindow(ba.Window):
ba.widget(
edit=tbutton.button,
up_widget=self._tournament_info_button
- if i == 0 else self._tournament_buttons[i - 1].button,
+ if i == 0
+ else self._tournament_buttons[i - 1].button,
down_widget=self._tournament_buttons[(i + 1)].button
- if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
+ if i + 1 < len(self._tournament_buttons)
+ else custom_h_scroll,
+ )
ba.widget(
edit=tbutton.more_scores_button,
- down_widget=self._tournament_buttons[(
- i + 1)].current_leader_name_text
- if i + 1 < len(self._tournament_buttons) else custom_h_scroll)
- ba.widget(edit=tbutton.current_leader_name_text,
- up_widget=self._tournament_info_button if i == 0 else
- self._tournament_buttons[i - 1].more_scores_button)
+ down_widget=self._tournament_buttons[
+ (i + 1)
+ ].current_leader_name_text
+ if i + 1 < len(self._tournament_buttons)
+ else custom_h_scroll,
+ )
+ ba.widget(
+ edit=tbutton.current_leader_name_text,
+ up_widget=self._tournament_info_button
+ if i == 0
+ else self._tournament_buttons[i - 1].more_scores_button,
+ )
for btn in self._custom_buttons:
try:
ba.widget(
edit=btn.get_button(),
- up_widget=tournament_h_scroll if self._tournament_buttons
- else self._tournament_info_button)
+ up_widget=tournament_h_scroll
+ if self._tournament_buttons
+ else self._tournament_info_button,
+ )
except Exception:
ba.print_exception('Error wiring up custom buttons.')
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- on_activate_call=self._back)
+ ba.buttonwidget(edit=self._back_button, on_activate_call=self._back)
else:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
# There's probably several 'onSelected' callbacks pushed onto the
# event queue.. we need to push ours too so we're enabled *after* them.
@@ -837,6 +1004,7 @@ class CoopBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.league.rankwindow import LeagueRankWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@@ -844,16 +1012,19 @@ class CoopBrowserWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
assert self._league_rank_button is not None
ba.app.ui.set_main_menu_window(
- LeagueRankWindow(origin_widget=self._league_rank_button.get_button(
- )).get_root_widget())
+ LeagueRankWindow(
+ origin_widget=self._league_rank_button.get_button()
+ ).get_root_widget()
+ )
def _switch_to_score(
self,
show_tab: StoreBrowserWindow.TabID
- | None = StoreBrowserWindow.TabID.EXTRAS
+ | None = StoreBrowserWindow.TabID.EXTRAS,
) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@@ -864,7 +1035,9 @@ class CoopBrowserWindow(ba.Window):
StoreBrowserWindow(
origin_widget=self._store_button.get_button(),
show_tab=show_tab,
- back_location='CoopBrowserWindow').get_root_widget())
+ back_location='CoopBrowserWindow',
+ ).get_root_widget()
+ )
def is_tourney_data_up_to_date(self) -> bool:
"""Return whether our tourney data is up to date."""
@@ -877,21 +1050,31 @@ class CoopBrowserWindow(ba.Window):
from bastd.ui.confirm import ConfirmWindow
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.account import show_sign_in_prompt
+
args: dict[str, Any] = {}
if game == 'Easy:The Last Stand':
- ConfirmWindow(ba.Lstr(resource='difficultyHardUnlockOnlyText',
- fallback_resource='difficultyHardOnlyText'),
- cancel_button=False,
- width=460,
- height=130)
+ ConfirmWindow(
+ ba.Lstr(
+ resource='difficultyHardUnlockOnlyText',
+ fallback_resource='difficultyHardOnlyText',
+ ),
+ cancel_button=False,
+ width=460,
+ height=130,
+ )
return
# Infinite onslaught/runaround require pro; bring up a store link
# if need be.
- if game in ('Challenges:Infinite Runaround',
- 'Challenges:Infinite Onslaught'
- ) and not ba.app.accounts_v1.have_pro():
+ if (
+ game
+ in (
+ 'Challenges:Infinite Runaround',
+ 'Challenges:Infinite Onslaught',
+ )
+ and not ba.app.accounts_v1.have_pro()
+ ):
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
@@ -902,8 +1085,8 @@ class CoopBrowserWindow(ba.Window):
if game in ['Challenges:Meteor Shower']:
required_purchase = 'games.meteor_shower'
elif game in [
- 'Challenges:Target Practice',
- 'Challenges:Target Practice B',
+ 'Challenges:Target Practice',
+ 'Challenges:Target Practice B',
]:
required_purchase = 'games.target_practice'
elif game in ['Challenges:Ninja Fight']:
@@ -911,15 +1094,16 @@ class CoopBrowserWindow(ba.Window):
elif game in ['Challenges:Pro Ninja Fight']:
required_purchase = 'games.ninja_fight'
elif game in [
- 'Challenges:Easter Egg Hunt',
- 'Challenges:Pro Easter Egg Hunt',
+ 'Challenges:Easter Egg Hunt',
+ 'Challenges:Pro Easter Egg Hunt',
]:
required_purchase = 'games.easter_egg_hunt'
else:
required_purchase = None
- if (required_purchase is not None
- and not ba.internal.get_purchased(required_purchase)):
+ if required_purchase is not None and not ba.internal.get_purchased(
+ required_purchase
+ ):
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
else:
@@ -943,20 +1127,23 @@ class CoopBrowserWindow(ba.Window):
if ba.internal.workspaces_in_use():
ba.screenmessage(
ba.Lstr(resource='tournamentsDisabledWorkspaceText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
if not self._tourney_data_up_to_date:
- ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
- color=(1, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 1, 0)
+ )
ba.playsound(ba.getsound('error'))
return
if tournament_button.tournament_id is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -964,19 +1151,27 @@ class CoopBrowserWindow(ba.Window):
ba.screenmessage(
ba.Lstr(
resource='league.tournamentLeagueText',
- subs=[('${NAME}',
- ba.Lstr(
- translate=('leagueNames',
- tournament_button.required_league)))
- ]),
+ subs=[
+ (
+ '${NAME}',
+ ba.Lstr(
+ translate=(
+ 'leagueNames',
+ tournament_button.required_league,
+ )
+ ),
+ )
+ ],
+ ),
color=(1, 0, 0),
)
ba.playsound(ba.getsound('error'))
return
if tournament_button.time_remaining <= 0:
- ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
@@ -985,7 +1180,8 @@ class CoopBrowserWindow(ba.Window):
assert tournament_button.tournament_id is not None
TournamentEntryWindow(
tournament_id=tournament_button.tournament_id,
- position=tournament_button.button.get_screen_space_center())
+ position=tournament_button.button.get_screen_space_center(),
+ )
def _back(self) -> None:
# pylint: disable=cyclic-import
@@ -993,10 +1189,12 @@ class CoopBrowserWindow(ba.Window):
# If something is selected, store it.
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- PlayWindow(transition='in_left').get_root_widget())
+ PlayWindow(transition='in_left').get_root_widget()
+ )
def _save_state(self) -> None:
cfg = ba.app.config
@@ -1023,8 +1221,9 @@ class CoopBrowserWindow(ba.Window):
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(type(self),
- {}).get('sel_name')
+ sel_name = ba.app.ui.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
diff --git a/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py b/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
index 6324783b..4182416f 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/gamebutton.py
@@ -16,11 +16,20 @@ if TYPE_CHECKING:
class GameButton:
"""Button for entering co-op games."""
- def __init__(self, window: CoopBrowserWindow, parent: ba.Widget, game: str,
- x: float, y: float, select: bool, row: str):
+ def __init__(
+ self,
+ window: CoopBrowserWindow,
+ parent: ba.Widget,
+ game: str,
+ x: float,
+ y: float,
+ select: bool,
+ row: str,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from ba.internal import getcampaign
+
self._game = game
sclx = 195.0
scly = 195.0
@@ -57,16 +66,19 @@ class GameButton:
on_activate_call=ba.Call(window.run_game, game),
button_type='square',
autoselect=True,
- on_select_call=ba.Call(window.sel_change, row, game))
- ba.widget(edit=btn,
- show_buffer_bottom=50,
- show_buffer_top=50,
- show_buffer_left=400,
- show_buffer_right=200)
+ on_select_call=ba.Call(window.sel_change, row, game),
+ )
+ ba.widget(
+ edit=btn,
+ show_buffer_bottom=50,
+ show_buffer_top=50,
+ show_buffer_left=400,
+ show_buffer_right=200,
+ )
if select:
- ba.containerwidget(edit=parent,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=parent, selected_child=btn, visible_child=btn
+ )
image_width = sclx * 0.85 * 0.75
self._preview_widget = ba.imagewidget(
parent=parent,
@@ -76,21 +88,23 @@ class GameButton:
model_transparent=window.lsbt,
model_opaque=window.lsbo,
texture=campaign.getlevel(levelname).get_preview_texture(),
- mask_texture=ba.gettexture('mapPreviewMask'))
+ mask_texture=ba.gettexture('mapPreviewMask'),
+ )
translated = campaign.getlevel(levelname).displayname
self._achievements = ba.app.ach.achievements_for_coop_level(game)
- self._name_widget = ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 20 + sclx * 0.5,
- y + scly - 27),
- size=(0, 0),
- h_align='center',
- text=translated,
- v_align='center',
- maxwidth=sclx * 0.76,
- scale=0.85)
+ self._name_widget = ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 20 + sclx * 0.5, y + scly - 27),
+ size=(0, 0),
+ h_align='center',
+ text=translated,
+ v_align='center',
+ maxwidth=sclx * 0.76,
+ scale=0.85,
+ )
xscl = x + (67 if self._achievements else 50)
yscl = y + scly - (137 if self._achievements else 157)
@@ -98,21 +112,25 @@ class GameButton:
self._star_widgets: list[ba.Widget] = []
for _i in range(stars):
- imw = ba.imagewidget(parent=parent,
- draw_controller=btn,
- position=(xscl, yscl),
- size=(starscale, starscale),
- texture=window.star_tex)
+ imw = ba.imagewidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(xscl, yscl),
+ size=(starscale, starscale),
+ texture=window.star_tex,
+ )
self._star_widgets.append(imw)
xscl += starscale
for _i in range(3 - stars):
- ba.imagewidget(parent=parent,
- draw_controller=btn,
- position=(xscl, yscl),
- size=(starscale, starscale),
- color=(0, 0, 0),
- texture=window.star_tex,
- opacity=0.3)
+ ba.imagewidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(xscl, yscl),
+ size=(starscale, starscale),
+ color=(0, 0, 0),
+ texture=window.star_tex,
+ opacity=0.3,
+ )
xscl += starscale
xach = x + 69
@@ -127,33 +145,40 @@ class GameButton:
position=(xach, yach),
size=(a_scale, a_scale),
color=tuple(ach.get_icon_color(a_complete)[:3])
- if a_complete else (1.2, 1.2, 1.2),
- texture=ach.get_icon_texture(a_complete))
- imw2 = ba.imagewidget(parent=parent,
- draw_controller=btn,
- position=(xach, yach),
- size=(a_scale, a_scale),
- color=(2, 1.4, 0.4),
- texture=window.a_outline_tex,
- model_transparent=window.a_outline_model)
+ if a_complete
+ else (1.2, 1.2, 1.2),
+ texture=ach.get_icon_texture(a_complete),
+ )
+ imw2 = ba.imagewidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(xach, yach),
+ size=(a_scale, a_scale),
+ color=(2, 1.4, 0.4),
+ texture=window.a_outline_tex,
+ model_transparent=window.a_outline_model,
+ )
self._achievement_widgets.append((imw, imw2))
# if a_complete:
xach += a_scale * 1.2
# if not unlocked:
- self._lock_widget = ba.imagewidget(parent=parent,
- draw_controller=btn,
- position=(x - 8 + sclx * 0.5,
- y + scly * 0.5 - 20),
- size=(60, 60),
- opacity=0.0,
- texture=ba.gettexture('lock'))
+ self._lock_widget = ba.imagewidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x - 8 + sclx * 0.5, y + scly * 0.5 - 20),
+ size=(60, 60),
+ opacity=0.0,
+ texture=ba.gettexture('lock'),
+ )
# give a quasi-random update increment to spread the load..
- self._update_timer = ba.Timer(0.001 * (900 + random.randrange(200)),
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 0.001 * (900 + random.randrange(200)),
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
self._update()
def get_button(self) -> ba.Widget:
@@ -195,46 +220,84 @@ class GameButton:
unlocked = False
# Hard-code games we haven't unlocked.
- if ((game in ('Challenges:Infinite Runaround',
- 'Challenges:Infinite Onslaught')
- and not ba.app.accounts_v1.have_pro())
- or (game in ('Challenges:Meteor Shower', )
- and not ba.internal.get_purchased('games.meteor_shower'))
- or (game in ('Challenges:Target Practice',
- 'Challenges:Target Practice B')
- and not ba.internal.get_purchased('games.target_practice'))
- or (game in ('Challenges:Ninja Fight', )
- and not ba.internal.get_purchased('games.ninja_fight'))
- or (game in ('Challenges:Pro Ninja Fight', )
- and not ba.internal.get_purchased('games.ninja_fight')) or
- (game in ('Challenges:Easter Egg Hunt',
- 'Challenges:Pro Easter Egg Hunt')
- and not ba.internal.get_purchased('games.easter_egg_hunt'))):
+ if (
+ (
+ game
+ in (
+ 'Challenges:Infinite Runaround',
+ 'Challenges:Infinite Onslaught',
+ )
+ and not ba.app.accounts_v1.have_pro()
+ )
+ or (
+ game in ('Challenges:Meteor Shower',)
+ and not ba.internal.get_purchased('games.meteor_shower')
+ )
+ or (
+ game
+ in (
+ 'Challenges:Target Practice',
+ 'Challenges:Target Practice B',
+ )
+ and not ba.internal.get_purchased('games.target_practice')
+ )
+ or (
+ game in ('Challenges:Ninja Fight',)
+ and not ba.internal.get_purchased('games.ninja_fight')
+ )
+ or (
+ game in ('Challenges:Pro Ninja Fight',)
+ and not ba.internal.get_purchased('games.ninja_fight')
+ )
+ or (
+ game
+ in (
+ 'Challenges:Easter Egg Hunt',
+ 'Challenges:Pro Easter Egg Hunt',
+ )
+ and not ba.internal.get_purchased('games.easter_egg_hunt')
+ )
+ ):
unlocked = False
# Let's tint levels a slightly different color when easy mode
# is selected.
- unlocked_color = (0.85, 0.95,
- 0.5) if game.startswith('Easy:') else (0.5, 0.7, 0.2)
+ unlocked_color = (
+ (0.85, 0.95, 0.5) if game.startswith('Easy:') else (0.5, 0.7, 0.2)
+ )
- ba.buttonwidget(edit=self._button,
- color=unlocked_color if unlocked else (0.5, 0.5, 0.5))
+ ba.buttonwidget(
+ edit=self._button,
+ color=unlocked_color if unlocked else (0.5, 0.5, 0.5),
+ )
- ba.imagewidget(edit=self._lock_widget,
- opacity=0.0 if unlocked else 1.0)
- ba.imagewidget(edit=self._preview_widget,
- opacity=1.0 if unlocked else 0.3)
- ba.textwidget(edit=self._name_widget,
- color=(0.8, 1.0, 0.8, 1.0) if unlocked else
- (0.7, 0.7, 0.7, 0.7))
+ ba.imagewidget(edit=self._lock_widget, opacity=0.0 if unlocked else 1.0)
+ ba.imagewidget(
+ edit=self._preview_widget, opacity=1.0 if unlocked else 0.3
+ )
+ ba.textwidget(
+ edit=self._name_widget,
+ color=(0.8, 1.0, 0.8, 1.0) if unlocked else (0.7, 0.7, 0.7, 0.7),
+ )
for widget in self._star_widgets:
- ba.imagewidget(edit=widget,
- opacity=1.0 if unlocked else 0.3,
- color=(2.2, 1.2, 0.3) if unlocked else (1, 1, 1))
+ ba.imagewidget(
+ edit=widget,
+ opacity=1.0 if unlocked else 0.3,
+ color=(2.2, 1.2, 0.3) if unlocked else (1, 1, 1),
+ )
for i, ach in enumerate(self._achievements):
a_complete = ach.complete
- ba.imagewidget(edit=self._achievement_widgets[i][0],
- opacity=1.0 if (a_complete and unlocked) else 0.3)
- ba.imagewidget(edit=self._achievement_widgets[i][1],
- opacity=(1.0 if (a_complete and unlocked) else
- 0.2 if a_complete else 0.0))
+ ba.imagewidget(
+ edit=self._achievement_widgets[i][0],
+ opacity=1.0 if (a_complete and unlocked) else 0.3,
+ )
+ ba.imagewidget(
+ edit=self._achievement_widgets[i][1],
+ opacity=(
+ 1.0
+ if (a_complete and unlocked)
+ else 0.2
+ if a_complete
+ else 0.0
+ ),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/coop/level.py b/assets/src/ba_data/python/bastd/ui/coop/level.py
index c2444696..fd19541a 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/level.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/level.py
@@ -15,44 +15,63 @@ class CoopLevelLockedWindow(ba.Window):
height = 250.0
lock_tex = ba.gettexture('lock')
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition='in_right',
- scale=(1.7 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0)))
- ba.textwidget(parent=self._root_widget,
- position=(150 - 20, height * 0.63),
- size=(0, 0),
- h_align='left',
- v_align='center',
- text=ba.Lstr(resource='levelIsLockedText',
- subs=[('${LEVEL}', name)]),
- maxwidth=400,
- color=(1, 0.8, 0.3, 1),
- scale=1.1)
- ba.textwidget(parent=self._root_widget,
- position=(150 - 20, height * 0.48),
- size=(0, 0),
- h_align='left',
- v_align='center',
- text=ba.Lstr(resource='levelMustBeCompletedFirstText',
- subs=[('${LEVEL}', dep_name)]),
- maxwidth=400,
- color=ba.app.ui.infotextcolor,
- scale=0.8)
- ba.imagewidget(parent=self._root_widget,
- position=(56 - 20, height * 0.39),
- size=(80, 80),
- texture=lock_tex,
- opacity=1.0)
- btn = ba.buttonwidget(parent=self._root_widget,
- position=((width - 140) / 2, 30),
- size=(140, 50),
- label=ba.Lstr(resource='okText'),
- on_activate_call=self._ok)
- ba.containerwidget(edit=self._root_widget,
- selected_child=btn,
- start_button=btn)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition='in_right',
+ scale=(
+ 1.7
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(150 - 20, height * 0.63),
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ text=ba.Lstr(
+ resource='levelIsLockedText', subs=[('${LEVEL}', name)]
+ ),
+ maxwidth=400,
+ color=(1, 0.8, 0.3, 1),
+ scale=1.1,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(150 - 20, height * 0.48),
+ size=(0, 0),
+ h_align='left',
+ v_align='center',
+ text=ba.Lstr(
+ resource='levelMustBeCompletedFirstText',
+ subs=[('${LEVEL}', dep_name)],
+ ),
+ maxwidth=400,
+ color=ba.app.ui.infotextcolor,
+ scale=0.8,
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(56 - 20, height * 0.39),
+ size=(80, 80),
+ texture=lock_tex,
+ opacity=1.0,
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=((width - 140) / 2, 30),
+ size=(140, 50),
+ label=ba.Lstr(resource='okText'),
+ on_activate_call=self._ok,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=btn, start_button=btn
+ )
ba.playsound(ba.getsound('error'))
def _ok(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/coop/tournamentbutton.py b/assets/src/ba_data/python/bastd/ui/coop/tournamentbutton.py
index 041b6c5b..9f439789 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/tournamentbutton.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/tournamentbutton.py
@@ -17,8 +17,14 @@ if TYPE_CHECKING:
class TournamentButton:
"""Button showing a tournament in coop window."""
- def __init__(self, parent: ba.Widget, x: float, y: float, select: bool,
- on_pressed: Callable[[TournamentButton], None]) -> None:
+ def __init__(
+ self,
+ parent: ba.Widget,
+ x: float,
+ y: float,
+ select: bool,
+ on_pressed: Callable[[TournamentButton], None],
+ ) -> None:
self._r = 'coopSelectWindow'
sclx = 300
scly = 195.0
@@ -39,16 +45,19 @@ class TournamentButton:
button_type='square',
autoselect=True,
# on_activate_call=lambda: self.run(None, tournament_button=data)
- on_activate_call=ba.WeakCall(self._pressed))
- ba.widget(edit=btn,
- show_buffer_bottom=50,
- show_buffer_top=50,
- show_buffer_left=400,
- show_buffer_right=200)
+ on_activate_call=ba.WeakCall(self._pressed),
+ )
+ ba.widget(
+ edit=btn,
+ show_buffer_bottom=50,
+ show_buffer_top=50,
+ show_buffer_left=400,
+ show_buffer_right=200,
+ )
if select:
- ba.containerwidget(edit=parent,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=parent, selected_child=btn, visible_child=btn
+ )
image_width = sclx * 0.85 * 0.75
self.image = ba.imagewidget(
@@ -60,103 +69,113 @@ class TournamentButton:
model_opaque=self.lsbo,
texture=ba.gettexture('black'),
opacity=0.2,
- mask_texture=ba.gettexture('mapPreviewMask'))
+ mask_texture=ba.gettexture('mapPreviewMask'),
+ )
self.lock_image = ba.imagewidget(
parent=parent,
draw_controller=btn,
- position=(x + 21 + sclx * 0.5 - image_width * 0.25,
- y + scly - 150),
+ position=(x + 21 + sclx * 0.5 - image_width * 0.25, y + scly - 150),
size=(image_width * 0.5, image_width * 0.5),
texture=ba.gettexture('lock'),
- opacity=0.0)
+ opacity=0.0,
+ )
- self.button_text = ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 20 + sclx * 0.5,
- y + scly - 35),
- size=(0, 0),
- h_align='center',
- text='-',
- v_align='center',
- maxwidth=sclx * 0.76,
- scale=0.85,
- color=(0.8, 1.0, 0.8, 1.0))
+ self.button_text = ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 20 + sclx * 0.5, y + scly - 35),
+ size=(0, 0),
+ h_align='center',
+ text='-',
+ v_align='center',
+ maxwidth=sclx * 0.76,
+ scale=0.85,
+ color=(0.8, 1.0, 0.8, 1.0),
+ )
header_color = (0.43, 0.4, 0.5, 1)
value_color = (0.6, 0.6, 0.6, 1)
x_offs = 0
- ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 360, y + scly - 20),
- size=(0, 0),
- h_align='center',
- text=ba.Lstr(resource=self._r + '.entryFeeText'),
- v_align='center',
- maxwidth=100,
- scale=0.9,
- color=header_color,
- flatness=1.0)
+ ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 360, y + scly - 20),
+ size=(0, 0),
+ h_align='center',
+ text=ba.Lstr(resource=self._r + '.entryFeeText'),
+ v_align='center',
+ maxwidth=100,
+ scale=0.9,
+ color=header_color,
+ flatness=1.0,
+ )
- self.entry_fee_text_top = ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 360,
- y + scly - 60),
- size=(0, 0),
- h_align='center',
- text='-',
- v_align='center',
- maxwidth=60,
- scale=1.3,
- color=value_color,
- flatness=1.0)
- self.entry_fee_text_or = ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 360,
- y + scly - 90),
- size=(0, 0),
- h_align='center',
- text='',
- v_align='center',
- maxwidth=60,
- scale=0.5,
- color=value_color,
- flatness=1.0)
- self.entry_fee_text_remaining = ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 360,
- y + scly - 90),
- size=(0, 0),
- h_align='center',
- text='',
- v_align='center',
- maxwidth=60,
- scale=0.5,
- color=value_color,
- flatness=1.0)
+ self.entry_fee_text_top = ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 360, y + scly - 60),
+ size=(0, 0),
+ h_align='center',
+ text='-',
+ v_align='center',
+ maxwidth=60,
+ scale=1.3,
+ color=value_color,
+ flatness=1.0,
+ )
+ self.entry_fee_text_or = ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 360, y + scly - 90),
+ size=(0, 0),
+ h_align='center',
+ text='',
+ v_align='center',
+ maxwidth=60,
+ scale=0.5,
+ color=value_color,
+ flatness=1.0,
+ )
+ self.entry_fee_text_remaining = ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 360, y + scly - 90),
+ size=(0, 0),
+ h_align='center',
+ text='',
+ v_align='center',
+ maxwidth=60,
+ scale=0.5,
+ color=value_color,
+ flatness=1.0,
+ )
- self.entry_fee_ad_image = ba.imagewidget(parent=parent,
- size=(40, 40),
- draw_controller=btn,
- position=(x + 360 - 20,
- y + scly - 140),
- opacity=0.0,
- texture=ba.gettexture('tv'))
+ self.entry_fee_ad_image = ba.imagewidget(
+ parent=parent,
+ size=(40, 40),
+ draw_controller=btn,
+ position=(x + 360 - 20, y + scly - 140),
+ opacity=0.0,
+ texture=ba.gettexture('tv'),
+ )
x_offs += 50
- ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 447 + x_offs, y + scly - 20),
- size=(0, 0),
- h_align='center',
- text=ba.Lstr(resource=self._r + '.prizesText'),
- v_align='center',
- maxwidth=130,
- scale=0.9,
- color=header_color,
- flatness=1.0)
+ ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 447 + x_offs, y + scly - 20),
+ size=(0, 0),
+ h_align='center',
+ text=ba.Lstr(resource=self._r + '.prizesText'),
+ v_align='center',
+ maxwidth=130,
+ scale=0.9,
+ color=header_color,
+ flatness=1.0,
+ )
self.button_x = x
self.button_y = y
@@ -176,7 +195,8 @@ class TournamentButton:
text='-',
scale=0.8,
color=header_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.prize_value_1_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -188,7 +208,8 @@ class TournamentButton:
maxwidth=100,
scale=prize_value_scale,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.prize_range_2_text = ba.textwidget(
parent=parent,
@@ -200,7 +221,8 @@ class TournamentButton:
maxwidth=50,
scale=0.8,
color=header_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.prize_value_2_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -212,7 +234,8 @@ class TournamentButton:
maxwidth=100,
scale=prize_value_scale,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.prize_range_3_text = ba.textwidget(
parent=parent,
@@ -224,7 +247,8 @@ class TournamentButton:
maxwidth=50,
scale=0.8,
color=header_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.prize_value_3_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -236,24 +260,29 @@ class TournamentButton:
maxwidth=100,
scale=prize_value_scale,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
- ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 620 + x_offs, y + scly - 20),
- size=(0, 0),
- h_align='center',
- text=ba.Lstr(resource=self._r + '.currentBestText'),
- v_align='center',
- maxwidth=180,
- scale=0.9,
- color=header_color,
- flatness=1.0)
+ ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 620 + x_offs, y + scly - 20),
+ size=(0, 0),
+ h_align='center',
+ text=ba.Lstr(resource=self._r + '.currentBestText'),
+ v_align='center',
+ maxwidth=180,
+ scale=0.9,
+ color=header_color,
+ flatness=1.0,
+ )
self.current_leader_name_text = ba.textwidget(
parent=parent,
draw_controller=btn,
- position=(x + 620 + x_offs - (170 / 1.4) * 0.5,
- y + scly - 60 - 40 * 0.5),
+ position=(
+ x + 620 + x_offs - (170 / 1.4) * 0.5,
+ y + scly - 60 - 40 * 0.5,
+ ),
selectable=True,
click_activate=True,
autoselect=True,
@@ -265,7 +294,8 @@ class TournamentButton:
maxwidth=170,
scale=1.4,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.current_leader_score_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -277,7 +307,8 @@ class TournamentButton:
maxwidth=170,
scale=1.8,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.more_scores_button = ba.buttonwidget(
parent=parent,
@@ -289,21 +320,26 @@ class TournamentButton:
autoselect=True,
up_widget=self.current_leader_name_text,
text_scale=0.6,
- on_activate_call=ba.WeakCall(self._show_scores))
- ba.widget(edit=self.current_leader_name_text,
- down_widget=self.more_scores_button)
+ on_activate_call=ba.WeakCall(self._show_scores),
+ )
+ ba.widget(
+ edit=self.current_leader_name_text,
+ down_widget=self.more_scores_button,
+ )
- ba.textwidget(parent=parent,
- draw_controller=btn,
- position=(x + 820 + x_offs, y + scly - 20),
- size=(0, 0),
- h_align='center',
- text=ba.Lstr(resource=self._r + '.timeRemainingText'),
- v_align='center',
- maxwidth=180,
- scale=0.9,
- color=header_color,
- flatness=1.0)
+ ba.textwidget(
+ parent=parent,
+ draw_controller=btn,
+ position=(x + 820 + x_offs, y + scly - 20),
+ size=(0, 0),
+ h_align='center',
+ text=ba.Lstr(resource=self._r + '.timeRemainingText'),
+ v_align='center',
+ maxwidth=180,
+ scale=0.9,
+ color=header_color,
+ flatness=1.0,
+ )
self.time_remaining_value_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -315,7 +351,8 @@ class TournamentButton:
maxwidth=180,
scale=2.0,
color=value_color,
- flatness=1.0)
+ flatness=1.0,
+ )
self.time_remaining_out_of_text = ba.textwidget(
parent=parent,
draw_controller=btn,
@@ -327,7 +364,8 @@ class TournamentButton:
maxwidth=120,
scale=0.72,
color=(0.4, 0.4, 0.5),
- flatness=1.0)
+ flatness=1.0,
+ )
def _pressed(self) -> None:
self.on_pressed(self)
@@ -335,23 +373,29 @@ class TournamentButton:
def _show_leader(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.viewer import AccountViewerWindow
+
tournament_id = self.tournament_id
# FIXME: This assumes a single player entry in leader; should expand
# this to work with multiple.
- if tournament_id is None or self.leader is None or len(
- self.leader[2]) != 1:
+ if (
+ tournament_id is None
+ or self.leader is None
+ or len(self.leader[2]) != 1
+ ):
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
AccountViewerWindow(
account_id=self.leader[2][0].get('a', None),
profile_id=self.leader[2][0].get('p', None),
- position=self.current_leader_name_text.get_screen_space_center())
+ position=self.current_leader_name_text.get_screen_space_center(),
+ )
def _show_scores(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.tournamentscores import TournamentScoresWindow
+
tournament_id = self.tournament_id
if tournament_id is None:
ba.playsound(ba.getsound('error'))
@@ -359,7 +403,8 @@ class TournamentButton:
TournamentScoresWindow(
tournament_id=tournament_id,
- position=self.more_scores_button.get_screen_space_center())
+ position=self.more_scores_button.get_screen_space_center(),
+ )
def update_for_data(self, entry: dict[str, Any]) -> None:
"""Update for new incoming data."""
@@ -367,22 +412,34 @@ class TournamentButton:
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
from ba.internal import getcampaign, get_tournament_prize_strings
- prize_y_offs = (34 if 'prizeRange3' in entry else
- 20 if 'prizeRange2' in entry else 12)
+
+ prize_y_offs = (
+ 34
+ if 'prizeRange3' in entry
+ else 20
+ if 'prizeRange2' in entry
+ else 12
+ )
x_offs = 90
# This seems to be a false alarm.
# pylint: disable=unbalanced-tuple-unpacking
- pr1, pv1, pr2, pv2, pr3, pv3 = (get_tournament_prize_strings(entry))
+ pr1, pv1, pr2, pv2, pr3, pv3 = get_tournament_prize_strings(entry)
# pylint: enable=unbalanced-tuple-unpacking
enabled = 'requiredLeague' not in entry
- ba.buttonwidget(edit=self.button,
- color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5))
+ ba.buttonwidget(
+ edit=self.button,
+ color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5),
+ )
ba.imagewidget(edit=self.lock_image, opacity=0.0 if enabled else 1.0)
- ba.textwidget(edit=self.prize_range_1_text,
- text='-' if pr1 == '' else pr1,
- position=(self.button_x + 365 + x_offs, self.button_y +
- self.button_scale_y - 93 + prize_y_offs))
+ ba.textwidget(
+ edit=self.prize_range_1_text,
+ text='-' if pr1 == '' else pr1,
+ position=(
+ self.button_x + 365 + x_offs,
+ self.button_y + self.button_scale_y - 93 + prize_y_offs,
+ ),
+ )
# We want to draw values containing tickets a bit smaller
# (scratch that; we now draw medals a bit bigger).
@@ -390,94 +447,144 @@ class TournamentButton:
prize_value_scale_large = 1.0
prize_value_scale_small = 1.0
- ba.textwidget(edit=self.prize_value_1_text,
- text='-' if pv1 == '' else pv1,
- scale=prize_value_scale_large
- if ticket_char not in pv1 else prize_value_scale_small,
- position=(self.button_x + 380 + x_offs, self.button_y +
- self.button_scale_y - 93 + prize_y_offs))
+ ba.textwidget(
+ edit=self.prize_value_1_text,
+ text='-' if pv1 == '' else pv1,
+ scale=prize_value_scale_large
+ if ticket_char not in pv1
+ else prize_value_scale_small,
+ position=(
+ self.button_x + 380 + x_offs,
+ self.button_y + self.button_scale_y - 93 + prize_y_offs,
+ ),
+ )
- ba.textwidget(edit=self.prize_range_2_text,
- text=pr2,
- position=(self.button_x + 365 + x_offs, self.button_y +
- self.button_scale_y - 93 - 45 + prize_y_offs))
- ba.textwidget(edit=self.prize_value_2_text,
- text=pv2,
- scale=prize_value_scale_large
- if ticket_char not in pv2 else prize_value_scale_small,
- position=(self.button_x + 380 + x_offs, self.button_y +
- self.button_scale_y - 93 - 45 + prize_y_offs))
+ ba.textwidget(
+ edit=self.prize_range_2_text,
+ text=pr2,
+ position=(
+ self.button_x + 365 + x_offs,
+ self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
+ ),
+ )
+ ba.textwidget(
+ edit=self.prize_value_2_text,
+ text=pv2,
+ scale=prize_value_scale_large
+ if ticket_char not in pv2
+ else prize_value_scale_small,
+ position=(
+ self.button_x + 380 + x_offs,
+ self.button_y + self.button_scale_y - 93 - 45 + prize_y_offs,
+ ),
+ )
- ba.textwidget(edit=self.prize_range_3_text,
- text=pr3,
- position=(self.button_x + 365 + x_offs, self.button_y +
- self.button_scale_y - 93 - 90 + prize_y_offs))
- ba.textwidget(edit=self.prize_value_3_text,
- text=pv3,
- scale=prize_value_scale_large
- if ticket_char not in pv3 else prize_value_scale_small,
- position=(self.button_x + 380 + x_offs, self.button_y +
- self.button_scale_y - 93 - 90 + prize_y_offs))
+ ba.textwidget(
+ edit=self.prize_range_3_text,
+ text=pr3,
+ position=(
+ self.button_x + 365 + x_offs,
+ self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
+ ),
+ )
+ ba.textwidget(
+ edit=self.prize_value_3_text,
+ text=pv3,
+ scale=prize_value_scale_large
+ if ticket_char not in pv3
+ else prize_value_scale_small,
+ position=(
+ self.button_x + 380 + x_offs,
+ self.button_y + self.button_scale_y - 93 - 90 + prize_y_offs,
+ ),
+ )
leader_name = '-'
leader_score: str | ba.Lstr = '-'
if entry['scores']:
score = self.leader = copy.deepcopy(entry['scores'][0])
leader_name = score[1]
- leader_score = (ba.timestring(
- score[0] * 10,
- centi=True,
- timeformat=ba.TimeFormat.MILLISECONDS,
- suppress_format_warning=True)
- if entry['scoreType'] == 'time' else str(score[0]))
+ leader_score = (
+ ba.timestring(
+ score[0] * 10,
+ centi=True,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ suppress_format_warning=True,
+ )
+ if entry['scoreType'] == 'time'
+ else str(score[0])
+ )
else:
self.leader = None
- ba.textwidget(edit=self.current_leader_name_text,
- text=ba.Lstr(value=leader_name))
+ ba.textwidget(
+ edit=self.current_leader_name_text, text=ba.Lstr(value=leader_name)
+ )
ba.textwidget(edit=self.current_leader_score_text, text=leader_score)
- ba.buttonwidget(edit=self.more_scores_button,
- label=ba.Lstr(resource=self._r + '.seeMoreText'))
+ ba.buttonwidget(
+ edit=self.more_scores_button,
+ label=ba.Lstr(resource=self._r + '.seeMoreText'),
+ )
out_of_time_text: str | ba.Lstr = (
- '-' if 'totalTime' not in entry else ba.Lstr(
+ '-'
+ if 'totalTime' not in entry
+ else ba.Lstr(
resource=self._r + '.ofTotalTimeText',
- subs=[('${TOTAL}',
- ba.timestring(entry['totalTime'],
- centi=False,
- suppress_format_warning=True))]))
- ba.textwidget(edit=self.time_remaining_out_of_text,
- text=out_of_time_text)
+ subs=[
+ (
+ '${TOTAL}',
+ ba.timestring(
+ entry['totalTime'],
+ centi=False,
+ suppress_format_warning=True,
+ ),
+ )
+ ],
+ )
+ )
+ ba.textwidget(
+ edit=self.time_remaining_out_of_text, text=out_of_time_text
+ )
self.time_remaining = entry['timeRemaining']
self.has_time_remaining = entry is not None
self.tournament_id = entry['tournamentID']
- self.required_league = (None if 'requiredLeague' not in entry else
- entry['requiredLeague'])
+ self.required_league = (
+ None if 'requiredLeague' not in entry else entry['requiredLeague']
+ )
game = ba.app.accounts_v1.tournament_info[self.tournament_id]['game']
if game is None:
ba.textwidget(edit=self.button_text, text='-')
- ba.imagewidget(edit=self.image,
- texture=ba.gettexture('black'),
- opacity=0.2)
+ ba.imagewidget(
+ edit=self.image, texture=ba.gettexture('black'), opacity=0.2
+ )
else:
campaignname, levelname = game.split(':')
campaign = getcampaign(campaignname)
max_players = ba.app.accounts_v1.tournament_info[
- self.tournament_id]['maxPlayers']
- txt = ba.Lstr(value='${A} ${B}',
- subs=[('${A}',
- campaign.getlevel(levelname).displayname),
- ('${B}',
- ba.Lstr(resource='playerCountAbbreviatedText',
- subs=[('${COUNT}', str(max_players))
- ]))])
+ self.tournament_id
+ ]['maxPlayers']
+ txt = ba.Lstr(
+ value='${A} ${B}',
+ subs=[
+ ('${A}', campaign.getlevel(levelname).displayname),
+ (
+ '${B}',
+ ba.Lstr(
+ resource='playerCountAbbreviatedText',
+ subs=[('${COUNT}', str(max_players))],
+ ),
+ ),
+ ],
+ )
ba.textwidget(edit=self.button_text, text=txt)
ba.imagewidget(
edit=self.image,
texture=campaign.getlevel(levelname).get_preview_texture(),
- opacity=1.0 if enabled else 0.5)
+ opacity=1.0 if enabled else 0.5,
+ )
fee = entry['fee']
@@ -498,9 +605,11 @@ class TournamentButton:
self.allow_ads = allow_ads = entry['allowAds']
- final_fee: int | None = (None if fee_var is None else
- ba.internal.get_v1_account_misc_read_val(
- fee_var, '?'))
+ final_fee: int | None = (
+ None
+ if fee_var is None
+ else ba.internal.get_v1_account_misc_read_val(fee_var, '?')
+ )
final_fee_str: str | ba.Lstr
if fee_var is None:
@@ -509,53 +618,77 @@ class TournamentButton:
if final_fee == 0:
final_fee_str = ba.Lstr(resource='getTicketsWindow.freeText')
else:
- final_fee_str = (ba.charstr(ba.SpecialChar.TICKET_BACKING) +
- str(final_fee))
+ final_fee_str = ba.charstr(ba.SpecialChar.TICKET_BACKING) + str(
+ final_fee
+ )
ad_tries_remaining = ba.app.accounts_v1.tournament_info[
- self.tournament_id]['adTriesRemaining']
+ self.tournament_id
+ ]['adTriesRemaining']
free_tries_remaining = ba.app.accounts_v1.tournament_info[
- self.tournament_id]['freeTriesRemaining']
+ self.tournament_id
+ ]['freeTriesRemaining']
# Now, if this fee allows ads and we support video ads, show
# the 'or ad' version.
if allow_ads and ba.internal.has_video_ads():
ads_enabled = ba.internal.have_incentivized_ad()
- ba.imagewidget(edit=self.entry_fee_ad_image,
- opacity=1.0 if ads_enabled else 0.25)
- or_text = ba.Lstr(resource='orText',
- subs=[('${A}', ''),
- ('${B}', '')]).evaluate().strip()
+ ba.imagewidget(
+ edit=self.entry_fee_ad_image,
+ opacity=1.0 if ads_enabled else 0.25,
+ )
+ or_text = (
+ ba.Lstr(resource='orText', subs=[('${A}', ''), ('${B}', '')])
+ .evaluate()
+ .strip()
+ )
ba.textwidget(edit=self.entry_fee_text_or, text=or_text)
- ba.textwidget(edit=self.entry_fee_text_top,
- position=(self.button_x + 360,
- self.button_y + self.button_scale_y - 60),
- scale=1.3,
- text=final_fee_str)
+ ba.textwidget(
+ edit=self.entry_fee_text_top,
+ position=(
+ self.button_x + 360,
+ self.button_y + self.button_scale_y - 60,
+ ),
+ scale=1.3,
+ text=final_fee_str,
+ )
# Possibly show number of ad-plays remaining.
- ba.textwidget(edit=self.entry_fee_text_remaining,
- position=(self.button_x + 360,
- self.button_y + self.button_scale_y - 146),
- text='' if ad_tries_remaining in [None, 0] else
- ('' + str(ad_tries_remaining)),
- color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2))
+ ba.textwidget(
+ edit=self.entry_fee_text_remaining,
+ position=(
+ self.button_x + 360,
+ self.button_y + self.button_scale_y - 146,
+ ),
+ text=''
+ if ad_tries_remaining in [None, 0]
+ else ('' + str(ad_tries_remaining)),
+ color=(0.6, 0.6, 0.6, 1 if ads_enabled else 0.2),
+ )
else:
ba.imagewidget(edit=self.entry_fee_ad_image, opacity=0.0)
ba.textwidget(edit=self.entry_fee_text_or, text='')
- ba.textwidget(edit=self.entry_fee_text_top,
- position=(self.button_x + 360,
- self.button_y + self.button_scale_y - 80),
- scale=1.3,
- text=final_fee_str)
+ ba.textwidget(
+ edit=self.entry_fee_text_top,
+ position=(
+ self.button_x + 360,
+ self.button_y + self.button_scale_y - 80,
+ ),
+ scale=1.3,
+ text=final_fee_str,
+ )
# Possibly show number of free-plays remaining.
ba.textwidget(
edit=self.entry_fee_text_remaining,
- position=(self.button_x + 360,
- self.button_y + self.button_scale_y - 100),
- text=('' if (free_tries_remaining in [None, 0]
- or final_fee != 0) else
- ('' + str(free_tries_remaining))),
+ position=(
+ self.button_x + 360,
+ self.button_y + self.button_scale_y - 100,
+ ),
+ text=(
+ ''
+ if (free_tries_remaining in [None, 0] or final_fee != 0)
+ else ('' + str(free_tries_remaining))
+ ),
color=(0.6, 0.6, 0.6, 1),
)
diff --git a/assets/src/ba_data/python/bastd/ui/creditslist.py b/assets/src/ba_data/python/bastd/ui/creditslist.py
index 7400005a..6a79582c 100644
--- a/assets/src/ba_data/python/bastd/ui/creditslist.py
+++ b/assets/src/ba_data/python/bastd/ui/creditslist.py
@@ -20,6 +20,7 @@ class CreditsListWindow(ba.Window):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
import json
+
ba.set_analytics_screen('Credits Window')
# if they provided an origin-widget, scale up from that
@@ -39,71 +40,93 @@ class CreditsListWindow(ba.Window):
height = 398 if uiscale is ba.UIScale.SMALL else 500
self._r = 'creditsWindow'
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
else:
btn = ba.buttonwidget(
parent=self._root_widget,
- position=(40 + x_inset, height -
- (68 if uiscale is ba.UIScale.SMALL else 62)),
+ position=(
+ 40 + x_inset,
+ height - (68 if uiscale is ba.UIScale.SMALL else 62),
+ ),
size=(140, 60),
scale=0.8,
label=ba.Lstr(resource='backText'),
button_type='back',
on_activate_call=self._back,
- autoselect=True)
+ autoselect=True,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
ba.buttonwidget(
edit=btn,
button_type='backSmall',
- position=(40 + x_inset, height -
- (68 if uiscale is ba.UIScale.SMALL else 62) + 5),
+ position=(
+ 40 + x_inset,
+ height - (68 if uiscale is ba.UIScale.SMALL else 62) + 5,
+ ),
size=(60, 48),
- label=ba.charstr(ba.SpecialChar.BACK))
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
- ba.textwidget(parent=self._root_widget,
- position=(0, height -
- (59 if uiscale is ba.UIScale.SMALL else 54)),
- size=(width, 30),
- text=ba.Lstr(resource=self._r + '.titleText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]),
- h_align='center',
- color=ba.app.ui.title_color,
- maxwidth=330,
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - (59 if uiscale is ba.UIScale.SMALL else 54)),
+ size=(width, 30),
+ text=ba.Lstr(
+ resource=self._r + '.titleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ h_align='center',
+ color=ba.app.ui.title_color,
+ maxwidth=330,
+ v_align='center',
+ )
- scroll = ba.scrollwidget(parent=self._root_widget,
- position=(40 + x_inset, 35),
- size=(width - (80 + 2 * x_inset),
- height - 100),
- capture_arrows=True)
+ scroll = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(40 + x_inset, 35),
+ size=(width - (80 + 2 * x_inset), height - 100),
+ capture_arrows=True,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=scroll,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if uiscale is ba.UIScale.SMALL:
ba.widget(
edit=scroll,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
def _format_names(names2: Sequence[str], inset: float) -> str:
sval = ''
# measure a series since there's overlaps and stuff..
- space_width = ba.internal.get_string_width(
- ' ' * 10, suppress_warning=True) / 10.0
+ space_width = (
+ ba.internal.get_string_width(' ' * 10, suppress_warning=True)
+ / 10.0
+ )
spacing = 330.0
col1 = inset
col2 = col1 + spacing
@@ -127,42 +150,69 @@ class CreditsListWindow(ba.Window):
nline += spacingstr
nline += name
line_width = ba.internal.get_string_width(
- nline, suppress_warning=True)
+ nline, suppress_warning=True
+ )
if nline != '':
sval += nline + '\n'
return sval
- sound_and_music = ba.Lstr(resource=self._r +
- '.songCreditText').evaluate()
+ sound_and_music = ba.Lstr(
+ resource=self._r + '.songCreditText'
+ ).evaluate()
sound_and_music = sound_and_music.replace(
- '${TITLE}', "'William Tell (Trumpet Entry)'")
+ '${TITLE}', "'William Tell (Trumpet Entry)'"
+ )
sound_and_music = sound_and_music.replace(
- '${PERFORMER}', 'The Apollo Symphony Orchestra')
+ '${PERFORMER}', 'The Apollo Symphony Orchestra'
+ )
sound_and_music = sound_and_music.replace(
- '${PERFORMER}', 'The Apollo Symphony Orchestra')
- sound_and_music = sound_and_music.replace('${COMPOSER}',
- 'Gioacchino Rossini')
+ '${PERFORMER}', 'The Apollo Symphony Orchestra'
+ )
+ sound_and_music = sound_and_music.replace(
+ '${COMPOSER}', 'Gioacchino Rossini'
+ )
sound_and_music = sound_and_music.replace('${ARRANGER}', 'Chris Worth')
sound_and_music = sound_and_music.replace('${PUBLISHER}', 'BMI')
- sound_and_music = sound_and_music.replace('${SOURCE}',
- 'www.AudioSparx.com')
+ sound_and_music = sound_and_music.replace(
+ '${SOURCE}', 'www.AudioSparx.com'
+ )
spc = ' '
sound_and_music = spc + sound_and_music.replace('\n', '\n' + spc)
names = [
- 'HubOfTheUniverseProd', 'Jovica', 'LG', 'Leady', 'Percy Duke',
- 'PhreaKsAccount', 'Pogotron', 'Rock Savage', 'anamorphosis',
- 'benboncan', 'cdrk', 'chipfork', 'guitarguy1985', 'jascha',
- 'joedeshon', 'loofa', 'm_O_m', 'mich3d', 'sandyrb', 'shakaharu',
- 'sirplus', 'stickman', 'thanvannispen', 'virotic', 'zimbot'
+ 'HubOfTheUniverseProd',
+ 'Jovica',
+ 'LG',
+ 'Leady',
+ 'Percy Duke',
+ 'PhreaKsAccount',
+ 'Pogotron',
+ 'Rock Savage',
+ 'anamorphosis',
+ 'benboncan',
+ 'cdrk',
+ 'chipfork',
+ 'guitarguy1985',
+ 'jascha',
+ 'joedeshon',
+ 'loofa',
+ 'm_O_m',
+ 'mich3d',
+ 'sandyrb',
+ 'shakaharu',
+ 'sirplus',
+ 'stickman',
+ 'thanvannispen',
+ 'virotic',
+ 'zimbot',
]
names.sort(key=lambda x: x.lower())
freesound_names = _format_names(names, 90)
try:
- with open('ba_data/data/langdata.json',
- encoding='utf-8') as infile:
- translation_contributors = (json.loads(
- infile.read())['translation_contributors'])
+ with open('ba_data/data/langdata.json', encoding='utf-8') as infile:
+ translation_contributors = json.loads(infile.read())[
+ 'translation_contributors'
+ ]
except Exception:
ba.print_exception('Error reading translation contributors.')
translation_contributors = []
@@ -174,41 +224,54 @@ class CreditsListWindow(ba.Window):
# We can remove that limit once we drop support for GL ES2.. :-/
# (or add mesh splitting under the hood)
credits_text = (
- ' ' + ba.Lstr(resource=self._r +
- '.codingGraphicsAudioText').evaluate().replace(
- '${NAME}', 'Eric Froemling') + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.codingGraphicsAudioText')
+ .evaluate()
+ .replace('${NAME}', 'Eric Froemling')
+ + '\n'
'\n'
- ' ' + ba.Lstr(resource=self._r +
- '.additionalAudioArtIdeasText').evaluate().replace(
- '${NAME}', 'Raphael Suter') + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.additionalAudioArtIdeasText')
+ .evaluate()
+ .replace('${NAME}', 'Raphael Suter')
+ + '\n'
'\n'
- ' ' +
- ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate() + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.soundAndMusicText').evaluate()
+ + '\n'
'\n' + sound_and_music + '\n'
'\n'
- ' ' + ba.Lstr(resource=self._r +
- '.publicDomainMusicViaText').evaluate().replace(
- '${NAME}', 'Musopen.com') + '\n'
- ' ' +
- ba.Lstr(resource=self._r +
- '.thanksEspeciallyToText').evaluate().replace(
- '${NAME}', 'the US Army, Navy, and Marine Bands') +
+ ' '
+ + ba.Lstr(resource=self._r + '.publicDomainMusicViaText')
+ .evaluate()
+ .replace('${NAME}', 'Musopen.com')
+ + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.thanksEspeciallyToText')
+ .evaluate()
+ .replace('${NAME}', 'the US Army, Navy, and Marine Bands')
+ + '\n'
'\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.additionalMusicFromText')
+ .evaluate()
+ .replace('${NAME}', 'The YouTube Audio Library')
+ + '\n'
'\n'
- ' ' + ba.Lstr(resource=self._r +
- '.additionalMusicFromText').evaluate().replace(
- '${NAME}', 'The YouTube Audio Library') +
- '\n'
- '\n'
- ' ' +
- ba.Lstr(resource=self._r + '.soundsText').evaluate().replace(
- '${SOURCE}', 'Freesound.org') + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.soundsText')
+ .evaluate()
+ .replace('${SOURCE}', 'Freesound.org')
+ + '\n'
'\n' + freesound_names + '\n'
'\n'
- ' ' + ba.Lstr(resource=self._r +
- '.languageTranslationsText').evaluate() + '\n'
- '\n' + '\n'.join(translation_names.splitlines()[:146]) +
- '\n'.join(translation_names.splitlines()[146:]) + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.languageTranslationsText').evaluate()
+ + '\n'
+ '\n'
+ + '\n'.join(translation_names.splitlines()[:146])
+ + '\n'.join(translation_names.splitlines()[146:])
+ + '\n'
'\n'
' Shout Out to Awesome Mods / Modders / Contributors:\n\n'
' BombDash ModPack\n'
@@ -222,24 +285,33 @@ class CreditsListWindow(ba.Window):
'\n'
' Holiday theme vector art designed by Freepik\n'
'\n'
- ' ' +
- ba.Lstr(resource=self._r + '.specialThanksText').evaluate() + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.specialThanksText').evaluate()
+ + '\n'
'\n'
' Todd, Laura, and Robert Froemling\n'
- ' ' +
- ba.Lstr(resource=self._r + '.allMyFamilyText').evaluate().replace(
- '\n', '\n ') + '\n'
- ' ' + ba.Lstr(resource=self._r +
- '.whoeverInventedCoffeeText').evaluate() + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.allMyFamilyText')
+ .evaluate()
+ .replace('\n', '\n ')
+ + '\n'
+ ' '
+ + ba.Lstr(
+ resource=self._r + '.whoeverInventedCoffeeText'
+ ).evaluate()
+ + '\n'
'\n'
' ' + ba.Lstr(resource=self._r + '.legalText').evaluate() + '\n'
'\n'
- ' ' + ba.Lstr(resource=self._r +
- '.softwareBasedOnText').evaluate().replace(
- '${NAME}', 'the Khronos Group') + '\n'
+ ' '
+ + ba.Lstr(resource=self._r + '.softwareBasedOnText')
+ .evaluate()
+ .replace('${NAME}', 'the Khronos Group')
+ + '\n'
'\n'
' '
- ' www.ballistica.net\n')
+ ' www.ballistica.net\n'
+ )
txt = credits_text
lines = txt.splitlines()
@@ -254,25 +326,31 @@ class CreditsListWindow(ba.Window):
size=(self._sub_width, self._sub_height),
background=False,
claims_left_right=False,
- claims_tab=False)
+ claims_tab=False,
+ )
voffs = 0
for line in lines:
- ba.textwidget(parent=container,
- padding=4,
- color=(0.7, 0.9, 0.7, 1.0),
- scale=scale,
- flatness=1.0,
- size=(0, 0),
- position=(0, self._sub_height - 20 + voffs),
- h_align='left',
- v_align='top',
- text=ba.Lstr(value=line))
+ ba.textwidget(
+ parent=container,
+ padding=4,
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=scale,
+ flatness=1.0,
+ size=(0, 0),
+ position=(0, self._sub_height - 20 + voffs),
+ h_align='left',
+ v_align='top',
+ text=ba.Lstr(value=line),
+ )
voffs -= line_height
def _back(self) -> None:
from bastd.ui.mainmenu import MainMenuWindow
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/debug.py b/assets/src/ba_data/python/bastd/ui/debug.py
index e8964542..888fd15f 100644
--- a/assets/src/ba_data/python/bastd/ui/debug.py
+++ b/assets/src/ba_data/python/bastd/ui/debug.py
@@ -23,8 +23,13 @@ class DebugWindow(ba.Window):
ba.app.ui.set_main_menu_location('Benchmarks & Stress Tests')
uiscale = ba.app.ui.uiscale
self._width = width = 580
- self._height = height = (350 if uiscale is ba.UIScale.SMALL else
- 420 if uiscale is ba.UIScale.MEDIUM else 520)
+ self._height = height = (
+ 350
+ if uiscale is ba.UIScale.SMALL
+ else 420
+ if uiscale is ba.UIScale.MEDIUM
+ else 520
+ )
self._scroll_width = self._width - 100
self._scroll_height = self._height - 120
@@ -39,12 +44,22 @@ class DebugWindow(ba.Window):
self._r = 'debugWindow'
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- scale=(2.35 if uiscale is ba.UIScale.SMALL else
- 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ scale=(
+ 2.35
+ if uiscale is ba.UIScale.SMALL
+ else 1.55
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -30)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._done_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -53,28 +68,33 @@ class DebugWindow(ba.Window):
scale=0.8,
autoselect=True,
label=ba.Lstr(resource='doneText'),
- on_activate_call=self._done)
+ on_activate_call=self._done,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(0, height - 60),
- size=(width, 30),
- text=ba.Lstr(resource=self._r + '.titleText'),
- h_align='center',
- color=ba.app.ui.title_color,
- v_align='center',
- maxwidth=260)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 60),
+ size=(width, 30),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ h_align='center',
+ color=ba.app.ui.title_color,
+ v_align='center',
+ maxwidth=260,
+ )
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
size=(self._scroll_width, self._scroll_height),
- position=((self._width - self._scroll_width) * 0.5, 50))
+ position=((self._width - self._scroll_width) * 0.5, 50),
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
v = self._sub_height - 70
button_width = 300
@@ -84,19 +104,21 @@ class DebugWindow(ba.Window):
size=(button_width, 60),
autoselect=True,
label=ba.Lstr(resource=self._r + '.runCPUBenchmarkText'),
- on_activate_call=self._run_cpu_benchmark_pressed)
- ba.widget(edit=btn,
- up_widget=self._done_button,
- left_widget=self._done_button)
+ on_activate_call=self._run_cpu_benchmark_pressed,
+ )
+ ba.widget(
+ edit=btn, up_widget=self._done_button, left_widget=self._done_button
+ )
v -= 60
- ba.buttonwidget(parent=self._subcontainer,
- position=((self._sub_width - button_width) * 0.5, v),
- size=(button_width, 60),
- autoselect=True,
- label=ba.Lstr(resource=self._r +
- '.runGPUBenchmarkText'),
- on_activate_call=self._run_gpu_benchmark_pressed)
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=((self._sub_width - button_width) * 0.5, v),
+ size=(button_width, 60),
+ autoselect=True,
+ label=ba.Lstr(resource=self._r + '.runGPUBenchmarkText'),
+ on_activate_call=self._run_gpu_benchmark_pressed,
+ )
v -= 60
ba.buttonwidget(
@@ -105,31 +127,35 @@ class DebugWindow(ba.Window):
size=(button_width, 60),
autoselect=True,
label=ba.Lstr(resource=self._r + '.runMediaReloadBenchmarkText'),
- on_activate_call=self._run_media_reload_benchmark_pressed)
+ on_activate_call=self._run_media_reload_benchmark_pressed,
+ )
v -= 60
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v + 22),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.stressTestTitleText'),
- maxwidth=200,
- color=ba.app.ui.heading_color,
- scale=0.85,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v + 22),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.stressTestTitleText'),
+ maxwidth=200,
+ color=ba.app.ui.heading_color,
+ scale=0.85,
+ h_align='center',
+ v_align='center',
+ )
v -= 45
x_offs = 165
- ba.textwidget(parent=self._subcontainer,
- position=(x_offs - 10, v + 22),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.stressTestPlaylistTypeText'),
- maxwidth=130,
- color=ba.app.ui.heading_color,
- scale=0.65,
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(x_offs - 10, v + 22),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.stressTestPlaylistTypeText'),
+ maxwidth=130,
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ h_align='right',
+ v_align='center',
+ )
popup.PopupMenu(
parent=self._subcontainer,
@@ -137,25 +163,29 @@ class DebugWindow(ba.Window):
width=150,
choices=['Random', 'Teams', 'Free-For-All'],
choices_display=[
- ba.Lstr(resource=a) for a in [
- 'randomText', 'playModes.teamsText',
- 'playModes.freeForAllText'
+ ba.Lstr(resource=a)
+ for a in [
+ 'randomText',
+ 'playModes.teamsText',
+ 'playModes.freeForAllText',
]
],
current_choice='Auto',
- on_value_change_call=self._stress_test_game_type_selected)
+ on_value_change_call=self._stress_test_game_type_selected,
+ )
v -= 46
- ba.textwidget(parent=self._subcontainer,
- position=(x_offs - 10, v + 22),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.stressTestPlaylistNameText'),
- maxwidth=130,
- color=ba.app.ui.heading_color,
- scale=0.65,
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(x_offs - 10, v + 22),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.stressTestPlaylistNameText'),
+ maxwidth=130,
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ h_align='right',
+ v_align='center',
+ )
self._stress_test_playlist_name_field = ba.textwidget(
parent=self._subcontainer,
@@ -166,24 +196,27 @@ class DebugWindow(ba.Window):
v_align='center',
autoselect=True,
color=(0.9, 0.9, 0.9, 1.0),
- description=ba.Lstr(resource=self._r +
- '.stressTestPlaylistDescriptionText'),
+ description=ba.Lstr(
+ resource=self._r + '.stressTestPlaylistDescriptionText'
+ ),
editable=True,
- padding=4)
+ padding=4,
+ )
v -= 29
x_sub = 60
# Player count.
- ba.textwidget(parent=self._subcontainer,
- position=(x_offs - 10, v),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.stressTestPlayerCountText'),
- color=(0.8, 0.8, 0.8, 1.0),
- h_align='right',
- v_align='center',
- scale=0.65,
- maxwidth=130)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(x_offs - 10, v),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.stressTestPlayerCountText'),
+ color=(0.8, 0.8, 0.8, 1.0),
+ h_align='right',
+ v_align='center',
+ scale=0.65,
+ maxwidth=130,
+ )
self._stress_test_player_count_text = ba.textwidget(
parent=self._subcontainer,
position=(246 - x_sub, v - 14),
@@ -193,38 +226,42 @@ class DebugWindow(ba.Window):
h_align='right',
v_align='center',
text=str(self._stress_test_player_count),
- padding=2)
- ba.buttonwidget(parent=self._subcontainer,
- position=(330 - x_sub, v - 11),
- size=(28, 28),
- label='-',
- autoselect=True,
- on_activate_call=ba.Call(
- self._stress_test_player_count_decrement),
- repeat=True,
- enable_sound=True)
- ba.buttonwidget(parent=self._subcontainer,
- position=(380 - x_sub, v - 11),
- size=(28, 28),
- label='+',
- autoselect=True,
- on_activate_call=ba.Call(
- self._stress_test_player_count_increment),
- repeat=True,
- enable_sound=True)
+ padding=2,
+ )
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(330 - x_sub, v - 11),
+ size=(28, 28),
+ label='-',
+ autoselect=True,
+ on_activate_call=ba.Call(self._stress_test_player_count_decrement),
+ repeat=True,
+ enable_sound=True,
+ )
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(380 - x_sub, v - 11),
+ size=(28, 28),
+ label='+',
+ autoselect=True,
+ on_activate_call=ba.Call(self._stress_test_player_count_increment),
+ repeat=True,
+ enable_sound=True,
+ )
v -= 42
# Round duration.
- ba.textwidget(parent=self._subcontainer,
- position=(x_offs - 10, v),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.stressTestRoundDurationText'),
- color=(0.8, 0.8, 0.8, 1.0),
- h_align='right',
- v_align='center',
- scale=0.65,
- maxwidth=130)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(x_offs - 10, v),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.stressTestRoundDurationText'),
+ color=(0.8, 0.8, 0.8, 1.0),
+ h_align='right',
+ v_align='center',
+ scale=0.65,
+ maxwidth=130,
+ )
self._stress_test_round_duration_text = ba.textwidget(
parent=self._subcontainer,
position=(246 - x_sub, v - 14),
@@ -234,25 +271,32 @@ class DebugWindow(ba.Window):
h_align='right',
v_align='center',
text=str(self._stress_test_round_duration),
- padding=2)
- ba.buttonwidget(parent=self._subcontainer,
- position=(330 - x_sub, v - 11),
- size=(28, 28),
- label='-',
- autoselect=True,
- on_activate_call=ba.Call(
- self._stress_test_round_duration_decrement),
- repeat=True,
- enable_sound=True)
- ba.buttonwidget(parent=self._subcontainer,
- position=(380 - x_sub, v - 11),
- size=(28, 28),
- label='+',
- autoselect=True,
- on_activate_call=ba.Call(
- self._stress_test_round_duration_increment),
- repeat=True,
- enable_sound=True)
+ padding=2,
+ )
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(330 - x_sub, v - 11),
+ size=(28, 28),
+ label='-',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._stress_test_round_duration_decrement
+ ),
+ repeat=True,
+ enable_sound=True,
+ )
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(380 - x_sub, v - 11),
+ size=(28, 28),
+ label='+',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._stress_test_round_duration_increment
+ ),
+ repeat=True,
+ enable_sound=True,
+ )
v -= 82
btn = ba.buttonwidget(
parent=self._subcontainer,
@@ -260,61 +304,78 @@ class DebugWindow(ba.Window):
size=(button_width, 60),
autoselect=True,
label=ba.Lstr(resource=self._r + '.runStressTestText'),
- on_activate_call=self._stress_test_pressed)
+ on_activate_call=self._stress_test_pressed,
+ )
ba.widget(btn, show_buffer_bottom=50)
def _stress_test_player_count_decrement(self) -> None:
self._stress_test_player_count = max(
- 1, self._stress_test_player_count - 1)
- ba.textwidget(edit=self._stress_test_player_count_text,
- text=str(self._stress_test_player_count))
+ 1, self._stress_test_player_count - 1
+ )
+ ba.textwidget(
+ edit=self._stress_test_player_count_text,
+ text=str(self._stress_test_player_count),
+ )
def _stress_test_player_count_increment(self) -> None:
self._stress_test_player_count = self._stress_test_player_count + 1
- ba.textwidget(edit=self._stress_test_player_count_text,
- text=str(self._stress_test_player_count))
+ ba.textwidget(
+ edit=self._stress_test_player_count_text,
+ text=str(self._stress_test_player_count),
+ )
def _stress_test_round_duration_decrement(self) -> None:
self._stress_test_round_duration = max(
- 10, self._stress_test_round_duration - 10)
- ba.textwidget(edit=self._stress_test_round_duration_text,
- text=str(self._stress_test_round_duration))
+ 10, self._stress_test_round_duration - 10
+ )
+ ba.textwidget(
+ edit=self._stress_test_round_duration_text,
+ text=str(self._stress_test_round_duration),
+ )
def _stress_test_round_duration_increment(self) -> None:
- self._stress_test_round_duration = (self._stress_test_round_duration +
- 10)
- ba.textwidget(edit=self._stress_test_round_duration_text,
- text=str(self._stress_test_round_duration))
+ self._stress_test_round_duration = self._stress_test_round_duration + 10
+ ba.textwidget(
+ edit=self._stress_test_round_duration_text,
+ text=str(self._stress_test_round_duration),
+ )
def _stress_test_game_type_selected(self, game_type: str) -> None:
self._stress_test_game_type = game_type
def _run_cpu_benchmark_pressed(self) -> None:
from ba.internal import run_cpu_benchmark
+
run_cpu_benchmark()
def _run_gpu_benchmark_pressed(self) -> None:
from ba.internal import run_gpu_benchmark
+
run_gpu_benchmark()
def _run_media_reload_benchmark_pressed(self) -> None:
from ba.internal import run_media_reload_benchmark
+
run_media_reload_benchmark()
def _stress_test_pressed(self) -> None:
from ba.internal import run_stress_test
+
run_stress_test(
playlist_type=self._stress_test_game_type,
playlist_name=cast(
- str,
- ba.textwidget(query=self._stress_test_playlist_name_field)),
+ str, ba.textwidget(query=self._stress_test_playlist_name_field)
+ ),
player_count=self._stress_test_player_count,
- round_duration=self._stress_test_round_duration)
+ round_duration=self._stress_test_round_duration,
+ )
ba.containerwidget(edit=self._root_widget, transition='out_right')
def _done(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget())
+ AdvancedSettingsWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/feedback.py b/assets/src/ba_data/python/bastd/ui/feedback.py
index c4b46654..b640d978 100644
--- a/assets/src/ba_data/python/bastd/ui/feedback.py
+++ b/assets/src/ba_data/python/bastd/ui/feedback.py
@@ -22,8 +22,10 @@ def ask_for_rating() -> ba.Widget | None:
# FIXME: should whitelist platforms we *do* want this for.
if ba.app.test_build:
return None
- if not (platform == 'mac' or (platform == 'android'
- and subplatform in ['google', 'cardboard'])):
+ if not (
+ platform == 'mac'
+ or (platform == 'android' and subplatform in ['google', 'cardboard'])
+ ):
return None
width = 700
height = 400
@@ -32,27 +34,38 @@ def ask_for_rating() -> ba.Widget | None:
dlg = ba.containerwidget(
size=(width, height),
transition='in_right',
- scale=(1.6 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0))
+ scale=(
+ 1.6
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
v = height - 50
v -= spacing
v -= 140
- ba.imagewidget(parent=dlg,
- position=(width / 2 - 100, v + 10),
- size=(200, 200),
- texture=ba.gettexture('cuteSpaz'))
- ba.textwidget(parent=dlg,
- position=(15, v - 55),
- size=(width - 30, 30),
- color=ba.app.ui.infotextcolor,
- text=ba.Lstr(resource='pleaseRateText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]),
- maxwidth=width * 0.95,
- max_height=130,
- scale=0.85,
- h_align='center',
- v_align='center')
+ ba.imagewidget(
+ parent=dlg,
+ position=(width / 2 - 100, v + 10),
+ size=(200, 200),
+ texture=ba.gettexture('cuteSpaz'),
+ )
+ ba.textwidget(
+ parent=dlg,
+ position=(15, v - 55),
+ size=(width - 30, 30),
+ color=ba.app.ui.infotextcolor,
+ text=ba.Lstr(
+ resource='pleaseRateText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ maxwidth=width * 0.95,
+ max_height=130,
+ scale=0.85,
+ h_align='center',
+ v_align='center',
+ )
def do_rating() -> None:
if platform == 'android':
@@ -67,21 +80,25 @@ def ask_for_rating() -> ba.Widget | None:
ba.open_url(url)
ba.containerwidget(edit=dlg, transition='out_left')
- ba.buttonwidget(parent=dlg,
- position=(60, 20),
- size=(200, 60),
- label=ba.Lstr(resource='wellSureText'),
- autoselect=True,
- on_activate_call=do_rating)
+ ba.buttonwidget(
+ parent=dlg,
+ position=(60, 20),
+ size=(200, 60),
+ label=ba.Lstr(resource='wellSureText'),
+ autoselect=True,
+ on_activate_call=do_rating,
+ )
def close() -> None:
ba.containerwidget(edit=dlg, transition='out_left')
- btn = ba.buttonwidget(parent=dlg,
- position=(width - 270, 20),
- size=(200, 60),
- label=ba.Lstr(resource='noThanksText'),
- autoselect=True,
- on_activate_call=close)
+ btn = ba.buttonwidget(
+ parent=dlg,
+ position=(width - 270, 20),
+ size=(200, 60),
+ label=ba.Lstr(resource='noThanksText'),
+ autoselect=True,
+ on_activate_call=close,
+ )
ba.containerwidget(edit=dlg, cancel_button=btn, selected_child=btn)
return dlg
diff --git a/assets/src/ba_data/python/bastd/ui/fileselector.py b/assets/src/ba_data/python/bastd/ui/fileselector.py
index 1f0963e4..a3876d5c 100644
--- a/assets/src/ba_data/python/bastd/ui/fileselector.py
+++ b/assets/src/ba_data/python/bastd/ui/fileselector.py
@@ -19,12 +19,14 @@ if TYPE_CHECKING:
class FileSelectorWindow(ba.Window):
"""Window for selecting files."""
- def __init__(self,
- path: str,
- callback: Callable[[str | None], Any] | None = None,
- show_base_path: bool = True,
- valid_file_extensions: Sequence[str] | None = None,
- allow_folders: bool = False):
+ def __init__(
+ self,
+ path: str,
+ callback: Callable[[str | None], Any] | None = None,
+ show_base_path: bool = True,
+ valid_file_extensions: Sequence[str] | None = None,
+ allow_folders: bool = False,
+ ):
if valid_file_extensions is None:
valid_file_extensions = []
uiscale = ba.app.ui.uiscale
@@ -45,12 +47,22 @@ class FileSelectorWindow(ba.Window):
self._scroll_width = self._width - (80 + 2 * x_inset)
self._scroll_height = self._height - 170
self._r = 'fileSelectorWindow'
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_right',
- scale=(2.23 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -35) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_right',
+ scale=(
+ 2.23
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -35)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 42),
@@ -58,12 +70,13 @@ class FileSelectorWindow(ba.Window):
color=ba.app.ui.title_color,
h_align='center',
v_align='center',
- text=ba.Lstr(resource=self._r + '.titleFolderText') if
- (allow_folders and not valid_file_extensions) else ba.Lstr(
- resource=self._r +
- '.titleFileText') if not allow_folders else ba.Lstr(
- resource=self._r + '.titleFileFolderText'),
- maxwidth=210)
+ text=ba.Lstr(resource=self._r + '.titleFolderText')
+ if (allow_folders and not valid_file_extensions)
+ else ba.Lstr(resource=self._r + '.titleFileText')
+ if not allow_folders
+ else ba.Lstr(resource=self._r + '.titleFileFolderText'),
+ maxwidth=210,
+ )
self._button_width = 146
self._cancel_button = ba.buttonwidget(
@@ -72,7 +85,8 @@ class FileSelectorWindow(ba.Window):
autoselect=True,
size=(self._button_width, 50),
label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._cancel)
+ on_activate_call=self._cancel,
+ )
ba.widget(edit=self._cancel_button, left_widget=self._cancel_button)
b_color = (0.6, 0.53, 0.63)
@@ -86,7 +100,8 @@ class FileSelectorWindow(ba.Window):
enable_sound=False,
size=(55, 35),
label=ba.charstr(ba.SpecialChar.LEFT_ARROW),
- on_activate_call=self._on_back_press)
+ on_activate_call=self._on_back_press,
+ )
self._folder_tex = ba.gettexture('folder')
self._folder_color = (1.1, 0.8, 0.2)
@@ -94,23 +109,27 @@ class FileSelectorWindow(ba.Window):
self._file_color = (1, 1, 1)
self._use_folder_button: ba.Widget | None = None
self._folder_center = self._width * 0.5 + 15
- self._folder_icon = ba.imagewidget(parent=self._root_widget,
- size=(40, 40),
- position=(40, self._height - 117),
- texture=self._folder_tex,
- color=self._folder_color)
- self._path_text = ba.textwidget(parent=self._root_widget,
- position=(self._folder_center,
- self._height - 98),
- size=(0, 0),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- text=self._path,
- maxwidth=self._width * 0.9)
+ self._folder_icon = ba.imagewidget(
+ parent=self._root_widget,
+ size=(40, 40),
+ position=(40, self._height - 117),
+ texture=self._folder_tex,
+ color=self._folder_color,
+ )
+ self._path_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._folder_center, self._height - 98),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ text=self._path,
+ maxwidth=self._width * 0.9,
+ )
self._scrollwidget: ba.Widget | None = None
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
self._set_path(path)
def _on_up_press(self) -> None:
@@ -154,26 +173,32 @@ class FileSelectorWindow(ba.Window):
elif os.path.isfile(test_path):
if self._is_valid_file_path(test_path):
ba.playsound(ba.getsound('swish'))
- ba.containerwidget(edit=self._root_widget,
- transition='out_right')
+ ba.containerwidget(
+ edit=self._root_widget, transition='out_right'
+ )
if self._callback is not None:
self._callback(test_path)
else:
ba.playsound(ba.getsound('error'))
else:
- print(('Error: FileSelectorWindow found non-file/dir:',
- test_path))
+ print(
+ (
+ 'Error: FileSelectorWindow found non-file/dir:',
+ test_path,
+ )
+ )
except Exception:
ba.print_exception(
- 'Error in FileSelectorWindow._on_entry_activated().')
+ 'Error in FileSelectorWindow._on_entry_activated().'
+ )
if new_path is not None:
self._set_path(new_path)
class _RefreshThread(threading.Thread):
-
- def __init__(self, path: str,
- callback: Callable[[list[str], str | None], Any]):
+ def __init__(
+ self, path: str, callback: Callable[[list[str], str | None], Any]
+ ):
super().__init__()
self._callback = callback
self._path = path
@@ -189,15 +214,18 @@ class FileSelectorWindow(ba.Window):
# has time to see the selection highlight.
if duration < min_time:
time.sleep(min_time - duration)
- ba.pushcall(ba.Call(self._callback, files, None),
- from_other_thread=True)
+ ba.pushcall(
+ ba.Call(self._callback, files, None), from_other_thread=True
+ )
except Exception as exc:
# Ignore permission-denied.
if 'Errno 13' not in str(exc):
ba.print_exception()
nofiles: list[str] = []
- ba.pushcall(ba.Call(self._callback, nofiles, str(exc)),
- from_other_thread=True)
+ ba.pushcall(
+ ba.Call(self._callback, nofiles, str(exc)),
+ from_other_thread=True,
+ )
def _set_path(self, path: str, add_to_recent: bool = True) -> None:
self._path = path
@@ -212,11 +240,12 @@ class FileSelectorWindow(ba.Window):
if not self._root_widget:
return
- scrollwidget_selected = (self._scrollwidget is None
- or self._root_widget.get_selected_child()
- == self._scrollwidget)
+ scrollwidget_selected = (
+ self._scrollwidget is None
+ or self._root_widget.get_selected_child() == self._scrollwidget
+ )
- in_top_folder = (self._path == self._base_path)
+ in_top_folder = self._path == self._base_path
hide_top_folder = in_top_folder and self._show_base_path is False
if hide_top_folder:
@@ -231,25 +260,34 @@ class FileSelectorWindow(ba.Window):
b_color_disabled = (0.65, 0.65, 0.65)
if len(self._recent_paths) < 2:
- ba.buttonwidget(edit=self._back_button,
- color=b_color_disabled,
- textcolor=(0.5, 0.5, 0.5))
+ ba.buttonwidget(
+ edit=self._back_button,
+ color=b_color_disabled,
+ textcolor=(0.5, 0.5, 0.5),
+ )
else:
- ba.buttonwidget(edit=self._back_button,
- color=b_color,
- textcolor=(0.75, 0.7, 0.8))
+ ba.buttonwidget(
+ edit=self._back_button,
+ color=b_color,
+ textcolor=(0.75, 0.7, 0.8),
+ )
max_str_width = 300.0
str_width = min(
max_str_width,
- ba.internal.get_string_width(folder_name, suppress_warning=True))
- ba.textwidget(edit=self._path_text,
- text=folder_name,
- maxwidth=max_str_width)
- ba.imagewidget(edit=self._folder_icon,
- position=(self._folder_center - str_width * 0.5 - 40,
- self._height - 117),
- opacity=0.0 if hide_top_folder else 1.0)
+ ba.internal.get_string_width(folder_name, suppress_warning=True),
+ )
+ ba.textwidget(
+ edit=self._path_text, text=folder_name, maxwidth=max_str_width
+ )
+ ba.imagewidget(
+ edit=self._folder_icon,
+ position=(
+ self._folder_center - str_width * 0.5 - 40,
+ self._height - 117,
+ ),
+ opacity=0.0 if hide_top_folder else 1.0,
+ )
if self._scrollwidget is not None:
self._scrollwidget.delete()
@@ -260,29 +298,38 @@ class FileSelectorWindow(ba.Window):
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - self._scroll_height - 119),
- size=(self._scroll_width, self._scroll_height))
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - self._scroll_height - 119,
+ ),
+ size=(self._scroll_width, self._scroll_height),
+ )
if scrollwidget_selected:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
# show error case..
if error is not None:
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._scroll_width,
- self._scroll_height),
- background=False)
- ba.textwidget(parent=self._subcontainer,
- color=(1, 1, 0, 1),
- text=error,
- maxwidth=self._scroll_width * 0.9,
- position=(self._scroll_width * 0.48,
- self._scroll_height * 0.57),
- size=(0, 0),
- h_align='center',
- v_align='center')
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._scroll_width, self._scroll_height),
+ background=False,
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ color=(1, 1, 0, 1),
+ text=error,
+ maxwidth=self._scroll_width * 0.9,
+ position=(
+ self._scroll_width * 0.48,
+ self._scroll_height * 0.57,
+ ),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
else:
file_names = [f for f in file_names if not f.startswith('.')]
@@ -292,41 +339,53 @@ class FileSelectorWindow(ba.Window):
entry_height = 35
folder_entry_height = 100
show_folder_entry = False
- show_use_folder_button = (self._allow_folders
- and not in_top_folder)
+ show_use_folder_button = self._allow_folders and not in_top_folder
self._subcontainerheight = entry_height * len(entries) + (
- folder_entry_height if show_folder_entry else 0)
- v = self._subcontainerheight - (folder_entry_height
- if show_folder_entry else 0)
+ folder_entry_height if show_folder_entry else 0
+ )
+ v = self._subcontainerheight - (
+ folder_entry_height if show_folder_entry else 0
+ )
self._subcontainer = ba.containerwidget(
parent=self._scrollwidget,
size=(self._scroll_width, self._subcontainerheight),
- background=False)
+ background=False,
+ )
- ba.containerwidget(edit=self._scrollwidget,
- claims_left_right=False,
- claims_tab=False)
- ba.containerwidget(edit=self._subcontainer,
- claims_left_right=False,
- claims_tab=False,
- selection_loops=False,
- print_list_exit_instructions=False)
+ ba.containerwidget(
+ edit=self._scrollwidget,
+ claims_left_right=False,
+ claims_tab=False,
+ )
+ ba.containerwidget(
+ edit=self._subcontainer,
+ claims_left_right=False,
+ claims_tab=False,
+ selection_loops=False,
+ print_list_exit_instructions=False,
+ )
ba.widget(edit=self._subcontainer, up_widget=self._back_button)
if show_use_folder_button:
self._use_folder_button = btn = ba.buttonwidget(
parent=self._root_widget,
- position=(self._width - self._button_width - 35 -
- self._x_inset, self._height - 67),
+ position=(
+ self._width - self._button_width - 35 - self._x_inset,
+ self._height - 67,
+ ),
size=(self._button_width, 50),
- label=ba.Lstr(resource=self._r +
- '.useThisFolderButtonText'),
- on_activate_call=self._on_folder_entry_activated)
- ba.widget(edit=btn,
- left_widget=self._cancel_button,
- down_widget=self._scrollwidget)
+ label=ba.Lstr(
+ resource=self._r + '.useThisFolderButtonText'
+ ),
+ on_activate_call=self._on_folder_entry_activated,
+ )
+ ba.widget(
+ edit=btn,
+ left_widget=self._cancel_button,
+ down_widget=self._scrollwidget,
+ )
ba.widget(edit=self._cancel_button, right_widget=btn)
ba.containerwidget(edit=self._root_widget, start_button=btn)
@@ -339,46 +398,57 @@ class FileSelectorWindow(ba.Window):
root_selectable=True,
background=False,
click_activate=True,
- on_activate_call=ba.Call(self._on_entry_activated, entry))
+ on_activate_call=ba.Call(self._on_entry_activated, entry),
+ )
if num == 0:
ba.widget(edit=cnt, up_widget=self._back_button)
is_valid_file_path = self._is_valid_file_path(entry)
assert self._path is not None
is_dir = os.path.isdir(self._path + '/' + entry)
if is_dir:
- ba.imagewidget(parent=cnt,
- size=(folder_icon_size, folder_icon_size),
- position=(10, 0.5 * entry_height -
- folder_icon_size * 0.5),
- draw_controller=cnt,
- texture=self._folder_tex,
- color=self._folder_color)
+ ba.imagewidget(
+ parent=cnt,
+ size=(folder_icon_size, folder_icon_size),
+ position=(
+ 10,
+ 0.5 * entry_height - folder_icon_size * 0.5,
+ ),
+ draw_controller=cnt,
+ texture=self._folder_tex,
+ color=self._folder_color,
+ )
else:
- ba.imagewidget(parent=cnt,
- size=(folder_icon_size, folder_icon_size),
- position=(10, 0.5 * entry_height -
- folder_icon_size * 0.5),
- opacity=1.0 if is_valid_file_path else 0.5,
- draw_controller=cnt,
- texture=self._file_tex,
- color=self._file_color)
- ba.textwidget(parent=cnt,
- draw_controller=cnt,
- text=entry,
- h_align='left',
- v_align='center',
- position=(10 + folder_icon_size * 1.05,
- entry_height * 0.5),
- size=(0, 0),
- maxwidth=self._scroll_width * 0.93 - 50,
- color=(1, 1, 1, 1) if
- (is_valid_file_path or is_dir) else
- (0.5, 0.5, 0.5, 1))
+ ba.imagewidget(
+ parent=cnt,
+ size=(folder_icon_size, folder_icon_size),
+ position=(
+ 10,
+ 0.5 * entry_height - folder_icon_size * 0.5,
+ ),
+ opacity=1.0 if is_valid_file_path else 0.5,
+ draw_controller=cnt,
+ texture=self._file_tex,
+ color=self._file_color,
+ )
+ ba.textwidget(
+ parent=cnt,
+ draw_controller=cnt,
+ text=entry,
+ h_align='left',
+ v_align='center',
+ position=(10 + folder_icon_size * 1.05, entry_height * 0.5),
+ size=(0, 0),
+ maxwidth=self._scroll_width * 0.93 - 50,
+ color=(1, 1, 1, 1)
+ if (is_valid_file_path or is_dir)
+ else (0.5, 0.5, 0.5, 1),
+ )
v -= entry_height
def _is_valid_file_path(self, path: str) -> bool:
- return any(path.lower().endswith(ext)
- for ext in self._valid_file_extensions)
+ return any(
+ path.lower().endswith(ext) for ext in self._valid_file_extensions
+ )
def _cancel(self) -> None:
ba.containerwidget(edit=self._root_widget, transition='out_right')
diff --git a/assets/src/ba_data/python/bastd/ui/gather/__init__.py b/assets/src/ba_data/python/bastd/ui/gather/__init__.py
index 70513930..0b3ed076 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/__init__.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/__init__.py
@@ -60,15 +60,18 @@ class GatherWindow(ba.Window):
class TabID(Enum):
"""Our available tab types."""
+
ABOUT = 'about'
INTERNET = 'internet'
PRIVATE = 'private'
NEARBY = 'nearby'
MANUAL = 'manual'
- def __init__(self,
- transition: str | None = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
@@ -92,25 +95,42 @@ class GatherWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
x_offs = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (582 if uiscale is ba.UIScale.SMALL else
- 680 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 582
+ if uiscale is ba.UIScale.SMALL
+ else 680
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
self._current_tab: GatherWindow.TabID | None = None
extra_top = 20 if uiscale is ba.UIScale.SMALL else 0
self._r = 'gatherWindow'
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(1.3 if uiscale is ba.UIScale.SMALL else
- 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8),
- stack_offset=(0, -11) if uiscale is ba.UIScale.SMALL else (
- 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + extra_top),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.3
+ if uiscale is ba.UIScale.SMALL
+ else 0.97
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ stack_offset=(0, -11)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ )
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
self._back_button = None
else:
self._back_button = btn = ba.buttonwidget(
@@ -121,56 +141,72 @@ class GatherWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- position=(70 + x_offs, self._height - 78),
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ position=(70 + x_offs, self._height - 78),
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
condensed = uiscale is not ba.UIScale.LARGE
- t_offs_y = (0 if not condensed else
- 25 if uiscale is ba.UIScale.MEDIUM else 17)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- self._height - 42 + t_offs_y),
- size=(0, 0),
- color=ba.app.ui.title_color,
- scale=(1.5 if not condensed else
- 1.0 if uiscale is ba.UIScale.MEDIUM else 0.6),
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=550)
+ t_offs_y = (
+ 0 if not condensed else 25 if uiscale is ba.UIScale.MEDIUM else 17
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 42 + t_offs_y),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ scale=(
+ 1.5
+ if not condensed
+ else 1.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.6
+ ),
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=550,
+ )
scroll_buffer_h = 130 + 2 * x_offs
- tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs)
+ tab_buffer_h = (320 if condensed else 250) + 2 * x_offs
# Build up the set of tabs we want.
tabdefs: list[tuple[GatherWindow.TabID, ba.Lstr]] = [
(self.TabID.ABOUT, ba.Lstr(resource=self._r + '.aboutText'))
]
- if ba.internal.get_v1_account_misc_read_val('enablePublicParties',
- True):
- tabdefs.append((self.TabID.INTERNET,
- ba.Lstr(resource=self._r + '.publicText')))
+ if ba.internal.get_v1_account_misc_read_val(
+ 'enablePublicParties', True
+ ):
+ tabdefs.append(
+ (self.TabID.INTERNET, ba.Lstr(resource=self._r + '.publicText'))
+ )
tabdefs.append(
- (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')))
+ (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText'))
+ )
tabdefs.append(
- (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')))
+ (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText'))
+ )
tabdefs.append(
- (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')))
+ (self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText'))
+ )
# On small UI, push our tabs up closer to the top of the screen to
# save a bit of space.
tabs_top_extra = 42 if condensed else 0
- self._tab_row = TabRow(self._root_widget,
- tabdefs,
- pos=(tab_buffer_h * 0.5,
- self._height - 130 + tabs_top_extra),
- size=(self._width - tab_buffer_h, 50),
- on_select_call=ba.WeakCall(self._set_tab))
+ self._tab_row = TabRow(
+ self._root_widget,
+ tabdefs,
+ pos=(tab_buffer_h * 0.5, self._height - 130 + tabs_top_extra),
+ size=(self._width - tab_buffer_h, 50),
+ on_select_call=ba.WeakCall(self._set_tab),
+ )
# Now instantiate handlers for these tabs.
tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
@@ -178,7 +214,7 @@ class GatherWindow(ba.Window):
self.TabID.MANUAL: ManualGatherTab,
self.TabID.PRIVATE: PrivateGatherTab,
self.TabID.INTERNET: PublicGatherTab,
- self.TabID.NEARBY: NearbyGatherTab
+ self.TabID.NEARBY: NearbyGatherTab,
}
self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
for tab_id in self._tab_row.tabs:
@@ -189,29 +225,38 @@ class GatherWindow(ba.Window):
if ba.app.ui.use_toolbars:
ba.widget(
edit=self._tab_row.tabs[tabdefs[-1][0]].button,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if uiscale is ba.UIScale.SMALL:
ba.widget(
edit=self._tab_row.tabs[tabdefs[0][0]].button,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180.0 + tabs_top_extra
self._scroll_left = (self._width - self._scroll_width) * 0.5
- self._scroll_bottom = (self._height - self._scroll_height - 79 - 48 +
- tabs_top_extra)
+ self._scroll_bottom = (
+ self._height - self._scroll_height - 79 - 48 + tabs_top_extra
+ )
buffer_h = 10
buffer_v = 4
# Not actually using a scroll widget anymore; just an image.
- ba.imagewidget(parent=self._root_widget,
- position=(self._scroll_left - buffer_h,
- self._scroll_bottom - buffer_v),
- size=(self._scroll_width + 2 * buffer_h,
- self._scroll_height + 2 * buffer_v),
- texture=ba.gettexture('scrollWidget'),
- model_transparent=ba.getmodel('softEdgeOutside'))
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ self._scroll_left - buffer_h,
+ self._scroll_bottom - buffer_v,
+ ),
+ size=(
+ self._scroll_width + 2 * buffer_h,
+ self._scroll_height + 2 * buffer_v,
+ ),
+ texture=ba.gettexture('scrollWidget'),
+ model_transparent=ba.getmodel('softEdgeOutside'),
+ )
self._tab_container: ba.Widget | None = None
self._restore_state()
@@ -222,11 +267,13 @@ class GatherWindow(ba.Window):
def playlist_select(self, origin_widget: ba.Widget) -> None:
"""Called by the private-hosting tab to select a playlist."""
from bastd.ui.play import PlayWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.selecting_private_party_playlist = True
ba.app.ui.set_main_menu_window(
- PlayWindow(origin_widget=origin_widget).get_root_widget())
+ PlayWindow(origin_widget=origin_widget).get_root_widget()
+ )
def _set_tab(self, tab_id: TabID) -> None:
if self._current_tab is tab_id:
@@ -270,7 +317,8 @@ class GatherWindow(ba.Window):
sel = self._root_widget.get_selected_child()
selected_tab_ids = [
- tab_id for tab_id, tab in self._tab_row.tabs.items()
+ tab_id
+ for tab_id, tab in self._tab_row.tabs.items()
if sel == tab.button
]
if sel == self._back_button:
@@ -290,6 +338,7 @@ class GatherWindow(ba.Window):
def _restore_state(self) -> None:
from efro.util import enum_by_value
+
try:
for tab in self._tabs.values():
tab.restore_state()
@@ -313,8 +362,9 @@ class GatherWindow(ba.Window):
sel = self._tab_container
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(self.TabID,
- sel_name.split(':')[-1])
+ sel_tab_id = enum_by_value(
+ self.TabID, sel_name.split(':')[-1]
+ )
except ValueError:
sel_tab_id = self.TabID.ABOUT
sel = self._tab_row.tabs[sel_tab_id].button
@@ -326,8 +376,11 @@ class GatherWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/gather/abouttab.py b/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
index 23dfccec..848032aa 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
@@ -30,12 +30,17 @@ class AboutGatherTab(GatherTab):
region_left: float,
region_bottom: float,
) -> ba.Widget:
- party_button_label = ('X' if ba.app.iircade_mode else ba.charstr(
- ba.SpecialChar.TOP_BUTTON))
+ party_button_label = (
+ 'X'
+ if ba.app.iircade_mode
+ else ba.charstr(ba.SpecialChar.TOP_BUTTON)
+ )
message = ba.Lstr(
resource='gatherWindow.aboutDescriptionText',
- subs=[('${PARTY}', ba.charstr(ba.SpecialChar.PARTY_ICON)),
- ('${BUTTON}', party_button_label)],
+ subs=[
+ ('${PARTY}', ba.charstr(ba.SpecialChar.PARTY_ICON)),
+ ('${BUTTON}', party_button_label),
+ ],
)
# Let's not talk about sharing in vr-mode; its tricky to fit more
@@ -43,69 +48,90 @@ class AboutGatherTab(GatherTab):
if not ba.app.vr_mode:
message = ba.Lstr(
value='${A}\n\n${B}',
- subs=[('${A}', message),
- ('${B}',
- ba.Lstr(resource='gatherWindow.'
- 'aboutDescriptionLocalMultiplayerExtraText'))])
+ subs=[
+ ('${A}', message),
+ (
+ '${B}',
+ ba.Lstr(
+ resource='gatherWindow.'
+ 'aboutDescriptionLocalMultiplayerExtraText'
+ ),
+ ),
+ ],
+ )
string_height = 400
include_invite = True
msc_scale = 1.1
c_height_2 = min(region_height, string_height * msc_scale + 100)
try_tickets = ba.internal.get_v1_account_misc_read_val(
- 'friendTryTickets', None)
+ 'friendTryTickets', None
+ )
if try_tickets is None:
include_invite = False
self._container = ba.containerwidget(
parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - c_height_2) * 0.5),
+ position=(
+ region_left,
+ region_bottom + (region_height - c_height_2) * 0.5,
+ ),
size=(region_width, c_height_2),
background=False,
- selectable=include_invite)
+ selectable=include_invite,
+ )
ba.widget(edit=self._container, up_widget=tab_button)
- ba.textwidget(parent=self._container,
- position=(region_width * 0.5, c_height_2 *
- (0.58 if include_invite else 0.5)),
- color=(0.6, 1.0, 0.6),
- scale=msc_scale,
- size=(0, 0),
- maxwidth=region_width * 0.9,
- max_height=c_height_2 * (0.7 if include_invite else 0.9),
- h_align='center',
- v_align='center',
- text=message)
+ ba.textwidget(
+ parent=self._container,
+ position=(
+ region_width * 0.5,
+ c_height_2 * (0.58 if include_invite else 0.5),
+ ),
+ color=(0.6, 1.0, 0.6),
+ scale=msc_scale,
+ size=(0, 0),
+ maxwidth=region_width * 0.9,
+ max_height=c_height_2 * (0.7 if include_invite else 0.9),
+ h_align='center',
+ v_align='center',
+ text=message,
+ )
if include_invite:
- ba.textwidget(parent=self._container,
- position=(region_width * 0.57, 35),
- color=(0, 1, 0),
- scale=0.6,
- size=(0, 0),
- maxwidth=region_width * 0.5,
- h_align='right',
- v_align='center',
- flatness=1.0,
- text=ba.Lstr(
- resource='gatherWindow.inviteAFriendText',
- subs=[('${COUNT}', str(try_tickets))]))
+ ba.textwidget(
+ parent=self._container,
+ position=(region_width * 0.57, 35),
+ color=(0, 1, 0),
+ scale=0.6,
+ size=(0, 0),
+ maxwidth=region_width * 0.5,
+ h_align='right',
+ v_align='center',
+ flatness=1.0,
+ text=ba.Lstr(
+ resource='gatherWindow.inviteAFriendText',
+ subs=[('${COUNT}', str(try_tickets))],
+ ),
+ )
ba.buttonwidget(
parent=self._container,
position=(region_width * 0.59, 10),
size=(230, 50),
color=(0.54, 0.42, 0.56),
textcolor=(0, 1, 0),
- label=ba.Lstr(resource='gatherWindow.inviteFriendsText',
- fallback_resource=(
- 'gatherWindow.getFriendInviteCodeText')),
+ label=ba.Lstr(
+ resource='gatherWindow.inviteFriendsText',
+ fallback_resource='gatherWindow.getFriendInviteCodeText',
+ ),
autoselect=True,
on_activate_call=ba.WeakCall(self._invite_to_try_press),
- up_widget=tab_button)
+ up_widget=tab_button,
+ )
return self._container
def _invite_to_try_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.appinvite import handle_app_invites_press
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
diff --git a/assets/src/ba_data/python/bastd/ui/gather/manualtab.py b/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
index 0fa1a7b6..960e6eef 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
@@ -1,6 +1,7 @@
# Released under the MIT License. See LICENSE for details.
#
"""Defines the manual tab in the gather UI."""
+# pylint: disable=too-many-lines
from __future__ import annotations
@@ -19,20 +20,21 @@ if TYPE_CHECKING:
from bastd.ui.gather import GatherWindow
-def _safe_set_text(txt: ba.Widget | None,
- val: str | ba.Lstr,
- success: bool = True) -> None:
+def _safe_set_text(
+ txt: ba.Widget | None, val: str | ba.Lstr, success: bool = True
+) -> None:
if txt:
- ba.textwidget(edit=txt,
- text=val,
- color=(0, 1, 0) if success else (1, 1, 0))
+ ba.textwidget(
+ edit=txt, text=val, color=(0, 1, 0) if success else (1, 1, 0)
+ )
class _HostLookupThread(threading.Thread):
"""Thread to fetch an addr."""
- def __init__(self, name: str, port: int, call: Callable[[str | None, int],
- Any]):
+ def __init__(
+ self, name: str, port: int, call: Callable[[str | None, int], Any]
+ ):
super().__init__()
self._name = name
self._port = port
@@ -42,15 +44,18 @@ class _HostLookupThread(threading.Thread):
result: str | None
try:
import socket
+
result = socket.gethostbyname(self._name)
except Exception:
result = None
- ba.pushcall(lambda: self._call(result, self._port),
- from_other_thread=True)
+ ba.pushcall(
+ lambda: self._call(result, self._port), from_other_thread=True
+ )
class SubTabType(Enum):
"""Available sub-tabs."""
+
JOIN_BY_ADDRESS = 'join_by_address'
FAVORITES = 'favorites'
@@ -58,6 +63,7 @@ class SubTabType(Enum):
@dataclass
class State:
"""State saved/restored only while the app is running."""
+
sub_tab: SubTabType = SubTabType.JOIN_BY_ADDRESS
@@ -107,11 +113,14 @@ class ManualGatherTab(GatherTab):
self._container = ba.containerwidget(
parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - c_height) * 0.5),
+ position=(
+ region_left,
+ region_bottom + (region_height - c_height) * 0.5,
+ ),
size=(c_width, c_height),
background=False,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
v = c_height - 30
self._join_by_address_text = ba.textwidget(
parent=self._container,
@@ -131,7 +140,8 @@ class ManualGatherTab(GatherTab):
region_height,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.manualJoinSectionText'))
+ text=ba.Lstr(resource='gatherWindow.manualJoinSectionText'),
+ )
self._favorites_text = ba.textwidget(
parent=self._container,
position=(c_width * 0.5 + 45, v - 13),
@@ -150,14 +160,18 @@ class ManualGatherTab(GatherTab):
region_height,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.favoritesText'))
+ text=ba.Lstr(resource='gatherWindow.favoritesText'),
+ )
ba.widget(edit=self._join_by_address_text, up_widget=tab_button)
- ba.widget(edit=self._favorites_text,
- left_widget=self._join_by_address_text,
- up_widget=tab_button)
+ ba.widget(
+ edit=self._favorites_text,
+ left_widget=self._join_by_address_text,
+ up_widget=tab_button,
+ )
ba.widget(edit=tab_button, down_widget=self._favorites_text)
- ba.widget(edit=self._join_by_address_text,
- right_widget=self._favorites_text)
+ ba.widget(
+ edit=self._join_by_address_text, right_widget=self._favorites_text
+ )
self._set_sub_tab(self._sub_tab, region_width, region_height)
return self._container
@@ -172,11 +186,13 @@ class ManualGatherTab(GatherTab):
assert isinstance(state, State)
self._sub_tab = state.sub_tab
- def _set_sub_tab(self,
- value: SubTabType,
- region_width: float,
- region_height: float,
- playsound: bool = False) -> None:
+ def _set_sub_tab(
+ self,
+ value: SubTabType,
+ region_width: float,
+ region_height: float,
+ playsound: bool = False,
+ ) -> None:
assert self._container
if playsound:
ba.playsound(ba.getsound('click01'))
@@ -184,17 +200,24 @@ class ManualGatherTab(GatherTab):
self._sub_tab = value
active_color = (0.6, 1.0, 0.6)
inactive_color = (0.5, 0.4, 0.5)
- ba.textwidget(edit=self._join_by_address_text,
- color=active_color if value is SubTabType.JOIN_BY_ADDRESS
- else inactive_color)
- ba.textwidget(edit=self._favorites_text,
- color=active_color
- if value is SubTabType.FAVORITES else inactive_color)
+ ba.textwidget(
+ edit=self._join_by_address_text,
+ color=active_color
+ if value is SubTabType.JOIN_BY_ADDRESS
+ else inactive_color,
+ )
+ ba.textwidget(
+ edit=self._favorites_text,
+ color=active_color
+ if value is SubTabType.FAVORITES
+ else inactive_color,
+ )
# Clear anything existing in the old sub-tab.
for widget in self._container.get_children():
if widget and widget not in {
- self._favorites_text, self._join_by_address_text
+ self._favorites_text,
+ self._join_by_address_text,
}:
widget.delete()
@@ -205,74 +228,80 @@ class ManualGatherTab(GatherTab):
self._build_favorites_tab(region_height)
# The old manual tab
- def _build_join_by_address_tab(self, region_width: float,
- region_height: float) -> None:
+ def _build_join_by_address_tab(
+ self, region_width: float, region_height: float
+ ) -> None:
c_width = region_width
c_height = region_height - 20
last_addr = ba.app.config.get('Last Manual Party Connect Address', '')
v = c_height - 70
v -= 70
- ba.textwidget(parent=self._container,
- position=(c_width * 0.5 - 260 - 50, v),
- color=(0.6, 1.0, 0.6),
- scale=1.0,
- size=(0, 0),
- maxwidth=130,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'manualAddressText'))
- txt = ba.textwidget(parent=self._container,
- editable=True,
- description=ba.Lstr(resource='gatherWindow.'
- 'manualAddressText'),
- position=(c_width * 0.5 - 240 - 50, v - 30),
- text=last_addr,
- autoselect=True,
- v_align='center',
- scale=1.0,
- size=(420, 60))
+ ba.textwidget(
+ parent=self._container,
+ position=(c_width * 0.5 - 260 - 50, v),
+ color=(0.6, 1.0, 0.6),
+ scale=1.0,
+ size=(0, 0),
+ maxwidth=130,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'manualAddressText'),
+ )
+ txt = ba.textwidget(
+ parent=self._container,
+ editable=True,
+ description=ba.Lstr(resource='gatherWindow.' 'manualAddressText'),
+ position=(c_width * 0.5 - 240 - 50, v - 30),
+ text=last_addr,
+ autoselect=True,
+ v_align='center',
+ scale=1.0,
+ size=(420, 60),
+ )
ba.widget(edit=self._join_by_address_text, down_widget=txt)
ba.widget(edit=self._favorites_text, down_widget=txt)
- ba.textwidget(parent=self._container,
- position=(c_width * 0.5 - 260 + 490, v),
- color=(0.6, 1.0, 0.6),
- scale=1.0,
- size=(0, 0),
- maxwidth=80,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'portText'))
- txt2 = ba.textwidget(parent=self._container,
- editable=True,
- description=ba.Lstr(resource='gatherWindow.'
- 'portText'),
- text='43210',
- autoselect=True,
- max_chars=5,
- position=(c_width * 0.5 - 240 + 490, v - 30),
- v_align='center',
- scale=1.0,
- size=(170, 60))
+ ba.textwidget(
+ parent=self._container,
+ position=(c_width * 0.5 - 260 + 490, v),
+ color=(0.6, 1.0, 0.6),
+ scale=1.0,
+ size=(0, 0),
+ maxwidth=80,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'portText'),
+ )
+ txt2 = ba.textwidget(
+ parent=self._container,
+ editable=True,
+ description=ba.Lstr(resource='gatherWindow.' 'portText'),
+ text='43210',
+ autoselect=True,
+ max_chars=5,
+ position=(c_width * 0.5 - 240 + 490, v - 30),
+ v_align='center',
+ scale=1.0,
+ size=(170, 60),
+ )
v -= 110
- btn = ba.buttonwidget(parent=self._container,
- size=(300, 70),
- label=ba.Lstr(resource='gatherWindow.'
- 'manualConnectText'),
- position=(c_width * 0.5 - 300, v),
- autoselect=True,
- on_activate_call=ba.Call(self._connect, txt,
- txt2))
+ btn = ba.buttonwidget(
+ parent=self._container,
+ size=(300, 70),
+ label=ba.Lstr(resource='gatherWindow.' 'manualConnectText'),
+ position=(c_width * 0.5 - 300, v),
+ autoselect=True,
+ on_activate_call=ba.Call(self._connect, txt, txt2),
+ )
savebutton = ba.buttonwidget(
parent=self._container,
size=(300, 70),
label=ba.Lstr(resource='gatherWindow.favoritesSaveText'),
position=(c_width * 0.5 - 240 + 490 - 200, v),
autoselect=True,
- on_activate_call=ba.Call(self._save_server, txt, txt2))
+ on_activate_call=ba.Call(self._save_server, txt, txt2),
+ )
ba.widget(edit=btn, right_widget=savebutton)
ba.widget(edit=savebutton, left_widget=btn, up_widget=txt2)
ba.textwidget(edit=txt, on_return_press_call=btn.activate)
@@ -282,8 +311,7 @@ class ManualGatherTab(GatherTab):
self._check_button = ba.textwidget(
parent=self._container,
size=(250, 60),
- text=ba.Lstr(resource='gatherWindow.'
- 'showMyAddressText'),
+ text=ba.Lstr(resource='gatherWindow.' 'showMyAddressText'),
v_align='center',
h_align='center',
click_activate=True,
@@ -292,8 +320,13 @@ class ManualGatherTab(GatherTab):
color=(0.5, 0.9, 0.5),
scale=0.8,
selectable=True,
- on_activate_call=ba.Call(self._on_show_my_address_button_press, v,
- self._container, c_width))
+ on_activate_call=ba.Call(
+ self._on_show_my_address_button_press,
+ v,
+ self._container,
+ c_width,
+ ),
+ )
ba.widget(edit=self._check_button, up_widget=btn)
# Tab containing saved favorite addresses
@@ -305,8 +338,13 @@ class ManualGatherTab(GatherTab):
uiscale = ba.app.ui.uiscale
self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (578 if uiscale is ba.UIScale.SMALL else
- 670 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 578
+ if uiscale is ba.UIScale.SMALL
+ else 670
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
self._scroll_width = self._width - 130 + 2 * x_inset
self._scroll_height = self._height - 180
@@ -315,19 +353,38 @@ class ManualGatherTab(GatherTab):
c_height = self._scroll_height - 20
sub_scroll_height = c_height - 63
self._favorites_scroll_width = sub_scroll_width = (
- 680 if uiscale is ba.UIScale.SMALL else 640)
+ 680 if uiscale is ba.UIScale.SMALL else 640
+ )
v = c_height - 30
b_width = 140 if uiscale is ba.UIScale.SMALL else 178
- b_height = (107 if uiscale is ba.UIScale.SMALL else
- 142 if uiscale is ba.UIScale.MEDIUM else 190)
- b_space_extra = (0 if uiscale is ba.UIScale.SMALL else
- -2 if uiscale is ba.UIScale.MEDIUM else -5)
+ b_height = (
+ 107
+ if uiscale is ba.UIScale.SMALL
+ else 142
+ if uiscale is ba.UIScale.MEDIUM
+ else 190
+ )
+ b_space_extra = (
+ 0
+ if uiscale is ba.UIScale.SMALL
+ else -2
+ if uiscale is ba.UIScale.MEDIUM
+ else -5
+ )
- btnv = (c_height - (48 if uiscale is ba.UIScale.SMALL else
- 45 if uiscale is ba.UIScale.MEDIUM else 40) -
- b_height)
+ btnv = (
+ c_height
+ - (
+ 48
+ if uiscale is ba.UIScale.SMALL
+ else 45
+ if uiscale is ba.UIScale.MEDIUM
+ else 40
+ )
+ - b_height
+ )
self._favorites_connect_button = btn1 = ba.buttonwidget(
parent=self._container,
@@ -339,56 +396,65 @@ class ManualGatherTab(GatherTab):
on_activate_call=self._on_favorites_connect_press,
text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2,
label=ba.Lstr(resource='gatherWindow.manualConnectText'),
- autoselect=True)
+ autoselect=True,
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.widget(
edit=btn1,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
btnv -= b_height + b_space_extra
- ba.buttonwidget(parent=self._container,
- size=(b_width, b_height),
- position=(40 if uiscale is ba.UIScale.SMALL else 40,
- btnv),
- button_type='square',
- color=(0.6, 0.53, 0.63),
- textcolor=(0.75, 0.7, 0.8),
- on_activate_call=self._on_favorites_edit_press,
- text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2,
- label=ba.Lstr(resource='editText'),
- autoselect=True)
+ ba.buttonwidget(
+ parent=self._container,
+ size=(b_width, b_height),
+ position=(40 if uiscale is ba.UIScale.SMALL else 40, btnv),
+ button_type='square',
+ color=(0.6, 0.53, 0.63),
+ textcolor=(0.75, 0.7, 0.8),
+ on_activate_call=self._on_favorites_edit_press,
+ text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2,
+ label=ba.Lstr(resource='editText'),
+ autoselect=True,
+ )
btnv -= b_height + b_space_extra
- ba.buttonwidget(parent=self._container,
- size=(b_width, b_height),
- position=(40 if uiscale is ba.UIScale.SMALL else 40,
- btnv),
- button_type='square',
- color=(0.6, 0.53, 0.63),
- textcolor=(0.75, 0.7, 0.8),
- on_activate_call=self._on_favorite_delete_press,
- text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2,
- label=ba.Lstr(resource='deleteText'),
- autoselect=True)
+ ba.buttonwidget(
+ parent=self._container,
+ size=(b_width, b_height),
+ position=(40 if uiscale is ba.UIScale.SMALL else 40, btnv),
+ button_type='square',
+ color=(0.6, 0.53, 0.63),
+ textcolor=(0.75, 0.7, 0.8),
+ on_activate_call=self._on_favorite_delete_press,
+ text_scale=1.0 if uiscale is ba.UIScale.SMALL else 1.2,
+ label=ba.Lstr(resource='deleteText'),
+ autoselect=True,
+ )
v -= sub_scroll_height + 23
self._scrollwidget = scrlw = ba.scrollwidget(
parent=self._container,
position=(190 if uiscale is ba.UIScale.SMALL else 225, v),
size=(sub_scroll_width, sub_scroll_height),
- claims_left_right=True)
- ba.widget(edit=self._favorites_connect_button,
- right_widget=self._scrollwidget)
- self._columnwidget = ba.columnwidget(parent=scrlw,
- left_border=10,
- border=2,
- margin=0,
- claims_left_right=True)
+ claims_left_right=True,
+ )
+ ba.widget(
+ edit=self._favorites_connect_button, right_widget=self._scrollwidget
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=scrlw,
+ left_border=10,
+ border=2,
+ margin=0,
+ claims_left_right=True,
+ )
self._favorite_selected = None
self._refresh_favorites()
def _no_favorite_selected_error(self) -> None:
- ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
def _on_favorites_connect_press(self) -> None:
@@ -397,10 +463,11 @@ class ManualGatherTab(GatherTab):
else:
config = ba.app.config['Saved Servers'][self._favorite_selected]
- _HostLookupThread(name=config['addr'],
- port=config['port'],
- call=ba.WeakCall(
- self._host_lookup_result)).start()
+ _HostLookupThread(
+ name=config['addr'],
+ port=config['port'],
+ call=ba.WeakCall(self._host_lookup_result),
+ ).start()
def _on_favorites_edit_press(self) -> None:
if self._favorite_selected is None:
@@ -411,108 +478,130 @@ class ManualGatherTab(GatherTab):
c_height = 310
uiscale = ba.app.ui.uiscale
self._favorite_edit_window = cnt = ba.containerwidget(
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0),
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.55
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
size=(c_width, c_height),
- transition='in_scale')
+ transition='in_scale',
+ )
- ba.textwidget(parent=cnt,
- size=(0, 0),
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='editText'),
- color=(0.6, 1.0, 0.6),
- maxwidth=c_width * 0.8,
- position=(c_width * 0.5, c_height - 60))
+ ba.textwidget(
+ parent=cnt,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='editText'),
+ color=(0.6, 1.0, 0.6),
+ maxwidth=c_width * 0.8,
+ position=(c_width * 0.5, c_height - 60),
+ )
- ba.textwidget(parent=cnt,
- position=(c_width * 0.2 - 15, c_height - 120),
- color=(0.6, 1.0, 0.6),
- scale=1.0,
- size=(0, 0),
- maxwidth=60,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='nameText'))
+ ba.textwidget(
+ parent=cnt,
+ position=(c_width * 0.2 - 15, c_height - 120),
+ color=(0.6, 1.0, 0.6),
+ scale=1.0,
+ size=(0, 0),
+ maxwidth=60,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='nameText'),
+ )
self._party_edit_name_text = ba.textwidget(
parent=cnt,
size=(c_width * 0.7, 40),
h_align='left',
v_align='center',
- text=ba.app.config['Saved Servers'][
- self._favorite_selected]['name'],
+ text=ba.app.config['Saved Servers'][self._favorite_selected][
+ 'name'
+ ],
editable=True,
description=ba.Lstr(resource='nameText'),
position=(c_width * 0.2, c_height - 140),
autoselect=True,
maxwidth=c_width * 0.6,
- max_chars=200)
+ max_chars=200,
+ )
- ba.textwidget(parent=cnt,
- position=(c_width * 0.2 - 15, c_height - 180),
- color=(0.6, 1.0, 0.6),
- scale=1.0,
- size=(0, 0),
- maxwidth=60,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'manualAddressText'))
+ ba.textwidget(
+ parent=cnt,
+ position=(c_width * 0.2 - 15, c_height - 180),
+ color=(0.6, 1.0, 0.6),
+ scale=1.0,
+ size=(0, 0),
+ maxwidth=60,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'manualAddressText'),
+ )
self._party_edit_addr_text = ba.textwidget(
parent=cnt,
size=(c_width * 0.4, 40),
h_align='left',
v_align='center',
- text=ba.app.config['Saved Servers'][
- self._favorite_selected]['addr'],
+ text=ba.app.config['Saved Servers'][self._favorite_selected][
+ 'addr'
+ ],
editable=True,
description=ba.Lstr(resource='gatherWindow.manualAddressText'),
position=(c_width * 0.2, c_height - 200),
autoselect=True,
maxwidth=c_width * 0.35,
- max_chars=200)
+ max_chars=200,
+ )
- ba.textwidget(parent=cnt,
- position=(c_width * 0.7 - 10, c_height - 180),
- color=(0.6, 1.0, 0.6),
- scale=1.0,
- size=(0, 0),
- maxwidth=45,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'portText'))
+ ba.textwidget(
+ parent=cnt,
+ position=(c_width * 0.7 - 10, c_height - 180),
+ color=(0.6, 1.0, 0.6),
+ scale=1.0,
+ size=(0, 0),
+ maxwidth=45,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'portText'),
+ )
self._party_edit_port_text = ba.textwidget(
parent=cnt,
size=(c_width * 0.2, 40),
h_align='left',
v_align='center',
- text=str(ba.app.config['Saved Servers'][self._favorite_selected]
- ['port']),
+ text=str(
+ ba.app.config['Saved Servers'][self._favorite_selected]['port']
+ ),
editable=True,
description=ba.Lstr(resource='gatherWindow.portText'),
position=(c_width * 0.7, c_height - 200),
autoselect=True,
maxwidth=c_width * 0.2,
- max_chars=6)
+ max_chars=6,
+ )
cbtn = ba.buttonwidget(
parent=cnt,
label=ba.Lstr(resource='cancelText'),
on_activate_call=ba.Call(
lambda c: ba.containerwidget(edit=c, transition='out_scale'),
- cnt),
+ cnt,
+ ),
size=(180, 60),
position=(30, 30),
- autoselect=True)
- okb = ba.buttonwidget(parent=cnt,
- label=ba.Lstr(resource='saveText'),
- size=(180, 60),
- position=(c_width - 230, 30),
- on_activate_call=ba.Call(self._edit_saved_party),
- autoselect=True)
+ autoselect=True,
+ )
+ okb = ba.buttonwidget(
+ parent=cnt,
+ label=ba.Lstr(resource='saveText'),
+ size=(180, 60),
+ position=(c_width - 230, 30),
+ on_activate_call=ba.Call(self._edit_saved_party),
+ autoselect=True,
+ )
ba.widget(edit=cbtn, right_widget=okb)
ba.widget(edit=okb, left_widget=cbtn)
ba.containerwidget(edit=cnt, cancel_button=cbtn, start_button=okb)
@@ -524,12 +613,15 @@ class ManualGatherTab(GatherTab):
return
if not self._party_edit_name_text or not self._party_edit_addr_text:
return
- new_name_raw = cast(str,
- ba.textwidget(query=self._party_edit_name_text))
- new_addr_raw = cast(str,
- ba.textwidget(query=self._party_edit_addr_text))
- new_port_raw = cast(str,
- ba.textwidget(query=self._party_edit_port_text))
+ new_name_raw = cast(
+ str, ba.textwidget(query=self._party_edit_name_text)
+ )
+ new_addr_raw = cast(
+ str, ba.textwidget(query=self._party_edit_addr_text)
+ )
+ new_port_raw = cast(
+ str, ba.textwidget(query=self._party_edit_port_text)
+ )
ba.app.config['Saved Servers'][server]['name'] = new_name_raw
ba.app.config['Saved Servers'][server]['addr'] = new_addr_raw
try:
@@ -541,19 +633,32 @@ class ManualGatherTab(GatherTab):
ba.playsound(ba.getsound('gunCocking'))
self._refresh_favorites()
- ba.containerwidget(edit=self._favorite_edit_window,
- transition='out_scale')
+ ba.containerwidget(
+ edit=self._favorite_edit_window, transition='out_scale'
+ )
def _on_favorite_delete_press(self) -> None:
from bastd.ui import confirm
+
if self._favorite_selected is None:
self._no_favorite_selected_error()
return
confirm.ConfirmWindow(
- ba.Lstr(resource='gameListWindow.deleteConfirmText',
- subs=[('${LIST}', ba.app.config['Saved Servers'][
- self._favorite_selected]['name'])]),
- self._delete_saved_party, 450, 150)
+ ba.Lstr(
+ resource='gameListWindow.deleteConfirmText',
+ subs=[
+ (
+ '${LIST}',
+ ba.app.config['Saved Servers'][self._favorite_selected][
+ 'name'
+ ],
+ )
+ ],
+ ),
+ self._delete_saved_party,
+ 450,
+ 150,
+ )
def _delete_saved_party(self) -> None:
if self._favorite_selected is None:
@@ -593,38 +698,50 @@ class ManualGatherTab(GatherTab):
always_highlight=True,
on_select_call=ba.Call(self._on_favorite_select, server),
on_activate_call=self._favorites_connect_button.activate,
- text=(config['Saved Servers'][server]['name']
- if config['Saved Servers'][server]['name'] != '' else
- config['Saved Servers'][server]['addr'] + ' ' +
- str(config['Saved Servers'][server]['port'])),
+ text=(
+ config['Saved Servers'][server]['name']
+ if config['Saved Servers'][server]['name'] != ''
+ else config['Saved Servers'][server]['addr']
+ + ' '
+ + str(config['Saved Servers'][server]['port'])
+ ),
h_align='left',
v_align='center',
corner_scale=t_scale,
- maxwidth=(self._favorites_scroll_width / t_scale) * 0.93)
+ maxwidth=(self._favorites_scroll_width / t_scale) * 0.93,
+ )
if i == 0:
ba.widget(edit=txt, up_widget=self._favorites_text)
- ba.widget(edit=txt,
- left_widget=self._favorites_connect_button,
- right_widget=txt)
+ ba.widget(
+ edit=txt,
+ left_widget=self._favorites_connect_button,
+ right_widget=txt,
+ )
# If there's no servers, allow selecting out of the scroll area
- ba.containerwidget(edit=self._scrollwidget,
- claims_left_right=bool(servers),
- claims_up_down=bool(servers))
- ba.widget(edit=self._scrollwidget,
- up_widget=self._favorites_text,
- left_widget=self._favorites_connect_button)
+ ba.containerwidget(
+ edit=self._scrollwidget,
+ claims_left_right=bool(servers),
+ claims_up_down=bool(servers),
+ )
+ ba.widget(
+ edit=self._scrollwidget,
+ up_widget=self._favorites_text,
+ left_widget=self._favorites_connect_button,
+ )
def on_deactivate(self) -> None:
self._access_check_timer = None
- def _connect(self, textwidget: ba.Widget,
- port_textwidget: ba.Widget) -> None:
+ def _connect(
+ self, textwidget: ba.Widget, port_textwidget: ba.Widget
+ ) -> None:
addr = cast(str, ba.textwidget(query=textwidget))
if addr == '':
ba.screenmessage(
ba.Lstr(resource='internal.invalidAddressErrorText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
try:
@@ -632,22 +749,26 @@ class ManualGatherTab(GatherTab):
except ValueError:
port = -1
if port > 65535 or port < 0:
- ba.screenmessage(ba.Lstr(resource='internal.invalidPortErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='internal.invalidPortErrorText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
- _HostLookupThread(name=addr,
- port=port,
- call=ba.WeakCall(self._host_lookup_result)).start()
+ _HostLookupThread(
+ name=addr, port=port, call=ba.WeakCall(self._host_lookup_result)
+ ).start()
- def _save_server(self, textwidget: ba.Widget,
- port_textwidget: ba.Widget) -> None:
+ def _save_server(
+ self, textwidget: ba.Widget, port_textwidget: ba.Widget
+ ) -> None:
addr = cast(str, ba.textwidget(query=textwidget))
if addr == '':
ba.screenmessage(
ba.Lstr(resource='internal.invalidAddressErrorText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
try:
@@ -655,8 +776,10 @@ class ManualGatherTab(GatherTab):
except ValueError:
port = -1
if port > 65535 or port < 0:
- ba.screenmessage(ba.Lstr(resource='internal.invalidPortErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='internal.invalidPortErrorText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
config = ba.app.config
@@ -667,7 +790,7 @@ class ManualGatherTab(GatherTab):
config['Saved Servers'][f'{addr}@{port}'] = {
'addr': addr,
'port': port,
- 'name': addr
+ 'name': addr,
}
config.commit()
ba.playsound(ba.getsound('gunCocking'))
@@ -675,12 +798,14 @@ class ManualGatherTab(GatherTab):
ba.screenmessage('Invalid Address', color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
- def _host_lookup_result(self, resolved_address: str | None,
- port: int) -> None:
+ def _host_lookup_result(
+ self, resolved_address: str | None, port: int
+ ) -> None:
if resolved_address is None:
ba.screenmessage(
ba.Lstr(resource='internal.unableToResolveHostText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
else:
# Store for later.
@@ -693,6 +818,7 @@ class ManualGatherTab(GatherTab):
try:
# FIXME: Update this to work with IPv6.
import socket
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('8.8.8.8', 80))
val = sock.getsockname()[0]
@@ -707,25 +833,39 @@ class ManualGatherTab(GatherTab):
)
except Exception as exc:
from efro.error import is_udp_communication_error
- if is_udp_communication_error(exc):
- ba.pushcall(ba.Call(
- _safe_set_text, self._checking_state_text,
- ba.Lstr(resource='gatherWindow.'
- 'noConnectionText'), False),
- from_other_thread=True)
- else:
- ba.pushcall(ba.Call(
- _safe_set_text, self._checking_state_text,
- ba.Lstr(resource='gatherWindow.'
- 'addressFetchErrorText'), False),
- from_other_thread=True)
- ba.pushcall(ba.Call(ba.print_error,
- 'error in AddrFetchThread: ' + str(exc)),
- from_other_thread=True)
- def _on_show_my_address_button_press(self, v2: float,
- container: ba.Widget | None,
- c_width: float) -> None:
+ if is_udp_communication_error(exc):
+ ba.pushcall(
+ ba.Call(
+ _safe_set_text,
+ self._checking_state_text,
+ ba.Lstr(resource='gatherWindow.' 'noConnectionText'),
+ False,
+ ),
+ from_other_thread=True,
+ )
+ else:
+ ba.pushcall(
+ ba.Call(
+ _safe_set_text,
+ self._checking_state_text,
+ ba.Lstr(
+ resource='gatherWindow.' 'addressFetchErrorText'
+ ),
+ False,
+ ),
+ from_other_thread=True,
+ )
+ ba.pushcall(
+ ba.Call(
+ ba.print_error, 'error in AddrFetchThread: ' + str(exc)
+ ),
+ from_other_thread=True,
+ )
+
+ def _on_show_my_address_button_press(
+ self, v2: float, container: ba.Widget | None, c_width: float
+ ) -> None:
if not container:
return
@@ -733,17 +873,18 @@ class ManualGatherTab(GatherTab):
tspc = 25
ba.playsound(ba.getsound('swish'))
- ba.textwidget(parent=container,
- position=(c_width * 0.5 - 10, v2),
- color=(0.6, 1.0, 0.6),
- scale=tscl,
- size=(0, 0),
- maxwidth=c_width * 0.45,
- flatness=1.0,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'manualYourLocalAddressText'))
+ ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5 - 10, v2),
+ color=(0.6, 1.0, 0.6),
+ scale=tscl,
+ size=(0, 0),
+ maxwidth=c_width * 0.45,
+ flatness=1.0,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'manualYourLocalAddressText'),
+ )
self._checking_state_text = ba.textwidget(
parent=container,
position=(c_width * 0.5, v2),
@@ -754,87 +895,106 @@ class ManualGatherTab(GatherTab):
flatness=1.0,
h_align='left',
v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'checkingText'))
+ text=ba.Lstr(resource='gatherWindow.' 'checkingText'),
+ )
threading.Thread(target=self._run_addr_fetch).start()
v2 -= tspc
- ba.textwidget(parent=container,
- position=(c_width * 0.5 - 10, v2),
- color=(0.6, 1.0, 0.6),
- scale=tscl,
- size=(0, 0),
- maxwidth=c_width * 0.45,
- flatness=1.0,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'manualYourAddressFromInternetText'))
+ ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5 - 10, v2),
+ color=(0.6, 1.0, 0.6),
+ scale=tscl,
+ size=(0, 0),
+ maxwidth=c_width * 0.45,
+ flatness=1.0,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(
+ resource='gatherWindow.' 'manualYourAddressFromInternetText'
+ ),
+ )
- t_addr = ba.textwidget(parent=container,
- position=(c_width * 0.5, v2),
- color=(0.5, 0.5, 0.5),
- scale=tscl,
- size=(0, 0),
- maxwidth=c_width * 0.45,
- h_align='left',
- v_align='center',
- flatness=1.0,
- text=ba.Lstr(resource='gatherWindow.'
- 'checkingText'))
+ t_addr = ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5, v2),
+ color=(0.5, 0.5, 0.5),
+ scale=tscl,
+ size=(0, 0),
+ maxwidth=c_width * 0.45,
+ h_align='left',
+ v_align='center',
+ flatness=1.0,
+ text=ba.Lstr(resource='gatherWindow.' 'checkingText'),
+ )
v2 -= tspc
- ba.textwidget(parent=container,
- position=(c_width * 0.5 - 10, v2),
- color=(0.6, 1.0, 0.6),
- scale=tscl,
- size=(0, 0),
- maxwidth=c_width * 0.45,
- flatness=1.0,
- h_align='right',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'manualJoinableFromInternetText'))
+ ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5 - 10, v2),
+ color=(0.6, 1.0, 0.6),
+ scale=tscl,
+ size=(0, 0),
+ maxwidth=c_width * 0.45,
+ flatness=1.0,
+ h_align='right',
+ v_align='center',
+ text=ba.Lstr(
+ resource='gatherWindow.' 'manualJoinableFromInternetText'
+ ),
+ )
- t_accessible = ba.textwidget(parent=container,
- position=(c_width * 0.5, v2),
- color=(0.5, 0.5, 0.5),
- scale=tscl,
- size=(0, 0),
- maxwidth=c_width * 0.45,
- flatness=1.0,
- h_align='left',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'checkingText'))
+ t_accessible = ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5, v2),
+ color=(0.5, 0.5, 0.5),
+ scale=tscl,
+ size=(0, 0),
+ maxwidth=c_width * 0.45,
+ flatness=1.0,
+ h_align='left',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.' 'checkingText'),
+ )
v2 -= 28
- t_accessible_extra = ba.textwidget(parent=container,
- position=(c_width * 0.5, v2),
- color=(1, 0.5, 0.2),
- scale=0.7,
- size=(0, 0),
- maxwidth=c_width * 0.9,
- flatness=1.0,
- h_align='center',
- v_align='center',
- text='')
+ t_accessible_extra = ba.textwidget(
+ parent=container,
+ position=(c_width * 0.5, v2),
+ color=(1, 0.5, 0.2),
+ scale=0.7,
+ size=(0, 0),
+ maxwidth=c_width * 0.9,
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ text='',
+ )
self._doing_access_check = False
self._access_check_count = 0 # Cap our refreshes eventually.
self._access_check_timer = ba.Timer(
10.0,
- ba.WeakCall(self._access_check_update, t_addr, t_accessible,
- t_accessible_extra),
+ ba.WeakCall(
+ self._access_check_update,
+ t_addr,
+ t_accessible,
+ t_accessible_extra,
+ ),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
# Kick initial off.
self._access_check_update(t_addr, t_accessible, t_accessible_extra)
if self._check_button:
self._check_button.delete()
- def _access_check_update(self, t_addr: ba.Widget, t_accessible: ba.Widget,
- t_accessible_extra: ba.Widget) -> None:
+ def _access_check_update(
+ self,
+ t_addr: ba.Widget,
+ t_accessible: ba.Widget,
+ t_accessible_extra: ba.Widget,
+ ) -> None:
from ba.internal import master_server_get
# If we don't have an outstanding query, start one..
@@ -846,9 +1006,11 @@ class ManualGatherTab(GatherTab):
self._t_addr = t_addr
self._t_accessible = t_accessible
self._t_accessible_extra = t_accessible_extra
- master_server_get('bsAccessCheck', {'b': ba.app.build_number},
- callback=ba.WeakCall(
- self._on_accessible_response))
+ master_server_get(
+ 'bsAccessCheck',
+ {'b': ba.app.build_number},
+ callback=ba.WeakCall(self._on_accessible_response),
+ )
def _on_accessible_response(self, data: dict[str, Any] | None) -> None:
t_addr = self._t_addr
@@ -859,37 +1021,42 @@ class ManualGatherTab(GatherTab):
color_good = (0, 1, 0)
if data is None or 'address' not in data or 'accessible' not in data:
if t_addr:
- ba.textwidget(edit=t_addr,
- text=ba.Lstr(resource='gatherWindow.'
- 'noConnectionText'),
- color=color_bad)
+ ba.textwidget(
+ edit=t_addr,
+ text=ba.Lstr(resource='gatherWindow.' 'noConnectionText'),
+ color=color_bad,
+ )
if t_accessible:
- ba.textwidget(edit=t_accessible,
- text=ba.Lstr(resource='gatherWindow.'
- 'noConnectionText'),
- color=color_bad)
+ ba.textwidget(
+ edit=t_accessible,
+ text=ba.Lstr(resource='gatherWindow.' 'noConnectionText'),
+ color=color_bad,
+ )
if t_accessible_extra:
- ba.textwidget(edit=t_accessible_extra,
- text='',
- color=color_bad)
+ ba.textwidget(edit=t_accessible_extra, text='', color=color_bad)
return
if t_addr:
ba.textwidget(edit=t_addr, text=data['address'], color=color_good)
if t_accessible:
if data['accessible']:
- ba.textwidget(edit=t_accessible,
- text=ba.Lstr(resource='gatherWindow.'
- 'manualJoinableYesText'),
- color=color_good)
+ ba.textwidget(
+ edit=t_accessible,
+ text=ba.Lstr(
+ resource='gatherWindow.' 'manualJoinableYesText'
+ ),
+ color=color_good,
+ )
if t_accessible_extra:
- ba.textwidget(edit=t_accessible_extra,
- text='',
- color=color_good)
+ ba.textwidget(
+ edit=t_accessible_extra, text='', color=color_good
+ )
else:
ba.textwidget(
edit=t_accessible,
- text=ba.Lstr(resource='gatherWindow.'
- 'manualJoinableNoWithAsteriskText'),
+ text=ba.Lstr(
+ resource='gatherWindow.'
+ 'manualJoinableNoWithAsteriskText'
+ ),
color=color_bad,
)
if t_accessible_extra:
diff --git a/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py b/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
index c5ebcdd6..16f68b39 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
@@ -19,23 +19,29 @@ if TYPE_CHECKING:
class NetScanner:
"""Class for scanning for games on the lan."""
- def __init__(self, tab: GatherTab, scrollwidget: ba.Widget,
- tab_button: ba.Widget, width: float):
+ def __init__(
+ self,
+ tab: GatherTab,
+ scrollwidget: ba.Widget,
+ tab_button: ba.Widget,
+ width: float,
+ ):
self._tab = weakref.ref(tab)
self._scrollwidget = scrollwidget
self._tab_button = tab_button
- self._columnwidget = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0,
- left_border=10)
+ self._columnwidget = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0, left_border=10
+ )
ba.widget(edit=self._columnwidget, up_widget=tab_button)
self._width = width
self._last_selected_host: dict[str, Any] | None = None
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self.update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self.update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
# Go ahead and run a few *almost* immediately so we don't
# have to wait a second.
self.update()
@@ -55,8 +61,10 @@ class NetScanner:
# In case our UI was killed from under us.
if not self._columnwidget:
- print(f'ERROR: NetScanner running without UI at time'
- f' {ba.time(timetype=ba.TimeType.REAL)}.')
+ print(
+ f'ERROR: NetScanner running without UI at time'
+ f' {ba.time(timetype=ba.TimeType.REAL)}.'
+ )
return
t_scale = 1.6
@@ -67,23 +75,26 @@ class NetScanner:
last_selected_host = self._last_selected_host
hosts = ba.internal.host_scan_cycle()
for i, host in enumerate(hosts):
- txt3 = ba.textwidget(parent=self._columnwidget,
- size=(self._width / t_scale, 30),
- selectable=True,
- color=(1, 1, 1),
- on_select_call=ba.Call(self._on_select, host),
- on_activate_call=ba.Call(
- self._on_activate, host),
- click_activate=True,
- text=host['display_string'],
- h_align='left',
- v_align='center',
- corner_scale=t_scale,
- maxwidth=(self._width / t_scale) * 0.93)
+ txt3 = ba.textwidget(
+ parent=self._columnwidget,
+ size=(self._width / t_scale, 30),
+ selectable=True,
+ color=(1, 1, 1),
+ on_select_call=ba.Call(self._on_select, host),
+ on_activate_call=ba.Call(self._on_activate, host),
+ click_activate=True,
+ text=host['display_string'],
+ h_align='left',
+ v_align='center',
+ corner_scale=t_scale,
+ maxwidth=(self._width / t_scale) * 0.93,
+ )
if host == last_selected_host:
- ba.containerwidget(edit=self._columnwidget,
- selected_child=txt3,
- visible_child=txt3)
+ ba.containerwidget(
+ edit=self._columnwidget,
+ selected_child=txt3,
+ visible_child=txt3,
+ )
if i == 0:
ba.widget(edit=txt3, up_widget=self._tab_button)
@@ -111,33 +122,39 @@ class NearbyGatherTab(GatherTab):
sub_scroll_width = 650
self._container = ba.containerwidget(
parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - c_height) * 0.5),
+ position=(
+ region_left,
+ region_bottom + (region_height - c_height) * 0.5,
+ ),
size=(c_width, c_height),
background=False,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
v = c_height - 30
- ba.textwidget(parent=self._container,
- position=(c_width * 0.5, v - 3),
- color=(0.6, 1.0, 0.6),
- scale=1.3,
- size=(0, 0),
- maxwidth=c_width * 0.9,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.'
- 'localNetworkDescriptionText'))
+ ba.textwidget(
+ parent=self._container,
+ position=(c_width * 0.5, v - 3),
+ color=(0.6, 1.0, 0.6),
+ scale=1.3,
+ size=(0, 0),
+ maxwidth=c_width * 0.9,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(
+ resource='gatherWindow.' 'localNetworkDescriptionText'
+ ),
+ )
v -= 15
v -= sub_scroll_height + 23
- scrollw = ba.scrollwidget(parent=self._container,
- position=((region_width - sub_scroll_width) *
- 0.5, v),
- size=(sub_scroll_width, sub_scroll_height))
+ scrollw = ba.scrollwidget(
+ parent=self._container,
+ position=((region_width - sub_scroll_width) * 0.5, v),
+ size=(sub_scroll_width, sub_scroll_height),
+ )
- self._net_scanner = NetScanner(self,
- scrollw,
- tab_button,
- width=sub_scroll_width)
+ self._net_scanner = NetScanner(
+ self, scrollw, tab_button, width=sub_scroll_width
+ )
ba.widget(edit=scrollw, autoselect=True, up_widget=tab_button)
return self._container
diff --git a/assets/src/ba_data/python/bastd/ui/gather/privatetab.py b/assets/src/ba_data/python/bastd/ui/gather/privatetab.py
index 3f7dcf1e..88ed22b7 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/privatetab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/privatetab.py
@@ -12,8 +12,11 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
-from bacommon.net import (PrivateHostingState, PrivateHostingConfig,
- PrivatePartyConnectResult)
+from bacommon.net import (
+ PrivateHostingState,
+ PrivateHostingConfig,
+ PrivatePartyConnectResult,
+)
import ba
import ba.internal
from bastd.ui.gather import GatherTab
@@ -29,6 +32,7 @@ DEBUG_SERVER_COMMUNICATION = os.environ.get('BA_DEBUG_PPTABCOM') == '1'
class SubTabType(Enum):
"""Available sub-tabs."""
+
JOIN = 'join'
HOST = 'host'
@@ -36,6 +40,7 @@ class SubTabType(Enum):
@dataclass
class State:
"""Our core state that persists while the app is running."""
+
sub_tab: SubTabType = SubTabType.JOIN
@@ -85,11 +90,14 @@ class PrivateGatherTab(GatherTab):
self._c_height = region_height - 20
self._container = ba.containerwidget(
parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - self._c_height) * 0.5),
+ position=(
+ region_left,
+ region_bottom + (region_height - self._c_height) * 0.5,
+ ),
size=(self._c_width, self._c_height),
background=False,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
v = self._c_height - 30.0
self._join_sub_tab_text = ba.textwidget(
parent=self._container,
@@ -107,7 +115,8 @@ class PrivateGatherTab(GatherTab):
SubTabType.JOIN,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.privatePartyJoinText'))
+ text=ba.Lstr(resource='gatherWindow.privatePartyJoinText'),
+ )
self._host_sub_tab_text = ba.textwidget(
parent=self._container,
position=(self._c_width * 0.5 + 45, v - 13),
@@ -124,18 +133,24 @@ class PrivateGatherTab(GatherTab):
SubTabType.HOST,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.privatePartyHostText'))
+ text=ba.Lstr(resource='gatherWindow.privatePartyHostText'),
+ )
ba.widget(edit=self._join_sub_tab_text, up_widget=tab_button)
- ba.widget(edit=self._host_sub_tab_text,
- left_widget=self._join_sub_tab_text,
- up_widget=tab_button)
- ba.widget(edit=self._join_sub_tab_text,
- right_widget=self._host_sub_tab_text)
+ ba.widget(
+ edit=self._host_sub_tab_text,
+ left_widget=self._join_sub_tab_text,
+ up_widget=tab_button,
+ )
+ ba.widget(
+ edit=self._join_sub_tab_text, right_widget=self._host_sub_tab_text
+ )
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
# Prevent taking any action until we've updated our state.
self._waiting_for_initial_state = True
@@ -153,6 +168,7 @@ class PrivateGatherTab(GatherTab):
# pylint: disable=too-many-branches
from bastd.ui.playlist import PlaylistTypeVars
from ba.internal import filter_playlist
+
hcfg = PrivateHostingConfig()
cfg = ba.app.config
sessiontypestr = cfg.get('Private Party Host Session Type', 'ffa')
@@ -170,23 +186,27 @@ class PrivateGatherTab(GatherTab):
pvars = PlaylistTypeVars(sessiontype)
playlist_name = ba.app.config.get(
- f'{pvars.config_name} Playlist Selection')
+ f'{pvars.config_name} Playlist Selection'
+ )
if not isinstance(playlist_name, str):
playlist_name = '__default__'
- hcfg.playlist_name = (pvars.default_list_name.evaluate()
- if playlist_name == '__default__' else
- playlist_name)
+ hcfg.playlist_name = (
+ pvars.default_list_name.evaluate()
+ if playlist_name == '__default__'
+ else playlist_name
+ )
playlist: list[dict[str, Any]] | None = None
if playlist_name != '__default__':
- playlist = (cfg.get(f'{pvars.config_name} Playlists',
- {}).get(playlist_name))
+ playlist = cfg.get(f'{pvars.config_name} Playlists', {}).get(
+ playlist_name
+ )
if playlist is None:
playlist = pvars.get_default_list_call()
- hcfg.playlist = filter_playlist(playlist,
- sessiontype,
- name=playlist_name)
+ hcfg.playlist = filter_playlist(
+ playlist, sessiontype, name=playlist_name
+ )
randomize = cfg.get(f'{pvars.config_name} Playlist Randomize')
if not isinstance(randomize, bool):
@@ -201,21 +221,27 @@ class PrivateGatherTab(GatherTab):
if hcfg.session_type == 'teams':
ctn: list[str] | None = cfg.get('Custom Team Names')
if ctn is not None:
- if (isinstance(ctn, (list, tuple)) and len(ctn) == 2
- and all(isinstance(x, str) for x in ctn)):
+ if (
+ isinstance(ctn, (list, tuple))
+ and len(ctn) == 2
+ and all(isinstance(x, str) for x in ctn)
+ ):
hcfg.custom_team_names = (ctn[0], ctn[1])
else:
print(f'Found invalid custom-team-names data: {ctn}')
ctc: list[list[float]] | None = cfg.get('Custom Team Colors')
if ctc is not None:
- if (isinstance(ctc, (list, tuple)) and len(ctc) == 2
- and all(isinstance(x, (list, tuple)) for x in ctc)
- and all(len(x) == 3 for x in ctc)):
- hcfg.custom_team_colors = ((ctc[0][0], ctc[0][1],
- ctc[0][2]),
- (ctc[1][0], ctc[1][1],
- ctc[1][2]))
+ if (
+ isinstance(ctc, (list, tuple))
+ and len(ctc) == 2
+ and all(isinstance(x, (list, tuple)) for x in ctc)
+ and all(len(x) == 3 for x in ctc)
+ ):
+ hcfg.custom_team_colors = (
+ (ctc[0][0], ctc[0][1], ctc[0][2]),
+ (ctc[1][0], ctc[1][1], ctc[1][2]),
+ )
else:
print(f'Found invalid custom-team-colors data: {ctc}')
@@ -231,11 +257,15 @@ class PrivateGatherTab(GatherTab):
except Exception:
t_str = '?'
if self._get_tickets_button:
- ba.buttonwidget(edit=self._get_tickets_button,
- label=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ ba.buttonwidget(
+ edit=self._get_tickets_button,
+ label=ba.charstr(ba.SpecialChar.TICKET) + t_str,
+ )
if self._ticket_count_text:
- ba.textwidget(edit=self._ticket_count_text,
- text=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ ba.textwidget(
+ edit=self._ticket_count_text,
+ text=ba.charstr(ba.SpecialChar.TICKET) + t_str,
+ )
def _update(self) -> None:
"""Periodic updating."""
@@ -247,14 +277,18 @@ class PrivateGatherTab(GatherTab):
if self._state.sub_tab is SubTabType.HOST:
# If we're not signed in, just refresh to show that.
- if (ba.internal.get_v1_account_state() != 'signed_in'
- and self._showing_not_signed_in_screen):
+ if (
+ ba.internal.get_v1_account_state() != 'signed_in'
+ and self._showing_not_signed_in_screen
+ ):
self._refresh_sub_tab()
else:
# Query an updated state periodically.
- if (self._last_hosting_state_query_time is None
- or now - self._last_hosting_state_query_time > 15.0):
+ if (
+ self._last_hosting_state_query_time is None
+ or now - self._last_hosting_state_query_time > 15.0
+ ):
self._debug_server_comm('querying private party state')
if ba.internal.get_v1_account_state() == 'signed_in':
ba.internal.add_transaction(
@@ -263,15 +297,17 @@ class PrivateGatherTab(GatherTab):
'expire_time': time.time() + 20,
},
callback=ba.WeakCall(
- self._hosting_state_idle_response),
+ self._hosting_state_idle_response
+ ),
)
ba.internal.run_transactions()
else:
self._hosting_state_idle_response(None)
self._last_hosting_state_query_time = now
- def _hosting_state_idle_response(self,
- result: dict[str, Any] | None) -> None:
+ def _hosting_state_idle_response(
+ self, result: dict[str, Any] | None
+ ) -> None:
# This simply passes through to our standard response handler.
# The one exception is if we've recently sent an action to the
@@ -279,10 +315,13 @@ class PrivateGatherTab(GatherTab):
# idle background updates and wait for the response to our action.
# (this keeps the button showing 'one moment...' until the change
# takes effect, etc.)
- if (self._last_action_send_time is not None
- and time.time() - self._last_action_send_time < 5.0):
- self._debug_server_comm('ignoring private party state response'
- ' due to recent action')
+ if (
+ self._last_action_send_time is not None
+ and time.time() - self._last_action_send_time < 5.0
+ ):
+ self._debug_server_comm(
+ 'ignoring private party state response due to recent action'
+ )
return
self._hosting_state_response(result)
@@ -297,9 +336,9 @@ class PrivateGatherTab(GatherTab):
if result is not None:
self._debug_server_comm('got private party state response')
try:
- state = dataclass_from_dict(PrivateHostingState,
- result,
- discard_unknown_attrs=True)
+ state = dataclass_from_dict(
+ PrivateHostingState, result, discard_unknown_attrs=True
+ )
except Exception:
ba.print_exception('Got invalid PrivateHostingState data')
else:
@@ -335,10 +374,12 @@ class PrivateGatherTab(GatherTab):
inactive_color = (0.5, 0.4, 0.5)
ba.textwidget(
edit=self._join_sub_tab_text,
- color=active_color if value is SubTabType.JOIN else inactive_color)
+ color=active_color if value is SubTabType.JOIN else inactive_color,
+ )
ba.textwidget(
edit=self._host_sub_tab_text,
- color=active_color if value is SubTabType.HOST else inactive_color)
+ color=active_color if value is SubTabType.HOST else inactive_color,
+ )
self._refresh_sub_tab()
@@ -348,9 +389,11 @@ class PrivateGatherTab(GatherTab):
def _selwidgets(self) -> list[ba.Widget | None]:
"""An indexed list of widgets we can use for saving/restoring sel."""
return [
- self._host_playlist_button, self._host_copy_button,
- self._host_connect_button, self._host_start_stop_button,
- self._get_tickets_button
+ self._host_playlist_button,
+ self._host_copy_button,
+ self._host_connect_button,
+ self._host_start_stop_button,
+ self._get_tickets_button,
]
def _refresh_sub_tab(self) -> None:
@@ -369,8 +412,8 @@ class PrivateGatherTab(GatherTab):
# Clear anything existing in the old sub-tab.
for widget in self._container.get_children():
if widget and widget not in {
- self._host_sub_tab_text,
- self._join_sub_tab_text,
+ self._host_sub_tab_text,
+ self._join_sub_tab_text,
}:
widget.delete()
@@ -385,20 +428,23 @@ class PrivateGatherTab(GatherTab):
if selindex is not None:
selwidget = self._selwidgets()[selindex]
if selwidget:
- ba.containerwidget(edit=self._container,
- selected_child=selwidget)
+ ba.containerwidget(
+ edit=self._container, selected_child=selwidget
+ )
def _build_join_tab(self) -> None:
- ba.textwidget(parent=self._container,
- position=(self._c_width * 0.5, self._c_height - 140),
- color=(0.5, 0.46, 0.5),
- scale=1.5,
- size=(0, 0),
- maxwidth=250,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='gatherWindow.partyCodeText'))
+ ba.textwidget(
+ parent=self._container,
+ position=(self._c_width * 0.5, self._c_height - 140),
+ color=(0.5, 0.46, 0.5),
+ scale=1.5,
+ size=(0, 0),
+ maxwidth=250,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.partyCodeText'),
+ )
self._join_party_code_text = ba.textwidget(
parent=self._container,
@@ -412,17 +458,19 @@ class PrivateGatherTab(GatherTab):
maxwidth=250,
h_align='left',
v_align='center',
- text='')
- btn = ba.buttonwidget(parent=self._container,
- size=(300, 70),
- label=ba.Lstr(resource='gatherWindow.'
- 'manualConnectText'),
- position=(self._c_width * 0.5 - 150,
- self._c_height - 350),
- on_activate_call=self._join_connect_press,
- autoselect=True)
- ba.textwidget(edit=self._join_party_code_text,
- on_return_press_call=btn.activate)
+ text='',
+ )
+ btn = ba.buttonwidget(
+ parent=self._container,
+ size=(300, 70),
+ label=ba.Lstr(resource='gatherWindow.' 'manualConnectText'),
+ position=(self._c_width * 0.5 - 150, self._c_height - 350),
+ on_activate_call=self._join_connect_press,
+ autoselect=True,
+ )
+ ba.textwidget(
+ edit=self._join_party_code_text, on_return_press_call=btn.activate
+ )
def _on_get_tickets_press(self) -> None:
@@ -431,23 +479,27 @@ class PrivateGatherTab(GatherTab):
# Bring up get-tickets window and then kill ourself (we're on the
# overlay layer so we'd show up above it).
- getcurrency.GetCurrencyWindow(modal=True,
- origin_widget=self._get_tickets_button)
+ getcurrency.GetCurrencyWindow(
+ modal=True, origin_widget=self._get_tickets_button
+ )
def _build_host_tab(self) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
+ hostingstate = self._hostingstate
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=(0.6, 0.56, 0.6),
- position=(self._c_width * 0.5, self._c_height * 0.5),
- text=ba.Lstr(resource='notSignedInErrorText'))
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=(0.6, 0.56, 0.6),
+ position=(self._c_width * 0.5, self._c_height * 0.5),
+ text=ba.Lstr(resource='notSignedInErrorText'),
+ )
self._showing_not_signed_in_screen = True
return
self._showing_not_signed_in_screen = False
@@ -477,36 +529,44 @@ class PrivateGatherTab(GatherTab):
# If we're not currently hosting and hosting requires tickets,
# Show our count (possibly with a link to purchase more).
- if (not self._waiting_for_initial_state
- and self._hostingstate.party_code is None
- and self._hostingstate.tickets_to_host_now != 0):
+ if (
+ not self._waiting_for_initial_state
+ and hostingstate.party_code is None
+ and hostingstate.tickets_to_host_now != 0
+ ):
if not ba.app.ui.use_toolbars:
if ba.app.allow_ticket_purchases:
self._get_tickets_button = ba.buttonwidget(
parent=self._container,
- position=(self._c_width - 210 + 125,
- self._c_height - 44),
+ position=(
+ self._c_width - 210 + 125,
+ self._c_height - 44,
+ ),
autoselect=True,
scale=0.6,
size=(120, 60),
textcolor=(0.2, 1, 0.2),
label=ba.charstr(ba.SpecialChar.TICKET),
color=(0.65, 0.5, 0.8),
- on_activate_call=self._on_get_tickets_press)
+ on_activate_call=self._on_get_tickets_press,
+ )
else:
self._ticket_count_text = ba.textwidget(
parent=self._container,
scale=0.6,
- position=(self._c_width - 210 + 125,
- self._c_height - 44),
+ position=(
+ self._c_width - 210 + 125,
+ self._c_height - 44,
+ ),
color=(0.2, 1, 0.2),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
# Set initial ticket count.
self._update_currency_ui()
v = self._c_height - 90
- if self._hostingstate.party_code is None:
+ if hostingstate.party_code is None:
ba.textwidget(
parent=self._container,
size=(0, 0),
@@ -518,20 +578,24 @@ class PrivateGatherTab(GatherTab):
color=(0.5, 0.46, 0.5),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
- resource='gatherWindow.privatePartyCloudDescriptionText'))
+ resource='gatherWindow.privatePartyCloudDescriptionText'
+ ),
+ )
v -= 100
- if self._hostingstate.party_code is None:
+ if hostingstate.party_code is None:
# We've got no current party running; show options to set one up.
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=(0.6, 0.56, 0.6),
- position=(self._c_width * 0.5 - 210, v),
- text=ba.Lstr(resource='playlistText'))
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=(0.6, 0.56, 0.6),
+ position=(self._c_width * 0.5 - 210, v),
+ text=ba.Lstr(resource='playlistText'),
+ )
self._host_playlist_button = ba.buttonwidget(
parent=self._container,
size=(400, 70),
@@ -541,13 +605,16 @@ class PrivateGatherTab(GatherTab):
on_activate_call=self._playlist_press,
position=(self._c_width * 0.5 - 200, v - 35),
up_widget=self._host_sub_tab_text,
- autoselect=True)
+ autoselect=True,
+ )
# If it appears we're coming back from playlist selection,
# re-select our playlist button.
if ba.app.ui.selecting_private_party_playlist:
- ba.containerwidget(edit=self._container,
- selected_child=self._host_playlist_button)
+ ba.containerwidget(
+ edit=self._container,
+ selected_child=self._host_playlist_button,
+ )
ba.app.ui.selecting_private_party_playlist = False
else:
# We've got a current party; show its info.
@@ -560,24 +627,29 @@ class PrivateGatherTab(GatherTab):
scale=0.9,
color=(0.7, 0.64, 0.7),
position=(self._c_width * 0.5, v + 90),
- text=ba.Lstr(resource='gatherWindow.partyServerRunningText'))
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=600,
- scale=0.7,
- color=(0.7, 0.64, 0.7),
- position=(self._c_width * 0.5, v + 50),
- text=ba.Lstr(resource='gatherWindow.partyCodeText'))
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=2.0,
- color=(0.0, 1.0, 0.0),
- position=(self._c_width * 0.5, v + 10),
- text=self._hostingstate.party_code)
+ text=ba.Lstr(resource='gatherWindow.partyServerRunningText'),
+ )
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=600,
+ scale=0.7,
+ color=(0.7, 0.64, 0.7),
+ position=(self._c_width * 0.5, v + 50),
+ text=ba.Lstr(resource='gatherWindow.partyCodeText'),
+ )
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=2.0,
+ color=(0.0, 1.0, 0.0),
+ position=(self._c_width * 0.5, v + 10),
+ text=hostingstate.party_code,
+ )
# Also action buttons to copy it and connect to it.
if ba.clipboard_is_supported():
@@ -590,7 +662,8 @@ class PrivateGatherTab(GatherTab):
label=ba.Lstr(resource='gatherWindow.copyCodeText'),
on_activate_call=self._host_copy_press,
position=(self._c_width * 0.5 - 150, v - 70),
- autoselect=True)
+ autoselect=True,
+ )
else:
cbtnoffs = -70
self._host_connect_button = ba.buttonwidget(
@@ -601,7 +674,8 @@ class PrivateGatherTab(GatherTab):
label=ba.Lstr(resource='gatherWindow.manualConnectText'),
on_activate_call=self._host_connect_press,
position=(self._c_width * 0.5 + cbtnoffs, v - 70),
- autoselect=True)
+ autoselect=True,
+ )
v -= 120
@@ -610,7 +684,7 @@ class PrivateGatherTab(GatherTab):
# If we don't want to show anything until we get a state:
if self._waiting_for_initial_state:
pass
- elif self._hostingstate.unavailable_error is not None:
+ elif hostingstate.unavailable_error is not None:
# If hosting is unavailable, show the associated reason.
ba.textwidget(
parent=self._container,
@@ -622,9 +696,14 @@ class PrivateGatherTab(GatherTab):
flatness=1.0,
color=(1.0, 0.0, 0.0),
position=(self._c_width * 0.5, v),
- text=ba.Lstr(translate=('serverResponses',
- self._hostingstate.unavailable_error)))
- elif self._hostingstate.free_host_minutes_remaining is not None:
+ text=ba.Lstr(
+ translate=(
+ 'serverResponses',
+ hostingstate.unavailable_error,
+ )
+ ),
+ )
+ elif hostingstate.free_host_minutes_remaining is not None:
# If we've been pre-approved to start/stop for free, show that.
ba.textwidget(
parent=self._container,
@@ -634,20 +713,27 @@ class PrivateGatherTab(GatherTab):
maxwidth=self._c_width * 0.9,
scale=0.7,
flatness=1.0,
- color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else
- (0.0, 1.0, 0.0)),
+ color=(
+ (0.7, 0.64, 0.7)
+ if hostingstate.party_code
+ else (0.0, 1.0, 0.0)
+ ),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
resource='gatherWindow.startStopHostingMinutesText',
- subs=[(
- '${MINUTES}',
- f'{self._hostingstate.free_host_minutes_remaining:.0f}'
- )]))
+ subs=[
+ (
+ '${MINUTES}',
+ f'{hostingstate.free_host_minutes_remaining:.0f}',
+ )
+ ],
+ ),
+ )
else:
# Otherwise tell whether the free cloud server is available
# or will be at some point.
- if self._hostingstate.party_code is None:
- if self._hostingstate.tickets_to_host_now == 0:
+ if hostingstate.party_code is None:
+ if hostingstate.tickets_to_host_now == 0:
ba.textwidget(
parent=self._container,
size=(0, 0),
@@ -659,10 +745,13 @@ class PrivateGatherTab(GatherTab):
color=(0.0, 1.0, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
- resource=
- 'gatherWindow.freeCloudServerAvailableNowText'))
+ resource=(
+ 'gatherWindow.freeCloudServerAvailableNowText'
+ )
+ ),
+ )
else:
- if self._hostingstate.minutes_until_free_host is None:
+ if hostingstate.minutes_until_free_host is None:
ba.textwidget(
parent=self._container,
size=(0, 0),
@@ -674,11 +763,14 @@ class PrivateGatherTab(GatherTab):
color=(1.0, 0.6, 0.0),
position=(self._c_width * 0.5, v),
text=ba.Lstr(
- resource=
- 'gatherWindow.freeCloudServerNotAvailableText')
+ resource=(
+ 'gatherWindow'
+ '.freeCloudServerNotAvailableText'
+ )
+ ),
)
else:
- availmins = self._hostingstate.minutes_until_free_host
+ availmins = hostingstate.minutes_until_free_host
ba.textwidget(
parent=self._container,
size=(0, 0),
@@ -689,47 +781,60 @@ class PrivateGatherTab(GatherTab):
flatness=1.0,
color=(1.0, 0.6, 0.0),
position=(self._c_width * 0.5, v),
- text=ba.Lstr(resource='gatherWindow.'
- 'freeCloudServerAvailableMinutesText',
- subs=[('${MINUTES}',
- f'{availmins:.0f}')]))
+ text=ba.Lstr(
+ resource='gatherWindow.'
+ 'freeCloudServerAvailableMinutesText',
+ subs=[('${MINUTES}', f'{availmins:.0f}')],
+ ),
+ )
v -= 100
- if (self._waiting_for_start_stop_response
- or self._waiting_for_initial_state):
+ if (
+ self._waiting_for_start_stop_response
+ or self._waiting_for_initial_state
+ ):
btnlabel = ba.Lstr(resource='oneMomentText')
else:
- if self._hostingstate.unavailable_error is not None:
+ if hostingstate.unavailable_error is not None:
btnlabel = ba.Lstr(
- resource='gatherWindow.hostingUnavailableText')
- elif self._hostingstate.party_code is None:
+ resource='gatherWindow.hostingUnavailableText'
+ )
+ elif hostingstate.party_code is None:
ticon = ba.internal.charstr(ba.SpecialChar.TICKET)
- nowtickets = self._hostingstate.tickets_to_host_now
+ nowtickets = hostingstate.tickets_to_host_now
if nowtickets > 0:
btnlabel = ba.Lstr(
resource='gatherWindow.startHostingPaidText',
- subs=[('${COST}', f'{ticon}{nowtickets}')])
+ subs=[('${COST}', f'{ticon}{nowtickets}')],
+ )
else:
- btnlabel = ba.Lstr(
- resource='gatherWindow.startHostingText')
+ btnlabel = ba.Lstr(resource='gatherWindow.startHostingText')
else:
btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText')
- disabled = (self._hostingstate.unavailable_error is not None
- or self._waiting_for_initial_state)
+ disabled = (
+ hostingstate.unavailable_error is not None
+ or self._waiting_for_initial_state
+ )
waiting = self._waiting_for_start_stop_response
self._host_start_stop_button = ba.buttonwidget(
parent=self._container,
size=(400, 80),
- color=((0.6, 0.6, 0.6) if disabled else
- (0.5, 1.0, 0.5) if waiting else None),
+ color=(
+ (0.6, 0.6, 0.6)
+ if disabled
+ else (0.5, 1.0, 0.5)
+ if waiting
+ else None
+ ),
enable_sound=False,
label=btnlabel,
textcolor=((0.7, 0.7, 0.7) if disabled else None),
position=(self._c_width * 0.5 - 200, v),
on_activate_call=self._start_stop_button_press,
- autoselect=True)
+ autoselect=True,
+ )
def _playlist_press(self) -> None:
assert self._host_playlist_button is not None
@@ -746,18 +851,23 @@ class PrivateGatherTab(GatherTab):
def _debug_server_comm(self, msg: str) -> None:
if DEBUG_SERVER_COMMUNICATION:
- print(f'PPTABCOM: {msg} at time '
- f'{time.time()-self._create_time:.2f}')
+ print(
+ f'PPTABCOM: {msg} at time '
+ f'{time.time()-self._create_time:.2f}'
+ )
def _connect_to_party_code(self, code: str) -> None:
# Ignore attempted followup sends for a few seconds.
# (this will reset if we get a response)
now = time.time()
- if (self._connect_press_time is not None
- and now - self._connect_press_time < 5.0):
+ if (
+ self._connect_press_time is not None
+ and now - self._connect_press_time < 5.0
+ ):
self._debug_server_comm(
- 'not sending private party connect (too soon)')
+ 'not sending private party connect (too soon)'
+ )
return
self._connect_press_time = now
@@ -766,15 +876,17 @@ class PrivateGatherTab(GatherTab):
{
'type': 'PRIVATE_PARTY_CONNECT',
'expire_time': time.time() + 20,
- 'code': code
+ 'code': code,
},
callback=ba.WeakCall(self._connect_response),
)
ba.internal.run_transactions()
def _start_stop_button_press(self) -> None:
- if (self._waiting_for_start_stop_response
- or self._waiting_for_initial_state):
+ if (
+ self._waiting_for_start_stop_response
+ or self._waiting_for_initial_state
+ ):
return
if ba.internal.get_v1_account_state() != 'signed_in':
@@ -813,7 +925,8 @@ class PrivateGatherTab(GatherTab):
'region_pings': ba.app.net.zone_pings,
'expire_time': time.time() + 20,
},
- callback=ba.WeakCall(self._hosting_state_response))
+ callback=ba.WeakCall(self._hosting_state_response),
+ )
ba.internal.run_transactions()
else:
@@ -823,7 +936,8 @@ class PrivateGatherTab(GatherTab):
'type': 'PRIVATE_PARTY_STOP',
'expire_time': time.time() + 20,
},
- callback=ba.WeakCall(self._hosting_state_response))
+ callback=ba.WeakCall(self._hosting_state_response),
+ )
ba.internal.run_transactions()
ba.playsound(ba.getsound('click01'))
@@ -839,7 +953,8 @@ class PrivateGatherTab(GatherTab):
if not code:
ba.screenmessage(
ba.Lstr(resource='internal.invalidAddressErrorText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -850,14 +965,15 @@ class PrivateGatherTab(GatherTab):
self._connect_press_time = None
if result is None:
raise RuntimeError()
- cresult = dataclass_from_dict(PrivatePartyConnectResult,
- result,
- discard_unknown_attrs=True)
+ cresult = dataclass_from_dict(
+ PrivatePartyConnectResult, result, discard_unknown_attrs=True
+ )
if cresult.error is not None:
self._debug_server_comm('got error connect response')
ba.screenmessage(
ba.Lstr(translate=('serverResponses', cresult.error)),
- (1, 0, 0))
+ (1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
self._debug_server_comm('got valid connect response')
diff --git a/assets/src/ba_data/python/bastd/ui/gather/publictab.py b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
index 522c9e35..fafd19b4 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/publictab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
@@ -27,6 +27,7 @@ DEBUG_PROCESSING = False
class SubTabType(Enum):
"""Available sub-tabs."""
+
JOIN = 'join'
HOST = 'host'
@@ -34,6 +35,7 @@ class SubTabType(Enum):
@dataclass
class PartyEntry:
"""Info about a public party."""
+
address: str
index: int
queue: str | None = None
@@ -69,17 +71,27 @@ class UIRow:
def _clear(self) -> None:
for widget in [
- self._name_widget, self._size_widget, self._ping_widget,
- self._stats_button
+ self._name_widget,
+ self._size_widget,
+ self._ping_widget,
+ self._stats_button,
]:
if widget:
widget.delete()
- def update(self, index: int, party: PartyEntry, sub_scroll_width: float,
- sub_scroll_height: float, lineheight: float,
- columnwidget: ba.Widget, join_text: ba.Widget,
- filter_text: ba.Widget, existing_selection: Selection | None,
- tab: PublicGatherTab) -> None:
+ def update(
+ self,
+ index: int,
+ party: PartyEntry,
+ sub_scroll_width: float,
+ sub_scroll_height: float,
+ lineheight: float,
+ columnwidget: ba.Widget,
+ join_text: ba.Widget,
+ filter_text: ba.Widget,
+ existing_selection: Selection | None,
+ tab: PublicGatherTab,
+ ) -> None:
"""Update for the given data."""
# pylint: disable=too-many-locals
@@ -102,7 +114,8 @@ class UIRow:
selectable=True,
on_select_call=ba.WeakCall(
tab.set_public_party_selection,
- Selection(party.get_key(), SelectionComponent.NAME)),
+ Selection(party.get_key(), SelectionComponent.NAME),
+ ),
on_activate_call=ba.WeakCall(tab.on_public_party_activate, party),
click_activate=True,
maxwidth=sub_scroll_width * 0.45,
@@ -110,20 +123,27 @@ class UIRow:
autoselect=True,
color=(1, 1, 1, 0.3 if party.ping is None else 1.0),
h_align='left',
- v_align='center')
- ba.widget(edit=self._name_widget,
- left_widget=join_text,
- show_buffer_top=64.0,
- show_buffer_bottom=64.0)
- if existing_selection == Selection(party.get_key(),
- SelectionComponent.NAME):
- ba.containerwidget(edit=columnwidget,
- selected_child=self._name_widget)
+ v_align='center',
+ )
+ ba.widget(
+ edit=self._name_widget,
+ left_widget=join_text,
+ show_buffer_top=64.0,
+ show_buffer_bottom=64.0,
+ )
+ if existing_selection == Selection(
+ party.get_key(), SelectionComponent.NAME
+ ):
+ ba.containerwidget(
+ edit=columnwidget, selected_child=self._name_widget
+ )
if party.stats_addr:
url = party.stats_addr.replace(
'${ACCOUNT}',
ba.internal.get_v1_account_misc_read_val_2(
- 'resolvedAccountID', 'UNKNOWN'))
+ 'resolvedAccountID', 'UNKNOWN'
+ ),
+ )
self._stats_button = ba.buttonwidget(
color=(0.3, 0.6, 0.94),
textcolor=(1.0, 1.0, 1.0),
@@ -133,15 +153,18 @@ class UIRow:
on_activate_call=ba.Call(ba.open_url, url),
on_select_call=ba.WeakCall(
tab.set_public_party_selection,
- Selection(party.get_key(),
- SelectionComponent.STATS_BUTTON)),
+ Selection(party.get_key(), SelectionComponent.STATS_BUTTON),
+ ),
size=(120, 40),
position=(sub_scroll_width * 0.66 + hpos, 1 + vpos),
- scale=0.9)
+ scale=0.9,
+ )
if existing_selection == Selection(
- party.get_key(), SelectionComponent.STATS_BUTTON):
- ba.containerwidget(edit=columnwidget,
- selected_child=self._stats_button)
+ party.get_key(), SelectionComponent.STATS_BUTTON
+ ):
+ ba.containerwidget(
+ edit=columnwidget, selected_child=self._stats_button
+ )
self._size_widget = ba.textwidget(
text=str(party.size) + '/' + str(party.size_max),
@@ -151,29 +174,36 @@ class UIRow:
scale=0.7,
color=(0.8, 0.8, 0.8),
h_align='right',
- v_align='center')
+ v_align='center',
+ )
if index == 0:
ba.widget(edit=self._name_widget, up_widget=filter_text)
if self._stats_button:
ba.widget(edit=self._stats_button, up_widget=filter_text)
- self._ping_widget = ba.textwidget(parent=columnwidget,
- size=(0, 0),
- position=(sub_scroll_width * 0.94 +
- hpos, 20 + vpos),
- scale=0.7,
- h_align='right',
- v_align='center')
+ self._ping_widget = ba.textwidget(
+ parent=columnwidget,
+ size=(0, 0),
+ position=(sub_scroll_width * 0.94 + hpos, 20 + vpos),
+ scale=0.7,
+ h_align='right',
+ v_align='center',
+ )
if party.ping is None:
- ba.textwidget(edit=self._ping_widget,
- text='-',
- color=(0.5, 0.5, 0.5))
+ ba.textwidget(
+ edit=self._ping_widget, text='-', color=(0.5, 0.5, 0.5)
+ )
else:
- ba.textwidget(edit=self._ping_widget,
- text=str(int(party.ping)),
- color=(0, 1, 0) if party.ping <= ping_good else
- (1, 1, 0) if party.ping <= ping_med else (1, 0, 0))
+ ba.textwidget(
+ edit=self._ping_widget,
+ text=str(int(party.ping)),
+ color=(0, 1, 0)
+ if party.ping <= ping_good
+ else (1, 1, 0)
+ if party.ping <= ping_med
+ else (1, 0, 0),
+ )
party.clean_display_index = index
@@ -181,6 +211,7 @@ class UIRow:
@dataclass
class State:
"""State saved/restored only while the app is running."""
+
sub_tab: SubTabType = SubTabType.JOIN
parties: list[tuple[str, PartyEntry]] | None = None
next_entry_index: int = 0
@@ -191,6 +222,7 @@ class State:
class SelectionComponent(Enum):
"""Describes what part of an entry is selected."""
+
NAME = 'name'
STATS_BUTTON = 'stats_button'
@@ -198,6 +230,7 @@ class SelectionComponent(Enum):
@dataclass
class Selection:
"""Describes the currently selected list element."""
+
entry_key: str
component: SelectionComponent
@@ -213,6 +246,7 @@ class AddrFetchThread(threading.Thread):
try:
# FIXME: Update this to work with IPv6 at some point.
import socket
+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(('8.8.8.8', 80))
val = sock.getsockname()[0]
@@ -220,6 +254,7 @@ class AddrFetchThread(threading.Thread):
ba.pushcall(ba.Call(self._call, val), from_other_thread=True)
except Exception as exc:
from efro.error import is_udp_communication_error
+
# Ignore expected network errors; log others.
if is_udp_communication_error(exc):
pass
@@ -230,8 +265,12 @@ class AddrFetchThread(threading.Thread):
class PingThread(threading.Thread):
"""Thread for sending out game pings."""
- def __init__(self, address: str, port: int,
- call: Callable[[str, int, float | None], int | None]):
+ def __init__(
+ self,
+ address: str,
+ port: int,
+ call: Callable[[str, int, float | None], int | None],
+ ):
super().__init__()
self._address = address
self._port = port
@@ -243,6 +282,7 @@ class PingThread(threading.Thread):
try:
import socket
from ba.internal import get_ip_address_type
+
socket_type = get_ip_address_type(self._address)
sock = socket.socket(socket_type, socket.SOCK_DGRAM)
sock.connect((self._address, self._port))
@@ -267,11 +307,18 @@ class PingThread(threading.Thread):
break
time.sleep(1)
ping = (time.time() - starttime) * 1000.0
- ba.pushcall(ba.Call(self._call, self._address, self._port,
- ping if accessible else None),
- from_other_thread=True)
+ ba.pushcall(
+ ba.Call(
+ self._call,
+ self._address,
+ self._port,
+ ping if accessible else None,
+ ),
+ from_other_thread=True,
+ )
except Exception as exc:
from efro.error import is_udp_communication_error
+
if is_udp_communication_error(exc):
pass
else:
@@ -347,11 +394,14 @@ class PublicGatherTab(GatherTab):
c_height = region_height - 20
self._container = ba.containerwidget(
parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - c_height) * 0.5),
+ position=(
+ region_left,
+ region_bottom + (region_height - c_height) * 0.5,
+ ),
size=(c_width, c_height),
background=False,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
v = c_height - 30
self._join_text = ba.textwidget(
parent=self._container,
@@ -371,8 +421,10 @@ class PublicGatherTab(GatherTab):
region_height,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.'
- 'joinPublicPartyDescriptionText'))
+ text=ba.Lstr(
+ resource='gatherWindow.' 'joinPublicPartyDescriptionText'
+ ),
+ )
self._host_text = ba.textwidget(
parent=self._container,
position=(c_width * 0.5 + 45, v - 13),
@@ -391,12 +443,16 @@ class PublicGatherTab(GatherTab):
region_height,
playsound=True,
),
- text=ba.Lstr(resource='gatherWindow.'
- 'hostPublicPartyDescriptionText'))
+ text=ba.Lstr(
+ resource='gatherWindow.' 'hostPublicPartyDescriptionText'
+ ),
+ )
ba.widget(edit=self._join_text, up_widget=tab_button)
- ba.widget(edit=self._host_text,
- left_widget=self._join_text,
- up_widget=tab_button)
+ ba.widget(
+ edit=self._host_text,
+ left_widget=self._join_text,
+ up_widget=tab_button,
+ )
ba.widget(edit=self._join_text, right_widget=self._host_text)
# Attempt to fetch our local address so we have it for error messages.
@@ -404,10 +460,12 @@ class PublicGatherTab(GatherTab):
AddrFetchThread(ba.WeakCall(self._fetch_local_addr_cb)).start()
self._set_sub_tab(self._sub_tab, region_width, region_height)
- self._update_timer = ba.Timer(0.1,
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 0.1,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
return self._container
def on_deactivate(self) -> None:
@@ -425,7 +483,8 @@ class PublicGatherTab(GatherTab):
next_entry_index=self._next_entry_index,
filter_value=self._filter_value,
have_server_list_response=self._have_server_list_response,
- have_valid_server_list=self._have_valid_server_list)
+ have_valid_server_list=self._have_valid_server_list,
+ )
def restore_state(self) -> None:
state = ba.app.ui.window_states.get(type(self))
@@ -437,8 +496,7 @@ class PublicGatherTab(GatherTab):
# Restore the parties we stored.
if state.parties:
self._parties = {
- key: copy.copy(party)
- for key, party in state.parties
+ key: copy.copy(party) for key, party in state.parties
}
self._parties_sorted = list(self._parties.items())
self._party_lists_dirty = True
@@ -450,11 +508,13 @@ class PublicGatherTab(GatherTab):
self._have_valid_server_list = state.have_valid_server_list
self._filter_value = state.filter_value
- def _set_sub_tab(self,
- value: SubTabType,
- region_width: float,
- region_height: float,
- playsound: bool = False) -> None:
+ def _set_sub_tab(
+ self,
+ value: SubTabType,
+ region_width: float,
+ region_height: float,
+ playsound: bool = False,
+ ) -> None:
assert self._container
if playsound:
ba.playsound(ba.getsound('click01'))
@@ -475,10 +535,12 @@ class PublicGatherTab(GatherTab):
inactive_color = (0.5, 0.4, 0.5)
ba.textwidget(
edit=self._join_text,
- color=active_color if value is SubTabType.JOIN else inactive_color)
+ color=active_color if value is SubTabType.JOIN else inactive_color,
+ )
ba.textwidget(
edit=self._host_text,
- color=active_color if value is SubTabType.HOST else inactive_color)
+ color=active_color if value is SubTabType.HOST else inactive_color,
+ )
# Clear anything existing in the old sub-tab.
for widget in self._container.get_children():
@@ -491,8 +553,9 @@ class PublicGatherTab(GatherTab):
if value is SubTabType.HOST:
self._build_host_tab(region_width, region_height)
- def _build_join_tab(self, region_width: float,
- region_height: float) -> None:
+ def _build_join_tab(
+ self, region_width: float, region_height: float
+ ) -> None:
c_width = region_width
c_height = region_height - 20
sub_scroll_height = c_height - 125
@@ -500,56 +563,66 @@ class PublicGatherTab(GatherTab):
v = c_height - 35
v -= 60
filter_txt = ba.Lstr(resource='filterText')
- self._filter_text = ba.textwidget(parent=self._container,
- text=self._filter_value,
- size=(350, 45),
- position=(290, v - 10),
- h_align='left',
- v_align='center',
- editable=True,
- description=filter_txt)
+ self._filter_text = ba.textwidget(
+ parent=self._container,
+ text=self._filter_value,
+ size=(350, 45),
+ position=(290, v - 10),
+ h_align='left',
+ v_align='center',
+ editable=True,
+ description=filter_txt,
+ )
ba.widget(edit=self._filter_text, up_widget=self._join_text)
- ba.textwidget(text=filter_txt,
- parent=self._container,
- size=(0, 0),
- position=(270, v + 13),
- maxwidth=150,
- scale=0.8,
- color=(0.5, 0.46, 0.5),
- flatness=1.0,
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ text=filter_txt,
+ parent=self._container,
+ size=(0, 0),
+ position=(270, v + 13),
+ maxwidth=150,
+ scale=0.8,
+ color=(0.5, 0.46, 0.5),
+ flatness=1.0,
+ h_align='right',
+ v_align='center',
+ )
- ba.textwidget(text=ba.Lstr(resource='nameText'),
- parent=self._container,
- size=(0, 0),
- position=(90, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.46, 0.5),
- flatness=1.0,
- h_align='center',
- v_align='center')
- ba.textwidget(text=ba.Lstr(resource='gatherWindow.partySizeText'),
- parent=self._container,
- size=(0, 0),
- position=(755, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.46, 0.5),
- flatness=1.0,
- h_align='center',
- v_align='center')
- ba.textwidget(text=ba.Lstr(resource='gatherWindow.pingText'),
- parent=self._container,
- size=(0, 0),
- position=(825, v - 8),
- maxwidth=60,
- scale=0.6,
- color=(0.5, 0.46, 0.5),
- flatness=1.0,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ text=ba.Lstr(resource='nameText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(90, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.46, 0.5),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ )
+ ba.textwidget(
+ text=ba.Lstr(resource='gatherWindow.partySizeText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(755, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.46, 0.5),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ )
+ ba.textwidget(
+ text=ba.Lstr(resource='gatherWindow.pingText'),
+ parent=self._container,
+ size=(0, 0),
+ position=(825, v - 8),
+ maxwidth=60,
+ scale=0.6,
+ color=(0.5, 0.46, 0.5),
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ )
v -= sub_scroll_height + 23
self._host_scrollwidget = scrollw = ba.scrollwidget(
parent=self._container,
@@ -558,26 +631,31 @@ class PublicGatherTab(GatherTab):
size=(sub_scroll_width, sub_scroll_height),
claims_up_down=False,
claims_left_right=True,
- autoselect=True)
- self._join_list_column = ba.containerwidget(parent=scrollw,
- background=False,
- size=(400, 400),
- claims_left_right=True)
- self._join_status_text = ba.textwidget(parent=self._container,
- text='',
- size=(0, 0),
- scale=0.9,
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='top',
- maxwidth=c_width,
- color=(0.6, 0.6, 0.6),
- position=(c_width * 0.5,
- c_height * 0.5))
+ autoselect=True,
+ )
+ self._join_list_column = ba.containerwidget(
+ parent=scrollw,
+ background=False,
+ size=(400, 400),
+ claims_left_right=True,
+ )
+ self._join_status_text = ba.textwidget(
+ parent=self._container,
+ text='',
+ size=(0, 0),
+ scale=0.9,
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='top',
+ maxwidth=c_width,
+ color=(0.6, 0.6, 0.6),
+ position=(c_width * 0.5, c_height * 0.5),
+ )
- def _build_host_tab(self, region_width: float,
- region_height: float) -> None:
+ def _build_host_tab(
+ self, region_width: float, region_height: float
+ ) -> None:
c_width = region_width
c_height = region_height - 20
v = c_height - 35
@@ -595,46 +673,55 @@ class PublicGatherTab(GatherTab):
flatness=1.0,
color=(0.5, 0.46, 0.5),
position=(region_width * 0.5, v + 10),
- text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText'))
+ text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText'),
+ )
v -= 30
party_name_text = ba.Lstr(
resource='gatherWindow.partyNameText',
- fallback_resource='editGameListWindow.nameText')
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=ba.app.ui.infotextcolor,
- position=(210, v - 9),
- text=party_name_text)
- self._host_name_text = ba.textwidget(parent=self._container,
- editable=True,
- size=(535, 40),
- position=(230, v - 30),
- text=ba.app.config.get(
- 'Public Party Name', ''),
- maxwidth=494,
- shadow=0.3,
- flatness=1.0,
- description=party_name_text,
- autoselect=True,
- v_align='center',
- corner_scale=1.0)
+ fallback_resource='editGameListWindow.nameText',
+ )
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=ba.app.ui.infotextcolor,
+ position=(210, v - 9),
+ text=party_name_text,
+ )
+ self._host_name_text = ba.textwidget(
+ parent=self._container,
+ editable=True,
+ size=(535, 40),
+ position=(230, v - 30),
+ text=ba.app.config.get('Public Party Name', ''),
+ maxwidth=494,
+ shadow=0.3,
+ flatness=1.0,
+ description=party_name_text,
+ autoselect=True,
+ v_align='center',
+ corner_scale=1.0,
+ )
v -= 60
- ba.textwidget(parent=self._container,
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- scale=0.8,
- color=ba.app.ui.infotextcolor,
- position=(210, v - 9),
- text=ba.Lstr(resource='maxPartySizeText',
- fallback_resource='maxConnectionsText'))
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=ba.app.ui.infotextcolor,
+ position=(210, v - 9),
+ text=ba.Lstr(
+ resource='maxPartySizeText',
+ fallback_resource='maxConnectionsText',
+ ),
+ )
self._host_max_party_size_value = ba.textwidget(
parent=self._container,
size=(0, 0),
@@ -643,42 +730,51 @@ class PublicGatherTab(GatherTab):
scale=1.2,
color=(1, 1, 1),
position=(240, v - 9),
- text=str(ba.internal.get_public_party_max_size()))
- btn1 = self._host_max_party_size_minus_button = (ba.buttonwidget(
+ text=str(ba.internal.get_public_party_max_size()),
+ )
+ btn1 = self._host_max_party_size_minus_button = ba.buttonwidget(
parent=self._container,
size=(40, 40),
on_activate_call=ba.WeakCall(
- self._on_max_public_party_size_minus_press),
+ self._on_max_public_party_size_minus_press
+ ),
position=(280, v - 26),
label='-',
- autoselect=True))
- btn2 = self._host_max_party_size_plus_button = (ba.buttonwidget(
+ autoselect=True,
+ )
+ btn2 = self._host_max_party_size_plus_button = ba.buttonwidget(
parent=self._container,
size=(40, 40),
on_activate_call=ba.WeakCall(
- self._on_max_public_party_size_plus_press),
+ self._on_max_public_party_size_plus_press
+ ),
position=(350, v - 26),
label='+',
- autoselect=True))
+ autoselect=True,
+ )
v -= 50
v -= 70
if is_public_enabled:
label = ba.Lstr(
resource='gatherWindow.makePartyPrivateText',
- fallback_resource='gatherWindow.stopAdvertisingText')
+ fallback_resource='gatherWindow.stopAdvertisingText',
+ )
else:
label = ba.Lstr(
resource='gatherWindow.makePartyPublicText',
- fallback_resource='gatherWindow.startAdvertisingText')
+ fallback_resource='gatherWindow.startAdvertisingText',
+ )
self._host_toggle_button = ba.buttonwidget(
parent=self._container,
label=label,
size=(400, 80),
on_activate_call=self._on_stop_advertising_press
- if is_public_enabled else self._on_start_advertizing_press,
+ if is_public_enabled
+ else self._on_start_advertizing_press,
position=(c_width * 0.5 - 200, v),
autoselect=True,
- up_widget=btn2)
+ up_widget=btn2,
+ )
ba.widget(edit=self._host_name_text, down_widget=btn2)
ba.widget(edit=btn2, up_widget=self._host_name_text)
ba.widget(edit=btn1, up_widget=self._host_name_text)
@@ -686,8 +782,7 @@ class PublicGatherTab(GatherTab):
v -= 10
self._host_status_text = ba.textwidget(
parent=self._container,
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusNotPublicText'),
+ text=ba.Lstr(resource='gatherWindow.' 'partyStatusNotPublicText'),
size=(0, 0),
scale=0.7,
flatness=1.0,
@@ -695,7 +790,8 @@ class PublicGatherTab(GatherTab):
v_align='top',
maxwidth=c_width * 0.9,
color=(0.6, 0.56, 0.6),
- position=(c_width * 0.5, v))
+ position=(c_width * 0.5, v),
+ )
v -= 90
ba.textwidget(
parent=self._container,
@@ -707,15 +803,17 @@ class PublicGatherTab(GatherTab):
v_align='center',
maxwidth=c_width * 0.9,
color=(0.5, 0.46, 0.5),
- position=(c_width * 0.5, v))
+ position=(c_width * 0.5, v),
+ )
# If public sharing is already on,
# launch a status-check immediately.
if ba.internal.get_public_party_enabled():
self._do_status_check()
- def _on_public_party_query_result(self,
- result: dict[str, Any] | None) -> None:
+ def _on_public_party_query_result(
+ self, result: dict[str, Any] | None
+ ) -> None:
starttime = time.time()
self._have_server_list_response = True
@@ -748,18 +846,17 @@ class PublicGatherTab(GatherTab):
if party is not None:
party.claimed = True
self._parties = {
- key: val
- for key, val in list(self._parties.items()) if val.claimed
+ key: val for key, val in list(self._parties.items()) if val.claimed
}
- self._parties_sorted = [
- p for p in self._parties_sorted if p[1].claimed
- ]
+ self._parties_sorted = [p for p in self._parties_sorted if p[1].claimed]
self._party_lists_dirty = True
# self._update_server_list()
if DEBUG_PROCESSING:
- print(f'Handled public party query results in '
- f'{time.time()-starttime:.5f}s.')
+ print(
+ f'Handled public party query results in '
+ f'{time.time()-starttime:.5f}s.'
+ )
def _update(self) -> None:
"""Periodic updating."""
@@ -812,8 +909,9 @@ class PublicGatherTab(GatherTab):
status_text = self._join_status_text
if status_text:
if not signed_in:
- ba.textwidget(edit=status_text,
- text=ba.Lstr(resource='notSignedInText'))
+ ba.textwidget(
+ edit=status_text, text=ba.Lstr(resource='notSignedInText')
+ )
else:
# If we have a valid list, show no status; just the list.
# Otherwise show either 'loading...' or 'error' depending
@@ -822,16 +920,22 @@ class PublicGatherTab(GatherTab):
ba.textwidget(edit=status_text, text='')
else:
if self._have_server_list_response:
- ba.textwidget(edit=status_text,
- text=ba.Lstr(resource='errorText'))
+ ba.textwidget(
+ edit=status_text, text=ba.Lstr(resource='errorText')
+ )
else:
ba.textwidget(
edit=status_text,
text=ba.Lstr(
value='${A}...',
- subs=[('${A}',
- ba.Lstr(resource='store.loadingText'))],
- ))
+ subs=[
+ (
+ '${A}',
+ ba.Lstr(resource='store.loadingText'),
+ )
+ ],
+ ),
+ )
self._update_party_rows()
@@ -845,8 +949,10 @@ class PublicGatherTab(GatherTab):
# Janky - allow escaping when there's nothing in our list.
assert self._host_scrollwidget
- ba.containerwidget(edit=self._host_scrollwidget,
- claims_up_down=(len(self._parties_displayed) > 0))
+ ba.containerwidget(
+ edit=self._host_scrollwidget,
+ claims_up_down=(len(self._parties_displayed) > 0),
+ )
# Clip if we have more UI rows than parties to show.
clipcount = len(self._ui_rows) - len(self._parties_displayed)
@@ -861,8 +967,9 @@ class PublicGatherTab(GatherTab):
sub_scroll_width = 830
lineheight = 42
sub_scroll_height = lineheight * len(self._parties_displayed) + 50
- ba.containerwidget(edit=columnwidget,
- size=(sub_scroll_width, sub_scroll_height))
+ ba.containerwidget(
+ edit=columnwidget, size=(sub_scroll_width, sub_scroll_height)
+ )
# Any time our height changes, reset the refresh back to the top
# so we don't see ugly empty spaces appearing during initial list
@@ -913,7 +1020,8 @@ class PublicGatherTab(GatherTab):
join_text=self._join_text,
existing_selection=self._selection,
filter_text=self._filter_text,
- tab=self)
+ tab=self,
+ )
self._refresh_ui_row = refresh_row + 1
rowcount -= 1
@@ -939,10 +1047,12 @@ class PublicGatherTab(GatherTab):
party = self._parties.get(party_key)
if party is None:
# If this party is new to us, init it.
- party = PartyEntry(address=addr,
- next_ping_time=ba.time(ba.TimeType.REAL) +
- 0.001 * party_in['pd'],
- index=self._next_entry_index)
+ party = PartyEntry(
+ address=addr,
+ next_ping_time=ba.time(ba.TimeType.REAL)
+ + 0.001 * party_in['pd'],
+ index=self._next_entry_index,
+ )
self._parties[party_key] = party
self._parties_sorted.append((party_key, party))
self._party_lists_dirty = True
@@ -971,8 +1081,10 @@ class PublicGatherTab(GatherTab):
party.clean_display_index = None
if DEBUG_PROCESSING and parties_in:
- print(f'Processed {len(parties_in)} raw party infos in'
- f' {time.time()-starttime:.5f}s.')
+ print(
+ f'Processed {len(parties_in)} raw party infos in'
+ f' {time.time()-starttime:.5f}s.'
+ )
def _update_party_lists(self) -> None:
if not self._party_lists_dirty:
@@ -980,14 +1092,19 @@ class PublicGatherTab(GatherTab):
starttime = time.time()
assert len(self._parties_sorted) == len(self._parties)
- self._parties_sorted.sort(key=lambda p: (
- p[1].queue is None, # Show non-queued last.
- p[1].ping if p[1].ping is not None else 999999.0,
- p[1].index))
+ self._parties_sorted.sort(
+ key=lambda p: (
+ p[1].queue is None, # Show non-queued last.
+ p[1].ping if p[1].ping is not None else 999999.0,
+ p[1].index,
+ )
+ )
# If signed out or errored, show no parties.
- if (ba.internal.get_v1_account_state() != 'signed_in'
- or not self._have_valid_server_list):
+ if (
+ ba.internal.get_v1_account_state() != 'signed_in'
+ or not self._have_valid_server_list
+ ):
self._parties_displayed = {}
else:
if self._filter_value:
@@ -1002,8 +1119,10 @@ class PublicGatherTab(GatherTab):
# Any time our selection disappears from the displayed list, go back to
# auto-selecting the top entry.
- if (self._selection is not None
- and self._selection.entry_key not in self._parties_displayed):
+ if (
+ self._selection is not None
+ and self._selection.entry_key not in self._parties_displayed
+ ):
self._have_user_selected_row = False
# Whenever the user hasn't selected something, keep the first visible
@@ -1014,17 +1133,23 @@ class PublicGatherTab(GatherTab):
self._party_lists_dirty = False
if DEBUG_PROCESSING:
- print(f'Sorted {len(self._parties_sorted)} parties in'
- f' {time.time()-starttime:.5f}s.')
+ print(
+ f'Sorted {len(self._parties_sorted)} parties in'
+ f' {time.time()-starttime:.5f}s.'
+ )
def _query_party_list_periodically(self) -> None:
now = ba.time(ba.TimeType.REAL)
# Fire off a new public-party query periodically.
- if (self._last_server_list_query_time is None
- or now - self._last_server_list_query_time >
- 0.001 * ba.internal.get_v1_account_misc_read_val(
- 'pubPartyRefreshMS', 10000)):
+ if (
+ self._last_server_list_query_time is None
+ or now - self._last_server_list_query_time
+ > 0.001
+ * ba.internal.get_v1_account_misc_read_val(
+ 'pubPartyRefreshMS', 10000
+ )
+ ):
self._last_server_list_query_time = now
if DEBUG_SERVER_COMMUNICATION:
print('REQUESTING SERVER LIST')
@@ -1033,9 +1158,10 @@ class PublicGatherTab(GatherTab):
{
'type': 'PUBLIC_PARTY_QUERY',
'proto': ba.app.protocol_version,
- 'lang': ba.app.lang.language
+ 'lang': ba.app.lang.language,
},
- callback=ba.WeakCall(self._on_public_party_query_result))
+ callback=ba.WeakCall(self._on_public_party_query_result),
+ )
ba.internal.run_transactions()
else:
self._on_public_party_query_result(None)
@@ -1057,23 +1183,28 @@ class PublicGatherTab(GatherTab):
elif party.ping_attempts > 2:
mult = 5
if party.ping is not None:
- mult = (10 if party.ping > 300 else
- 5 if party.ping > 150 else 2)
+ mult = (
+ 10 if party.ping > 300 else 5 if party.ping > 150 else 2
+ )
interval = party.ping_interval * mult
if DEBUG_SERVER_COMMUNICATION:
- print(f'pinging #{party.index} cur={party.ping} '
- f'interval={interval} '
- f'({party.ping_responses}/{party.ping_attempts})')
+ print(
+ f'pinging #{party.index} cur={party.ping} '
+ f'interval={interval} '
+ f'({party.ping_responses}/{party.ping_attempts})'
+ )
party.next_ping_time = now + party.ping_interval * mult
party.ping_attempts += 1
- PingThread(party.address, party.port,
- ba.WeakCall(self._ping_callback)).start()
+ PingThread(
+ party.address, party.port, ba.WeakCall(self._ping_callback)
+ ).start()
- def _ping_callback(self, address: str, port: int | None,
- result: float | None) -> None:
+ def _ping_callback(
+ self, address: str, port: int | None, result: float | None
+ ) -> None:
# Look for a widget corresponding to this target.
# If we find one, update our list.
party_key = f'{address}_{port}'
@@ -1085,11 +1216,11 @@ class PublicGatherTab(GatherTab):
# We now smooth ping a bit to reduce jumping around in the list
# (only where pings are relatively good).
current_ping = party.ping
- if (current_ping is not None and result is not None
- and result < 150):
+ if current_ping is not None and result is not None and result < 150:
smoothing = 0.7
- party.ping = (smoothing * current_ping +
- (1.0 - smoothing) * result)
+ party.ping = (
+ smoothing * current_ping + (1.0 - smoothing) * result
+ )
else:
party.ping = result
@@ -1101,7 +1232,8 @@ class PublicGatherTab(GatherTab):
self._local_address = str(val)
def _on_public_party_accessible_response(
- self, data: dict[str, Any] | None) -> None:
+ self, data: dict[str, Any] | None
+ ) -> None:
# If we've got status text widgets, update them.
text = self._host_status_text
@@ -1109,8 +1241,9 @@ class PublicGatherTab(GatherTab):
if data is None:
ba.textwidget(
edit=text,
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusNoConnectionText'),
+ text=ba.Lstr(
+ resource='gatherWindow.' 'partyStatusNoConnectionText'
+ ),
color=(1, 0, 0),
)
else:
@@ -1119,10 +1252,17 @@ class PublicGatherTab(GatherTab):
if self._local_address is not None:
ex_line = ba.Lstr(
value='\n${A} ${B}',
- subs=[('${A}',
- ba.Lstr(resource='gatherWindow.'
- 'manualYourLocalAddressText')),
- ('${B}', self._local_address)])
+ subs=[
+ (
+ '${A}',
+ ba.Lstr(
+ resource='gatherWindow.'
+ 'manualYourLocalAddressText'
+ ),
+ ),
+ ('${B}', self._local_address),
+ ],
+ )
else:
ex_line = ''
ba.textwidget(
@@ -1130,44 +1270,69 @@ class PublicGatherTab(GatherTab):
text=ba.Lstr(
value='${A}\n${B}${C}',
subs=[
- ('${A}',
- ba.Lstr(resource='gatherWindow.'
- 'partyStatusNotJoinableText')),
- ('${B}',
- ba.Lstr(resource='gatherWindow.'
- 'manualRouterForwardingText',
- subs=[
- ('${PORT}',
- str(ba.internal.get_game_port()))
- ])), ('${C}', ex_line)
- ]),
- color=(1, 0, 0))
+ (
+ '${A}',
+ ba.Lstr(
+ resource='gatherWindow.'
+ 'partyStatusNotJoinableText'
+ ),
+ ),
+ (
+ '${B}',
+ ba.Lstr(
+ resource='gatherWindow.'
+ 'manualRouterForwardingText',
+ subs=[
+ (
+ '${PORT}',
+ str(
+ ba.internal.get_game_port()
+ ),
+ )
+ ],
+ ),
+ ),
+ ('${C}', ex_line),
+ ],
+ ),
+ color=(1, 0, 0),
+ )
else:
- ba.textwidget(edit=text,
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusJoinableText'),
- color=(0, 1, 0))
+ ba.textwidget(
+ edit=text,
+ text=ba.Lstr(
+ resource='gatherWindow.' 'partyStatusJoinableText'
+ ),
+ color=(0, 1, 0),
+ )
def _do_status_check(self) -> None:
from ba.internal import master_server_get
- ba.textwidget(edit=self._host_status_text,
- color=(1, 1, 0),
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusCheckingText'))
- master_server_get('bsAccessCheck', {'b': ba.app.build_number},
- callback=ba.WeakCall(
- self._on_public_party_accessible_response))
+
+ ba.textwidget(
+ edit=self._host_status_text,
+ color=(1, 1, 0),
+ text=ba.Lstr(resource='gatherWindow.' 'partyStatusCheckingText'),
+ )
+ master_server_get(
+ 'bsAccessCheck',
+ {'b': ba.app.build_number},
+ callback=ba.WeakCall(self._on_public_party_accessible_response),
+ )
def _on_start_advertizing_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
name = cast(str, ba.textwidget(query=self._host_name_text))
if name == '':
- ba.screenmessage(ba.Lstr(resource='internal.invalidNameErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='internal.invalidNameErrorText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
ba.internal.set_public_party_name(name)
@@ -1186,8 +1351,10 @@ class PublicGatherTab(GatherTab):
edit=self._host_toggle_button,
label=ba.Lstr(
resource='gatherWindow.makePartyPrivateText',
- fallback_resource='gatherWindow.stopAdvertisingText'),
- on_activate_call=self._on_stop_advertising_press)
+ fallback_resource='gatherWindow.stopAdvertisingText',
+ ),
+ on_activate_call=self._on_stop_advertising_press,
+ )
def _on_stop_advertising_press(self) -> None:
ba.internal.set_public_party_enabled(False)
@@ -1200,22 +1367,26 @@ class PublicGatherTab(GatherTab):
if text:
ba.textwidget(
edit=text,
- text=ba.Lstr(resource='gatherWindow.'
- 'partyStatusNotPublicText'),
+ text=ba.Lstr(
+ resource='gatherWindow.' 'partyStatusNotPublicText'
+ ),
color=(0.6, 0.6, 0.6),
)
ba.buttonwidget(
edit=self._host_toggle_button,
label=ba.Lstr(
resource='gatherWindow.makePartyPublicText',
- fallback_resource='gatherWindow.startAdvertisingText'),
- on_activate_call=self._on_start_advertizing_press)
+ fallback_resource='gatherWindow.startAdvertisingText',
+ ),
+ on_activate_call=self._on_start_advertizing_press,
+ )
def on_public_party_activate(self, party: PartyEntry) -> None:
"""Called when a party is clicked or otherwise activated."""
self.save_state()
if party.queue is not None:
from bastd.ui.partyqueue import PartyQueueWindow
+
ba.playsound(ba.getsound('swish'))
PartyQueueWindow(party.queue, party.address, party.port)
else:
diff --git a/assets/src/ba_data/python/bastd/ui/getcurrency.py b/assets/src/ba_data/python/bastd/ui/getcurrency.py
index 549fec42..6d4cd90b 100644
--- a/assets/src/ba_data/python/bastd/ui/getcurrency.py
+++ b/assets/src/ba_data/python/bastd/ui/getcurrency.py
@@ -16,12 +16,14 @@ if TYPE_CHECKING:
class GetCurrencyWindow(ba.Window):
"""Window for purchasing/acquiring currency."""
- def __init__(self,
- transition: str = 'in_right',
- from_modal_store: bool = False,
- modal: bool = False,
- origin_widget: ba.Widget | None = None,
- store_back_location: str | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ from_modal_store: bool = False,
+ modal: bool = False,
+ origin_widget: ba.Widget | None = None,
+ store_back_location: str | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
@@ -58,14 +60,22 @@ class GetCurrencyWindow(ba.Window):
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- color=(0.4, 0.37, 0.55),
- scale=(1.63 if uiscale is ba.UIScale.SMALL else
- 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ color=(0.4, 0.37, 0.55),
+ scale=(
+ 1.63
+ if uiscale is ba.UIScale.SMALL
+ else 1.2
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
btn = ba.buttonwidget(
parent=self._root_widget,
@@ -75,25 +85,30 @@ class GetCurrencyWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='doneText' if modal else 'backText'),
button_type='regular' if modal else 'back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 55),
- size=(0, 0),
- color=ba.app.ui.title_color,
- scale=1.2,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=290)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 55),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ scale=1.2,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=290,
+ )
if not modal:
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
b_size = (220.0, 180.0)
v = self._height - b_size[1] - 80
@@ -101,16 +116,18 @@ class GetCurrencyWindow(ba.Window):
self._ad_button = None
- def _add_button(item: str,
- position: tuple[float, float],
- size: tuple[float, float],
- label: ba.Lstr,
- price: str | None = None,
- tex_name: str | None = None,
- tex_opacity: float = 1.0,
- tex_scale: float = 1.0,
- enabled: bool = True,
- text_scale: float = 1.0) -> ba.Widget:
+ def _add_button(
+ item: str,
+ position: tuple[float, float],
+ size: tuple[float, float],
+ label: ba.Lstr,
+ price: str | None = None,
+ tex_name: str | None = None,
+ tex_opacity: float = 1.0,
+ tex_scale: float = 1.0,
+ enabled: bool = True,
+ text_scale: float = 1.0,
+ ) -> ba.Widget:
btn2 = ba.buttonwidget(
parent=self._root_widget,
position=position,
@@ -119,42 +136,57 @@ class GetCurrencyWindow(ba.Window):
label='',
autoselect=True,
color=None if enabled else (0.5, 0.5, 0.5),
- on_activate_call=(ba.Call(self._purchase, item)
- if enabled else self._disabled_press))
- txt = ba.textwidget(parent=self._root_widget,
- text=label,
- position=(position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.3),
- scale=text_scale,
- maxwidth=size[0] * 0.75,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn2,
- color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2))
+ on_activate_call=(
+ ba.Call(self._purchase, item)
+ if enabled
+ else self._disabled_press
+ ),
+ )
+ txt = ba.textwidget(
+ parent=self._root_widget,
+ text=label,
+ position=(
+ position[0] + size[0] * 0.5,
+ position[1] + size[1] * 0.3,
+ ),
+ scale=text_scale,
+ maxwidth=size[0] * 0.75,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn2,
+ color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
+ )
if price is not None and enabled:
- ba.textwidget(parent=self._root_widget,
- text=price,
- position=(position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.17),
- scale=0.7,
- maxwidth=size[0] * 0.75,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn2,
- color=(0.4, 0.9, 0.4, 1.0))
+ ba.textwidget(
+ parent=self._root_widget,
+ text=price,
+ position=(
+ position[0] + size[0] * 0.5,
+ position[1] + size[1] * 0.17,
+ ),
+ scale=0.7,
+ maxwidth=size[0] * 0.75,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn2,
+ color=(0.4, 0.9, 0.4, 1.0),
+ )
i = None
if tex_name is not None:
tex_size = 90.0 * tex_scale
i = ba.imagewidget(
parent=self._root_widget,
texture=ba.gettexture(tex_name),
- position=(position[0] + size[0] * 0.5 - tex_size * 0.5,
- position[1] + size[1] * 0.66 - tex_size * 0.5),
+ position=(
+ position[0] + size[0] * 0.5 - tex_size * 0.5,
+ position[1] + size[1] * 0.66 - tex_size * 0.5,
+ ),
size=(tex_size, tex_size),
draw_controller=btn2,
- opacity=tex_opacity * (1.0 if enabled else 0.25))
+ opacity=tex_opacity * (1.0 if enabled else 0.25),
+ )
if item == 'ad':
self._ad_button = btn2
self._ad_label = txt
@@ -163,39 +195,74 @@ class GetCurrencyWindow(ba.Window):
self._ad_time_text = ba.textwidget(
parent=self._root_widget,
text='1m 10s',
- position=(position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.5),
+ position=(
+ position[0] + size[0] * 0.5,
+ position[1] + size[1] * 0.5,
+ ),
scale=text_scale * 1.2,
maxwidth=size[0] * 0.85,
size=(0, 0),
h_align='center',
v_align='center',
draw_controller=btn2,
- color=(0.4, 0.9, 0.4, 1.0))
+ color=(0.4, 0.9, 0.4, 1.0),
+ )
return btn2
rsrc = self._r + '.ticketsText'
- c2txt = ba.Lstr(resource=rsrc,
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'tickets2Amount', 500)))])
- c3txt = ba.Lstr(resource=rsrc,
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'tickets3Amount', 1500)))])
- c4txt = ba.Lstr(resource=rsrc,
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'tickets4Amount', 5000)))])
- c5txt = ba.Lstr(resource=rsrc,
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'tickets5Amount', 15000)))])
+ c2txt = ba.Lstr(
+ resource=rsrc,
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'tickets2Amount', 500
+ )
+ ),
+ )
+ ],
+ )
+ c3txt = ba.Lstr(
+ resource=rsrc,
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'tickets3Amount', 1500
+ )
+ ),
+ )
+ ],
+ )
+ c4txt = ba.Lstr(
+ resource=rsrc,
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'tickets4Amount', 5000
+ )
+ ),
+ )
+ ],
+ )
+ c5txt = ba.Lstr(
+ resource=rsrc,
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'tickets5Amount', 15000
+ )
+ ),
+ )
+ ],
+ )
h = 110.0
@@ -212,41 +279,57 @@ class GetCurrencyWindow(ba.Window):
# tickets4_price = '$19.99'
# tickets5_price = '$49.99'
- _add_button('tickets2',
- enabled=(tickets2_price is not None),
- position=(self._width * 0.5 - spacing * 1.5 -
- b_size[0] * 2.0 + h, v),
- size=b_size,
- label=c2txt,
- price=tickets2_price,
- tex_name='ticketsMore') # 0.99-ish
- _add_button('tickets3',
- enabled=(tickets3_price is not None),
- position=(self._width * 0.5 - spacing * 0.5 -
- b_size[0] * 1.0 + h, v),
- size=b_size,
- label=c3txt,
- price=tickets3_price,
- tex_name='ticketRoll') # 4.99-ish
+ _add_button(
+ 'tickets2',
+ enabled=(tickets2_price is not None),
+ position=(
+ self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
+ v,
+ ),
+ size=b_size,
+ label=c2txt,
+ price=tickets2_price,
+ tex_name='ticketsMore',
+ ) # 0.99-ish
+ _add_button(
+ 'tickets3',
+ enabled=(tickets3_price is not None),
+ position=(
+ self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
+ v,
+ ),
+ size=b_size,
+ label=c3txt,
+ price=tickets3_price,
+ tex_name='ticketRoll',
+ ) # 4.99-ish
v -= b_size[1] - 5
- _add_button('tickets4',
- enabled=(tickets4_price is not None),
- position=(self._width * 0.5 - spacing * 1.5 -
- b_size[0] * 2.0 + h, v),
- size=b_size,
- label=c4txt,
- price=tickets4_price,
- tex_name='ticketRollBig',
- tex_scale=1.2) # 9.99-ish
- _add_button('tickets5',
- enabled=(tickets5_price is not None),
- position=(self._width * 0.5 - spacing * 0.5 -
- b_size[0] * 1.0 + h, v),
- size=b_size,
- label=c5txt,
- price=tickets5_price,
- tex_name='ticketRolls',
- tex_scale=1.2) # 19.99-ish
+ _add_button(
+ 'tickets4',
+ enabled=(tickets4_price is not None),
+ position=(
+ self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
+ v,
+ ),
+ size=b_size,
+ label=c4txt,
+ price=tickets4_price,
+ tex_name='ticketRollBig',
+ tex_scale=1.2,
+ ) # 9.99-ish
+ _add_button(
+ 'tickets5',
+ enabled=(tickets5_price is not None),
+ position=(
+ self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
+ v,
+ ),
+ size=b_size,
+ label=c5txt,
+ price=tickets5_price,
+ tex_name='ticketRolls',
+ tex_scale=1.2,
+ ) # 19.99-ish
self._enable_ad_button = ba.internal.has_video_ads()
h = self._width * 0.5 + 110.0
@@ -261,35 +344,49 @@ class GetCurrencyWindow(ba.Window):
size=b_size_3,
label=ba.Lstr(
resource=self._r + '.ticketsFromASponsorText',
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'sponsorTickets', 5)))]),
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'sponsorTickets', 5
+ )
+ ),
+ )
+ ],
+ ),
tex_name='ticketsMore',
enabled=self._enable_ad_button,
tex_opacity=0.6,
tex_scale=0.7,
- text_scale=0.7)
- ba.buttonwidget(edit=cdb,
- color=(0.65, 0.5,
- 0.7) if self._enable_ad_button else
- (0.5, 0.5, 0.5))
+ text_scale=0.7,
+ )
+ ba.buttonwidget(
+ edit=cdb,
+ color=(0.65, 0.5, 0.7)
+ if self._enable_ad_button
+ else (0.5, 0.5, 0.5),
+ )
self._ad_free_text = ba.textwidget(
parent=self._root_widget,
text=ba.Lstr(resource=self._r + '.freeText'),
- position=(h + h_offs + b_size_3[0] * 0.5,
- v + b_size_3[1] * 0.5 + 25),
+ position=(
+ h + h_offs + b_size_3[0] * 0.5,
+ v + b_size_3[1] * 0.5 + 25,
+ ),
size=(0, 0),
- color=(1, 1, 0, 1.0) if self._enable_ad_button else
- (1, 1, 1, 0.2),
+ color=(1, 1, 0, 1.0)
+ if self._enable_ad_button
+ else (1, 1, 1, 0.2),
draw_controller=cdb,
rotate=15,
shadow=1.0,
maxwidth=150,
h_align='center',
v_align='center',
- scale=1.0)
+ scale=1.0,
+ )
v -= 125
else:
v -= 20
@@ -303,80 +400,106 @@ class GetCurrencyWindow(ba.Window):
size=b_size_3,
label=ba.Lstr(
resource='gatherWindow.earnTicketsForRecommendingText',
- subs=[('${COUNT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'sponsorTickets', 5)))]),
+ subs=[
+ (
+ '${COUNT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'sponsorTickets', 5
+ )
+ ),
+ )
+ ],
+ ),
tex_name='ticketsMore',
enabled=True,
tex_opacity=0.6,
tex_scale=0.7,
- text_scale=0.7)
+ text_scale=0.7,
+ )
ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
- ba.textwidget(parent=self._root_widget,
- text=ba.Lstr(resource=self._r + '.freeText'),
- position=(h + h_offs + b_size_3[0] * 0.5,
- v + b_size_3[1] * 0.5 + 25),
- size=(0, 0),
- color=(1, 1, 0, 1.0),
- draw_controller=cdb,
- rotate=15,
- shadow=1.0,
- maxwidth=150,
- h_align='center',
- v_align='center',
- scale=1.0)
+ ba.textwidget(
+ parent=self._root_widget,
+ text=ba.Lstr(resource=self._r + '.freeText'),
+ position=(
+ h + h_offs + b_size_3[0] * 0.5,
+ v + b_size_3[1] * 0.5 + 25,
+ ),
+ size=(0, 0),
+ color=(1, 1, 0, 1.0),
+ draw_controller=cdb,
+ rotate=15,
+ shadow=1.0,
+ maxwidth=150,
+ h_align='center',
+ v_align='center',
+ scale=1.0,
+ )
tc_y_offs = 0
h = self._width - (185 + x_inset)
v = self._height - 95 + tc_y_offs
- txt1 = (ba.Lstr(
- resource=self._r +
- '.youHaveText').evaluate().partition('${COUNT}')[0].strip())
- txt2 = (ba.Lstr(
- resource=self._r +
- '.youHaveText').evaluate().rpartition('${COUNT}')[-1].strip())
+ txt1 = (
+ ba.Lstr(resource=self._r + '.youHaveText')
+ .evaluate()
+ .partition('${COUNT}')[0]
+ .strip()
+ )
+ txt2 = (
+ ba.Lstr(resource=self._r + '.youHaveText')
+ .evaluate()
+ .rpartition('${COUNT}')[-1]
+ .strip()
+ )
- ba.textwidget(parent=self._root_widget,
- text=txt1,
- position=(h, v),
- size=(0, 0),
- color=(0.5, 0.5, 0.6),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=0.8)
+ ba.textwidget(
+ parent=self._root_widget,
+ text=txt1,
+ position=(h, v),
+ size=(0, 0),
+ color=(0.5, 0.5, 0.6),
+ maxwidth=200,
+ h_align='center',
+ v_align='center',
+ scale=0.8,
+ )
v -= 30
- self._ticket_count_text = ba.textwidget(parent=self._root_widget,
- position=(h, v),
- size=(0, 0),
- color=(0.2, 1.0, 0.2),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=1.6)
+ self._ticket_count_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(0, 0),
+ color=(0.2, 1.0, 0.2),
+ maxwidth=200,
+ h_align='center',
+ v_align='center',
+ scale=1.6,
+ )
v -= 30
- ba.textwidget(parent=self._root_widget,
- text=txt2,
- position=(h, v),
- size=(0, 0),
- color=(0.5, 0.5, 0.6),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=0.8)
+ ba.textwidget(
+ parent=self._root_widget,
+ text=txt2,
+ position=(h, v),
+ size=(0, 0),
+ color=(0.5, 0.5, 0.6),
+ maxwidth=200,
+ h_align='center',
+ v_align='center',
+ scale=0.8,
+ )
# update count now and once per second going forward..
self._ticking_node: ba.Node | None = None
self._smooth_ticket_count: float | None = None
self._ticket_count = 0
self._update()
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._smooth_increase_speed = 1.0
def __del__(self) -> None:
@@ -400,7 +523,8 @@ class GetCurrencyWindow(ba.Window):
# we're going up; start a sound if need be
self._smooth_ticket_count = min(
self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
- self._ticket_count)
+ self._ticket_count,
+ )
if int(self._smooth_ticket_count) >= self._ticket_count:
finished = True
self._smooth_ticket_count = float(self._ticket_count)
@@ -410,11 +534,14 @@ class GetCurrencyWindow(ba.Window):
'sound',
attrs={
'sound': ba.getsound('scoreIncrease'),
- 'positional': False
- })
+ 'positional': False,
+ },
+ )
- ba.textwidget(edit=self._ticket_count_text,
- text=str(int(self._smooth_ticket_count)))
+ ba.textwidget(
+ edit=self._ticket_count_text,
+ text=str(int(self._smooth_ticket_count)),
+ )
# if we've reached the target, kill the timer/sound/etc
if finished:
@@ -438,13 +565,16 @@ class GetCurrencyWindow(ba.Window):
# available
if self._ad_button is not None:
next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2(
- 'nextRewardAdTime', None)
+ 'nextRewardAdTime', None
+ )
if next_reward_ad_time is not None:
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
- next_reward_ad_time)
+ next_reward_ad_time
+ )
now = datetime.datetime.utcnow()
- if (ba.internal.have_incentivized_ad() and
- (next_reward_ad_time is None or next_reward_ad_time <= now)):
+ if ba.internal.have_incentivized_ad() and (
+ next_reward_ad_time is None or next_reward_ad_time <= now
+ ):
self._ad_button_greyed = False
ba.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
@@ -458,12 +588,15 @@ class GetCurrencyWindow(ba.Window):
ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
ba.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
sval: str | ba.Lstr
- if (next_reward_ad_time is not None
- and next_reward_ad_time > now):
+ if (
+ next_reward_ad_time is not None
+ and next_reward_ad_time > now
+ ):
sval = ba.timestring(
(next_reward_ad_time - now).total_seconds() * 1000.0,
centi=False,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
else:
sval = ''
ba.textwidget(edit=self._ad_time_text, text=sval)
@@ -474,42 +607,57 @@ class GetCurrencyWindow(ba.Window):
self._smooth_ticket_count = float(self._ticket_count)
self._smooth_update() # will set the text widget
- elif (self._ticket_count != int(self._smooth_ticket_count)
- and self._smooth_update_timer is None):
- self._smooth_update_timer = ba.Timer(0.05,
- ba.WeakCall(
- self._smooth_update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ elif (
+ self._ticket_count != int(self._smooth_ticket_count)
+ and self._smooth_update_timer is None
+ ):
+ self._smooth_update_timer = ba.Timer(
+ 0.05,
+ ba.WeakCall(self._smooth_update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
- self._smooth_increase_speed = (diff /
- 100.0 if diff >= 5000 else diff /
- 50.0 if diff >= 1500 else diff /
- 30.0 if diff >= 500 else diff /
- 15.0)
+ self._smooth_increase_speed = (
+ diff / 100.0
+ if diff >= 5000
+ else diff / 50.0
+ if diff >= 1500
+ else diff / 30.0
+ if diff >= 500
+ else diff / 15.0
+ )
def _disabled_press(self) -> None:
# if we're on a platform without purchases, inform the user they
# can link their accounts and buy stuff elsewhere
app = ba.app
- if ((app.test_build or
- (app.platform == 'android'
- and app.subplatform in ['oculus', 'cardboard']))
- and ba.internal.get_v1_account_misc_read_val(
- 'allowAccountLinking2', False)):
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.unavailableLinkAccountText'),
- color=(1, 0.5, 0))
+ if (
+ app.test_build
+ or (
+ app.platform == 'android'
+ and app.subplatform in ['oculus', 'cardboard']
+ )
+ ) and ba.internal.get_v1_account_misc_read_val(
+ 'allowAccountLinking2', False
+ ):
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.unavailableLinkAccountText'),
+ color=(1, 0.5, 0),
+ )
else:
- ba.screenmessage(ba.Lstr(resource=self._r + '.unavailableText'),
- color=(1, 0.5, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.unavailableText'),
+ color=(1, 0.5, 0),
+ )
ba.playsound(ba.getsound('error'))
def _purchase(self, item: str) -> None:
from bastd.ui import account
from bastd.ui import appinvite
from ba.internal import master_server_get
+
if item == 'app_invite':
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
@@ -519,23 +667,27 @@ class GetCurrencyWindow(ba.Window):
# here we ping the server to ask if it's valid for us to
# purchase this.. (better to fail now than after we've paid locally)
app = ba.app
- master_server_get('bsAccountPurchaseCheck', {
- 'item': item,
- 'platform': app.platform,
- 'subplatform': app.subplatform,
- 'version': app.version,
- 'buildNumber': app.build_number
- },
- callback=ba.WeakCall(self._purchase_check_result,
- item))
+ master_server_get(
+ 'bsAccountPurchaseCheck',
+ {
+ 'item': item,
+ 'platform': app.platform,
+ 'subplatform': app.subplatform,
+ 'version': app.version,
+ 'buildNumber': app.build_number,
+ },
+ callback=ba.WeakCall(self._purchase_check_result, item),
+ )
- def _purchase_check_result(self, item: str,
- result: dict[str, Any] | None) -> None:
+ def _purchase_check_result(
+ self, item: str, result: dict[str, Any] | None
+ ) -> None:
if result is None:
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
else:
if result['allow']:
self._do_purchase(item)
@@ -544,30 +696,39 @@ class GetCurrencyWindow(ba.Window):
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='getTicketsWindow.versionTooOldText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
else:
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='getTicketsWindow.unavailableText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
# actually start the purchase locally..
def _do_purchase(self, item: str) -> None:
if item == 'ad':
import datetime
+
# if ads are disabled until some time, error..
next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2(
- 'nextRewardAdTime', None)
+ 'nextRewardAdTime', None
+ )
if next_reward_ad_time is not None:
next_reward_ad_time = datetime.datetime.utcfromtimestamp(
- next_reward_ad_time)
+ next_reward_ad_time
+ )
now = datetime.datetime.utcnow()
- if ((next_reward_ad_time is not None and next_reward_ad_time > now)
- or self._ad_button_greyed):
+ if (
+ next_reward_ad_time is not None and next_reward_ad_time > now
+ ) or self._ad_button_greyed:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.unavailableTemporarilyText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='getTicketsWindow.unavailableTemporarilyText'
+ ),
+ color=(1, 0, 0),
+ )
elif self._enable_ad_button:
ba.app.ads.show_ad('tickets')
else:
@@ -575,15 +736,18 @@ class GetCurrencyWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.store import browser
+
if self._transitioning_out:
return
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
window = browser.StoreBrowserWindow(
transition='in_left',
modal=self._from_modal_store,
- back_location=self._store_back_location).get_root_widget()
+ back_location=self._store_back_location,
+ ).get_root_widget()
if not self._from_modal_store:
ba.app.ui.set_main_menu_window(window)
self._transitioning_out = True
@@ -596,18 +760,29 @@ def show_get_tickets_prompt() -> None:
depending on the build of the game.
"""
from bastd.ui.confirm import ConfirmWindow
+
if ba.app.allow_ticket_purchases:
ConfirmWindow(
- ba.Lstr(translate=('serverResponses',
- 'You don\'t have enough tickets for this!')),
+ ba.Lstr(
+ translate=(
+ 'serverResponses',
+ 'You don\'t have enough tickets for this!',
+ )
+ ),
lambda: GetCurrencyWindow(modal=True),
ok_text=ba.Lstr(resource='getTicketsWindow.titleText'),
width=460,
- height=130)
+ height=130,
+ )
else:
ConfirmWindow(
- ba.Lstr(translate=('serverResponses',
- 'You don\'t have enough tickets for this!')),
+ ba.Lstr(
+ translate=(
+ 'serverResponses',
+ 'You don\'t have enough tickets for this!',
+ )
+ ),
cancel_button=False,
width=460,
- height=130)
+ height=130,
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/getremote.py b/assets/src/ba_data/python/bastd/ui/getremote.py
index 4f8f401c..c25d5ff8 100644
--- a/assets/src/ba_data/python/bastd/ui/getremote.py
+++ b/assets/src/ba_data/python/bastd/ui/getremote.py
@@ -19,17 +19,24 @@ class GetBSRemoteWindow(popup.PopupWindow):
def __init__(self) -> None:
position = (0.0, 0.0)
uiscale = ba.app.ui.uiscale
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 570
self._height = 350
bg_color = (0.5, 0.4, 0.6)
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color)
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
position=(50, self._height - 30),
@@ -40,23 +47,32 @@ class GetBSRemoteWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.imagewidget(parent=self.root_widget,
- position=(self._width * 0.5 - 110,
- self._height * 0.67 - 110),
- size=(220, 220),
- texture=ba.gettexture('multiplayerExamples'))
- ba.textwidget(parent=self.root_widget,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=self._width * 0.9,
- position=(self._width * 0.5, 60),
- text=ba.Lstr(
- resource='remoteAppInfoShortText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText')),
- ('${REMOTE_APP_NAME}',
- ba.Lstr(resource='remote_app.app_name'))]))
+ iconscale=1.2,
+ )
+ ba.imagewidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5 - 110, self._height * 0.67 - 110),
+ size=(220, 220),
+ texture=ba.gettexture('multiplayerExamples'),
+ )
+ ba.textwidget(
+ parent=self.root_widget,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._width * 0.9,
+ position=(self._width * 0.5, 60),
+ text=ba.Lstr(
+ resource='remoteAppInfoShortText',
+ subs=[
+ ('${APP_NAME}', ba.Lstr(resource='titleText')),
+ (
+ '${REMOTE_APP_NAME}',
+ ba.Lstr(resource='remote_app.app_name'),
+ ),
+ ],
+ ),
+ )
def _on_cancel_press(self) -> None:
self._transition_out()
diff --git a/assets/src/ba_data/python/bastd/ui/helpui.py b/assets/src/ba_data/python/bastd/ui/helpui.py
index 8983640f..17baee0a 100644
--- a/assets/src/ba_data/python/bastd/ui/helpui.py
+++ b/assets/src/ba_data/python/bastd/ui/helpui.py
@@ -16,12 +16,13 @@ if TYPE_CHECKING:
class HelpWindow(ba.Window):
"""A window providing help on how to play."""
- def __init__(self,
- main_menu: bool = False,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self, main_menu: bool = False, origin_widget: ba.Widget | None = None
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from ba.internal import get_remote_app_name
+
ba.set_analytics_screen('Help Window')
# If they provided an origin-widget, scale up from that.
@@ -43,87 +44,121 @@ class HelpWindow(ba.Window):
uiscale = ba.app.ui.uiscale
width = 950 if uiscale is ba.UIScale.SMALL else 750
x_offs = 100 if uiscale is ba.UIScale.SMALL else 0
- height = (460 if uiscale is ba.UIScale.SMALL else
- 530 if uiscale is ba.UIScale.MEDIUM else 600)
+ height = (
+ 460
+ if uiscale is ba.UIScale.SMALL
+ else 530
+ if uiscale is ba.UIScale.MEDIUM
+ else 600
+ )
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(1.77 if uiscale is ba.UIScale.SMALL else
- 1.25 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (
- 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.77
+ if uiscale is ba.UIScale.SMALL
+ else 1.25
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -30)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 15)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ )
+ )
- ba.textwidget(parent=self._root_widget,
- position=(0, height -
- (50 if uiscale is ba.UIScale.SMALL else 45)),
- size=(width, 25),
- text=ba.Lstr(resource=self._r + '.titleText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='top')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - (50 if uiscale is ba.UIScale.SMALL else 45)),
+ size=(width, 25),
+ text=ba.Lstr(
+ resource=self._r + '.titleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='top',
+ )
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
position=(44 + x_offs, 55 if uiscale is ba.UIScale.SMALL else 55),
simple_culling_v=100.0,
- size=(width - (88 + 2 * x_offs),
- height - 120 + (5 if uiscale is ba.UIScale.SMALL else 0)),
- capture_arrows=True)
+ size=(
+ width - (88 + 2 * x_offs),
+ height - 120 + (5 if uiscale is ba.UIScale.SMALL else 0),
+ ),
+ capture_arrows=True,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=self._scrollwidget,
- right_widget=ba.internal.get_special_widget('party_button'))
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
# ugly: create this last so it gets first dibs at touch events (since
# we have it close to the scroll widget)
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._close)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._close
+ )
ba.widget(
edit=self._scrollwidget,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
else:
btn = ba.buttonwidget(
parent=self._root_widget,
- position=(x_offs +
- (40 + 0 if uiscale is ba.UIScale.SMALL else 70),
- height -
- (59 if uiscale is ba.UIScale.SMALL else 50)),
+ position=(
+ x_offs + (40 + 0 if uiscale is ba.UIScale.SMALL else 70),
+ height - (59 if uiscale is ba.UIScale.SMALL else 50),
+ ),
size=(140, 60),
scale=0.7 if uiscale is ba.UIScale.SMALL else 0.8,
- label=ba.Lstr(
- resource='backText') if self._main_menu else 'Close',
+ label=ba.Lstr(resource='backText')
+ if self._main_menu
+ else 'Close',
button_type='back' if self._main_menu else None,
extra_touch_border_scale=2.0,
autoselect=True,
- on_activate_call=self._close)
+ on_activate_call=self._close,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
if self._main_menu:
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 55),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 55),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._sub_width = 660
- self._sub_height = 1590 + ba.app.lang.get_resource(
- self._r + '.someDaysExtraSpace') + ba.app.lang.get_resource(
- self._r + '.orPunchingSomethingExtraSpace')
+ self._sub_height = (
+ 1590
+ + ba.app.lang.get_resource(self._r + '.someDaysExtraSpace')
+ + ba.app.lang.get_resource(
+ self._r + '.orPunchingSomethingExtraSpace'
+ )
+ )
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False,
- claims_left_right=False,
- claims_tab=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ claims_left_right=False,
+ claims_tab=False,
+ )
spacing = 1.0
h = self._sub_width * 0.5
@@ -134,39 +169,45 @@ class HelpWindow(ba.Window):
header2 = (0.8, 0.8, 1.0, 1.0)
paragraph = (0.8, 0.8, 1.0, 1.0)
- txt = ba.Lstr(resource=self._r + '.welcomeText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate()
+ txt = ba.Lstr(
+ resource=self._r + '.welcomeText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate()
txt_scale = 1.4
txt_maxwidth = 480
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- flatness=0.5,
- res_scale=1.5,
- text=txt,
- h_align='center',
- color=header,
- v_align='center',
- maxwidth=txt_maxwidth)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=0.5,
+ res_scale=1.5,
+ text=txt,
+ h_align='center',
+ color=header,
+ v_align='center',
+ maxwidth=txt_maxwidth,
+ )
txt_width = min(
txt_maxwidth,
- ba.internal.get_string_width(txt, suppress_warning=True) *
- txt_scale)
+ ba.internal.get_string_width(txt, suppress_warning=True)
+ * txt_scale,
+ )
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- v - 0.45 * icon_size),
- texture=logo_tex)
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size),
+ texture=logo_tex,
+ )
force_test = False
app = ba.app
- if (app.platform == 'android'
- and app.subplatform == 'alibaba') or force_test:
+ if (
+ app.platform == 'android' and app.subplatform == 'alibaba'
+ ) or force_test:
v -= 120.0
txtv = (
'\xe8\xbf\x99\xe6\x98\xaf\xe4\xb8\x80\xe4\xb8\xaa\xe5\x8f\xaf'
@@ -185,201 +226,242 @@ class HelpWindow(ba.Window):
'\xbf\xe5\xa4\x96\xe8\xae\xbe\xe3\x80\x82\n'
'\xe6\x9c\x80\xe5\xa4\x9a\xe6\x94\xaf\xe6\x8c\x81\xe6\x8e\xa5'
'\xe5\x85\xa5\xc2\xa08\xc2\xa0\xe4\xb8\xaa\xe5\xa4\x96\xe8'
- '\xae\xbe')
- ba.textwidget(parent=self._subcontainer,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=self._sub_width * 0.9,
- position=(self._sub_width * 0.5, v - 180),
- text=txtv)
- ba.imagewidget(parent=self._subcontainer,
- position=(self._sub_width - 320, v - 120),
- size=(200, 200),
- texture=ba.gettexture('aliControllerQR'))
- ba.imagewidget(parent=self._subcontainer,
- position=(90, v - 130),
- size=(210, 210),
- texture=ba.gettexture('multiplayerExamples'))
+ '\xae\xbe'
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._sub_width * 0.9,
+ position=(self._sub_width * 0.5, v - 180),
+ text=txtv,
+ )
+ ba.imagewidget(
+ parent=self._subcontainer,
+ position=(self._sub_width - 320, v - 120),
+ size=(200, 200),
+ texture=ba.gettexture('aliControllerQR'),
+ )
+ ba.imagewidget(
+ parent=self._subcontainer,
+ position=(90, v - 130),
+ size=(210, 210),
+ texture=ba.gettexture('multiplayerExamples'),
+ )
v -= 120.0
else:
v -= spacing * 50.0
txt = ba.Lstr(resource=self._r + '.someDaysText').evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=1.2,
- maxwidth=self._sub_width * 0.9,
- text=txt,
- h_align='center',
- color=paragraph,
- v_align='center',
- flatness=1.0)
- v -= (spacing * 25.0 + getres(self._r + '.someDaysExtraSpace'))
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=1.2,
+ maxwidth=self._sub_width * 0.9,
+ text=txt,
+ h_align='center',
+ color=paragraph,
+ v_align='center',
+ flatness=1.0,
+ )
+ v -= spacing * 25.0 + getres(self._r + '.someDaysExtraSpace')
txt_scale = 0.66
- txt = ba.Lstr(resource=self._r +
- '.orPunchingSomethingText').evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=self._sub_width * 0.9,
- text=txt,
- h_align='center',
- color=paragraph,
- v_align='center',
- flatness=1.0)
- v -= (spacing * 27.0 +
- getres(self._r + '.orPunchingSomethingExtraSpace'))
+ txt = ba.Lstr(
+ resource=self._r + '.orPunchingSomethingText'
+ ).evaluate()
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=self._sub_width * 0.9,
+ text=txt,
+ h_align='center',
+ color=paragraph,
+ v_align='center',
+ flatness=1.0,
+ )
+ v -= spacing * 27.0 + getres(
+ self._r + '.orPunchingSomethingExtraSpace'
+ )
txt_scale = 1.0
- txt = ba.Lstr(resource=self._r + '.canHelpText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- flatness=1.0,
- text=txt,
- h_align='center',
- color=paragraph,
- v_align='center')
+ txt = ba.Lstr(
+ resource=self._r + '.canHelpText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate()
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=1.0,
+ text=txt,
+ h_align='center',
+ color=paragraph,
+ v_align='center',
+ )
v -= spacing * 70.0
txt_scale = 1.0
txt = ba.Lstr(resource=self._r + '.toGetTheMostText').evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=self._sub_width * 0.9,
- text=txt,
- h_align='center',
- color=header,
- v_align='center',
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=self._sub_width * 0.9,
+ text=txt,
+ h_align='center',
+ color=header,
+ v_align='center',
+ flatness=1.0,
+ )
v -= spacing * 40.0
txt_scale = 0.74
txt = ba.Lstr(resource=self._r + '.friendsText').evaluate()
hval2 = h - 220
- ba.textwidget(parent=self._subcontainer,
- position=(hval2, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=100,
- text=txt,
- h_align='right',
- color=header,
- v_align='center',
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(hval2, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=100,
+ text=txt,
+ h_align='right',
+ color=header,
+ v_align='center',
+ flatness=1.0,
+ )
- txt = ba.Lstr(resource=self._r + '.friendsGoodText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate()
+ txt = ba.Lstr(
+ resource=self._r + '.friendsGoodText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate()
txt_scale = 0.7
- ba.textwidget(parent=self._subcontainer,
- position=(hval2 + 10, v + 8),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=500,
- text=txt,
- h_align='left',
- color=paragraph,
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(hval2 + 10, v + 8),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=500,
+ text=txt,
+ h_align='left',
+ color=paragraph,
+ flatness=1.0,
+ )
app = ba.app
v -= spacing * 45.0
- txt = (ba.Lstr(resource=self._r + '.devicesText').evaluate()
- if app.vr_mode else ba.Lstr(resource=self._r +
- '.controllersText').evaluate())
+ txt = (
+ ba.Lstr(resource=self._r + '.devicesText').evaluate()
+ if app.vr_mode
+ else ba.Lstr(resource=self._r + '.controllersText').evaluate()
+ )
txt_scale = 0.74
hval2 = h - 220
- ba.textwidget(parent=self._subcontainer,
- position=(hval2, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=100,
- text=txt,
- h_align='right',
- v_align='center',
- color=header,
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(hval2, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=100,
+ text=txt,
+ h_align='right',
+ v_align='center',
+ color=header,
+ flatness=1.0,
+ )
txt_scale = 0.7
if not app.vr_mode:
- infotxt = ('.controllersInfoTextRemoteOnly'
- if app.iircade_mode else '.controllersInfoText')
+ infotxt = (
+ '.controllersInfoTextRemoteOnly'
+ if app.iircade_mode
+ else '.controllersInfoText'
+ )
txt = ba.Lstr(
resource=self._r + infotxt,
fallback_resource=self._r + '.controllersInfoText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText')),
- ('${REMOTE_APP_NAME}', get_remote_app_name())
- ]).evaluate()
+ subs=[
+ ('${APP_NAME}', ba.Lstr(resource='titleText')),
+ ('${REMOTE_APP_NAME}', get_remote_app_name()),
+ ],
+ ).evaluate()
else:
- txt = ba.Lstr(resource=self._r + '.devicesInfoText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))
- ]).evaluate()
+ txt = ba.Lstr(
+ resource=self._r + '.devicesInfoText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(hval2 + 10, v + 8),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=500,
- max_height=105,
- text=txt,
- h_align='left',
- color=paragraph,
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(hval2 + 10, v + 8),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=500,
+ max_height=105,
+ text=txt,
+ h_align='left',
+ color=paragraph,
+ flatness=1.0,
+ )
v -= spacing * 150.0
txt = ba.Lstr(resource=self._r + '.controlsText').evaluate()
txt_scale = 1.4
txt_maxwidth = 480
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- flatness=0.5,
- text=txt,
- h_align='center',
- color=header,
- v_align='center',
- res_scale=1.5,
- maxwidth=txt_maxwidth)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=0.5,
+ text=txt,
+ h_align='center',
+ color=header,
+ v_align='center',
+ res_scale=1.5,
+ maxwidth=txt_maxwidth,
+ )
txt_width = min(
txt_maxwidth,
- ba.internal.get_string_width(txt, suppress_warning=True) *
- txt_scale)
+ ba.internal.get_string_width(txt, suppress_warning=True)
+ * txt_scale,
+ )
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- v - 0.45 * icon_size),
- texture=logo_tex)
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size),
+ texture=logo_tex,
+ )
v -= spacing * 45.0
txt_scale = 0.7
- txt = ba.Lstr(resource=self._r + '.controlsSubtitleText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]).evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=self._sub_width * 0.9,
- flatness=1.0,
- text=txt,
- h_align='center',
- color=paragraph,
- v_align='center')
+ txt = ba.Lstr(
+ resource=self._r + '.controlsSubtitleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ).evaluate()
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=self._sub_width * 0.9,
+ flatness=1.0,
+ text=txt,
+ h_align='center',
+ color=paragraph,
+ v_align='center',
+ )
v -= spacing * 160.0
sep = 70
@@ -387,142 +469,162 @@ class HelpWindow(ba.Window):
# icon_size_2 = 30
hval2 = h - sep
vval2 = v
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- vval2 - 0.5 * icon_size),
- texture=ba.gettexture('buttonPunch'),
- color=(1, 0.7, 0.3))
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size),
+ texture=ba.gettexture('buttonPunch'),
+ color=(1, 0.7, 0.3),
+ )
txt_scale = getres(self._r + '.punchInfoTextScale')
txt = ba.Lstr(resource=self._r + '.punchInfoText').evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h - sep - 185 + 70, v + 120),
- size=(0, 0),
- scale=txt_scale,
- flatness=1.0,
- text=txt,
- h_align='center',
- color=(1, 0.7, 0.3, 1.0),
- v_align='top')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h - sep - 185 + 70, v + 120),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=1.0,
+ text=txt,
+ h_align='center',
+ color=(1, 0.7, 0.3, 1.0),
+ v_align='top',
+ )
hval2 = h + sep
vval2 = v
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- vval2 - 0.5 * icon_size),
- texture=ba.gettexture('buttonBomb'),
- color=(1, 0.3, 0.3))
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size),
+ texture=ba.gettexture('buttonBomb'),
+ color=(1, 0.3, 0.3),
+ )
txt = ba.Lstr(resource=self._r + '.bombInfoText').evaluate()
txt_scale = getres(self._r + '.bombInfoTextScale')
- ba.textwidget(parent=self._subcontainer,
- position=(h + sep + 50 + 60, v - 35),
- size=(0, 0),
- scale=txt_scale,
- flatness=1.0,
- maxwidth=270,
- text=txt,
- h_align='center',
- color=(1, 0.3, 0.3, 1.0),
- v_align='top')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + sep + 50 + 60, v - 35),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=1.0,
+ maxwidth=270,
+ text=txt,
+ h_align='center',
+ color=(1, 0.3, 0.3, 1.0),
+ v_align='top',
+ )
hval2 = h
vval2 = v + sep
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- vval2 - 0.5 * icon_size),
- texture=ba.gettexture('buttonPickUp'),
- color=(0.5, 0.5, 1))
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size),
+ texture=ba.gettexture('buttonPickUp'),
+ color=(0.5, 0.5, 1),
+ )
txtl = ba.Lstr(resource=self._r + '.pickUpInfoText')
txt_scale = getres(self._r + '.pickUpInfoTextScale')
- ba.textwidget(parent=self._subcontainer,
- position=(h + 60 + 120, v + sep + 50),
- size=(0, 0),
- scale=txt_scale,
- flatness=1.0,
- text=txtl,
- h_align='center',
- color=(0.5, 0.5, 1, 1.0),
- v_align='top')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 60 + 120, v + sep + 50),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=1.0,
+ text=txtl,
+ h_align='center',
+ color=(0.5, 0.5, 1, 1.0),
+ v_align='top',
+ )
hval2 = h
vval2 = v - sep
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- vval2 - 0.5 * icon_size),
- texture=ba.gettexture('buttonJump'),
- color=(0.4, 1, 0.4))
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, vval2 - 0.5 * icon_size),
+ texture=ba.gettexture('buttonJump'),
+ color=(0.4, 1, 0.4),
+ )
txt = ba.Lstr(resource=self._r + '.jumpInfoText').evaluate()
txt_scale = getres(self._r + '.jumpInfoTextScale')
- ba.textwidget(parent=self._subcontainer,
- position=(h - 250 + 75, v - sep - 15 + 30),
- size=(0, 0),
- scale=txt_scale,
- flatness=1.0,
- text=txt,
- h_align='center',
- color=(0.4, 1, 0.4, 1.0),
- v_align='top')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h - 250 + 75, v - sep - 15 + 30),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=1.0,
+ text=txt,
+ h_align='center',
+ color=(0.4, 1, 0.4, 1.0),
+ v_align='top',
+ )
txt = ba.Lstr(resource=self._r + '.runInfoText').evaluate()
txt_scale = getres(self._r + '.runInfoTextScale')
- ba.textwidget(parent=self._subcontainer,
- position=(h, v - sep - 100),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=self._sub_width * 0.93,
- flatness=1.0,
- text=txt,
- h_align='center',
- color=(0.7, 0.7, 1.0, 1.0),
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v - sep - 100),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=self._sub_width * 0.93,
+ flatness=1.0,
+ text=txt,
+ h_align='center',
+ color=(0.7, 0.7, 1.0, 1.0),
+ v_align='center',
+ )
v -= spacing * 280.0
txt = ba.Lstr(resource=self._r + '.powerupsText').evaluate()
txt_scale = 1.4
txt_maxwidth = 480
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- flatness=0.5,
- text=txt,
- h_align='center',
- color=header,
- v_align='center',
- maxwidth=txt_maxwidth)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ flatness=0.5,
+ text=txt,
+ h_align='center',
+ color=header,
+ v_align='center',
+ maxwidth=txt_maxwidth,
+ )
txt_width = min(
txt_maxwidth,
- ba.internal.get_string_width(txt, suppress_warning=True) *
- txt_scale)
+ ba.internal.get_string_width(txt, suppress_warning=True)
+ * txt_scale,
+ )
icon_size = 70
hval2 = h - (txt_width * 0.5 + icon_size * 0.5 * icon_buffer)
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(hval2 - 0.5 * icon_size,
- v - 0.45 * icon_size),
- texture=logo_tex)
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size),
+ texture=logo_tex,
+ )
v -= spacing * 50.0
txt_scale = getres(self._r + '.powerupsSubtitleTextScale')
txt = ba.Lstr(resource=self._r + '.powerupsSubtitleText').evaluate()
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=self._sub_width * 0.9,
- text=txt,
- h_align='center',
- color=paragraph,
- v_align='center',
- flatness=1.0)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=self._sub_width * 0.9,
+ text=txt,
+ h_align='center',
+ color=paragraph,
+ v_align='center',
+ flatness=1.0,
+ )
v -= spacing * 1.0
@@ -539,9 +641,15 @@ class HelpWindow(ba.Window):
shadow_tex = ba.gettexture('shadowSharp')
for tex in [
- 'powerupPunch', 'powerupShield', 'powerupBomb',
- 'powerupHealth', 'powerupIceBombs', 'powerupImpactBombs',
- 'powerupStickyBombs', 'powerupLandMines', 'powerupCurse'
+ 'powerupPunch',
+ 'powerupShield',
+ 'powerupBomb',
+ 'powerupHealth',
+ 'powerupIceBombs',
+ 'powerupImpactBombs',
+ 'powerupStickyBombs',
+ 'powerupLandMines',
+ 'powerupCurse',
]:
name = ba.Lstr(resource=self._r + '.' + tex + 'NameText')
desc = ba.Lstr(resource=self._r + '.' + tex + 'DescriptionText')
@@ -551,48 +659,59 @@ class HelpWindow(ba.Window):
ba.imagewidget(
parent=self._subcontainer,
size=(shadow_size, shadow_size),
- position=(h + mm1 + shadow_offs_x - 0.5 * shadow_size,
- v + shadow_offs_y - 0.5 * shadow_size),
+ position=(
+ h + mm1 + shadow_offs_x - 0.5 * shadow_size,
+ v + shadow_offs_y - 0.5 * shadow_size,
+ ),
texture=shadow_tex,
color=(0, 0, 0),
- opacity=0.5)
- ba.imagewidget(parent=self._subcontainer,
- size=(icon_size, icon_size),
- position=(h + mm1 - 0.5 * icon_size,
- v - 0.5 * icon_size),
- texture=ba.gettexture(tex))
+ opacity=0.5,
+ )
+ ba.imagewidget(
+ parent=self._subcontainer,
+ size=(icon_size, icon_size),
+ position=(h + mm1 - 0.5 * icon_size, v - 0.5 * icon_size),
+ texture=ba.gettexture(tex),
+ )
txt_scale = t_big
txtl = name
- ba.textwidget(parent=self._subcontainer,
- position=(h + mm2, v + 3),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=200,
- flatness=1.0,
- text=txtl,
- h_align='left',
- color=header2,
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + mm2, v + 3),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=200,
+ flatness=1.0,
+ text=txtl,
+ h_align='left',
+ color=header2,
+ v_align='center',
+ )
txt_scale = t_small
txtl = desc
- ba.textwidget(parent=self._subcontainer,
- position=(h + mm3, v),
- size=(0, 0),
- scale=txt_scale,
- maxwidth=300,
- flatness=1.0,
- text=txtl,
- h_align='left',
- color=paragraph,
- v_align='center',
- res_scale=0.5)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + mm3, v),
+ size=(0, 0),
+ scale=txt_scale,
+ maxwidth=300,
+ flatness=1.0,
+ text=txtl,
+ h_align='left',
+ color=paragraph,
+ v_align='center',
+ res_scale=0.5,
+ )
def _close(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.mainmenu import MainMenuWindow
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if self._main_menu:
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/iconpicker.py b/assets/src/ba_data/python/bastd/ui/iconpicker.py
index dbe109b4..bcbac259 100644
--- a/assets/src/ba_data/python/bastd/ui/iconpicker.py
+++ b/assets/src/ba_data/python/bastd/ui/iconpicker.py
@@ -18,29 +18,37 @@ if TYPE_CHECKING:
class IconPicker(popup.PopupWindow):
"""Picker for icons."""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float] = (0.0, 0.0),
- delegate: Any = None,
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- tint_color: Sequence[float] = (1.0, 1.0, 1.0),
- tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
- selected_icon: str | None = None):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float] = (0.0, 0.0),
+ delegate: Any = None,
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ tint_color: Sequence[float] = (1.0, 1.0, 1.0),
+ tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
+ selected_icon: str | None = None,
+ ):
# pylint: disable=too-many-locals
del parent # unused here
del tint_color # unused_here
del tint2_color # unused here
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (1.85 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 1.85
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._delegate = delegate
self._transitioning_out = False
- self._icons = [ba.charstr(ba.SpecialChar.LOGO)
- ] + ba.app.accounts_v1.get_purchased_icons()
+ self._icons = [
+ ba.charstr(ba.SpecialChar.LOGO)
+ ] + ba.app.accounts_v1.get_purchased_icons()
count = len(self._icons)
columns = 4
rows = int(math.ceil(float(count) / columns))
@@ -50,73 +58,89 @@ class IconPicker(popup.PopupWindow):
button_buffer_h = 10
button_buffer_v = 5
- self._width = (10 + columns * (button_width + 2 * button_buffer_h) *
- (1.0 / 0.95) * (1.0 / 0.8))
- self._height = (self._width *
- (0.8 if uiscale is ba.UIScale.SMALL else 1.06))
+ self._width = 10 + columns * (button_width + 2 * button_buffer_h) * (
+ 1.0 / 0.95
+ ) * (1.0 / 0.8)
+ self._height = self._width * (
+ 0.8 if uiscale is ba.UIScale.SMALL else 1.06
+ )
self._scroll_width = self._width * 0.8
self._scroll_height = self._height * 0.8
- self._scroll_position = ((self._width - self._scroll_width) * 0.5,
- (self._height - self._scroll_height) * 0.5)
+ self._scroll_position = (
+ (self._width - self._scroll_width) * 0.5,
+ (self._height - self._scroll_height) * 0.5,
+ )
# creates our _root_widget
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=(0.5, 0.5, 0.5),
- offset=offset,
- focus_position=self._scroll_position,
- focus_size=(self._scroll_width,
- self._scroll_height))
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=(0.5, 0.5, 0.5),
+ offset=offset,
+ focus_position=self._scroll_position,
+ focus_size=(self._scroll_width, self._scroll_height),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._scroll_width,
- self._scroll_height),
- color=(0.55, 0.55, 0.55),
- highlight=False,
- position=self._scroll_position)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._scroll_width, self._scroll_height),
+ color=(0.55, 0.55, 0.55),
+ highlight=False,
+ position=self._scroll_position,
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._sub_width = self._scroll_width * 0.95
- self._sub_height = 5 + rows * (button_height +
- 2 * button_buffer_v) + 100
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._sub_height = (
+ 5 + rows * (button_height + 2 * button_buffer_v) + 100
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
index = 0
for y in range(rows):
for x in range(columns):
- pos = (x * (button_width + 2 * button_buffer_h) +
- button_buffer_h, self._sub_height - (y + 1) *
- (button_height + 2 * button_buffer_v) + 0)
- btn = ba.buttonwidget(parent=self._subcontainer,
- button_type='square',
- size=(button_width, button_height),
- autoselect=True,
- text_scale=1.2,
- label='',
- color=(0.65, 0.65, 0.65),
- on_activate_call=ba.Call(
- self._select_icon,
- self._icons[index]),
- position=pos)
- ba.textwidget(parent=self._subcontainer,
- h_align='center',
- v_align='center',
- size=(0, 0),
- position=(pos[0] + 0.5 * button_width - 1,
- pos[1] + 15),
- draw_controller=btn,
- text=self._icons[index],
- scale=1.8)
+ pos = (
+ x * (button_width + 2 * button_buffer_h) + button_buffer_h,
+ self._sub_height
+ - (y + 1) * (button_height + 2 * button_buffer_v)
+ + 0,
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ button_type='square',
+ size=(button_width, button_height),
+ autoselect=True,
+ text_scale=1.2,
+ label='',
+ color=(0.65, 0.65, 0.65),
+ on_activate_call=ba.Call(
+ self._select_icon, self._icons[index]
+ ),
+ position=pos,
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(pos[0] + 0.5 * button_width - 1, pos[1] + 15),
+ draw_controller=btn,
+ text=self._icons[index],
+ scale=1.8,
+ )
ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
if self._icons[index] == selected_icon:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=btn,
+ visible_child=btn,
+ )
index += 1
if index >= count:
@@ -131,19 +155,23 @@ class IconPicker(popup.PopupWindow):
on_activate_call=self._on_store_press,
color=(0.6, 0.6, 0.6),
textcolor=(0.8, 0.8, 0.8),
- autoselect=True)
+ autoselect=True,
+ )
ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
def _on_store_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._transition_out()
- StoreBrowserWindow(modal=True,
- show_tab=StoreBrowserWindow.TabID.ICONS,
- origin_widget=self._get_more_icons_button)
+ StoreBrowserWindow(
+ modal=True,
+ show_tab=StoreBrowserWindow.TabID.ICONS,
+ origin_widget=self._get_more_icons_button,
+ )
def _select_icon(self, icon: str) -> None:
if self._delegate is not None:
diff --git a/assets/src/ba_data/python/bastd/ui/kiosk.py b/assets/src/ba_data/python/bastd/ui/kiosk.py
index 73fadc96..ae041f4f 100644
--- a/assets/src/ba_data/python/bastd/ui/kiosk.py
+++ b/assets/src/ba_data/python/bastd/ui/kiosk.py
@@ -19,6 +19,7 @@ class KioskWindow(ba.Window):
def __init__(self, transition: str = 'in_right'):
# pylint: disable=too-many-locals, too-many-statements
from bastd.ui.confirm import QuitWindow
+
self._width = 720.0
self._height = 340.0
@@ -26,11 +27,14 @@ class KioskWindow(ba.Window):
QuitWindow(swish=True, back=True)
super().__init__(
- root_widget=ba.containerwidget(size=(self._width, self._height),
- transition=transition,
- on_cancel_call=_do_cancel,
- background=False,
- stack_offset=(0, -130)))
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ on_cancel_call=_do_cancel,
+ background=False,
+ stack_offset=(0, -130),
+ )
+ )
self._r = 'kioskWindow'
@@ -73,7 +77,8 @@ class KioskWindow(ba.Window):
scale=1.2,
h_align='center',
v_align='center',
- shadow=1.0)
+ shadow=1.0,
+ )
else:
tdelay = t_delay_base + t_delay_scale * 0.7
ba.textwidget(
@@ -81,104 +86,125 @@ class KioskWindow(ba.Window):
size=(0, 0),
position=(self._width * 0.5, self._height + y_extra - 34),
transition_delay=tdelay,
- text=(ba.Lstr(resource='demoText',
- fallback_resource='mainMenu.demoMenuText')
- if ba.app.demo_mode else 'ARCADE'),
+ text=(
+ ba.Lstr(
+ resource='demoText',
+ fallback_resource='mainMenu.demoMenuText',
+ )
+ if ba.app.demo_mode
+ else 'ARCADE'
+ ),
flatness=1.0,
scale=1.2,
h_align='center',
v_align='center',
- shadow=1.0)
+ shadow=1.0,
+ )
h = self._width * 0.5 - b_space
tdelay = t_delay_base + t_delay_scale * 0.7
- self._b1 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'easy'),
- transition_delay=tdelay,
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square')
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text=ba.Lstr(resource=self._r + '.easyText'),
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- size=(img_width, 0.5 * img_width),
- transition_delay=tdelay,
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('doomShroomPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b1 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'easy'),
+ transition_delay=tdelay,
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text=ba.Lstr(resource=self._r + '.easyText'),
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ size=(img_width, 0.5 * img_width),
+ transition_delay=tdelay,
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('doomShroomPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
h = self._width * 0.5
tdelay = t_delay_base + t_delay_scale * 0.65
- self._b2 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'medium'),
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square',
- transition_delay=tdelay)
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text=ba.Lstr(resource=self._r + '.mediumText'),
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- size=(img_width, 0.5 * img_width),
- transition_delay=tdelay,
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('footballStadiumPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b2 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'medium'),
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ transition_delay=tdelay,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text=ba.Lstr(resource=self._r + '.mediumText'),
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ size=(img_width, 0.5 * img_width),
+ transition_delay=tdelay,
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('footballStadiumPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
h = self._width * 0.5 + b_space
tdelay = t_delay_base + t_delay_scale * 0.6
- self._b3 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'hard'),
- transition_delay=tdelay,
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square')
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text='Hard',
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(img_width, 0.5 * img_width),
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('courtyardPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b3 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'hard'),
+ transition_delay=tdelay,
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text='Hard',
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(img_width, 0.5 * img_width),
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('courtyardPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
if not ba.app.did_menu_intro:
ba.app.did_menu_intro = True
@@ -197,99 +223,114 @@ class KioskWindow(ba.Window):
scale=1.2,
h_align='center',
v_align='center',
- shadow=1.0)
+ shadow=1.0,
+ )
h = self._width * 0.5 - b_space
tdelay = t_delay_base + t_delay_scale * 0.7
- self._b4 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'ctf'),
- transition_delay=tdelay,
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square')
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text=ba.Lstr(translate=('gameNames',
- 'Capture the Flag')),
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- size=(img_width, 0.5 * img_width),
- transition_delay=tdelay,
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('bridgitPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b4 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'ctf'),
+ transition_delay=tdelay,
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text=ba.Lstr(translate=('gameNames', 'Capture the Flag')),
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ size=(img_width, 0.5 * img_width),
+ transition_delay=tdelay,
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('bridgitPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
h = self._width * 0.5
tdelay = t_delay_base + t_delay_scale * 0.65
- self._b5 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'hockey'),
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square',
- transition_delay=tdelay)
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text=ba.Lstr(translate=('gameNames', 'Hockey')),
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- size=(img_width, 0.5 * img_width),
- transition_delay=tdelay,
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('hockeyStadiumPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b5 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'hockey'),
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ transition_delay=tdelay,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text=ba.Lstr(translate=('gameNames', 'Hockey')),
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ size=(img_width, 0.5 * img_width),
+ transition_delay=tdelay,
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('hockeyStadiumPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
h = self._width * 0.5 + b_space
tdelay = t_delay_base + t_delay_scale * 0.6
- self._b6 = btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- size=(b_width, b_height),
- on_activate_call=ba.Call(
- self._do_game, 'epic'),
- transition_delay=tdelay,
- position=(h - b_width * 0.5, b_v),
- label='',
- button_type='square')
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(0, 0),
- position=(h, label_height),
- maxwidth=b_width * 0.7,
- text=ba.Lstr(resource=self._r + '.epicModeText'),
- scale=1.3,
- h_align='center',
- v_align='center')
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- transition_delay=tdelay,
- size=(img_width, 0.5 * img_width),
- position=(h - img_width * 0.5, img_v),
- texture=ba.gettexture('tipTopPreview'),
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ self._b6 = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ size=(b_width, b_height),
+ on_activate_call=ba.Call(self._do_game, 'epic'),
+ transition_delay=tdelay,
+ position=(h - b_width * 0.5, b_v),
+ label='',
+ button_type='square',
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(0, 0),
+ position=(h, label_height),
+ maxwidth=b_width * 0.7,
+ text=ba.Lstr(resource=self._r + '.epicModeText'),
+ scale=1.3,
+ h_align='center',
+ v_align='center',
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ transition_delay=tdelay,
+ size=(img_width, 0.5 * img_width),
+ position=(h - img_width * 0.5, img_v),
+ texture=ba.gettexture('tipTopPreview'),
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
else:
self._b4 = self._b5 = self._b6 = None
@@ -305,15 +346,18 @@ class KioskWindow(ba.Window):
position=(self._width * 0.5 - 60.0, b_v - 70.0),
transition_delay=tdelay,
label=ba.Lstr(resource=self._r + '.fullMenuText'),
- on_activate_call=self._do_full_menu)
+ on_activate_call=self._do_full_menu,
+ )
else:
self._b7 = None
self._restore_state()
self._update()
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
def _restore_state(self) -> None:
sel_name = ba.app.ui.window_states.get(type(self))
@@ -379,63 +423,79 @@ class KioskWindow(ba.Window):
appconfig['Free-for-All Playlists'] = {}
appconfig['Show Tutorial'] = False
if mode == 'epic':
- appconfig['Free-for-All Playlists']['Just Epic Elim'] = [{
- 'settings': {
- 'Epic Mode': 1,
- 'Lives Per Player': 1,
- 'Respawn Times': 1.0,
- 'Time Limit': 0,
- 'map': 'Tip Top'
- },
- 'type': 'bs_elimination.EliminationGame'
- }]
+ appconfig['Free-for-All Playlists']['Just Epic Elim'] = [
+ {
+ 'settings': {
+ 'Epic Mode': 1,
+ 'Lives Per Player': 1,
+ 'Respawn Times': 1.0,
+ 'Time Limit': 0,
+ 'map': 'Tip Top',
+ },
+ 'type': 'bs_elimination.EliminationGame',
+ }
+ ]
appconfig['Free-for-All Playlist Selection'] = 'Just Epic Elim'
- ba.internal.fade_screen(False,
- endcall=ba.Call(
- ba.pushcall,
- ba.Call(
- ba.internal.new_host_session,
- ba.FreeForAllSession)))
+ ba.internal.fade_screen(
+ False,
+ endcall=ba.Call(
+ ba.pushcall,
+ ba.Call(
+ ba.internal.new_host_session, ba.FreeForAllSession
+ ),
+ ),
+ )
else:
if mode == 'ctf':
- appconfig['Team Tournament Playlists']['Just CTF'] = [{
- 'settings': {
- 'Epic Mode': False,
- 'Flag Idle Return Time': 30,
- 'Flag Touch Return Time': 0,
- 'Respawn Times': 1.0,
- 'Score to Win': 3,
- 'Time Limit': 0,
- 'map': 'Bridgit'
- },
- 'type': 'bs_capture_the_flag.CTFGame'
- }]
- appconfig[
- 'Team Tournament Playlist Selection'] = 'Just CTF'
+ appconfig['Team Tournament Playlists']['Just CTF'] = [
+ {
+ 'settings': {
+ 'Epic Mode': False,
+ 'Flag Idle Return Time': 30,
+ 'Flag Touch Return Time': 0,
+ 'Respawn Times': 1.0,
+ 'Score to Win': 3,
+ 'Time Limit': 0,
+ 'map': 'Bridgit',
+ },
+ 'type': 'bs_capture_the_flag.CTFGame',
+ }
+ ]
+ appconfig['Team Tournament Playlist Selection'] = 'Just CTF'
else:
- appconfig['Team Tournament Playlists']['Just Hockey'] = [{
- 'settings': {
- 'Respawn Times': 1.0,
- 'Score to Win': 1,
- 'Time Limit': 0,
- 'map': 'Hockey Stadium'
- },
- 'type': 'bs_hockey.HockeyGame'
- }]
- appconfig['Team Tournament Playlist Selection'] = (
- 'Just Hockey')
- ba.internal.fade_screen(False,
- endcall=ba.Call(
- ba.pushcall,
- ba.Call(
- ba.internal.new_host_session,
- ba.DualTeamSession)))
+ appconfig['Team Tournament Playlists']['Just Hockey'] = [
+ {
+ 'settings': {
+ 'Respawn Times': 1.0,
+ 'Score to Win': 1,
+ 'Time Limit': 0,
+ 'map': 'Hockey Stadium',
+ },
+ 'type': 'bs_hockey.HockeyGame',
+ }
+ ]
+ appconfig[
+ 'Team Tournament Playlist Selection'
+ ] = 'Just Hockey'
+ ba.internal.fade_screen(
+ False,
+ endcall=ba.Call(
+ ba.pushcall,
+ ba.Call(
+ ba.internal.new_host_session, ba.DualTeamSession
+ ),
+ ),
+ )
ba.containerwidget(edit=self._root_widget, transition='out_left')
return
- game = ('Easy:Onslaught Training'
- if mode == 'easy' else 'Easy:Rookie Football'
- if mode == 'medium' else 'Easy:Uber Onslaught')
+ game = (
+ 'Easy:Onslaught Training'
+ if mode == 'easy'
+ else 'Easy:Rookie Football'
+ if mode == 'medium'
+ else 'Easy:Uber Onslaught'
+ )
cfg = ba.app.config
cfg['Selected Coop Game'] = game
cfg.commit()
@@ -444,6 +504,7 @@ class KioskWindow(ba.Window):
def _do_full_menu(self) -> None:
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.did_menu_intro = True # prevent delayed transition-in
diff --git a/assets/src/ba_data/python/bastd/ui/league/rankbutton.py b/assets/src/ba_data/python/bastd/ui/league/rankbutton.py
index a679b8af..ce8166ac 100644
--- a/assets/src/ba_data/python/bastd/ui/league/rankbutton.py
+++ b/assets/src/ba_data/python/bastd/ui/league/rankbutton.py
@@ -16,16 +16,18 @@ if TYPE_CHECKING:
class LeagueRankButton:
"""Button showing league rank."""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float],
- size: tuple[float, float],
- scale: float,
- on_activate_call: Callable[[], Any] | None = None,
- transition_delay: float | None = None,
- color: tuple[float, float, float] | None = None,
- textcolor: tuple[float, float, float] | None = None,
- smooth_update_delay: float | None = None):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float],
+ size: tuple[float, float],
+ scale: float,
+ on_activate_call: Callable[[], Any] | None = None,
+ transition_delay: float | None = None,
+ color: tuple[float, float, float] | None = None,
+ textcolor: tuple[float, float, float] | None = None,
+ smooth_update_delay: float | None = None,
+ ):
if on_activate_call is None:
on_activate_call = ba.WeakCall(self._default_on_activate_call)
self._on_activate_call = on_activate_call
@@ -44,15 +46,17 @@ class LeagueRankButton:
self._parent = parent
self._position: tuple[float, float] = (0.0, 0.0)
- self._button = ba.buttonwidget(parent=parent,
- size=size,
- label='',
- button_type='square',
- scale=scale,
- autoselect=True,
- on_activate_call=self._on_activate,
- transition_delay=transition_delay,
- color=color)
+ self._button = ba.buttonwidget(
+ parent=parent,
+ size=size,
+ label='',
+ button_type='square',
+ scale=scale,
+ autoselect=True,
+ on_activate_call=self._on_activate,
+ transition_delay=transition_delay,
+ color=color,
+ )
self._title_text = ba.textwidget(
parent=parent,
@@ -63,24 +67,28 @@ class LeagueRankButton:
maxwidth=size[0] * scale * 0.85,
text=ba.Lstr(
resource='league.leagueRankText',
- fallback_resource='coopSelectWindow.powerRankingText'),
+ fallback_resource='coopSelectWindow.powerRankingText',
+ ),
color=self._header_color,
flatness=1.0,
shadow=1.0,
scale=scale * 0.5,
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
- self._value_text = ba.textwidget(parent=parent,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=size[0] * scale * 0.85,
- text='-',
- draw_controller=self._button,
- big=True,
- scale=scale,
- transition_delay=transition_delay,
- color=textcolor)
+ self._value_text = ba.textwidget(
+ parent=parent,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=size[0] * scale * 0.85,
+ text='-',
+ draw_controller=self._button,
+ big=True,
+ scale=scale,
+ transition_delay=transition_delay,
+ color=textcolor,
+ )
self._smooth_percent: float | None = None
self._percent: int | None = None
@@ -99,10 +107,12 @@ class LeagueRankButton:
self._doing_power_ranking_query = False
self.set_position(position)
self._bg_flash = False
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
# If we've got cached power-ranking data already, apply it.
@@ -119,10 +129,12 @@ class LeagueRankButton:
self._ticking_node.delete()
def _start_smooth_update(self) -> None:
- self._smooth_update_timer = ba.Timer(0.05,
- ba.WeakCall(self._smooth_update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._smooth_update_timer = ba.Timer(
+ 0.05,
+ ba.WeakCall(self._smooth_update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
def _smooth_update(self) -> None:
# pylint: disable=too-many-branches
@@ -136,24 +148,28 @@ class LeagueRankButton:
'sound',
attrs={
'sound': ba.getsound('scoreIncrease'),
- 'positional': False
- })
- self._bg_flash = (not self._bg_flash)
- color_used = ((self._color[0] * 2, self._color[1] * 2,
- self._color[2] *
- 2) if self._bg_flash else self._color)
- textcolor_used = ((1, 1, 1) if self._bg_flash else self._textcolor)
- header_color_used = ((1, 1,
- 1) if self._bg_flash else self._header_color)
+ 'positional': False,
+ },
+ )
+ self._bg_flash = not self._bg_flash
+ color_used = (
+ (self._color[0] * 2, self._color[1] * 2, self._color[2] * 2)
+ if self._bg_flash
+ else self._color
+ )
+ textcolor_used = (1, 1, 1) if self._bg_flash else self._textcolor
+ header_color_used = (
+ (1, 1, 1) if self._bg_flash else self._header_color
+ )
if self._rank is not None:
assert self._smooth_rank is not None
self._smooth_rank -= 1.0 * self._smooth_increase_speed
- finished = (int(self._smooth_rank) <= self._rank)
+ finished = int(self._smooth_rank) <= self._rank
elif self._smooth_percent is not None:
self._smooth_percent += 1.0 * self._smooth_increase_speed
assert self._percent is not None
- finished = (int(self._smooth_percent) >= self._percent)
+ finished = int(self._smooth_percent) >= self._percent
else:
finished = True
if finished:
@@ -175,35 +191,39 @@ class LeagueRankButton:
h_align='center',
v_align='center',
text='+' + self._improvement_text + '!',
- position=(self._position[0] +
- self._size[0] * 0.5 * self._scale,
- self._position[1] +
- self._size[1] * -0.2 * self._scale),
+ position=(
+ self._position[0] + self._size[0] * 0.5 * self._scale,
+ self._position[1] + self._size[1] * -0.2 * self._scale,
+ ),
color=(0, 1, 0),
flatness=1.0,
shadow=0.0,
- scale=self._scale * 0.7)
+ scale=self._scale * 0.7,
+ )
def safe_delete(widget: ba.Widget) -> None:
if widget:
widget.delete()
- ba.timer(2.0,
- ba.Call(safe_delete, diff_text),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 2.0,
+ ba.Call(safe_delete, diff_text),
+ timetype=ba.TimeType.REAL,
+ )
status_text: str | ba.Lstr
if self._rank is not None:
assert self._smooth_rank is not None
- status_text = ba.Lstr(resource='numberText',
- subs=[('${NUMBER}',
- str(int(self._smooth_rank)))])
+ status_text = ba.Lstr(
+ resource='numberText',
+ subs=[('${NUMBER}', str(int(self._smooth_rank)))],
+ )
elif self._smooth_percent is not None:
status_text = str(int(self._smooth_percent)) + '%'
else:
status_text = '-'
- ba.textwidget(edit=self._value_text,
- text=status_text,
- color=textcolor_used)
+ ba.textwidget(
+ edit=self._value_text, text=status_text, color=textcolor_used
+ )
ba.textwidget(edit=self._title_text, color=header_color_used)
ba.buttonwidget(edit=self._button, color=color_used)
@@ -211,8 +231,7 @@ class LeagueRankButton:
ba.print_exception('Error doing smooth update.')
self._smooth_update_timer = None
- def _update_for_league_rank_data(self,
- data: dict[str, Any] | None) -> None:
+ def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -236,20 +255,23 @@ class LeagueRankButton:
# If this is the first set, league has changed, or rank has gotten
# worse, snap the smooth value immediately.
assert self._rank is not None
- if (self._smooth_rank is None or prev_league != self._league
- or self._rank > int(self._smooth_rank)):
+ if (
+ self._smooth_rank is None
+ or prev_league != self._league
+ or self._rank > int(self._smooth_rank)
+ ):
self._smooth_rank = float(self._rank)
- status_text = ba.Lstr(resource='numberText',
- subs=[('${NUMBER}',
- str(int(self._smooth_rank)))])
+ status_text = ba.Lstr(
+ resource='numberText',
+ subs=[('${NUMBER}', str(int(self._smooth_rank)))],
+ )
else:
try:
if not data['scores'] or data['scores'][-1][1] <= 0:
self._percent = self._rank = None
status_text = '-'
else:
- our_points = ba.app.accounts_v1.get_league_rank_points(
- data)
+ our_points = ba.app.accounts_v1.get_league_rank_points(data)
progress = float(our_points) / data['scores'][-1][1]
self._percent = int(progress * 100.0)
self._rank = None
@@ -259,9 +281,11 @@ class LeagueRankButton:
# If this is the first set, league has changed, or percent
# has decreased, snap the smooth value immediately.
- if (self._smooth_percent is None
- or prev_league != self._league
- or self._percent < int(self._smooth_percent)):
+ if (
+ self._smooth_percent is None
+ or prev_league != self._league
+ or self._percent < int(self._smooth_percent)
+ ):
self._smooth_percent = float(self._percent)
status_text = str(int(self._smooth_percent)) + '%'
@@ -271,10 +295,14 @@ class LeagueRankButton:
status_text = '-'
# If we're doing a smooth update, set a timer.
- if (self._rank is not None and self._smooth_rank is not None
- and int(self._smooth_rank) != self._rank):
- self._improvement_text = str(-(int(self._rank) -
- int(self._smooth_rank)))
+ if (
+ self._rank is not None
+ and self._smooth_rank is not None
+ and int(self._smooth_rank) != self._rank
+ ):
+ self._improvement_text = str(
+ -(int(self._rank) - int(self._smooth_rank))
+ )
diff = abs(self._rank - self._smooth_rank)
if diff > 100:
self._smooth_increase_speed = diff / 80.0
@@ -285,25 +313,34 @@ class LeagueRankButton:
else:
self._smooth_increase_speed = diff / 40.0
self._smooth_increase_speed = max(0.4, self._smooth_increase_speed)
- ba.timer(self._smooth_update_delay,
- ba.WeakCall(self._start_smooth_update),
- timetype=ba.TimeType.REAL,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ self._smooth_update_delay,
+ ba.WeakCall(self._start_smooth_update),
+ timetype=ba.TimeType.REAL,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
- if (self._percent is not None and self._smooth_percent is not None
- and int(self._smooth_percent) != self._percent):
+ if (
+ self._percent is not None
+ and self._smooth_percent is not None
+ and int(self._smooth_percent) != self._percent
+ ):
self._improvement_text = str(
- (int(self._percent) - int(self._smooth_percent)))
+ (int(self._percent) - int(self._smooth_percent))
+ )
self._smooth_increase_speed = 0.3
- ba.timer(self._smooth_update_delay,
- ba.WeakCall(self._start_smooth_update),
- timetype=ba.TimeType.REAL,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ ba.timer(
+ self._smooth_update_delay,
+ ba.WeakCall(self._start_smooth_update),
+ timetype=ba.TimeType.REAL,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
if do_percent:
ba.textwidget(
edit=self._title_text,
- text=ba.Lstr(resource='coopSelectWindow.toRankedText'))
+ text=ba.Lstr(resource='coopSelectWindow.toRankedText'),
+ )
else:
try:
assert data is not None
@@ -320,13 +357,15 @@ class LeagueRankButton:
except Exception:
txt = ba.Lstr(
resource='league.leagueRankText',
- fallback_resource='coopSelectWindow.powerRankingText')
+ fallback_resource='coopSelectWindow.powerRankingText',
+ )
t_color = ba.app.ui.title_color
ba.textwidget(edit=self._title_text, text=txt, color=t_color)
ba.textwidget(edit=self._value_text, text=status_text)
- def _on_power_ranking_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_power_ranking_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
self._doing_power_ranking_query = False
ba.app.accounts_v1.cache_league_rank_data(data)
self._update_for_league_rank_data(data)
@@ -346,16 +385,19 @@ class LeagueRankButton:
# Send off a new power-ranking query if its been
# long enough or whatnot.
if not self._doing_power_ranking_query and (
- self._last_power_ranking_query_time is None
- or cur_time - self._last_power_ranking_query_time > 30.0):
+ self._last_power_ranking_query_time is None
+ or cur_time - self._last_power_ranking_query_time > 30.0
+ ):
self._last_power_ranking_query_time = cur_time
self._doing_power_ranking_query = True
ba.internal.power_ranking_query(
- callback=ba.WeakCall(self._on_power_ranking_query_response))
+ callback=ba.WeakCall(self._on_power_ranking_query_response)
+ )
def _default_on_activate_call(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.league.rankwindow import LeagueRankWindow
+
LeagueRankWindow(modal=True, origin_widget=self._button)
def set_position(self, position: tuple[float, float]) -> None:
@@ -366,12 +408,18 @@ class LeagueRankButton:
ba.buttonwidget(edit=self._button, position=self._position)
ba.textwidget(
edit=self._title_text,
- position=(self._position[0] + self._size[0] * 0.5 * self._scale,
- self._position[1] + self._size[1] * 0.82 * self._scale))
+ position=(
+ self._position[0] + self._size[0] * 0.5 * self._scale,
+ self._position[1] + self._size[1] * 0.82 * self._scale,
+ ),
+ )
ba.textwidget(
edit=self._value_text,
- position=(self._position[0] + self._size[0] * 0.5 * self._scale,
- self._position[1] + self._size[1] * 0.36 * self._scale))
+ position=(
+ self._position[0] + self._size[0] * 0.5 * self._scale,
+ self._position[1] + self._size[1] * 0.36 * self._scale,
+ ),
+ )
def get_button(self) -> ba.Widget:
"""Return the underlying button ba.Widget>"""
diff --git a/assets/src/ba_data/python/bastd/ui/league/rankwindow.py b/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
index 6c6dab43..3bf2589b 100644
--- a/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
+++ b/assets/src/ba_data/python/bastd/ui/league/rankwindow.py
@@ -1,6 +1,7 @@
# Released under the MIT License. See LICENSE for details.
#
"""UI related to league rank."""
+# pylint: disable=too-many-lines
from __future__ import annotations
@@ -18,10 +19,12 @@ if TYPE_CHECKING:
class LeagueRankWindow(ba.Window):
"""Window for showing league rank."""
- def __init__(self,
- transition: str = 'in_right',
- modal: bool = False,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ modal: bool = False,
+ origin_widget: ba.Widget | None = None,
+ ):
ba.set_analytics_screen('League Rank Window')
self._league_rank_data: dict[str, Any] | None = None
@@ -40,8 +43,13 @@ class LeagueRankWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (657 if uiscale is ba.UIScale.SMALL else
- 710 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 657
+ if uiscale is ba.UIScale.SMALL
+ else 710
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
self._r = 'coopSelectWindow'
self._rdict = ba.app.lang.get_resource(self._r)
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
@@ -51,25 +59,39 @@ class LeagueRankWindow(ba.Window):
self._is_current_season = False
self._can_do_more_button = True
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (
- 0, 10) if uiscale is ba.UIScale.MEDIUM else (0, 0),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- scale=(1.2 if uiscale is ba.UIScale.SMALL else
- 0.93 if uiscale is ba.UIScale.MEDIUM else 0.8)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ stack_offset=(0, -15)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 10)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.2
+ if uiscale is ba.UIScale.SMALL
+ else 0.93
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ )
+ )
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
- position=(75 + x_inset, self._height - 87 -
- (4 if uiscale is ba.UIScale.SMALL else 0)),
+ position=(
+ 75 + x_inset,
+ self._height - 87 - (4 if uiscale is ba.UIScale.SMALL else 0),
+ ),
size=(120, 60),
scale=1.2,
autoselect=True,
label=ba.Lstr(resource='doneText' if self._modal else 'backText'),
button_type=None if self._modal else 'back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
self._title_text = ba.textwidget(
parent=self._root_widget,
@@ -77,33 +99,42 @@ class LeagueRankWindow(ba.Window):
size=(0, 0),
text=ba.Lstr(
resource='league.leagueRankText',
- fallback_resource='coopSelectWindow.powerRankingText'),
+ fallback_resource='coopSelectWindow.powerRankingText',
+ ),
h_align='center',
color=ba.app.ui.title_color,
scale=1.4,
maxwidth=600,
- v_align='center')
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- position=(75 + x_inset, self._height - 87 -
- (2 if uiscale is ba.UIScale.SMALL else 0)),
- size=(60, 55),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ position=(
+ 75 + x_inset,
+ self._height - 87 - (2 if uiscale is ba.UIScale.SMALL else 0),
+ ),
+ size=(60, 55),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._scroll_width = self._width - (130 + 2 * x_inset)
self._scroll_height = self._height - 160
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- highlight=False,
- position=(65 + x_inset, 70),
- size=(self._scroll_width,
- self._scroll_height),
- center_small_content=True)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ highlight=False,
+ position=(65 + x_inset, 70),
+ size=(self._scroll_width, self._scroll_height),
+ center_small_content=True,
+ )
ba.widget(edit=self._scrollwidget, autoselect=True)
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button,
- selected_child=self._back_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._back_button,
+ selected_child=self._back_button,
+ )
self._last_power_ranking_query_time: float | None = None
self._doing_power_ranking_query = False
@@ -128,71 +159,107 @@ class LeagueRankWindow(ba.Window):
if info is not None:
self._update_for_league_rank_data(info)
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update(show=(info is None))
def _on_achievements_press(self) -> None:
from bastd.ui import achievements
+
# only allow this for all-time or the current season
# (we currently don't keep specific achievement data for old seasons)
if self._season == 'a' or self._is_current_season:
+ prab = self._power_ranking_achievements_button
achievements.AchievementsWindow(
- position=(self._power_ranking_achievements_button.
- get_screen_space_center()))
+ position=prab.get_screen_space_center()
+ )
else:
- ba.screenmessage(ba.Lstr(
- resource='achievementsUnavailableForOldSeasonsText',
- fallback_resource='unavailableText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='achievementsUnavailableForOldSeasonsText',
+ fallback_resource='unavailableText',
+ ),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
def _on_activity_mult_press(self) -> None:
from bastd.ui import confirm
+
txt = ba.Lstr(
resource='coopSelectWindow.activenessAllTimeInfoText'
- if self._season == 'a' else 'coopSelectWindow.activenessInfoText',
- subs=[('${MAX}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'activenessMax', 1.0)))])
- confirm.ConfirmWindow(txt,
- cancel_button=False,
- width=460,
- height=150,
- origin_widget=self._activity_mult_button)
+ if self._season == 'a'
+ else 'coopSelectWindow.activenessInfoText',
+ subs=[
+ (
+ '${MAX}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'activenessMax', 1.0
+ )
+ ),
+ )
+ ],
+ )
+ confirm.ConfirmWindow(
+ txt,
+ cancel_button=False,
+ width=460,
+ height=150,
+ origin_widget=self._activity_mult_button,
+ )
def _on_pro_mult_press(self) -> None:
from bastd.ui import confirm
- txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText',
- subs=[('${PERCENT}',
- str(
- ba.internal.get_v1_account_misc_read_val(
- 'proPowerRankingBoost', 10))),
- ('${PRO}',
- ba.Lstr(resource='store.bombSquadProNameText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]))])
- confirm.ConfirmWindow(txt,
- cancel_button=False,
- width=460,
- height=130,
- origin_widget=self._pro_mult_button)
+
+ txt = ba.Lstr(
+ resource='coopSelectWindow.proMultInfoText',
+ subs=[
+ (
+ '${PERCENT}',
+ str(
+ ba.internal.get_v1_account_misc_read_val(
+ 'proPowerRankingBoost', 10
+ )
+ ),
+ ),
+ (
+ '${PRO}',
+ ba.Lstr(
+ resource='store.bombSquadProNameText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ ),
+ ],
+ )
+ confirm.ConfirmWindow(
+ txt,
+ cancel_button=False,
+ width=460,
+ height=130,
+ origin_widget=self._pro_mult_button,
+ )
def _on_trophies_press(self) -> None:
from bastd.ui.trophies import TrophiesWindow
+
info = self._league_rank_data
if info is not None:
- TrophiesWindow(position=self._power_ranking_trophies_button.
- get_screen_space_center(),
- data=info)
+ prtb = self._power_ranking_trophies_button
+ TrophiesWindow(
+ position=prtb.get_screen_space_center(),
+ data=info,
+ )
else:
ba.playsound(ba.getsound('error'))
- def _on_power_ranking_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_power_ranking_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
self._doing_power_ranking_query = False
# important: *only* cache this if we requested the current season..
if data is not None and data.get('s', None) is None:
@@ -222,8 +289,9 @@ class LeagueRankWindow(ba.Window):
# send off a new power-ranking query if its been long enough or our
# requested season has changed or whatnot..
if not self._doing_power_ranking_query and (
- self._last_power_ranking_query_time is None
- or cur_time - self._last_power_ranking_query_time > 30.0):
+ self._last_power_ranking_query_time is None
+ or cur_time - self._last_power_ranking_query_time > 30.0
+ ):
try:
if show:
ba.textwidget(edit=self._league_title_text, text='')
@@ -231,9 +299,11 @@ class LeagueRankWindow(ba.Window):
ba.textwidget(edit=self._league_number_text, text='')
ba.textwidget(
edit=self._your_power_ranking_text,
- text=ba.Lstr(value='${A}...',
- subs=[('${A}',
- ba.Lstr(resource='loadingText'))]))
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
+ )
ba.textwidget(edit=self._to_ranked_text, text='')
ba.textwidget(edit=self._power_ranking_rank_text, text='')
ba.textwidget(edit=self._season_ends_text, text='')
@@ -245,7 +315,8 @@ class LeagueRankWindow(ba.Window):
self._doing_power_ranking_query = True
ba.internal.power_ranking_query(
season=self._requested_season,
- callback=ba.WeakCall(self._on_power_ranking_query_response))
+ callback=ba.WeakCall(self._on_power_ranking_query_response),
+ )
def _refresh(self) -> None:
# pylint: disable=too-many-statements
@@ -256,7 +327,8 @@ class LeagueRankWindow(ba.Window):
self._subcontainer = ba.containerwidget(
parent=self._scrollwidget,
size=(self._subcontainerwidth, self._subcontainerheight),
- background=False)
+ background=False,
+ )
w_parent = self._subcontainer
v = self._subcontainerheight - 20
@@ -273,17 +345,19 @@ class LeagueRankWindow(ba.Window):
tally_maxwidth = 120
v2 -= 70
- ba.textwidget(parent=w_parent,
- position=(h2 - 60, v2 + 106),
- size=(0, 0),
- flatness=1.0,
- shadow=0.0,
- text=ba.Lstr(resource='coopSelectWindow.pointsText'),
- h_align='left',
- v_align='center',
- scale=0.8,
- color=(1, 1, 1, 0.3),
- maxwidth=200)
+ ba.textwidget(
+ parent=w_parent,
+ position=(h2 - 60, v2 + 106),
+ size=(0, 0),
+ flatness=1.0,
+ shadow=0.0,
+ text=ba.Lstr(resource='coopSelectWindow.pointsText'),
+ h_align='left',
+ v_align='center',
+ scale=0.8,
+ color=(1, 1, 1, 0.3),
+ maxwidth=200,
+ )
self._power_ranking_achievements_button = ba.buttonwidget(
parent=w_parent,
@@ -296,7 +370,8 @@ class LeagueRankWindow(ba.Window):
left_widget=self._back_button,
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
- label='')
+ label='',
+ )
self._power_ranking_achievement_total_text = ba.textwidget(
parent=w_parent,
@@ -309,7 +384,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
color=tally_color,
- maxwidth=tally_maxwidth)
+ maxwidth=tally_maxwidth,
+ )
v2 -= 80
@@ -323,7 +399,8 @@ class LeagueRankWindow(ba.Window):
left_widget=self._back_button,
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
- label='')
+ label='',
+ )
self._power_ranking_trophies_total_text = ba.textwidget(
parent=w_parent,
position=(h2 + h_offs_tally, v2 + 45),
@@ -335,7 +412,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
color=tally_color,
- maxwidth=tally_maxwidth)
+ maxwidth=tally_maxwidth,
+ )
v2 -= 100
@@ -350,7 +428,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
color=(1, 1, 1, 0.3),
- maxwidth=200)
+ maxwidth=200,
+ )
self._activity_mult_button: ba.Widget | None
if ba.internal.get_v1_account_misc_read_val('act', False):
@@ -365,7 +444,8 @@ class LeagueRankWindow(ba.Window):
on_activate_call=ba.WeakCall(self._on_activity_mult_press),
left_widget=self._back_button,
color=(0.5, 0.5, 0.6),
- textcolor=(0.7, 0.7, 0.8))
+ textcolor=(0.7, 0.7, 0.8),
+ )
self._activity_mult_text = ba.textwidget(
parent=w_parent,
@@ -378,7 +458,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
color=tally_color,
- maxwidth=tally_maxwidth)
+ maxwidth=tally_maxwidth,
+ )
v2 -= 65
else:
self._activity_mult_button = None
@@ -389,41 +470,46 @@ class LeagueRankWindow(ba.Window):
size=(200, 60),
icon=ba.gettexture('logo'),
icon_color=(0.3, 0, 0.3),
- label=ba.Lstr(resource='store.bombSquadProNameText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]),
+ label=ba.Lstr(
+ resource='store.bombSquadProNameText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
autoselect=True,
on_activate_call=ba.WeakCall(self._on_pro_mult_press),
left_widget=self._back_button,
color=(0.5, 0.5, 0.6),
- textcolor=(0.7, 0.7, 0.8))
+ textcolor=(0.7, 0.7, 0.8),
+ )
- self._pro_mult_text = ba.textwidget(parent=w_parent,
- position=(h2 + h_offs_tally,
- v2 + 40),
- size=(0, 0),
- flatness=1.0,
- shadow=0.0,
- text='-',
- h_align='left',
- v_align='center',
- scale=0.8,
- color=tally_color,
- maxwidth=tally_maxwidth)
+ self._pro_mult_text = ba.textwidget(
+ parent=w_parent,
+ position=(h2 + h_offs_tally, v2 + 40),
+ size=(0, 0),
+ flatness=1.0,
+ shadow=0.0,
+ text='-',
+ h_align='left',
+ v_align='center',
+ scale=0.8,
+ color=tally_color,
+ maxwidth=tally_maxwidth,
+ )
v2 -= 30
v2 -= spc
- ba.textwidget(parent=w_parent,
- position=(h2 + h_offs_tally - 10 - 40, v2 + 35),
- size=(0, 0),
- flatness=1.0,
- shadow=0.0,
- text=ba.Lstr(resource='finalScoreText'),
- h_align='right',
- v_align='center',
- scale=0.9,
- color=worth_color,
- maxwidth=150)
+ ba.textwidget(
+ parent=w_parent,
+ position=(h2 + h_offs_tally - 10 - 40, v2 + 35),
+ size=(0, 0),
+ flatness=1.0,
+ shadow=0.0,
+ text=ba.Lstr(resource='finalScoreText'),
+ h_align='right',
+ v_align='center',
+ scale=0.9,
+ color=worth_color,
+ maxwidth=150,
+ )
self._power_ranking_total_text = ba.textwidget(
parent=w_parent,
position=(h2 + h_offs_tally - 40, v2 + 35),
@@ -435,7 +521,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.9,
color=tally_color,
- maxwidth=tally_maxwidth)
+ maxwidth=tally_maxwidth,
+ )
self._season_show_text = ba.textwidget(
parent=w_parent,
@@ -448,92 +535,105 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
shadow=0,
- flatness=1.0)
+ flatness=1.0,
+ )
- self._league_title_text = ba.textwidget(parent=w_parent,
- position=(470, v - 97),
- size=(0, 0),
- color=(0.6, 0.6, 0.7),
- maxwidth=230,
- text='',
- h_align='center',
- v_align='center',
- scale=0.9,
- shadow=0,
- flatness=1.0)
+ self._league_title_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 97),
+ size=(0, 0),
+ color=(0.6, 0.6, 0.7),
+ maxwidth=230,
+ text='',
+ h_align='center',
+ v_align='center',
+ scale=0.9,
+ shadow=0,
+ flatness=1.0,
+ )
self._league_text_scale = 1.8
self._league_text_maxwidth = 210
- self._league_text = ba.textwidget(parent=w_parent,
- position=(470, v - 140),
- size=(0, 0),
- color=(1, 1, 1),
- maxwidth=self._league_text_maxwidth,
- text='-',
- h_align='center',
- v_align='center',
- scale=self._league_text_scale,
- shadow=1.0,
- flatness=1.0)
+ self._league_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 140),
+ size=(0, 0),
+ color=(1, 1, 1),
+ maxwidth=self._league_text_maxwidth,
+ text='-',
+ h_align='center',
+ v_align='center',
+ scale=self._league_text_scale,
+ shadow=1.0,
+ flatness=1.0,
+ )
self._league_number_base_pos = (470, v - 140)
- self._league_number_text = ba.textwidget(parent=w_parent,
- position=(470, v - 140),
- size=(0, 0),
- color=(1, 1, 1),
- maxwidth=100,
- text='',
- h_align='left',
- v_align='center',
- scale=0.8,
- shadow=1.0,
- flatness=1.0)
+ self._league_number_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 140),
+ size=(0, 0),
+ color=(1, 1, 1),
+ maxwidth=100,
+ text='',
+ h_align='left',
+ v_align='center',
+ scale=0.8,
+ shadow=1.0,
+ flatness=1.0,
+ )
- self._your_power_ranking_text = ba.textwidget(parent=w_parent,
- position=(470,
- v - 142 - 70),
- size=(0, 0),
- color=(0.6, 0.6, 0.7),
- maxwidth=230,
- text='',
- h_align='center',
- v_align='center',
- scale=0.9,
- shadow=0,
- flatness=1.0)
+ self._your_power_ranking_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 142 - 70),
+ size=(0, 0),
+ color=(0.6, 0.6, 0.7),
+ maxwidth=230,
+ text='',
+ h_align='center',
+ v_align='center',
+ scale=0.9,
+ shadow=0,
+ flatness=1.0,
+ )
- self._to_ranked_text = ba.textwidget(parent=w_parent,
- position=(470, v - 250 - 70),
- size=(0, 0),
- color=(0.6, 0.6, 0.7),
- maxwidth=230,
- text='',
- h_align='center',
- v_align='center',
- scale=0.8,
- shadow=0,
- flatness=1.0)
+ self._to_ranked_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 250 - 70),
+ size=(0, 0),
+ color=(0.6, 0.6, 0.7),
+ maxwidth=230,
+ text='',
+ h_align='center',
+ v_align='center',
+ scale=0.8,
+ shadow=0,
+ flatness=1.0,
+ )
- self._power_ranking_rank_text = ba.textwidget(parent=w_parent,
- position=(473,
- v - 210 - 70),
- size=(0, 0),
- big=False,
- text='-',
- h_align='center',
- v_align='center',
- scale=1.0)
+ self._power_ranking_rank_text = ba.textwidget(
+ parent=w_parent,
+ position=(473, v - 210 - 70),
+ size=(0, 0),
+ big=False,
+ text='-',
+ h_align='center',
+ v_align='center',
+ scale=1.0,
+ )
- self._season_ends_text = ba.textwidget(parent=w_parent,
- position=(470, v - 380),
- size=(0, 0),
- color=(0.6, 0.6, 0.6),
- maxwidth=230,
- text='',
- h_align='center',
- v_align='center',
- scale=0.9,
- shadow=0,
- flatness=1.0)
+ self._season_ends_text = ba.textwidget(
+ parent=w_parent,
+ position=(470, v - 380),
+ size=(0, 0),
+ color=(0.6, 0.6, 0.6),
+ maxwidth=230,
+ text='',
+ h_align='center',
+ v_align='center',
+ scale=0.9,
+ shadow=0,
+ flatness=1.0,
+ )
self._trophy_counts_reset_text = ba.textwidget(
parent=w_parent,
position=(470, v - 410),
@@ -545,7 +645,8 @@ class LeagueRankWindow(ba.Window):
v_align='center',
scale=0.8,
shadow=0,
- flatness=1.0)
+ flatness=1.0,
+ )
self._power_ranking_score_widgets = []
@@ -554,15 +655,16 @@ class LeagueRankWindow(ba.Window):
h = 707
v -= 451
- self._see_more_button = ba.buttonwidget(parent=w_parent,
- label=self._rdict.seeMoreText,
- position=(h, v),
- color=(0.5, 0.5, 0.6),
- textcolor=(0.7, 0.7, 0.8),
- size=(230, 60),
- autoselect=True,
- on_activate_call=ba.WeakCall(
- self._on_more_press))
+ self._see_more_button = ba.buttonwidget(
+ parent=w_parent,
+ label=self._rdict.seeMoreText,
+ position=(h, v),
+ color=(0.5, 0.5, 0.6),
+ textcolor=(0.7, 0.7, 0.8),
+ size=(230, 60),
+ autoselect=True,
+ on_activate_call=ba.WeakCall(self._on_more_press),
+ )
def _on_more_press(self) -> None:
our_login_id = ba.internal.get_public_login_id()
@@ -570,32 +672,37 @@ class LeagueRankWindow(ba.Window):
# 'resolvedAccountID', None)
if not self._can_do_more_button or our_login_id is None:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='unavailableText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='unavailableText'), color=(1, 0, 0)
+ )
return
if self._season is None:
season_str = ''
else:
- season_str = (
- '&season=' +
- ('all_time' if self._season == 'a' else self._season))
+ season_str = '&season=' + (
+ 'all_time' if self._season == 'a' else self._season
+ )
if self._league_url_arg != '':
league_str = '&league=' + self._league_url_arg
else:
league_str = ''
- ba.open_url(ba.internal.get_master_server_address() +
- '/highscores?list=powerRankings&v=2' + league_str +
- season_str + '&player=' + our_login_id)
+ ba.open_url(
+ ba.internal.get_master_server_address()
+ + '/highscores?list=powerRankings&v=2'
+ + league_str
+ + season_str
+ + '&player='
+ + our_login_id
+ )
- def _update_for_league_rank_data(self,
- data: dict[str, Any] | None) -> None:
+ def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None:
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
if not self._root_widget:
return
accounts = ba.app.accounts_v1
- in_top = (data is not None and data['rank'] is not None)
+ in_top = data is not None and data['rank'] is not None
eq_text = self._rdict.powerRankingPointsEqualsText
pts_txt = self._rdict.powerRankingPointsText
num_text = ba.Lstr(resource='numberText').evaluate()
@@ -604,8 +711,9 @@ class LeagueRankWindow(ba.Window):
self._can_do_more_button = True
extra_text = ''
if ba.internal.get_v1_account_state() != 'signed_in':
- status_text = '(' + ba.Lstr(
- resource='notSignedInText').evaluate() + ')'
+ status_text = (
+ '(' + ba.Lstr(resource='notSignedInText').evaluate() + ')'
+ )
elif in_top:
assert data is not None
status_text = num_text.replace('${NUMBER}', str(data['rank']))
@@ -615,25 +723,27 @@ class LeagueRankWindow(ba.Window):
# at the end..
if not data['scores']:
status_text = (
- self._rdict.powerRankingFinishedSeasonUnrankedText)
+ self._rdict.powerRankingFinishedSeasonUnrankedText
+ )
extra_text = ''
finished_season_unranked = True
self._can_do_more_button = False
else:
our_points = accounts.get_league_rank_points(data)
- progress = float(our_points) / max(1,
- data['scores'][-1][1])
+ progress = float(our_points) / max(1, data['scores'][-1][1])
status_text = str(int(progress * 100.0)) + '%'
extra_text = (
- '\n' +
- self._rdict.powerRankingPointsToRankedText.replace(
- '${CURRENT}', str(our_points)).replace(
- '${REMAINING}', str(data['scores'][-1][1])))
+ '\n'
+ + self._rdict.powerRankingPointsToRankedText.replace(
+ '${CURRENT}', str(our_points)
+ ).replace('${REMAINING}', str(data['scores'][-1][1]))
+ )
do_percent = True
except Exception:
ba.print_exception('Error updating power ranking.')
status_text = self._rdict.powerRankingNotInTopText.replace(
- '${NUMBER}', str(data['listSize']))
+ '${NUMBER}', str(data['listSize'])
+ )
extra_text = ''
else:
status_text = '-'
@@ -658,8 +768,11 @@ class LeagueRankWindow(ba.Window):
season_choices.append(ssn)
if ssn != 'a' and not did_first:
season_choices_display.append(
- ba.Lstr(resource='league.currentSeasonText',
- subs=[('${NUMBER}', ssn)]))
+ ba.Lstr(
+ resource='league.currentSeasonText',
+ subs=[('${NUMBER}', ssn)],
+ )
+ )
did_first = True
# if we either did not specify a season or specified the
# first, we're looking at the current..
@@ -667,11 +780,15 @@ class LeagueRankWindow(ba.Window):
self._is_current_season = True
elif ssn == 'a':
season_choices_display.append(
- ba.Lstr(resource='league.allTimeText'))
+ ba.Lstr(resource='league.allTimeText')
+ )
else:
season_choices_display.append(
- ba.Lstr(resource='league.seasonText',
- subs=[('${NUMBER}', ssn)]))
+ ba.Lstr(
+ resource='league.seasonText',
+ subs=[('${NUMBER}', ssn)],
+ )
+ )
assert self._subcontainer
self._season_popup_menu = popup_ui.PopupMenu(
parent=self._subcontainer,
@@ -681,21 +798,30 @@ class LeagueRankWindow(ba.Window):
choices=season_choices,
on_value_change_call=ba.WeakCall(self._on_season_change),
choices_display=season_choices_display,
- current_choice=self._season)
+ current_choice=self._season,
+ )
if popup_was_selected:
ba.containerwidget(
edit=self._subcontainer,
- selected_child=self._season_popup_menu.get_button())
+ selected_child=self._season_popup_menu.get_button(),
+ )
ba.widget(edit=self._see_more_button, show_buffer_bottom=100)
- ba.widget(edit=self._season_popup_menu.get_button(),
- up_widget=self._back_button)
- ba.widget(edit=self._back_button,
- down_widget=self._power_ranking_achievements_button,
- right_widget=self._season_popup_menu.get_button())
+ ba.widget(
+ edit=self._season_popup_menu.get_button(),
+ up_widget=self._back_button,
+ )
+ ba.widget(
+ edit=self._back_button,
+ down_widget=self._power_ranking_achievements_button,
+ right_widget=self._season_popup_menu.get_button(),
+ )
- ba.textwidget(edit=self._league_title_text,
- text='' if self._season == 'a' else ba.Lstr(
- resource='league.leagueText'))
+ ba.textwidget(
+ edit=self._league_title_text,
+ text=''
+ if self._season == 'a'
+ else ba.Lstr(resource='league.leagueText'),
+ )
if data is None:
lname = ''
@@ -709,11 +835,13 @@ class LeagueRankWindow(ba.Window):
self._league_url_arg = ''
else:
lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else ''
- lname = ba.Lstr(translate=('leagueNames',
- data['l']['n'])).evaluate()
+ lname = ba.Lstr(
+ translate=('leagueNames', data['l']['n'])
+ ).evaluate()
lcolor = data['l']['c']
- self._league_url_arg = (data['l']['n'] + '_' +
- str(data['l']['i'])).lower()
+ self._league_url_arg = (
+ data['l']['n'] + '_' + str(data['l']['i'])
+ ).lower()
to_end_string: ba.Lstr | str
if data is None or self._season == 'a' or data['se'] is None:
@@ -724,87 +852,127 @@ class LeagueRankWindow(ba.Window):
days_to_end = data['se'][0]
minutes_to_end = data['se'][1]
if days_to_end > 0:
- to_end_string = ba.Lstr(resource='league.seasonEndsDaysText',
- subs=[('${NUMBER}', str(days_to_end))])
+ to_end_string = ba.Lstr(
+ resource='league.seasonEndsDaysText',
+ subs=[('${NUMBER}', str(days_to_end))],
+ )
elif days_to_end == 0 and minutes_to_end >= 60:
- to_end_string = ba.Lstr(resource='league.seasonEndsHoursText',
- subs=[('${NUMBER}',
- str(minutes_to_end // 60))])
+ to_end_string = ba.Lstr(
+ resource='league.seasonEndsHoursText',
+ subs=[('${NUMBER}', str(minutes_to_end // 60))],
+ )
elif days_to_end == 0 and minutes_to_end >= 0:
to_end_string = ba.Lstr(
resource='league.seasonEndsMinutesText',
- subs=[('${NUMBER}', str(minutes_to_end))])
+ subs=[('${NUMBER}', str(minutes_to_end))],
+ )
else:
to_end_string = ba.Lstr(
resource='league.seasonEndedDaysAgoText',
- subs=[('${NUMBER}', str(-(days_to_end + 1)))])
+ subs=[('${NUMBER}', str(-(days_to_end + 1)))],
+ )
ba.textwidget(edit=self._season_ends_text, text=to_end_string)
- ba.textwidget(edit=self._trophy_counts_reset_text,
- text=ba.Lstr(resource='league.trophyCountsResetText')
- if self._is_current_season and show_season_end else '')
+ ba.textwidget(
+ edit=self._trophy_counts_reset_text,
+ text=ba.Lstr(resource='league.trophyCountsResetText')
+ if self._is_current_season and show_season_end
+ else '',
+ )
ba.textwidget(edit=self._league_text, text=lname, color=lcolor)
l_text_width = min(
self._league_text_maxwidth,
- ba.internal.get_string_width(lname, suppress_warning=True) *
- self._league_text_scale)
+ ba.internal.get_string_width(lname, suppress_warning=True)
+ * self._league_text_scale,
+ )
ba.textwidget(
edit=self._league_number_text,
text=lnum,
color=lcolor,
- position=(self._league_number_base_pos[0] + l_text_width * 0.5 + 8,
- self._league_number_base_pos[1] + 10))
+ position=(
+ self._league_number_base_pos[0] + l_text_width * 0.5 + 8,
+ self._league_number_base_pos[1] + 10,
+ ),
+ )
ba.textwidget(
edit=self._to_ranked_text,
- text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate() +
- '' + extra_text if do_percent else '')
+ text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate()
+ + ''
+ + extra_text
+ if do_percent
+ else '',
+ )
ba.textwidget(
edit=self._your_power_ranking_text,
text=ba.Lstr(
resource='rankText',
- fallback_resource='coopSelectWindow.yourPowerRankingText') if
- (not do_percent) else '')
+ fallback_resource='coopSelectWindow.yourPowerRankingText',
+ )
+ if (not do_percent)
+ else '',
+ )
- ba.textwidget(edit=self._power_ranking_rank_text,
- position=(473, v - 70 - (170 if do_percent else 220)),
- text=status_text,
- big=(in_top or do_percent),
- scale=3.0 if (in_top or do_percent) else
- 0.7 if finished_season_unranked else 1.0)
+ ba.textwidget(
+ edit=self._power_ranking_rank_text,
+ position=(473, v - 70 - (170 if do_percent else 220)),
+ text=status_text,
+ big=(in_top or do_percent),
+ scale=3.0
+ if (in_top or do_percent)
+ else 0.7
+ if finished_season_unranked
+ else 1.0,
+ )
if self._activity_mult_button is not None:
if data is None or data['act'] is None:
- ba.buttonwidget(edit=self._activity_mult_button,
- textcolor=(0.7, 0.7, 0.8, 0.5),
- icon_color=(0.5, 0, 0.5, 0.3))
+ ba.buttonwidget(
+ edit=self._activity_mult_button,
+ textcolor=(0.7, 0.7, 0.8, 0.5),
+ icon_color=(0.5, 0, 0.5, 0.3),
+ )
ba.textwidget(edit=self._activity_mult_text, text=' -')
else:
- ba.buttonwidget(edit=self._activity_mult_button,
- textcolor=(0.7, 0.7, 0.8, 1.0),
- icon_color=(0.5, 0, 0.5, 1.0))
+ ba.buttonwidget(
+ edit=self._activity_mult_button,
+ textcolor=(0.7, 0.7, 0.8, 1.0),
+ icon_color=(0.5, 0, 0.5, 1.0),
+ )
# pylint: disable=consider-using-f-string
- ba.textwidget(edit=self._activity_mult_text,
- text='x ' + ('%.2f' % data['act']))
+ ba.textwidget(
+ edit=self._activity_mult_text,
+ text='x ' + ('%.2f' % data['act']),
+ )
have_pro = False if data is None else data['p']
- pro_mult = 1.0 + float(
- ba.internal.get_v1_account_misc_read_val('proPowerRankingBoost',
- 0.0)) * 0.01
+ pro_mult = (
+ 1.0
+ + float(
+ ba.internal.get_v1_account_misc_read_val(
+ 'proPowerRankingBoost', 0.0
+ )
+ )
+ * 0.01
+ )
# pylint: disable=consider-using-f-string
- ba.textwidget(edit=self._pro_mult_text,
- text=' -' if
- (data is None or not have_pro) else 'x ' +
- ('%.2f' % pro_mult))
- ba.buttonwidget(edit=self._pro_mult_button,
- textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)),
- icon_color=(0.5, 0, 0.5) if have_pro else
- (0.5, 0, 0.5, 0.2))
- ba.buttonwidget(edit=self._power_ranking_achievements_button,
- label=('' if data is None else
- (str(data['a']) + ' ')) +
- ba.Lstr(resource='achievementsText').evaluate())
+ ba.textwidget(
+ edit=self._pro_mult_text,
+ text=' -'
+ if (data is None or not have_pro)
+ else 'x ' + ('%.2f' % pro_mult),
+ )
+ ba.buttonwidget(
+ edit=self._pro_mult_button,
+ textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)),
+ icon_color=(0.5, 0, 0.5) if have_pro else (0.5, 0, 0.5, 0.2),
+ )
+ ba.buttonwidget(
+ edit=self._power_ranking_achievements_button,
+ label=('' if data is None else (str(data['a']) + ' '))
+ + ba.Lstr(resource='achievementsText').evaluate(),
+ )
# for the achievement value, use the number they gave us for
# non-current seasons; otherwise calc our own
@@ -816,28 +984,39 @@ class LeagueRankWindow(ba.Window):
if data is not None and 'at' in data:
total_ach_value = data['at']
- ba.textwidget(edit=self._power_ranking_achievement_total_text,
- text='-' if data is None else
- ('+ ' +
- pts_txt.replace('${NUMBER}', str(total_ach_value))))
+ ba.textwidget(
+ edit=self._power_ranking_achievement_total_text,
+ text='-'
+ if data is None
+ else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))),
+ )
- total_trophies_count = (accounts.get_league_rank_points(
- data, 'trophyCount'))
- total_trophies_value = (accounts.get_league_rank_points(
- data, 'trophies'))
- ba.buttonwidget(edit=self._power_ranking_trophies_button,
- label=('' if data is None else
- (str(total_trophies_count) + ' ')) +
- ba.Lstr(resource='trophiesText').evaluate())
+ total_trophies_count = accounts.get_league_rank_points(
+ data, 'trophyCount'
+ )
+ total_trophies_value = accounts.get_league_rank_points(data, 'trophies')
+ ba.buttonwidget(
+ edit=self._power_ranking_trophies_button,
+ label=('' if data is None else (str(total_trophies_count) + ' '))
+ + ba.Lstr(resource='trophiesText').evaluate(),
+ )
ba.textwidget(
edit=self._power_ranking_trophies_total_text,
- text='-' if data is None else
- ('+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value))))
+ text='-'
+ if data is None
+ else (
+ '+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value))
+ ),
+ )
ba.textwidget(
edit=self._power_ranking_total_text,
- text='-' if data is None else eq_text.replace(
- '${NUMBER}', str(accounts.get_league_rank_points(data))))
+ text='-'
+ if data is None
+ else eq_text.replace(
+ '${NUMBER}', str(accounts.get_league_rank_points(data))
+ ),
+ )
for widget in self._power_ranking_score_widgets:
widget.delete()
self._power_ranking_score_widgets = []
@@ -851,60 +1030,73 @@ class LeagueRankWindow(ba.Window):
h2 = 680
is_us = score[3]
self._power_ranking_score_widgets.append(
- ba.textwidget(parent=w_parent,
- position=(h2 - 20, v2),
- size=(0, 0),
- color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
- maxwidth=40,
- flatness=1.0,
- shadow=0.0,
- text=num_text.replace('${NUMBER}',
- str(score[0])),
- h_align='right',
- v_align='center',
- scale=0.5))
+ ba.textwidget(
+ parent=w_parent,
+ position=(h2 - 20, v2),
+ size=(0, 0),
+ color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
+ maxwidth=40,
+ flatness=1.0,
+ shadow=0.0,
+ text=num_text.replace('${NUMBER}', str(score[0])),
+ h_align='right',
+ v_align='center',
+ scale=0.5,
+ )
+ )
self._power_ranking_score_widgets.append(
- ba.textwidget(parent=w_parent,
- position=(h2 + 20, v2),
- size=(0, 0),
- color=(1, 1, 1) if is_us else tally_color,
- maxwidth=60,
- text=str(score[1]),
- flatness=1.0,
- shadow=0.0,
- h_align='center',
- v_align='center',
- scale=0.7))
- txt = ba.textwidget(parent=w_parent,
- position=(h2 + 60, v2 - (28 * 0.5) / 0.9),
- size=(210 / 0.9, 28),
- color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
- maxwidth=210,
- flatness=1.0,
- shadow=0.0,
- autoselect=True,
- selectable=True,
- click_activate=True,
- text=score[2],
- h_align='left',
- v_align='center',
- scale=0.9)
+ ba.textwidget(
+ parent=w_parent,
+ position=(h2 + 20, v2),
+ size=(0, 0),
+ color=(1, 1, 1) if is_us else tally_color,
+ maxwidth=60,
+ text=str(score[1]),
+ flatness=1.0,
+ shadow=0.0,
+ h_align='center',
+ v_align='center',
+ scale=0.7,
+ )
+ )
+ txt = ba.textwidget(
+ parent=w_parent,
+ position=(h2 + 60, v2 - (28 * 0.5) / 0.9),
+ size=(210 / 0.9, 28),
+ color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
+ maxwidth=210,
+ flatness=1.0,
+ shadow=0.0,
+ autoselect=True,
+ selectable=True,
+ click_activate=True,
+ text=score[2],
+ h_align='left',
+ v_align='center',
+ scale=0.9,
+ )
self._power_ranking_score_widgets.append(txt)
- ba.textwidget(edit=txt,
- on_activate_call=ba.Call(self._show_account_info,
- score[4], txt))
+ ba.textwidget(
+ edit=txt,
+ on_activate_call=ba.Call(
+ self._show_account_info, score[4], txt
+ ),
+ )
assert self._season_popup_menu is not None
- ba.widget(edit=txt,
- left_widget=self._season_popup_menu.get_button())
+ ba.widget(
+ edit=txt, left_widget=self._season_popup_menu.get_button()
+ )
v2 -= 28
- def _show_account_info(self, account_id: str,
- textwidget: ba.Widget) -> None:
+ def _show_account_info(
+ self, account_id: str, textwidget: ba.Widget
+ ) -> None:
from bastd.ui.account import viewer
+
ba.playsound(ba.getsound('swish'))
viewer.AccountViewerWindow(
- account_id=account_id,
- position=textwidget.get_screen_space_center())
+ account_id=account_id, position=textwidget.get_screen_space_center()
+ )
def _on_season_change(self, value: str) -> None:
self._requested_season = value
@@ -916,9 +1108,12 @@ class LeagueRankWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.coop.browser import CoopBrowserWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
ba.app.ui.set_main_menu_window(
- CoopBrowserWindow(transition='in_left').get_root_widget())
+ CoopBrowserWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/mainmenu.py b/assets/src/ba_data/python/bastd/ui/mainmenu.py
index 52919c19..a3d67738 100644
--- a/assets/src/ba_data/python/bastd/ui/mainmenu.py
+++ b/assets/src/ba_data/python/bastd/ui/mainmenu.py
@@ -21,8 +21,10 @@ class MainMenuWindow(ba.Window):
# pylint: disable=cyclic-import
import threading
from bastd.mainmenu import MainMenuSession
+
self._in_game = not isinstance(
- ba.internal.get_foreground_host_session(), MainMenuSession)
+ ba.internal.get_foreground_host_session(), MainMenuSession
+ )
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
@@ -33,10 +35,14 @@ class MainMenuWindow(ba.Window):
self._show_remote_app_info_on_first_launch()
# Make a vanilla container; we'll modify it to our needs in refresh.
- super().__init__(root_widget=ba.containerwidget(
- transition=transition,
- toolbar_visibility='menu_minimal_no_back' if self.
- _in_game else 'menu_minimal_no_back'))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ transition=transition,
+ toolbar_visibility='menu_minimal_no_back'
+ if self._in_game
+ else 'menu_minimal_no_back',
+ )
+ )
# Grab this stuff in case it changes.
self._is_demo = ba.app.demo_mode
@@ -70,12 +76,17 @@ class MainMenuWindow(ba.Window):
# Keep an eye on a few things and refresh if they change.
self._account_state = ba.internal.get_v1_account_state()
self._account_state_num = ba.internal.get_v1_account_state_num()
- self._account_type = (ba.internal.get_v1_account_type()
- if self._account_state == 'signed_in' else None)
- self._refresh_timer = ba.Timer(0.27,
- ba.WeakCall(self._check_refresh),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._account_type = (
+ ba.internal.get_v1_account_type()
+ if self._account_state == 'signed_in'
+ else None
+ )
+ self._refresh_timer = ba.Timer(
+ 0.27,
+ ba.WeakCall(self._check_refresh),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
# noinspection PyUnresolvedReferences
@staticmethod
@@ -103,31 +114,38 @@ class MainMenuWindow(ba.Window):
app = ba.app
force_test = False
ba.internal.get_local_active_input_devices_count()
- if (((app.on_tv or app.platform == 'mac')
- and ba.app.config.get('launchCount', 0) <= 1)
- or force_test):
+ if (
+ (app.on_tv or app.platform == 'mac')
+ and ba.app.config.get('launchCount', 0) <= 1
+ ) or force_test:
def _check_show_bs_remote_window() -> None:
try:
from bastd.ui.getremote import GetBSRemoteWindow
+
ba.playsound(ba.getsound('swish'))
GetBSRemoteWindow()
except Exception:
ba.print_exception(
- 'Error showing get-remote window.')
+ 'Error showing get-remote window.'
+ )
- ba.timer(2.5,
- _check_show_bs_remote_window,
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 2.5,
+ _check_show_bs_remote_window,
+ timetype=ba.TimeType.REAL,
+ )
except Exception:
ba.print_exception('Error showing get-remote-app info')
def _get_store_char_tex(self) -> str:
return (
- 'storeCharacterXmas' if ba.internal.get_v1_account_misc_read_val(
- 'xmas', False) else
- 'storeCharacterEaster' if ba.internal.get_v1_account_misc_read_val(
- 'easter', False) else 'storeCharacter')
+ 'storeCharacterXmas'
+ if ba.internal.get_v1_account_misc_read_val('xmas', False)
+ else 'storeCharacterEaster'
+ if ba.internal.get_v1_account_misc_read_val('easter', False)
+ else 'storeCharacter'
+ )
def _check_refresh(self) -> None:
if not self._root_widget:
@@ -145,14 +163,20 @@ class MainMenuWindow(ba.Window):
store_char_tex = self._get_store_char_tex()
account_state_num = ba.internal.get_v1_account_state_num()
- if (account_state_num != self._account_state_num
- or store_char_tex != self._store_char_tex):
+ if (
+ account_state_num != self._account_state_num
+ or store_char_tex != self._store_char_tex
+ ):
self._store_char_tex = store_char_tex
self._account_state_num = account_state_num
- account_state = self._account_state = (
- ba.internal.get_v1_account_state())
- self._account_type = (ba.internal.get_v1_account_type()
- if account_state == 'signed_in' else None)
+ account_state = (
+ self._account_state
+ ) = ba.internal.get_v1_account_state()
+ self._account_type = (
+ ba.internal.get_v1_account_type()
+ if account_state == 'signed_in'
+ else None
+ )
self._save_state()
self._refresh()
self._restore_state()
@@ -182,21 +206,23 @@ class MainMenuWindow(ba.Window):
self._r = 'mainMenu'
app = ba.app
- self._have_quit_button = (app.ui.uiscale is ba.UIScale.LARGE
- or (app.platform == 'windows'
- and app.subplatform == 'oculus'))
+ self._have_quit_button = app.ui.uiscale is ba.UIScale.LARGE or (
+ app.platform == 'windows' and app.subplatform == 'oculus'
+ )
self._have_store_button = not self._in_game
self._have_settings_button = (
- (not self._in_game or not app.toolbar_test)
- and not (self._is_demo or self._is_arcade or self._is_iircade))
+ not self._in_game or not app.toolbar_test
+ ) and not (self._is_demo or self._is_arcade or self._is_iircade)
self._input_device = input_device = ba.internal.get_ui_input_device()
self._input_player = input_device.player if input_device else None
self._connected_to_remote_player = (
input_device.is_connected_to_remote_player()
- if input_device else False)
+ if input_device
+ else False
+ )
positions: list[tuple[float, float, float]] = []
self._p_index = 0
@@ -217,20 +243,26 @@ class MainMenuWindow(ba.Window):
autoselect=self._use_autoselect,
label=ba.Lstr(resource=self._r + '.settingsText'),
transition_delay=self._tdelay,
- on_activate_call=self._settings)
+ on_activate_call=self._settings,
+ )
# Scattered eggs on easter.
- if ba.internal.get_v1_account_misc_read_val(
- 'easter', False) and not self._in_game:
+ if (
+ ba.internal.get_v1_account_misc_read_val('easter', False)
+ and not self._in_game
+ ):
icon_size = 34
- ba.imagewidget(parent=self._root_widget,
- position=(h - icon_size * 0.5 - 15,
- v + self._button_height * scale -
- icon_size * 0.24 + 1.5),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=ba.gettexture('egg3'),
- tilt_scale=0.0)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ h - icon_size * 0.5 - 15,
+ v + self._button_height * scale - icon_size * 0.24 + 1.5,
+ ),
+ transition_delay=self._tdelay,
+ size=(icon_size, icon_size),
+ texture=ba.gettexture('egg3'),
+ tilt_scale=0.0,
+ )
self._tdelay += self._t_delay_inc
@@ -240,14 +272,15 @@ class MainMenuWindow(ba.Window):
# If we're in a replay, we have a 'Leave Replay' button.
if ba.internal.is_in_replay():
- ba.buttonwidget(parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale,
- v),
- scale=scale,
- size=(self._button_width, self._button_height),
- autoselect=self._use_autoselect,
- label=ba.Lstr(resource='replayEndText'),
- on_activate_call=self._confirm_end_replay)
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width * 0.5 * scale, v),
+ scale=scale,
+ size=(self._button_width, self._button_height),
+ autoselect=self._use_autoselect,
+ label=ba.Lstr(resource='replayEndText'),
+ on_activate_call=self._confirm_end_replay,
+ )
elif ba.internal.get_foreground_host_session() is not None:
ba.buttonwidget(
parent=self._root_widget,
@@ -255,12 +288,20 @@ class MainMenuWindow(ba.Window):
scale=scale,
size=(self._button_width, self._button_height),
autoselect=self._use_autoselect,
- label=ba.Lstr(resource=self._r +
- ('.endTestText' if self._is_benchmark(
- ) else '.endGameText')),
- on_activate_call=(self._confirm_end_test
- if self._is_benchmark() else
- self._confirm_end_game))
+ label=ba.Lstr(
+ resource=self._r
+ + (
+ '.endTestText'
+ if self._is_benchmark()
+ else '.endGameText'
+ )
+ ),
+ on_activate_call=(
+ self._confirm_end_test
+ if self._is_benchmark()
+ else self._confirm_end_game
+ ),
+ )
# Assume we're in a client-session.
else:
ba.buttonwidget(
@@ -270,7 +311,8 @@ class MainMenuWindow(ba.Window):
size=(self._button_width, self._button_height),
autoselect=self._use_autoselect,
label=ba.Lstr(resource=self._r + '.leavePartyText'),
- on_activate_call=self._confirm_leave_party)
+ on_activate_call=self._confirm_leave_party,
+ )
self._store_button: ba.Widget | None
if self._have_store_button:
@@ -285,20 +327,29 @@ class MainMenuWindow(ba.Window):
scale=scale,
on_activate_call=ba.WeakCall(self._on_store_pressed),
sale_scale=1.3,
- transition_delay=self._tdelay)
+ transition_delay=self._tdelay,
+ )
self._store_button = store_button = sbtn.get_button()
uiscale = ba.app.ui.uiscale
- icon_size = (55 if uiscale is ba.UIScale.SMALL else
- 55 if uiscale is ba.UIScale.MEDIUM else 70)
+ icon_size = (
+ 55
+ if uiscale is ba.UIScale.SMALL
+ else 55
+ if uiscale is ba.UIScale.MEDIUM
+ else 70
+ )
ba.imagewidget(
parent=self._root_widget,
- position=(h - icon_size * 0.5,
- v + self._button_height * scale - icon_size * 0.23),
+ position=(
+ h - icon_size * 0.5,
+ v + self._button_height * scale - icon_size * 0.23,
+ ),
transition_delay=self._tdelay,
size=(icon_size, icon_size),
texture=ba.gettexture(self._store_char_tex),
tilt_scale=0.0,
- draw_controller=store_button)
+ draw_controller=store_button,
+ )
self._tdelay += self._t_delay_inc
else:
@@ -314,40 +365,57 @@ class MainMenuWindow(ba.Window):
position=(h - self._button_width * 0.5 * scale, v),
size=(self._button_width, self._button_height),
scale=scale,
- label=ba.Lstr(resource=self._r +
- ('.quitText' if 'Mac' in
- ba.app.user_agent_string else '.exitGameText')),
+ label=ba.Lstr(
+ resource=self._r
+ + (
+ '.quitText'
+ if 'Mac' in ba.app.user_agent_string
+ else '.exitGameText'
+ )
+ ),
on_activate_call=self._quit,
- transition_delay=self._tdelay)
+ transition_delay=self._tdelay,
+ )
# Scattered eggs on easter.
if ba.internal.get_v1_account_misc_read_val('easter', False):
icon_size = 30
- ba.imagewidget(parent=self._root_widget,
- position=(h - icon_size * 0.5 + 25,
- v + self._button_height * scale -
- icon_size * 0.24 + 1.5),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=ba.gettexture('egg1'),
- tilt_scale=0.0)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ h - icon_size * 0.5 + 25,
+ v
+ + self._button_height * scale
+ - icon_size * 0.24
+ + 1.5,
+ ),
+ transition_delay=self._tdelay,
+ size=(icon_size, icon_size),
+ texture=ba.gettexture('egg1'),
+ tilt_scale=0.0,
+ )
- ba.containerwidget(edit=self._root_widget,
- cancel_button=quit_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=quit_button
+ )
self._tdelay += self._t_delay_inc
else:
self._quit_button = None
# If we're not in-game, have no quit button, and this is android,
# we want back presses to quit our activity.
- if (not self._in_game and not self._have_quit_button
- and ba.app.platform == 'android'):
+ if (
+ not self._in_game
+ and not self._have_quit_button
+ and ba.app.platform == 'android'
+ ):
def _do_quit() -> None:
QuitWindow(swish=True, back=True)
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=_do_quit)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=_do_quit
+ )
# Add speed-up/slow-down buttons for replays.
# (ideally this should be part of a fading-out playback bar like most
@@ -368,13 +436,16 @@ class MainMenuWindow(ba.Window):
v_offs = -100
self._replay_speed_text = ba.textwidget(
parent=self._root_widget,
- text=ba.Lstr(resource='watchWindow.playbackSpeedText',
- subs=[('${SPEED}', str(1.23))]),
+ text=ba.Lstr(
+ resource='watchWindow.playbackSpeedText',
+ subs=[('${SPEED}', str(1.23))],
+ ),
position=(h, v + v_offs + 7 * t_scale),
h_align='center',
v_align='center',
size=(0, 0),
- scale=t_scale)
+ scale=t_scale,
+ )
# Update to current value.
self._change_replay_speed(0)
@@ -384,26 +455,33 @@ class MainMenuWindow(ba.Window):
0.25,
ba.WeakCall(self._change_replay_speed, 0),
timetype=ba.TimeType.REAL,
- repeat=True)
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(h - b_size - b_buffer,
- v - b_size - b_buffer + v_offs),
- button_type='square',
- size=(b_size, b_size),
- label='',
- autoselect=True,
- on_activate_call=ba.Call(
- self._change_replay_speed, -1))
+ repeat=True,
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ h - b_size - b_buffer,
+ v - b_size - b_buffer + v_offs,
+ ),
+ button_type='square',
+ size=(b_size, b_size),
+ label='',
+ autoselect=True,
+ on_activate_call=ba.Call(self._change_replay_speed, -1),
+ )
ba.textwidget(
parent=self._root_widget,
draw_controller=btn,
text='-',
- position=(h - b_size * 0.5 - b_buffer,
- v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs),
+ position=(
+ h - b_size * 0.5 - b_buffer,
+ v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs,
+ ),
h_align='center',
v_align='center',
size=(0, 0),
- scale=3.0 * t_scale)
+ scale=3.0 * t_scale,
+ )
btn = ba.buttonwidget(
parent=self._root_widget,
position=(h + b_buffer, v - b_size - b_buffer + v_offs),
@@ -411,21 +489,25 @@ class MainMenuWindow(ba.Window):
size=(b_size, b_size),
label='',
autoselect=True,
- on_activate_call=ba.Call(self._change_replay_speed, 1))
+ on_activate_call=ba.Call(self._change_replay_speed, 1),
+ )
ba.textwidget(
parent=self._root_widget,
draw_controller=btn,
text='+',
- position=(h + b_size * 0.5 + b_buffer,
- v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs),
+ position=(
+ h + b_size * 0.5 + b_buffer,
+ v - b_size * 0.5 - b_buffer + 5 * t_scale + v_offs,
+ ),
h_align='center',
v_align='center',
size=(0, 0),
- scale=3.0 * t_scale)
+ scale=3.0 * t_scale,
+ )
def _refresh_not_in_game(
- self, positions: list[tuple[float, float,
- float]]) -> tuple[float, float, float]:
+ self, positions: list[tuple[float, float, float]]
+ ) -> tuple[float, float, float]:
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@@ -446,7 +528,8 @@ class MainMenuWindow(ba.Window):
else:
account_type_name = ba.Lstr(
resource='notSignedInText',
- fallback_resource='accountSettingsWindow.titleText')
+ fallback_resource='accountSettingsWindow.titleText',
+ )
account_type_icon = None
account_textcolor = (1.0, 0.2, 0.2)
account_type_icon_color = (1.0, 1.0, 1.0)
@@ -490,23 +573,32 @@ class MainMenuWindow(ba.Window):
self._button_height *= 1.2
button_spacing = 1.1
spc = self._button_width * small_button_scale * button_spacing
- ba.containerwidget(edit=self._root_widget,
- size=(self._width, self._height),
- background=False,
- scale=root_widget_scale)
+ ba.containerwidget(
+ edit=self._root_widget,
+ size=(self._width, self._height),
+ background=False,
+ scale=root_widget_scale,
+ )
assert not positions
positions.append((self._width * 0.5, button_y_offs, 1.7))
x_offs = self._width * 0.5 - (spc * (b_count - 1) * 0.5) + (spc * 0.5)
for i in range(b_count - 1):
positions.append(
- (x_offs + spc * i - 1.0, button_y_offs + button_y_offs2,
- small_button_scale))
+ (
+ x_offs + spc * i - 1.0,
+ button_y_offs + button_y_offs2,
+ small_button_scale,
+ )
+ )
# In kiosk mode, provide a button to get back to the kiosk menu.
if ba.app.demo_mode or ba.app.arcade_mode:
h, v, scale = positions[self._p_index]
this_b_width = self._button_width * 0.4 * scale
- demo_menu_delay = 0.0 if self._t_delay_play == 0.0 else max(
- 0, self._t_delay_play + 0.1)
+ demo_menu_delay = (
+ 0.0
+ if self._t_delay_play == 0.0
+ else max(0, self._t_delay_play + 0.1)
+ )
self._demo_menu_button = ba.buttonwidget(
parent=self._root_widget,
position=(self._width * 0.5 - this_b_width * 0.5, v + 90),
@@ -514,19 +606,31 @@ class MainMenuWindow(ba.Window):
autoselect=True,
color=(0.45, 0.55, 0.45),
textcolor=(0.7, 0.8, 0.7),
- label=ba.Lstr(resource='modeArcadeText' if ba.app.
- arcade_mode else 'modeDemoText'),
+ label=ba.Lstr(
+ resource='modeArcadeText'
+ if ba.app.arcade_mode
+ else 'modeDemoText'
+ ),
transition_delay=demo_menu_delay,
- on_activate_call=self._demo_menu_press)
+ on_activate_call=self._demo_menu_press,
+ )
else:
self._demo_menu_button = None
uiscale = ba.app.ui.uiscale
- foof = (-1 if uiscale is ba.UIScale.SMALL else
- 1 if uiscale is ba.UIScale.MEDIUM else 3)
+ foof = (
+ -1
+ if uiscale is ba.UIScale.SMALL
+ else 1
+ if uiscale is ba.UIScale.MEDIUM
+ else 3
+ )
h, v, scale = positions[self._p_index]
v = v + foof
- gather_delay = 0.0 if self._t_delay_play == 0.0 else max(
- 0.0, self._t_delay_play + 0.1)
+ gather_delay = (
+ 0.0
+ if self._t_delay_play == 0.0
+ else max(0.0, self._t_delay_play + 0.1)
+ )
assert play_button_width is not None
assert play_button_height is not None
this_h = h - play_button_width * 0.5 * scale - 40 * scale
@@ -540,26 +644,30 @@ class MainMenuWindow(ba.Window):
button_type='square',
label='',
transition_delay=gather_delay,
- on_activate_call=self._gather_press)
- ba.textwidget(parent=self._root_widget,
- position=(this_h, v + self._button_height * 0.33),
- size=(0, 0),
- scale=0.75,
- transition_delay=gather_delay,
- draw_controller=btn,
- color=(0.75, 1.0, 0.7),
- maxwidth=self._button_width * 0.33,
- text=ba.Lstr(resource='gatherWindow.titleText'),
- h_align='center',
- v_align='center')
+ on_activate_call=self._gather_press,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(this_h, v + self._button_height * 0.33),
+ size=(0, 0),
+ scale=0.75,
+ transition_delay=gather_delay,
+ draw_controller=btn,
+ color=(0.75, 1.0, 0.7),
+ maxwidth=self._button_width * 0.33,
+ text=ba.Lstr(resource='gatherWindow.titleText'),
+ h_align='center',
+ v_align='center',
+ )
icon_size = this_b_width * 0.6
- ba.imagewidget(parent=self._root_widget,
- size=(icon_size, icon_size),
- draw_controller=btn,
- transition_delay=gather_delay,
- position=(this_h - 0.5 * icon_size,
- v + 0.31 * this_b_height),
- texture=ba.gettexture('usersButton'))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(icon_size, icon_size),
+ draw_controller=btn,
+ transition_delay=gather_delay,
+ position=(this_h - 0.5 * icon_size, v + 0.31 * this_b_height),
+ texture=ba.gettexture('usersButton'),
+ )
# Play button.
h, v, scale = positions[self._p_index]
@@ -573,13 +681,19 @@ class MainMenuWindow(ba.Window):
text_res_scale=2.0,
label=ba.Lstr(resource='playText'),
transition_delay=self._t_delay_play,
- on_activate_call=self._play_press)
- ba.containerwidget(edit=self._root_widget,
- start_button=start_button,
- selected_child=start_button)
+ on_activate_call=self._play_press,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ start_button=start_button,
+ selected_child=start_button,
+ )
v = v + foof
- watch_delay = 0.0 if self._t_delay_play == 0.0 else max(
- 0.0, self._t_delay_play - 0.1)
+ watch_delay = (
+ 0.0
+ if self._t_delay_play == 0.0
+ else max(0.0, self._t_delay_play - 0.1)
+ )
this_h = h + play_button_width * 0.5 * scale + 40 * scale
this_b_width = self._button_width * 0.25 * scale
this_b_height = self._button_height * 0.82 * scale
@@ -591,26 +705,30 @@ class MainMenuWindow(ba.Window):
button_type='square',
label='',
transition_delay=watch_delay,
- on_activate_call=self._watch_press)
- ba.textwidget(parent=self._root_widget,
- position=(this_h, v + self._button_height * 0.33),
- size=(0, 0),
- scale=0.75,
- transition_delay=watch_delay,
- color=(0.75, 1.0, 0.7),
- draw_controller=btn,
- maxwidth=self._button_width * 0.33,
- text=ba.Lstr(resource='watchWindow.titleText'),
- h_align='center',
- v_align='center')
+ on_activate_call=self._watch_press,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(this_h, v + self._button_height * 0.33),
+ size=(0, 0),
+ scale=0.75,
+ transition_delay=watch_delay,
+ color=(0.75, 1.0, 0.7),
+ draw_controller=btn,
+ maxwidth=self._button_width * 0.33,
+ text=ba.Lstr(resource='watchWindow.titleText'),
+ h_align='center',
+ v_align='center',
+ )
icon_size = this_b_width * 0.55
- ba.imagewidget(parent=self._root_widget,
- size=(icon_size, icon_size),
- draw_controller=btn,
- transition_delay=watch_delay,
- position=(this_h - 0.5 * icon_size,
- v + 0.33 * this_b_height),
- texture=ba.gettexture('tv'))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(icon_size, icon_size),
+ draw_controller=btn,
+ transition_delay=watch_delay,
+ position=(this_h - 0.5 * icon_size, v + 0.33 * this_b_height),
+ texture=ba.gettexture('tv'),
+ )
if not self._in_game and enable_account_button:
this_b_width = self._button_width
h, v, scale = positions[self._p_index]
@@ -627,20 +745,29 @@ class MainMenuWindow(ba.Window):
icon=account_type_icon,
icon_color=account_type_icon_color,
transition_delay=self._tdelay,
- enable_sound=account_type_enable_button_sound)
+ enable_sound=account_type_enable_button_sound,
+ )
# Scattered eggs on easter.
- if ba.internal.get_v1_account_misc_read_val(
- 'easter', False) and not self._in_game:
+ if (
+ ba.internal.get_v1_account_misc_read_val('easter', False)
+ and not self._in_game
+ ):
icon_size = 32
- ba.imagewidget(parent=self._root_widget,
- position=(h - icon_size * 0.5 + 35,
- v + self._button_height * scale -
- icon_size * 0.24 + 1.5),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=ba.gettexture('egg2'),
- tilt_scale=0.0)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ h - icon_size * 0.5 + 35,
+ v
+ + self._button_height * scale
+ - icon_size * 0.24
+ + 1.5,
+ ),
+ transition_delay=self._tdelay,
+ size=(icon_size, icon_size),
+ texture=ba.gettexture('egg2'),
+ tilt_scale=0.0,
+ )
self._tdelay += self._t_delay_inc
else:
self._account_button = None
@@ -656,21 +783,27 @@ class MainMenuWindow(ba.Window):
size=(self._button_width, self._button_height),
label=ba.Lstr(resource=self._r + '.howToPlayText'),
transition_delay=self._tdelay,
- on_activate_call=self._howtoplay)
+ on_activate_call=self._howtoplay,
+ )
self._how_to_play_button = btn
# Scattered eggs on easter.
- if ba.internal.get_v1_account_misc_read_val(
- 'easter', False) and not self._in_game:
+ if (
+ ba.internal.get_v1_account_misc_read_val('easter', False)
+ and not self._in_game
+ ):
icon_size = 28
- ba.imagewidget(parent=self._root_widget,
- position=(h - icon_size * 0.5 + 30,
- v + self._button_height * scale -
- icon_size * 0.24 + 1.5),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=ba.gettexture('egg4'),
- tilt_scale=0.0)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ h - icon_size * 0.5 + 30,
+ v + self._button_height * scale - icon_size * 0.24 + 1.5,
+ ),
+ transition_delay=self._tdelay,
+ size=(icon_size, icon_size),
+ texture=ba.gettexture('egg4'),
+ tilt_scale=0.0,
+ )
# Credits button.
self._tdelay += self._t_delay_inc
h, v, scale = positions[self._p_index]
@@ -683,13 +816,14 @@ class MainMenuWindow(ba.Window):
label=ba.Lstr(resource=self._r + '.creditsText'),
scale=scale,
transition_delay=self._tdelay,
- on_activate_call=self._credits)
+ on_activate_call=self._credits,
+ )
self._tdelay += self._t_delay_inc
return h, v, scale
def _refresh_in_game(
- self, positions: list[tuple[float, float,
- float]]) -> tuple[float, float, float]:
+ self, positions: list[tuple[float, float, float]]
+ ) -> tuple[float, float, float]:
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@@ -699,15 +833,21 @@ class MainMenuWindow(ba.Window):
try:
custom_menu_entries = session.get_custom_menu_entries()
for cme in custom_menu_entries:
- if (not isinstance(cme, dict) or 'label' not in cme
- or not isinstance(cme['label'], (str, ba.Lstr))
- or 'call' not in cme or not callable(cme['call'])):
- raise ValueError('invalid custom menu entry: ' +
- str(cme))
+ if (
+ not isinstance(cme, dict)
+ or 'label' not in cme
+ or not isinstance(cme['label'], (str, ba.Lstr))
+ or 'call' not in cme
+ or not callable(cme['call'])
+ ):
+ raise ValueError(
+ 'invalid custom menu entry: ' + str(cme)
+ )
except Exception:
custom_menu_entries = []
ba.print_exception(
- f'Error getting custom menu entries for {session}')
+ f'Error getting custom menu entries for {session}'
+ )
self._width = 250.0
self._height = 250.0 if self._input_player else 180.0
if (self._is_demo or self._is_arcade) and self._input_player:
@@ -722,10 +862,16 @@ class MainMenuWindow(ba.Window):
ba.containerwidget(
edit=self._root_widget,
size=(self._width, self._height),
- scale=(2.15 if uiscale is ba.UIScale.SMALL else
- 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0))
+ scale=(
+ 2.15
+ if uiscale is ba.UIScale.SMALL
+ else 1.6
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
h = 125.0
- v = (self._height - 80.0 if self._input_player else self._height - 60)
+ v = self._height - 80.0 if self._input_player else self._height - 60
h_offset = 0
d_h_offset = 0
v_offset = -50
@@ -742,24 +888,28 @@ class MainMenuWindow(ba.Window):
player_name = self._input_player.getname()
h, v, scale = positions[self._p_index]
v += 35
- ba.textwidget(parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- color=(1, 1, 1, 0.5),
- scale=0.7,
- h_align='center',
- text=ba.Lstr(value=player_name))
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ color=(1, 1, 1, 0.5),
+ scale=0.7,
+ h_align='center',
+ text=ba.Lstr(value=player_name),
+ )
else:
player_name = ''
h, v, scale = positions[self._p_index]
self._p_index += 1
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- label=ba.Lstr(resource=self._r + '.resumeText'),
- autoselect=self._use_autoselect,
- on_activate_call=self._resume)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ label=ba.Lstr(resource=self._r + '.resumeText'),
+ autoselect=self._use_autoselect,
+ on_activate_call=self._resume,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
# Add any custom options defined by the current game.
@@ -776,54 +926,71 @@ class MainMenuWindow(ba.Window):
else:
call = ba.Call(entry['call'], ba.WeakCall(self._resume))
- ba.buttonwidget(parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- on_activate_call=call,
- label=entry['label'],
- autoselect=self._use_autoselect)
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ on_activate_call=call,
+ label=entry['label'],
+ autoselect=self._use_autoselect,
+ )
# Add a 'leave' button if the menu-owner has a player.
- if ((self._input_player or self._connected_to_remote_player)
- and not (self._is_demo or self._is_arcade)):
+ if (self._input_player or self._connected_to_remote_player) and not (
+ self._is_demo or self._is_arcade
+ ):
h, v, scale = positions[self._p_index]
self._p_index += 1
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width,
- self._button_height),
- scale=scale,
- on_activate_call=self._leave,
- label='',
- autoselect=self._use_autoselect)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ on_activate_call=self._leave,
+ label='',
+ autoselect=self._use_autoselect,
+ )
- if (player_name != '' and player_name[0] != '<'
- and player_name[-1] != '>'):
- txt = ba.Lstr(resource=self._r + '.justPlayerText',
- subs=[('${NAME}', player_name)])
+ if (
+ player_name != ''
+ and player_name[0] != '<'
+ and player_name[-1] != '>'
+ ):
+ txt = ba.Lstr(
+ resource=self._r + '.justPlayerText',
+ subs=[('${NAME}', player_name)],
+ )
else:
txt = ba.Lstr(value=player_name)
- ba.textwidget(parent=self._root_widget,
- position=(h, v + self._button_height *
- (0.64 if player_name != '' else 0.5)),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.leaveGameText'),
- scale=(0.83 if player_name != '' else 1.0),
- color=(0.75, 1.0, 0.7),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- maxwidth=self._button_width * 0.9)
- ba.textwidget(parent=self._root_widget,
- position=(h, v + self._button_height * 0.27),
- size=(0, 0),
- text=txt,
- color=(0.75, 1.0, 0.7),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- scale=0.45,
- maxwidth=self._button_width * 0.9)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(
+ h,
+ v
+ + self._button_height
+ * (0.64 if player_name != '' else 0.5),
+ ),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.leaveGameText'),
+ scale=(0.83 if player_name != '' else 1.0),
+ color=(0.75, 1.0, 0.7),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ maxwidth=self._button_width * 0.9,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(h, v + self._button_height * 0.27),
+ size=(0, 0),
+ text=txt,
+ color=(0.75, 1.0, 0.7),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ scale=0.45,
+ maxwidth=self._button_width * 0.9,
+ )
return h, v, scale
def _change_replay_speed(self, offs: int) -> None:
@@ -832,38 +999,50 @@ class MainMenuWindow(ba.Window):
print('_change_replay_speed called without widget')
return
ba.internal.set_replay_speed_exponent(
- ba.internal.get_replay_speed_exponent() + offs)
+ ba.internal.get_replay_speed_exponent() + offs
+ )
actual_speed = pow(2.0, ba.internal.get_replay_speed_exponent())
- ba.textwidget(edit=self._replay_speed_text,
- text=ba.Lstr(resource='watchWindow.playbackSpeedText',
- subs=[('${SPEED}', str(actual_speed))]))
+ ba.textwidget(
+ edit=self._replay_speed_text,
+ text=ba.Lstr(
+ resource='watchWindow.playbackSpeedText',
+ subs=[('${SPEED}', str(actual_speed))],
+ ),
+ )
def _quit(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.confirm import QuitWindow
+
QuitWindow(origin_widget=self._quit_button)
def _demo_menu_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.kiosk import KioskWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
- KioskWindow(transition='in_left').get_root_widget())
+ KioskWindow(transition='in_left').get_root_widget()
+ )
def _show_account_window(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.settings import AccountSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
AccountSettingsWindow(
- origin_widget=self._account_button).get_root_widget())
+ origin_widget=self._account_button
+ ).get_root_widget()
+ )
def _on_store_pressed(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.store.browser import StoreBrowserWindow
from bastd.ui.account import show_sign_in_prompt
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@@ -871,23 +1050,30 @@ class MainMenuWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
StoreBrowserWindow(
- origin_widget=self._store_button).get_root_widget())
+ origin_widget=self._store_button
+ ).get_root_widget()
+ )
def _is_benchmark(self) -> bool:
session = ba.internal.get_foreground_host_session()
- return (getattr(session, 'benchmark_type', None) == 'cpu'
- or ba.app.stress_test_reset_timer is not None)
+ return (
+ getattr(session, 'benchmark_type', None) == 'cpu'
+ or ba.app.stress_test_reset_timer is not None
+ )
def _confirm_end_game(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.confirm import ConfirmWindow
+
# FIXME: Currently we crash calling this on client-sessions.
# Select cancel by default; this occasionally gets called by accident
# in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True)
+ ConfirmWindow(
+ ba.Lstr(resource=self._r + '.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
def _confirm_end_test(self) -> None:
# pylint: disable=cyclic-import
@@ -895,9 +1081,11 @@ class MainMenuWindow(ba.Window):
# Select cancel by default; this occasionally gets called by accident
# in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True)
+ ConfirmWindow(
+ ba.Lstr(resource=self._r + '.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
def _confirm_end_replay(self) -> None:
# pylint: disable=cyclic-import
@@ -905,9 +1093,11 @@ class MainMenuWindow(ba.Window):
# Select cancel by default; this occasionally gets called by accident
# in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(ba.Lstr(resource=self._r + '.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True)
+ ConfirmWindow(
+ ba.Lstr(resource=self._r + '.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
def _confirm_leave_party(self) -> None:
# pylint: disable=cyclic-import
@@ -915,9 +1105,11 @@ class MainMenuWindow(ba.Window):
# Select cancel by default; this occasionally gets called by accident
# in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(ba.Lstr(resource=self._r + '.leavePartyConfirmText'),
- self._leave_party,
- cancel_is_selected=True)
+ ConfirmWindow(
+ ba.Lstr(resource=self._r + '.leavePartyConfirmText'),
+ self._leave_party,
+ cancel_is_selected=True,
+ )
def _leave_party(self) -> None:
ba.internal.disconnect_from_host()
@@ -939,30 +1131,38 @@ class MainMenuWindow(ba.Window):
def _credits(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.creditslist import CreditsListWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
CreditsListWindow(
- origin_widget=self._credits_button).get_root_widget())
+ origin_widget=self._credits_button
+ ).get_root_widget()
+ )
def _howtoplay(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.helpui import HelpWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
HelpWindow(
- main_menu=True,
- origin_widget=self._how_to_play_button).get_root_widget())
+ main_menu=True, origin_widget=self._how_to_play_button
+ ).get_root_widget()
+ )
def _settings(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.allsettings import AllSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
AllSettingsWindow(
- origin_widget=self._settings_button).get_root_widget())
+ origin_widget=self._settings_button
+ ).get_root_widget()
+ )
def _resume_and_call(self, call: Callable[[], Any]) -> None:
self._resume()
@@ -1038,28 +1238,34 @@ class MainMenuWindow(ba.Window):
def _gather_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.gather import GatherWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- GatherWindow(origin_widget=self._gather_button).get_root_widget())
+ GatherWindow(origin_widget=self._gather_button).get_root_widget()
+ )
def _watch_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.watch import WatchWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- WatchWindow(origin_widget=self._watch_button).get_root_widget())
+ WatchWindow(origin_widget=self._watch_button).get_root_widget()
+ )
def _play_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.play import PlayWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.selecting_private_party_playlist = False
ba.app.ui.set_main_menu_window(
- PlayWindow(origin_widget=self._start_button).get_root_widget())
+ PlayWindow(origin_widget=self._start_button).get_root_widget()
+ )
def _resume(self) -> None:
ba.app.resume()
diff --git a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py
index 842ecd16..19d6c95c 100644
--- a/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py
+++ b/assets/src/ba_data/python/bastd/ui/onscreenkeyboard.py
@@ -22,35 +22,53 @@ class OnScreenKeyboardWindow(ba.Window):
self._height = 400
uiscale = ba.app.ui.uiscale
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- parent=ba.internal.get_special_widget('overlay_stack'),
- size=(self._width, self._height + top_extra),
- transition='in_scale',
- scale_origin_stack_offset=self._target_text.
- get_screen_space_center(),
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else (
- 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
- self._done_button = ba.buttonwidget(parent=self._root_widget,
- position=(self._width - 200, 44),
- size=(140, 60),
- autoselect=True,
- label=ba.Lstr(resource='doneText'),
- on_activate_call=self._done)
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._cancel,
- start_button=self._done_button)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ parent=ba.internal.get_special_widget('overlay_stack'),
+ size=(self._width, self._height + top_extra),
+ transition='in_scale',
+ scale_origin_stack_offset=(
+ self._target_text.get_screen_space_center()
+ ),
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, 0)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ )
+ )
+ self._done_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width - 200, 44),
+ size=(140, 60),
+ autoselect=True,
+ label=ba.Lstr(resource='doneText'),
+ on_activate_call=self._done,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ on_cancel_call=self._cancel,
+ start_button=self._done_button,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 41),
- size=(0, 0),
- scale=0.95,
- text=label,
- maxwidth=self._width - 140,
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 41),
+ size=(0, 0),
+ scale=0.95,
+ text=label,
+ maxwidth=self._width - 140,
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ )
self._text_field = ba.textwidget(
parent=self._root_widget,
@@ -64,7 +82,8 @@ class OnScreenKeyboardWindow(ba.Window):
editable=True,
maxwidth=self._width - 175,
force_internal_editing=True,
- always_show_carat=True)
+ always_show_carat=True,
+ )
self._key_color_lit = (1.4, 1.2, 1.4)
self._key_color = (0.69, 0.6, 0.74)
@@ -165,8 +184,9 @@ class OnScreenKeyboardWindow(ba.Window):
color=key_color_dark,
label=ba.charstr(ba.SpecialChar.DELETE),
button_type='square',
- on_activate_call=self._del)
- v -= (key_height + 9)
+ on_activate_call=self._del,
+ )
+ v -= key_height + 9
# Do space bar and stuff.
if row_num == 2:
if self._num_mode_button is None:
@@ -207,13 +227,16 @@ class OnScreenKeyboardWindow(ba.Window):
textcolor=key_textcolor,
color=key_color_dark,
label=ba.Lstr(resource='spaceKeyText'),
- on_activate_call=ba.Call(self._type_char, ' '))
+ on_activate_call=ba.Call(self._type_char, ' '),
+ )
# Show change instructions only if we have more than one
# keyboard option.
- keyboards = (ba.app.meta.scanresults.exports_of_class(
- ba.Keyboard) if ba.app.meta.scanresults is not None
- else [])
+ keyboards = (
+ ba.app.meta.scanresults.exports_of_class(ba.Keyboard)
+ if ba.app.meta.scanresults is not None
+ else []
+ )
if len(keyboards) > 1:
ba.textwidget(
parent=self._root_widget,
@@ -221,26 +244,30 @@ class OnScreenKeyboardWindow(ba.Window):
position=(210, v - 70),
size=(key_width * 6.1, key_height + 15),
text=ba.Lstr(
- resource='keyboardChangeInstructionsText'),
- scale=0.75)
+ resource='keyboardChangeInstructionsText'
+ ),
+ scale=0.75,
+ )
btn2 = self._space_button
btn3 = self._emoji_button
ba.widget(edit=btn1, right_widget=btn2, left_widget=btn3)
- ba.widget(edit=btn2,
- left_widget=btn1,
- right_widget=self._done_button)
+ ba.widget(
+ edit=btn2, left_widget=btn1, right_widget=self._done_button
+ )
ba.widget(edit=btn3, left_widget=btn1)
ba.widget(edit=self._done_button, left_widget=btn2)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._char_keys[14])
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._char_keys[14]
+ )
self._refresh()
def _get_keyboard(self) -> ba.Keyboard:
assert ba.app.meta.scanresults is not None
- classname = ba.app.meta.scanresults.exports_of_class(
- ba.Keyboard)[self._keyboard_index]
+ classname = ba.app.meta.scanresults.exports_of_class(ba.Keyboard)[
+ self._keyboard_index
+ ]
kbclass = ba.getclass(classname, ba.Keyboard)
return kbclass()
@@ -250,34 +277,47 @@ class OnScreenKeyboardWindow(ba.Window):
chars = list(self._chars)
if self._mode == 'caps':
chars = [c.upper() for c in chars]
- ba.buttonwidget(edit=self._shift_button,
- color=self._key_color_lit
- if self._mode == 'caps' else self._key_color_dark,
- label=ba.charstr(ba.SpecialChar.SHIFT),
- on_activate_call=self._shift)
- ba.buttonwidget(edit=self._num_mode_button,
- label='123#&*',
- on_activate_call=self._num_mode)
- ba.buttonwidget(edit=self._emoji_button,
- color=self._key_color_dark,
- label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
- on_activate_call=self._next_mode)
+ ba.buttonwidget(
+ edit=self._shift_button,
+ color=self._key_color_lit
+ if self._mode == 'caps'
+ else self._key_color_dark,
+ label=ba.charstr(ba.SpecialChar.SHIFT),
+ on_activate_call=self._shift,
+ )
+ ba.buttonwidget(
+ edit=self._num_mode_button,
+ label='123#&*',
+ on_activate_call=self._num_mode,
+ )
+ ba.buttonwidget(
+ edit=self._emoji_button,
+ color=self._key_color_dark,
+ label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
+ on_activate_call=self._next_mode,
+ )
else:
if self._mode == 'num':
chars = list(self._keyboard.nums)
else:
chars = list(self._keyboard.pages[self._mode])
- ba.buttonwidget(edit=self._shift_button,
- color=self._key_color_dark,
- label='',
- on_activate_call=self._null_press)
- ba.buttonwidget(edit=self._num_mode_button,
- label='abc',
- on_activate_call=self._abc_mode)
- ba.buttonwidget(edit=self._emoji_button,
- color=self._key_color_dark,
- label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
- on_activate_call=self._next_mode)
+ ba.buttonwidget(
+ edit=self._shift_button,
+ color=self._key_color_dark,
+ label='',
+ on_activate_call=self._null_press,
+ )
+ ba.buttonwidget(
+ edit=self._num_mode_button,
+ label='abc',
+ on_activate_call=self._abc_mode,
+ )
+ ba.buttonwidget(
+ edit=self._emoji_button,
+ color=self._key_color_dark,
+ label=ba.charstr(ba.SpecialChar.LOGO_FLAT),
+ on_activate_call=self._next_mode,
+ )
for i, btn in enumerate(self._char_keys):
assert chars is not None
@@ -291,12 +331,15 @@ class OnScreenKeyboardWindow(ba.Window):
f' "{self._keyboard.name}" is incorrect:'
f' {len(chars)} != {len(self._chars)}'
f' (size of default "normal" page)',
- once=True)
- ba.buttonwidget(edit=btn,
- label=chars[i] if have_char else ' ',
- on_activate_call=ba.Call(
- self._type_char,
- chars[i] if have_char else ' '))
+ once=True,
+ )
+ ba.buttonwidget(
+ edit=btn,
+ label=chars[i] if have_char else ' ',
+ on_activate_call=ba.Call(
+ self._type_char, chars[i] if have_char else ' '
+ ),
+ )
def _null_press(self) -> None:
ba.playsound(self._click_sound)
@@ -325,12 +368,18 @@ class OnScreenKeyboardWindow(ba.Window):
self._load_keyboard()
if len(kbexports) < 2:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='keyboardNoOthersAvailableText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='keyboardNoOthersAvailableText'),
+ color=(1, 0, 0),
+ )
else:
- ba.screenmessage(ba.Lstr(resource='keyboardSwitchText',
- subs=[('${NAME}', self._keyboard.name)]),
- color=(0, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='keyboardSwitchText',
+ subs=[('${NAME}', self._keyboard.name)],
+ ),
+ color=(0, 1, 0),
+ )
def _shift(self) -> None:
ba.playsound(self._click_sound)
@@ -354,8 +403,10 @@ class OnScreenKeyboardWindow(ba.Window):
def _type_char(self, char: str) -> None:
ba.playsound(self._click_sound)
if char.isspace():
- if (ba.time(ba.TimeType.REAL) - self._last_space_press <
- self._double_space_interval):
+ if (
+ ba.time(ba.TimeType.REAL) - self._last_space_press
+ < self._double_space_interval
+ ):
self._last_space_press = 0
self._next_keyboard()
self._del() # We typed unneeded space around 1s ago.
@@ -380,6 +431,7 @@ class OnScreenKeyboardWindow(ba.Window):
def _done(self) -> None:
ba.containerwidget(edit=self._root_widget, transition='out_scale')
if self._target_text:
- ba.textwidget(edit=self._target_text,
- text=cast(str,
- ba.textwidget(query=self._text_field)))
+ ba.textwidget(
+ edit=self._target_text,
+ text=cast(str, ba.textwidget(query=self._text_field)),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/party.py b/assets/src/ba_data/python/bastd/ui/party.py
index 7de7b62d..f2ea8202 100644
--- a/assets/src/ba_data/python/bastd/ui/party.py
+++ b/assets/src/ba_data/python/bastd/ui/party.py
@@ -29,32 +29,51 @@ class PartyWindow(ba.Window):
self._popup_party_member_is_host: bool | None = None
self._width = 500
uiscale = ba.app.ui.uiscale
- self._height = (365 if uiscale is ba.UIScale.SMALL else
- 480 if uiscale is ba.UIScale.MEDIUM else 600)
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_scale',
- color=(0.40, 0.55, 0.20),
- parent=ba.internal.get_special_widget('overlay_stack'),
- on_outside_click_call=self.close_with_sound,
- scale_origin_stack_offset=origin,
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (
- 240, 0) if uiscale is ba.UIScale.MEDIUM else (330, 20)))
+ self._height = (
+ 365
+ if uiscale is ba.UIScale.SMALL
+ else 480
+ if uiscale is ba.UIScale.MEDIUM
+ else 600
+ )
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_scale',
+ color=(0.40, 0.55, 0.20),
+ parent=ba.internal.get_special_widget('overlay_stack'),
+ on_outside_click_call=self.close_with_sound,
+ scale_origin_stack_offset=origin,
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -10)
+ if uiscale is ba.UIScale.SMALL
+ else (240, 0)
+ if uiscale is ba.UIScale.MEDIUM
+ else (330, 20),
+ )
+ )
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.7,
- position=(30, self._height - 47),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.45, 0.63, 0.15),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.7,
+ position=(30, self._height - 47),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.45, 0.63, 0.15),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
self._menu_button = ba.buttonwidget(
parent=self._root_widget,
@@ -66,7 +85,8 @@ class PartyWindow(ba.Window):
button_type='square',
on_activate_call=ba.WeakCall(self._on_menu_button_press),
color=(0.55, 0.73, 0.25),
- iconscale=1.2)
+ iconscale=1.2,
+ )
info = ba.internal.get_connection_to_host_info()
if info.get('name', '') != '':
@@ -74,35 +94,38 @@ class PartyWindow(ba.Window):
else:
title = ba.Lstr(resource=self._r + '.titleText')
- self._title_text = ba.textwidget(parent=self._root_widget,
- scale=0.9,
- color=(0.5, 0.7, 0.5),
- text=title,
- size=(0, 0),
- position=(self._width * 0.5,
- self._height - 29),
- maxwidth=self._width * 0.7,
- h_align='center',
- v_align='center')
+ self._title_text = ba.textwidget(
+ parent=self._root_widget,
+ scale=0.9,
+ color=(0.5, 0.7, 0.5),
+ text=title,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height - 29),
+ maxwidth=self._width * 0.7,
+ h_align='center',
+ v_align='center',
+ )
- self._empty_str = ba.textwidget(parent=self._root_widget,
- scale=0.75,
- size=(0, 0),
- position=(self._width * 0.5,
- self._height - 65),
- maxwidth=self._width * 0.85,
- h_align='center',
- v_align='center')
+ self._empty_str = ba.textwidget(
+ parent=self._root_widget,
+ scale=0.75,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height - 65),
+ maxwidth=self._width * 0.85,
+ h_align='center',
+ v_align='center',
+ )
self._scroll_width = self._width - 50
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- size=(self._scroll_width,
- self._height - 200),
- position=(30, 80),
- color=(0.4, 0.6, 0.3))
- self._columnwidget = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ size=(self._scroll_width, self._height - 200),
+ position=(30, 80),
+ color=(0.4, 0.6, 0.3),
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0
+ )
ba.widget(edit=self._menu_button, down_widget=self._columnwidget)
self._muted_text = ba.textwidget(
@@ -111,7 +134,8 @@ class PartyWindow(ba.Window):
size=(0, 0),
h_align='center',
v_align='center',
- text=ba.Lstr(resource='chatMutedText'))
+ text=ba.Lstr(resource='chatMutedText'),
+ )
self._chat_texts: list[ba.Widget] = []
# add all existing messages if chat is not muted
@@ -132,32 +156,41 @@ class PartyWindow(ba.Window):
description=ba.Lstr(resource=self._r + '.chatMessageText'),
autoselect=True,
v_align='center',
- corner_scale=0.7)
+ corner_scale=0.7,
+ )
- ba.widget(edit=self._scrollwidget,
- autoselect=True,
- left_widget=self._cancel_button,
- up_widget=self._cancel_button,
- down_widget=self._text_field)
- ba.widget(edit=self._columnwidget,
- autoselect=True,
- up_widget=self._cancel_button,
- down_widget=self._text_field)
+ ba.widget(
+ edit=self._scrollwidget,
+ autoselect=True,
+ left_widget=self._cancel_button,
+ up_widget=self._cancel_button,
+ down_widget=self._text_field,
+ )
+ ba.widget(
+ edit=self._columnwidget,
+ autoselect=True,
+ up_widget=self._cancel_button,
+ down_widget=self._text_field,
+ )
ba.containerwidget(edit=self._root_widget, selected_child=txt)
- btn = ba.buttonwidget(parent=self._root_widget,
- size=(50, 35),
- label=ba.Lstr(resource=self._r + '.sendText'),
- button_type='square',
- autoselect=True,
- position=(self._width - 70, 35),
- on_activate_call=self._send_chat_message)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ size=(50, 35),
+ label=ba.Lstr(resource=self._r + '.sendText'),
+ button_type='square',
+ autoselect=True,
+ position=(self._width - 70, 35),
+ on_activate_call=self._send_chat_message,
+ )
ba.textwidget(edit=txt, on_return_press_call=btn.activate)
self._name_widgets: list[ba.Widget] = []
self._roster: list[dict[str, Any]] | None = None
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
self._update()
def on_chat_message(self, msg: str) -> None:
@@ -166,15 +199,17 @@ class PartyWindow(ba.Window):
self._add_msg(msg)
def _add_msg(self, msg: str) -> None:
- txt = ba.textwidget(parent=self._columnwidget,
- text=msg,
- h_align='left',
- v_align='center',
- size=(0, 13),
- scale=0.55,
- maxwidth=self._scroll_width * 0.94,
- shadow=0.3,
- flatness=1.0)
+ txt = ba.textwidget(
+ parent=self._columnwidget,
+ text=msg,
+ h_align='left',
+ v_align='center',
+ size=(0, 13),
+ scale=0.55,
+ maxwidth=self._scroll_width * 0.94,
+ shadow=0.3,
+ flatness=1.0,
+ )
self._chat_texts.append(txt)
if len(self._chat_texts) > 40:
first = self._chat_texts.pop(0)
@@ -186,15 +221,22 @@ class PartyWindow(ba.Window):
uiscale = ba.app.ui.uiscale
popup.PopupMenuWindow(
position=self._menu_button.get_screen_space_center(),
- scale=(2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23),
+ scale=(
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ ),
choices=['unmute' if is_muted else 'mute'],
choices_display=[
ba.Lstr(
- resource='chatUnMuteText' if is_muted else 'chatMuteText')
+ resource='chatUnMuteText' if is_muted else 'chatMuteText'
+ )
],
current_choice='unmute' if is_muted else 'mute',
- delegate=self)
+ delegate=self,
+ )
self._popup_type = 'menu'
def _update(self) -> None:
@@ -225,15 +267,26 @@ class PartyWindow(ba.Window):
self._name_widgets = []
if not self._roster:
top_section_height = 60
- ba.textwidget(edit=self._empty_str,
- text=ba.Lstr(resource=self._r + '.emptyText'))
- ba.scrollwidget(edit=self._scrollwidget,
- size=(self._width - 50,
- self._height - top_section_height - 110),
- position=(30, 80))
+ ba.textwidget(
+ edit=self._empty_str,
+ text=ba.Lstr(resource=self._r + '.emptyText'),
+ )
+ ba.scrollwidget(
+ edit=self._scrollwidget,
+ size=(
+ self._width - 50,
+ self._height - top_section_height - 110,
+ ),
+ position=(30, 80),
+ )
else:
- columns = 1 if len(
- self._roster) == 1 else 2 if len(self._roster) == 2 else 3
+ columns = (
+ 1
+ if len(self._roster) == 1
+ else 2
+ if len(self._roster) == 2
+ else 3
+ )
rows = int(math.ceil(float(len(self._roster)) / columns))
c_width = (self._width * 0.9) / max(3, columns)
c_width_total = c_width * columns
@@ -244,9 +297,13 @@ class PartyWindow(ba.Window):
index = y * columns + x
if index < len(self._roster):
t_scale = 0.65
- pos = (self._width * 0.53 - c_width_total * 0.5 +
- c_width * x - 23,
- self._height - 65 - c_height * y - 15)
+ pos = (
+ self._width * 0.53
+ - c_width_total * 0.5
+ + c_width * x
+ - 23,
+ self._height - 65 - c_height * y - 15,
+ )
# if there are players present for this client, use
# their names as a display string instead of the
@@ -255,39 +312,45 @@ class PartyWindow(ba.Window):
if self._roster[index]['players']:
# if there's just one, use the full name;
# otherwise combine short names
- if len(self._roster[index]
- ['players']) == 1:
+ if len(self._roster[index]['players']) == 1:
p_str = self._roster[index]['players'][
- 0]['name_full']
+ 0
+ ]['name_full']
else:
- p_str = ('/'.join([
- entry['name'] for entry in
- self._roster[index]['players']
- ]))
+ p_str = '/'.join(
+ [
+ entry['name']
+ for entry in self._roster[
+ index
+ ]['players']
+ ]
+ )
if len(p_str) > 25:
p_str = p_str[:25] + '...'
else:
p_str = self._roster[index][
- 'display_string']
+ 'display_string'
+ ]
except Exception:
ba.print_exception(
- 'Error calcing client name str.')
+ 'Error calcing client name str.'
+ )
p_str = '???'
- widget = ba.textwidget(parent=self._root_widget,
- position=(pos[0], pos[1]),
- scale=t_scale,
- size=(c_width * 0.85, 30),
- maxwidth=c_width * 0.85,
- color=(1, 1,
- 1) if index == 0 else
- (1, 1, 1),
- selectable=True,
- autoselect=True,
- click_activate=True,
- text=ba.Lstr(value=p_str),
- h_align='left',
- v_align='center')
+ widget = ba.textwidget(
+ parent=self._root_widget,
+ position=(pos[0], pos[1]),
+ scale=t_scale,
+ size=(c_width * 0.85, 30),
+ maxwidth=c_width * 0.85,
+ color=(1, 1, 1) if index == 0 else (1, 1, 1),
+ selectable=True,
+ autoselect=True,
+ click_activate=True,
+ text=ba.Lstr(value=p_str),
+ h_align='left',
+ v_align='center',
+ )
self._name_widgets.append(widget)
# in newer versions client_id will be present and
@@ -295,22 +358,28 @@ class PartyWindow(ba.Window):
# in older versions we assume the first client is
# host
if self._roster[index]['client_id'] is not None:
- is_host = self._roster[index][
- 'client_id'] == -1
+ is_host = self._roster[index]['client_id'] == -1
else:
- is_host = (index == 0)
+ is_host = index == 0
# FIXME: Should pass client_id to these sort of
# calls; not spec-string (perhaps should wait till
# client_id is more readily available though).
- ba.textwidget(edit=widget,
- on_activate_call=ba.Call(
- self._on_party_member_press,
- self._roster[index]['client_id'],
- is_host, widget))
- pos = (self._width * 0.53 - c_width_total * 0.5 +
- c_width * x,
- self._height - 65 - c_height * y)
+ ba.textwidget(
+ edit=widget,
+ on_activate_call=ba.Call(
+ self._on_party_member_press,
+ self._roster[index]['client_id'],
+ is_host,
+ widget,
+ ),
+ )
+ pos = (
+ self._width * 0.53
+ - c_width_total * 0.5
+ + c_width * x,
+ self._height - 65 - c_height * y,
+ )
# Make the assumption that the first roster
# entry is the server.
@@ -319,32 +388,43 @@ class PartyWindow(ba.Window):
twd = min(
c_width * 0.85,
ba.internal.get_string_width(
- p_str, suppress_warning=True) *
- t_scale)
+ p_str, suppress_warning=True
+ )
+ * t_scale,
+ )
self._name_widgets.append(
ba.textwidget(
parent=self._root_widget,
- position=(pos[0] + twd + 1,
- pos[1] - 0.5),
+ position=(
+ pos[0] + twd + 1,
+ pos[1] - 0.5,
+ ),
size=(0, 0),
h_align='left',
v_align='center',
maxwidth=c_width * 0.96 - twd,
color=(0.1, 1, 0.1, 0.5),
- text=ba.Lstr(resource=self._r +
- '.hostText'),
+ text=ba.Lstr(
+ resource=self._r + '.hostText'
+ ),
scale=0.4,
shadow=0.1,
- flatness=1.0))
+ flatness=1.0,
+ )
+ )
ba.textwidget(edit=self._empty_str, text='')
- ba.scrollwidget(edit=self._scrollwidget,
- size=(self._width - 50,
- max(100, self._height - 139 -
- c_height_total)),
- position=(30, 80))
+ ba.scrollwidget(
+ edit=self._scrollwidget,
+ size=(
+ self._width - 50,
+ max(100, self._height - 139 - c_height_total),
+ ),
+ position=(30, 80),
+ )
- def popup_menu_selected_choice(self, popup_window: popup.PopupMenuWindow,
- choice: str) -> None:
+ def popup_menu_selected_choice(
+ self, popup_window: popup.PopupMenuWindow, choice: str
+ ) -> None:
"""Called when a choice is selected in the popup."""
del popup_window # unused
if self._popup_type == 'partyMemberPress':
@@ -352,22 +432,25 @@ class PartyWindow(ba.Window):
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='internal.cantKickHostError'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
else:
assert self._popup_party_member_client_id is not None
# Ban for 5 minutes.
result = ba.internal.disconnect_client(
- self._popup_party_member_client_id, ban_time=5 * 60)
+ self._popup_party_member_client_id, ban_time=5 * 60
+ )
if not result:
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='getTicketsWindow.unavailableText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
elif self._popup_type == 'menu':
if choice in ('mute', 'unmute'):
cfg = ba.app.config
- cfg['Chat Muted'] = (choice == 'mute')
+ cfg['Chat Muted'] = choice == 'mute'
cfg.apply_and_commit()
self._update()
else:
@@ -376,33 +459,43 @@ class PartyWindow(ba.Window):
def popup_menu_closing(self, popup_window: popup.PopupWindow) -> None:
"""Called when the popup is closing."""
- def _on_party_member_press(self, client_id: int, is_host: bool,
- widget: ba.Widget) -> None:
+ def _on_party_member_press(
+ self, client_id: int, is_host: bool, widget: ba.Widget
+ ) -> None:
# if we're the host, pop up 'kick' options for all non-host members
if ba.internal.get_foreground_host_session() is not None:
kick_str = ba.Lstr(resource='kickText')
else:
# kick-votes appeared in build 14248
- if (ba.internal.get_connection_to_host_info().get(
- 'build_number', 0) < 14248):
+ if (
+ ba.internal.get_connection_to_host_info().get('build_number', 0)
+ < 14248
+ ):
return
kick_str = ba.Lstr(resource='kickVoteText')
uiscale = ba.app.ui.uiscale
popup.PopupMenuWindow(
position=widget.get_screen_space_center(),
- scale=(2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23),
+ scale=(
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ ),
choices=['kick'],
choices_display=[kick_str],
current_choice='kick',
- delegate=self)
+ delegate=self,
+ )
self._popup_type = 'partyMemberPress'
self._popup_party_member_client_id = client_id
self._popup_party_member_is_host = is_host
def _send_chat_message(self) -> None:
ba.internal.chatmessage(
- cast(str, ba.textwidget(query=self._text_field)))
+ cast(str, ba.textwidget(query=self._text_field))
+ )
ba.textwidget(edit=self._text_field, text='')
def close(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/partyqueue.py b/assets/src/ba_data/python/bastd/ui/partyqueue.py
index 78673223..f5355da2 100644
--- a/assets/src/ba_data/python/bastd/ui/partyqueue.py
+++ b/assets/src/ba_data/python/bastd/ui/partyqueue.py
@@ -24,9 +24,15 @@ class PartyQueueWindow(ba.Window):
class Dude:
"""Represents a single dude waiting in a server line."""
- def __init__(self, parent: PartyQueueWindow, distance: float,
- initial_offset: float, is_player: bool, account_id: str,
- name: str):
+ def __init__(
+ self,
+ parent: PartyQueueWindow,
+ distance: float,
+ initial_offset: float,
+ is_player: bool,
+ account_id: str,
+ name: str,
+ ):
self.claimed = False
self._line_left = parent.get_line_left()
self._line_width = parent.get_line_width()
@@ -38,13 +44,20 @@ class PartyQueueWindow(ba.Window):
self._sc = sc = 1.1 if is_player else 0.6 + random.random() * 0.2
self._y_offs = -30.0 if is_player else -47.0 * sc
self._last_boost_time = 0.0
- self._color = (0.2, 1.0,
- 0.2) if is_player else (0.5 + 0.3 * random.random(),
- 0.4 + 0.2 * random.random(),
- 0.5 + 0.3 * random.random())
- self._eye_color = (0.7 * 1.0 + 0.3 * self._color[0],
- 0.7 * 1.0 + 0.3 * self._color[1],
- 0.7 * 1.0 + 0.3 * self._color[2])
+ self._color = (
+ (0.2, 1.0, 0.2)
+ if is_player
+ else (
+ 0.5 + 0.3 * random.random(),
+ 0.4 + 0.2 * random.random(),
+ 0.5 + 0.3 * random.random(),
+ )
+ )
+ self._eye_color = (
+ 0.7 * 1.0 + 0.3 * self._color[0],
+ 0.7 * 1.0 + 0.3 * self._color[1],
+ 0.7 * 1.0 + 0.3 * self._color[2],
+ )
self._body_image = ba.buttonwidget(
parent=parent.get_root_widget(),
selectable=True,
@@ -52,28 +65,34 @@ class PartyQueueWindow(ba.Window):
size=(sc * 60, sc * 80),
color=self._color,
texture=parent.lineup_tex,
- model_transparent=parent.lineup_1_transparent_model)
- ba.buttonwidget(edit=self._body_image,
- on_activate_call=ba.WeakCall(
- parent.on_account_press, account_id,
- self._body_image))
+ model_transparent=parent.lineup_1_transparent_model,
+ )
+ ba.buttonwidget(
+ edit=self._body_image,
+ on_activate_call=ba.WeakCall(
+ parent.on_account_press, account_id, self._body_image
+ ),
+ )
ba.widget(edit=self._body_image, autoselect=True)
self._eyes_image = ba.imagewidget(
parent=parent.get_root_widget(),
size=(sc * 36, sc * 18),
texture=parent.lineup_tex,
color=self._eye_color,
- model_transparent=parent.eyes_model)
- self._name_text = ba.textwidget(parent=parent.get_root_widget(),
- size=(0, 0),
- shadow=0,
- flatness=1.0,
- text=name,
- maxwidth=100,
- h_align='center',
- v_align='center',
- scale=0.75,
- color=(1, 1, 1, 0.6))
+ model_transparent=parent.eyes_model,
+ )
+ self._name_text = ba.textwidget(
+ parent=parent.get_root_widget(),
+ size=(0, 0),
+ shadow=0,
+ flatness=1.0,
+ text=name,
+ maxwidth=100,
+ h_align='center',
+ v_align='center',
+ scale=0.75,
+ color=(1, 1, 1, 0.6),
+ )
self._update_image()
# DEBUG: vis target pos..
@@ -85,13 +104,15 @@ class PartyQueueWindow(ba.Window):
size=(sc * 60, sc * 80),
color=self._color,
texture=parent.lineup_tex,
- model_transparent=parent.lineup_1_transparent_model)
+ model_transparent=parent.lineup_1_transparent_model,
+ )
self._eyes_image_target = ba.imagewidget(
parent=parent.get_root_widget(),
size=(sc * 36, sc * 18),
texture=parent.lineup_tex,
color=self._eye_color,
- model_transparent=parent.eyes_model)
+ model_transparent=parent.eyes_model,
+ )
# (updates our image positions)
self.set_target_distance(self._target_distance)
else:
@@ -111,54 +132,87 @@ class PartyQueueWindow(ba.Window):
widget.delete()
ba.pushcall(
- ba.Call(kill_widgets, [
- self._body_image, self._eyes_image,
- self._body_image_target, self._eyes_image_target,
- self._name_text
- ]))
+ ba.Call(
+ kill_widgets,
+ [
+ self._body_image,
+ self._eyes_image,
+ self._body_image_target,
+ self._eyes_image_target,
+ self._name_text,
+ ],
+ )
+ )
def set_target_distance(self, dist: float) -> None:
"""Set distance for a dude."""
self._target_distance = dist
if self._debug:
sc = self._sc
- position = (self._line_left + self._line_width *
- (1.0 - self._target_distance),
- self._line_bottom - 30)
- ba.imagewidget(edit=self._body_image_target,
- position=(position[0] - sc * 30,
- position[1] - sc * 25 - 70))
- ba.imagewidget(edit=self._eyes_image_target,
- position=(position[0] - sc * 18,
- position[1] + sc * 31 - 70))
+ position = (
+ self._line_left
+ + self._line_width * (1.0 - self._target_distance),
+ self._line_bottom - 30,
+ )
+ ba.imagewidget(
+ edit=self._body_image_target,
+ position=(
+ position[0] - sc * 30,
+ position[1] - sc * 25 - 70,
+ ),
+ )
+ ba.imagewidget(
+ edit=self._eyes_image_target,
+ position=(
+ position[0] - sc * 18,
+ position[1] + sc * 31 - 70,
+ ),
+ )
def step(self, smoothing: float) -> None:
"""Step this dude."""
- self._distance = (smoothing * self._distance +
- (1.0 - smoothing) * self._target_distance)
+ self._distance = (
+ smoothing * self._distance
+ + (1.0 - smoothing) * self._target_distance
+ )
self._update_image()
self._boost_brightness *= 0.9
def _update_image(self) -> None:
sc = self._sc
- position = (self._line_left + self._line_width *
- (1.0 - self._distance), self._line_bottom + 40)
+ position = (
+ self._line_left + self._line_width * (1.0 - self._distance),
+ self._line_bottom + 40,
+ )
brightness = 1.0 + self._boost_brightness
- ba.buttonwidget(edit=self._body_image,
- position=(position[0] - sc * 30,
- position[1] - sc * 25 + self._y_offs),
- color=(self._color[0] * brightness,
- self._color[1] * brightness,
- self._color[2] * brightness))
- ba.imagewidget(edit=self._eyes_image,
- position=(position[0] - sc * 18,
- position[1] + sc * 31 + self._y_offs),
- color=(self._eye_color[0] * brightness,
- self._eye_color[1] * brightness,
- self._eye_color[2] * brightness))
- ba.textwidget(edit=self._name_text,
- position=(position[0] - sc * 0,
- position[1] + sc * 40.0))
+ ba.buttonwidget(
+ edit=self._body_image,
+ position=(
+ position[0] - sc * 30,
+ position[1] - sc * 25 + self._y_offs,
+ ),
+ color=(
+ self._color[0] * brightness,
+ self._color[1] * brightness,
+ self._color[2] * brightness,
+ ),
+ )
+ ba.imagewidget(
+ edit=self._eyes_image,
+ position=(
+ position[0] - sc * 18,
+ position[1] + sc * 31 + self._y_offs,
+ ),
+ color=(
+ self._eye_color[0] * brightness,
+ self._eye_color[1] * brightness,
+ self._eye_color[2] * brightness,
+ ),
+ )
+ ba.textwidget(
+ edit=self._name_text,
+ position=(position[0] - sc * 0, position[1] + sc * 40.0),
+ )
def boost(self, amount: float, smoothing: float) -> None:
"""Boost this dude."""
@@ -192,39 +246,55 @@ class PartyQueueWindow(ba.Window):
self._boost_tickets = 0
self._boost_strength = 0.0
self._angry_computer_transparent_model = ba.getmodel(
- 'angryComputerTransparent')
+ 'angryComputerTransparent'
+ )
self._angry_computer_image: ba.Widget | None = None
self.lineup_1_transparent_model = ba.getmodel(
- 'playerLineup1Transparent')
+ 'playerLineup1Transparent'
+ )
self._lineup_2_transparent_model = ba.getmodel(
- 'playerLineup2Transparent')
+ 'playerLineup2Transparent'
+ )
self._lineup_3_transparent_model = ba.getmodel(
- 'playerLineup3Transparent')
+ 'playerLineup3Transparent'
+ )
self._lineup_4_transparent_model = ba.getmodel(
- 'playerLineup4Transparent')
+ 'playerLineup4Transparent'
+ )
self._line_image: ba.Widget | None = None
self.eyes_model = ba.getmodel('plasticEyesTransparent')
self._white_tex = ba.gettexture('white')
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- color=(0.45, 0.63, 0.15),
- transition='in_scale',
- scale=(1.4 if uiscale is ba.UIScale.SMALL else
- 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ color=(0.45, 0.63, 0.15),
+ transition='in_scale',
+ scale=(
+ 1.4
+ if uiscale is ba.UIScale.SMALL
+ else 1.2
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=1.0,
- position=(60, self._height - 80),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.45, 0.63, 0.15),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=1.0,
+ position=(60, self._height - 80),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.45, 0.63, 0.15),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
self._title_text = ba.textwidget(
parent=self._root_widget,
@@ -235,32 +305,35 @@ class PartyQueueWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource='internal.connectingToPartyText'),
- maxwidth=self._width * 0.65)
+ maxwidth=self._width * 0.65,
+ )
- self._tickets_text = ba.textwidget(parent=self._root_widget,
- position=(self._width - 180,
- self._height - 20),
- size=(0, 0),
- color=(0.2, 1.0, 0.2),
- scale=0.7,
- h_align='center',
- v_align='center',
- text='')
+ self._tickets_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width - 180, self._height - 20),
+ size=(0, 0),
+ color=(0.2, 1.0, 0.2),
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ text='',
+ )
# update at roughly 30fps
- self._update_timer = ba.Timer(0.033,
- ba.WeakCall(self.update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 0.033,
+ ba.WeakCall(self.update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
self.update()
def __del__(self) -> None:
try:
ba.app.ui.have_party_queue_window = False
- ba.internal.add_transaction({
- 'type': 'PARTY_QUEUE_REMOVE',
- 'q': self._queue_id
- })
+ ba.internal.add_transaction(
+ {'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id}
+ )
ba.internal.run_transactions()
except Exception:
ba.print_exception('Error removing self from party queue.')
@@ -277,16 +350,19 @@ class PartyQueueWindow(ba.Window):
"""(internal)"""
return self._line_bottom
- def on_account_press(self, account_id: str | None,
- origin_widget: ba.Widget) -> None:
+ def on_account_press(
+ self, account_id: str | None, origin_widget: ba.Widget
+ ) -> None:
"""A dude was clicked so we should show his account info."""
from bastd.ui.account import viewer
+
if account_id is None:
ba.playsound(ba.getsound('error'))
return
viewer.AccountViewerWindow(
account_id=account_id,
- position=origin_widget.get_screen_space_center())
+ position=origin_widget.get_screen_space_center(),
+ )
def close(self) -> None:
"""Close the ui."""
@@ -299,7 +375,8 @@ class PartyQueueWindow(ba.Window):
position=(self._width - 180, self._height * 0.5 - 65),
size=(150, 150),
texture=self.lineup_tex,
- model_transparent=self._angry_computer_transparent_model)
+ model_transparent=self._angry_computer_transparent_model,
+ )
if self._line_image is None:
self._line_image = ba.imagewidget(
parent=self._root_widget,
@@ -307,7 +384,8 @@ class PartyQueueWindow(ba.Window):
opacity=0.2,
position=(self._line_left, self._line_bottom - 2.0),
size=(self._line_width, 4.0),
- texture=self._white_tex)
+ texture=self._white_tex,
+ )
# now go through the data they sent, creating dudes for us and our
# enemies as needed and updating target positions on all of them..
@@ -319,10 +397,15 @@ class PartyQueueWindow(ba.Window):
# always have a dude for ourself..
if -1 not in self._dudes_by_id:
dude = self.Dude(
- self, response['d'], self._initial_offset, True,
+ self,
+ response['d'],
+ self._initial_offset,
+ True,
ba.internal.get_v1_account_misc_read_val_2(
- 'resolvedAccountID', None),
- ba.internal.get_v1_account_display_string())
+ 'resolvedAccountID', None
+ ),
+ ba.internal.get_v1_account_display_string(),
+ )
self._dudes_by_id[-1] = dude
self._dudes.append(dude)
else:
@@ -330,11 +413,21 @@ class PartyQueueWindow(ba.Window):
self._dudes_by_id[-1].claimed = True
# now create/destroy enemies
- for (enemy_id, enemy_distance, enemy_account_id,
- enemy_name) in response['e']:
+ for (
+ enemy_id,
+ enemy_distance,
+ enemy_account_id,
+ enemy_name,
+ ) in response['e']:
if enemy_id not in self._dudes_by_id:
- dude = self.Dude(self, enemy_distance, self._initial_offset,
- False, enemy_account_id, enemy_name)
+ dude = self.Dude(
+ self,
+ enemy_distance,
+ self._initial_offset,
+ False,
+ enemy_account_id,
+ enemy_name,
+ )
self._dudes_by_id[enemy_id] = dude
self._dudes.append(dude)
else:
@@ -343,9 +436,13 @@ class PartyQueueWindow(ba.Window):
# remove unclaimed dudes from both of our lists
# noinspection PyUnresolvedReferences
- self._dudes_by_id = dict([
- item for item in list(self._dudes_by_id.items()) if item[1].claimed
- ])
+ self._dudes_by_id = dict(
+ [
+ item
+ for item in list(self._dudes_by_id.items())
+ if item[1].claimed
+ ]
+ )
self._dudes = [dude for dude in self._dudes if dude.claimed]
def _hide_field(self) -> None:
@@ -370,23 +467,25 @@ class PartyQueueWindow(ba.Window):
return
if response is not None:
- should_show_field = (response.get('d') is not None)
+ should_show_field = response.get('d') is not None
self._smoothing = response['s']
self._initial_offset = response['o']
# If they gave us a position, show the field.
if should_show_field:
- ba.textwidget(edit=self._title_text,
- text=ba.Lstr(resource='waitingInLineText'),
- position=(self._width * 0.5,
- self._height * 0.85))
+ ba.textwidget(
+ edit=self._title_text,
+ text=ba.Lstr(resource='waitingInLineText'),
+ position=(self._width * 0.5, self._height * 0.85),
+ )
self._update_field(response)
self._field_shown = True
if not should_show_field and self._field_shown:
ba.textwidget(
edit=self._title_text,
text=ba.Lstr(resource='internal.connectingToPartyText'),
- position=(self._width * 0.5, self._height * 0.55))
+ position=(self._width * 0.5, self._height * 0.55),
+ )
self._hide_field()
self._field_shown = False
@@ -405,7 +504,8 @@ class PartyQueueWindow(ba.Window):
on_activate_call=self.on_boost_press,
enable_sound=False,
color=(0, 1, 0),
- autoselect=True)
+ autoselect=True,
+ )
self._boost_label = ba.textwidget(
parent=self._root_widget,
draw_controller=self._boost_button,
@@ -416,7 +516,8 @@ class PartyQueueWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource='boostText'),
- maxwidth=150)
+ maxwidth=150,
+ )
self._boost_price = ba.textwidget(
parent=self._root_widget,
draw_controller=self._boost_button,
@@ -426,9 +527,10 @@ class PartyQueueWindow(ba.Window):
scale=0.9,
h_align='center',
v_align='center',
- text=ba.charstr(ba.SpecialChar.TICKET) +
- str(self._boost_tickets),
- maxwidth=150)
+ text=ba.charstr(ba.SpecialChar.TICKET)
+ + str(self._boost_tickets),
+ maxwidth=150,
+ )
else:
if self._boost_button is not None:
self._boost_button.delete()
@@ -447,17 +549,22 @@ class PartyQueueWindow(ba.Window):
# enforce a delay between connection attempts
# (in case they're jamming on the boost button)
now = time.time()
- if (self._last_connect_attempt_time is None
- or now - self._last_connect_attempt_time > 10.0):
- ba.internal.connect_to_party(address=self._address,
- port=self._port,
- print_progress=False)
+ if (
+ self._last_connect_attempt_time is None
+ or now - self._last_connect_attempt_time > 10.0
+ ):
+ ba.internal.connect_to_party(
+ address=self._address,
+ port=self._port,
+ print_progress=False,
+ )
self._last_connect_attempt_time = now
def on_boost_press(self) -> None:
"""Boost was pressed."""
from bastd.ui import account
from bastd.ui import getcurrency
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
@@ -472,9 +579,10 @@ class PartyQueueWindow(ba.Window):
{
'type': 'PARTY_QUEUE_BOOST',
't': self._boost_tickets,
- 'q': self._queue_id
+ 'q': self._queue_id,
},
- callback=ba.WeakCall(self.on_update_response))
+ callback=ba.WeakCall(self.on_update_response),
+ )
# lets not run these immediately (since they may be rapid-fire,
# just bucket them until the next tick)
@@ -492,24 +600,31 @@ class PartyQueueWindow(ba.Window):
# Update boost-price.
if self._boost_price is not None:
- ba.textwidget(edit=self._boost_price,
- text=ba.charstr(ba.SpecialChar.TICKET) +
- str(self._boost_tickets))
+ ba.textwidget(
+ edit=self._boost_price,
+ text=ba.charstr(ba.SpecialChar.TICKET)
+ + str(self._boost_tickets),
+ )
# Update boost button color based on if we have enough moola.
if self._boost_button is not None:
- can_boost = ((ba.internal.get_v1_account_state() == 'signed_in'
- and ba.internal.get_v1_account_ticket_count() >=
- self._boost_tickets))
- ba.buttonwidget(edit=self._boost_button,
- color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7))
+ can_boost = (
+ ba.internal.get_v1_account_state() == 'signed_in'
+ and ba.internal.get_v1_account_ticket_count()
+ >= self._boost_tickets
+ )
+ ba.buttonwidget(
+ edit=self._boost_button,
+ color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7),
+ )
# Update ticket-count.
if self._tickets_text is not None:
if self._boost_button is not None:
if ba.internal.get_v1_account_state() == 'signed_in':
val = ba.charstr(ba.SpecialChar.TICKET) + str(
- ba.internal.get_v1_account_ticket_count())
+ ba.internal.get_v1_account_ticket_count()
+ )
else:
val = ba.charstr(ba.SpecialChar.TICKET) + '???'
ba.textwidget(edit=self._tickets_text, text=val)
@@ -517,16 +632,16 @@ class PartyQueueWindow(ba.Window):
ba.textwidget(edit=self._tickets_text, text='')
current_time = ba.time(ba.TimeType.REAL)
- if (self._last_transaction_time is None
- or current_time - self._last_transaction_time > 0.001 *
- ba.internal.get_v1_account_misc_read_val('pqInt', 5000)):
+ if (
+ self._last_transaction_time is None
+ or current_time - self._last_transaction_time
+ > 0.001 * ba.internal.get_v1_account_misc_read_val('pqInt', 5000)
+ ):
self._last_transaction_time = current_time
ba.internal.add_transaction(
- {
- 'type': 'PARTY_QUEUE_QUERY',
- 'q': self._queue_id
- },
- callback=ba.WeakCall(self.on_update_response))
+ {'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id},
+ callback=ba.WeakCall(self.on_update_response),
+ )
ba.internal.run_transactions()
# step our dudes
diff --git a/assets/src/ba_data/python/bastd/ui/play.py b/assets/src/ba_data/python/bastd/ui/play.py
index 498c0cc7..976109f8 100644
--- a/assets/src/ba_data/python/bastd/ui/play.py
+++ b/assets/src/ba_data/python/bastd/ui/play.py
@@ -16,9 +16,11 @@ if TYPE_CHECKING:
class PlayWindow(ba.Window):
"""Window for selecting overall play type."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
import threading
@@ -48,14 +50,22 @@ class PlayWindow(ba.Window):
self._r = 'playWindow'
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
- scale=(1.6 if uiscale is ba.UIScale.SMALL else
- 0.9 if uiscale is ba.UIScale.MEDIUM else 0.8),
- stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ toolbar_visibility='menu_full',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.6
+ if uiscale is ba.UIScale.SMALL
+ else 0.9
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ stack_offset=(0, 0) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
self._back_button = back_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(55 + x_offs, height - 132),
@@ -65,7 +75,8 @@ class PlayWindow(ba.Window):
text_scale=1.2,
autoselect=True,
label=ba.Lstr(resource='backText'),
- button_type='back')
+ button_type='back',
+ )
txt = ba.textwidget(
parent=self._root_widget,
@@ -73,20 +84,25 @@ class PlayWindow(ba.Window):
# position=(width * 0.5, height -
# (101 if main_menu else 61)),
size=(0, 0),
- text=ba.Lstr(resource=(
- self._r +
- '.titleText') if self._is_main_menu else 'playlistsText'),
+ text=ba.Lstr(
+ resource=(self._r + '.titleText')
+ if self._is_main_menu
+ else 'playlistsText'
+ ),
scale=1.7,
res_scale=2.0,
maxwidth=400,
color=ba.app.ui.heading_color,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
ba.textwidget(edit=txt, text='')
@@ -94,22 +110,26 @@ class PlayWindow(ba.Window):
v -= 100
clr = (0.6, 0.7, 0.6, 1.0)
v -= 280 if self._is_main_menu else 180
- v += (30
- if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL else 0)
+ v += 30 if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL else 0
hoffs = x_offs + 80 if self._is_main_menu else x_offs - 100
scl = 1.13 if self._is_main_menu else 0.68
self._lineup_tex = ba.gettexture('playerLineup')
angry_computer_transparent_model = ba.getmodel(
- 'angryComputerTransparent')
+ 'angryComputerTransparent'
+ )
self._lineup_1_transparent_model = ba.getmodel(
- 'playerLineup1Transparent')
+ 'playerLineup1Transparent'
+ )
self._lineup_2_transparent_model = ba.getmodel(
- 'playerLineup2Transparent')
+ 'playerLineup2Transparent'
+ )
self._lineup_3_transparent_model = ba.getmodel(
- 'playerLineup3Transparent')
+ 'playerLineup3Transparent'
+ )
self._lineup_4_transparent_model = ba.getmodel(
- 'playerLineup4Transparent')
+ 'playerLineup4Transparent'
+ )
self._eyes_model = ba.getmodel('plasticEyesTransparent')
self._coop_button: ba.Widget | None = None
@@ -119,87 +139,103 @@ class PlayWindow(ba.Window):
self._coop_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)),
- size=(scl * button_width,
- scl * (300 if self._is_main_menu else 360)),
+ size=(
+ scl * button_width,
+ scl * (300 if self._is_main_menu else 360),
+ ),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
- on_activate_call=self._coop)
+ on_activate_call=self._coop,
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
ba.widget(
edit=btn,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
ba.widget(
edit=btn,
- up_widget=ba.internal.get_special_widget('account_button'))
- ba.widget(edit=btn,
- down_widget=ba.internal.get_special_widget(
- 'settings_button'))
+ up_widget=ba.internal.get_special_widget('account_button'),
+ )
+ ba.widget(
+ edit=btn,
+ down_widget=ba.internal.get_special_widget(
+ 'settings_button'
+ ),
+ )
- self._draw_dude(0,
- btn,
- hoffs,
- v,
- scl,
- position=(140, 30),
- color=(0.72, 0.4, 1.0))
- self._draw_dude(1,
- btn,
- hoffs,
- v,
- scl,
- position=(185, 53),
- color=(0.71, 0.5, 1.0))
- self._draw_dude(2,
- btn,
- hoffs,
- v,
- scl,
- position=(220, 27),
- color=(0.67, 0.44, 1.0))
- self._draw_dude(3,
- btn,
- hoffs,
- v,
- scl,
- position=(255, 57),
- color=(0.7, 0.3, 1.0))
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * 230, v + scl * 153),
- size=(scl * 115, scl * 115),
- texture=self._lineup_tex,
- model_transparent=angry_computer_transparent_model)
+ self._draw_dude(
+ 0,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(140, 30),
+ color=(0.72, 0.4, 1.0),
+ )
+ self._draw_dude(
+ 1,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(185, 53),
+ color=(0.71, 0.5, 1.0),
+ )
+ self._draw_dude(
+ 2,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(220, 27),
+ color=(0.67, 0.44, 1.0),
+ )
+ self._draw_dude(
+ 3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0)
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * 230, v + scl * 153),
+ size=(scl * 115, scl * 115),
+ texture=self._lineup_tex,
+ model_transparent=angry_computer_transparent_model,
+ )
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + scl * 95),
- size=(scl * button_width, scl * 50),
- text=ba.Lstr(
- resource='playModes.singlePlayerCoopText',
- fallback_resource='playModes.coopText'),
- maxwidth=scl * button_width * 0.7,
- res_scale=1.5,
- h_align='center',
- v_align='center',
- color=(0.7, 0.9, 0.7, 1.0),
- scale=scl * 2.3)
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + scl * 95),
+ size=(scl * button_width, scl * 50),
+ text=ba.Lstr(
+ resource='playModes.singlePlayerCoopText',
+ fallback_resource='playModes.coopText',
+ ),
+ maxwidth=scl * button_width * 0.7,
+ res_scale=1.5,
+ h_align='center',
+ v_align='center',
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=scl * 2.3,
+ )
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + (scl * 54)),
- size=(scl * button_width, scl * 30),
- text=ba.Lstr(resource=self._r +
- '.oneToFourPlayersText'),
- h_align='center',
- v_align='center',
- scale=0.83 * scl,
- flatness=1.0,
- maxwidth=scl * button_width * 0.7,
- color=clr)
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + (scl * 54)),
+ size=(scl * button_width, scl * 30),
+ text=ba.Lstr(resource=self._r + '.oneToFourPlayersText'),
+ h_align='center',
+ v_align='center',
+ scale=0.83 * scl,
+ flatness=1.0,
+ maxwidth=scl * button_width * 0.7,
+ color=clr,
+ )
scl = 0.5 if self._is_main_menu else 0.68
hoffs += 440 if self._is_main_menu else 216
@@ -208,216 +244,269 @@ class PlayWindow(ba.Window):
self._teams_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)),
- size=(scl * button_width,
- scl * (300 if self._is_main_menu else 360)),
+ size=(
+ scl * button_width,
+ scl * (300 if self._is_main_menu else 360),
+ ),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
- on_activate_call=self._team_tourney)
+ on_activate_call=self._team_tourney,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=btn,
- up_widget=ba.internal.get_special_widget(
- 'tickets_plus_button'),
- right_widget=ba.internal.get_special_widget('party_button'))
+ up_widget=ba.internal.get_special_widget('tickets_plus_button'),
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
xxx = -14
- self._draw_dude(2,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 148, 30),
- color=(0.2, 0.4, 1.0))
- self._draw_dude(3,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 181, 53),
- color=(0.3, 0.4, 1.0))
- self._draw_dude(1,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 216, 33),
- color=(0.3, 0.5, 1.0))
- self._draw_dude(0,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 245, 57),
- color=(0.3, 0.5, 1.0))
+ self._draw_dude(
+ 2,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 148, 30),
+ color=(0.2, 0.4, 1.0),
+ )
+ self._draw_dude(
+ 3,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 181, 53),
+ color=(0.3, 0.4, 1.0),
+ )
+ self._draw_dude(
+ 1,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 216, 33),
+ color=(0.3, 0.5, 1.0),
+ )
+ self._draw_dude(
+ 0,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 245, 57),
+ color=(0.3, 0.5, 1.0),
+ )
xxx = 155
- self._draw_dude(0,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 151, 30),
- color=(1.0, 0.5, 0.4))
- self._draw_dude(1,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 189, 53),
- color=(1.0, 0.58, 0.58))
- self._draw_dude(3,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 223, 27),
- color=(1.0, 0.5, 0.5))
- self._draw_dude(2,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 257, 57),
- color=(1.0, 0.5, 0.5))
+ self._draw_dude(
+ 0,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 151, 30),
+ color=(1.0, 0.5, 0.4),
+ )
+ self._draw_dude(
+ 1,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 189, 53),
+ color=(1.0, 0.58, 0.58),
+ )
+ self._draw_dude(
+ 3,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 223, 27),
+ color=(1.0, 0.5, 0.5),
+ )
+ self._draw_dude(
+ 2,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 257, 57),
+ color=(1.0, 0.5, 0.5),
+ )
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + scl * 95),
- size=(scl * button_width, scl * 50),
- text=ba.Lstr(resource='playModes.teamsText',
- fallback_resource='teamsText'),
- res_scale=1.5,
- maxwidth=scl * button_width * 0.7,
- h_align='center',
- v_align='center',
- color=(0.7, 0.9, 0.7, 1.0),
- scale=scl * 2.3)
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + (scl * 54)),
- size=(scl * button_width, scl * 30),
- text=ba.Lstr(resource=self._r +
- '.twoToEightPlayersText'),
- h_align='center',
- v_align='center',
- res_scale=1.5,
- scale=0.9 * scl,
- flatness=1.0,
- maxwidth=scl * button_width * 0.7,
- color=clr)
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + scl * 95),
+ size=(scl * button_width, scl * 50),
+ text=ba.Lstr(
+ resource='playModes.teamsText', fallback_resource='teamsText'
+ ),
+ res_scale=1.5,
+ maxwidth=scl * button_width * 0.7,
+ h_align='center',
+ v_align='center',
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=scl * 2.3,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + (scl * 54)),
+ size=(scl * button_width, scl * 30),
+ text=ba.Lstr(resource=self._r + '.twoToEightPlayersText'),
+ h_align='center',
+ v_align='center',
+ res_scale=1.5,
+ scale=0.9 * scl,
+ flatness=1.0,
+ maxwidth=scl * button_width * 0.7,
+ color=clr,
+ )
hoffs += 0 if self._is_main_menu else 300
v -= 155 if self._is_main_menu else 0
self._free_for_all_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)),
- size=(scl * button_width,
- scl * (300 if self._is_main_menu else 360)),
+ size=(
+ scl * button_width,
+ scl * (300 if self._is_main_menu else 360),
+ ),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
- on_activate_call=self._free_for_all)
+ on_activate_call=self._free_for_all,
+ )
xxx = -5
- self._draw_dude(0,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 140, 30),
- color=(0.4, 1.0, 0.4))
- self._draw_dude(3,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 185, 53),
- color=(1.0, 0.4, 0.5))
- self._draw_dude(1,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 220, 27),
- color=(0.4, 0.5, 1.0))
- self._draw_dude(2,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 255, 57),
- color=(0.5, 1.0, 0.4))
+ self._draw_dude(
+ 0,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 140, 30),
+ color=(0.4, 1.0, 0.4),
+ )
+ self._draw_dude(
+ 3,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 185, 53),
+ color=(1.0, 0.4, 0.5),
+ )
+ self._draw_dude(
+ 1,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 220, 27),
+ color=(0.4, 0.5, 1.0),
+ )
+ self._draw_dude(
+ 2,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 255, 57),
+ color=(0.5, 1.0, 0.4),
+ )
xxx = 140
- self._draw_dude(2,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 148, 30),
- color=(1.0, 0.9, 0.4))
- self._draw_dude(0,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 182, 53),
- color=(0.7, 1.0, 0.5))
- self._draw_dude(3,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 233, 27),
- color=(0.7, 0.5, 0.9))
- self._draw_dude(1,
- btn,
- hoffs,
- v,
- scl,
- position=(xxx + 266, 53),
- color=(0.4, 0.5, 0.8))
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + scl * 95),
- size=(scl * button_width, scl * 50),
- text=ba.Lstr(resource='playModes.freeForAllText',
- fallback_resource='freeForAllText'),
- maxwidth=scl * button_width * 0.7,
- h_align='center',
- v_align='center',
- color=(0.7, 0.9, 0.7, 1.0),
- scale=scl * 1.9)
- ba.textwidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (-10), v + (scl * 54)),
- size=(scl * button_width, scl * 30),
- text=ba.Lstr(resource=self._r +
- '.twoToEightPlayersText'),
- h_align='center',
- v_align='center',
- scale=0.9 * scl,
- flatness=1.0,
- maxwidth=scl * button_width * 0.7,
- color=clr)
+ self._draw_dude(
+ 2,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 148, 30),
+ color=(1.0, 0.9, 0.4),
+ )
+ self._draw_dude(
+ 0,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 182, 53),
+ color=(0.7, 1.0, 0.5),
+ )
+ self._draw_dude(
+ 3,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 233, 27),
+ color=(0.7, 0.5, 0.9),
+ )
+ self._draw_dude(
+ 1,
+ btn,
+ hoffs,
+ v,
+ scl,
+ position=(xxx + 266, 53),
+ color=(0.4, 0.5, 0.8),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + scl * 95),
+ size=(scl * button_width, scl * 50),
+ text=ba.Lstr(
+ resource='playModes.freeForAllText',
+ fallback_resource='freeForAllText',
+ ),
+ maxwidth=scl * button_width * 0.7,
+ h_align='center',
+ v_align='center',
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=scl * 1.9,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(hoffs + scl * (-10), v + (scl * 54)),
+ size=(scl * button_width, scl * 30),
+ text=ba.Lstr(resource=self._r + '.twoToEightPlayersText'),
+ h_align='center',
+ v_align='center',
+ scale=0.9 * scl,
+ flatness=1.0,
+ maxwidth=scl * button_width * 0.7,
+ color=clr,
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
back_button.delete()
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back,
- selected_child=self._coop_button
- if self._is_main_menu else self._teams_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ on_cancel_call=self._back,
+ selected_child=self._coop_button
+ if self._is_main_menu
+ else self._teams_button,
+ )
else:
ba.buttonwidget(edit=back_button, on_activate_call=self._back)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=back_button,
- selected_child=self._coop_button
- if self._is_main_menu else self._teams_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=back_button,
+ selected_child=self._coop_button
+ if self._is_main_menu
+ else self._teams_button,
+ )
self._restore_state()
@@ -434,128 +523,181 @@ class PlayWindow(ba.Window):
# pylint: disable=cyclic-import
if self._is_main_menu:
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
else:
from bastd.ui.gather import GatherWindow
+
self._save_state()
ba.app.ui.set_main_menu_window(
- GatherWindow(transition='in_left').get_root_widget())
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ GatherWindow(transition='in_left').get_root_widget()
+ )
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
def _coop(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.coop.browser import CoopBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- CoopBrowserWindow(
- origin_widget=self._coop_button).get_root_widget())
+ CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget()
+ )
def _team_tourney(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist.browser import PlaylistBrowserWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
PlaylistBrowserWindow(
- origin_widget=self._teams_button,
- sessiontype=ba.DualTeamSession).get_root_widget())
+ origin_widget=self._teams_button, sessiontype=ba.DualTeamSession
+ ).get_root_widget()
+ )
def _free_for_all(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist.browser import PlaylistBrowserWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
PlaylistBrowserWindow(
origin_widget=self._free_for_all_button,
- sessiontype=ba.FreeForAllSession).get_root_widget())
+ sessiontype=ba.FreeForAllSession,
+ ).get_root_widget()
+ )
- def _draw_dude(self, i: int, btn: ba.Widget, hoffs: float, v: float,
- scl: float, position: tuple[float, float],
- color: tuple[float, float, float]) -> None:
+ def _draw_dude(
+ self,
+ i: int,
+ btn: ba.Widget,
+ hoffs: float,
+ v: float,
+ scl: float,
+ position: tuple[float, float],
+ color: tuple[float, float, float],
+ ) -> None:
h_extra = -100
v_extra = 130
- eye_color = (0.7 * 1.0 + 0.3 * color[0], 0.7 * 1.0 + 0.3 * color[1],
- 0.7 * 1.0 + 0.3 * color[2])
+ eye_color = (
+ 0.7 * 1.0 + 0.3 * color[0],
+ 0.7 * 1.0 + 0.3 * color[1],
+ 0.7 * 1.0 + 0.3 * color[2],
+ )
if i == 0:
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0]),
- v + scl * (v_extra + position[1])),
- size=(scl * 60, scl * 80),
- color=color,
- texture=self._lineup_tex,
- model_transparent=self._lineup_1_transparent_model)
ba.imagewidget(
parent=self._root_widget,
draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0] + 12),
- v + scl * (v_extra + position[1] + 53)),
+ position=(
+ hoffs + scl * (h_extra + position[0]),
+ v + scl * (v_extra + position[1]),
+ ),
+ size=(scl * 60, scl * 80),
+ color=color,
+ texture=self._lineup_tex,
+ model_transparent=self._lineup_1_transparent_model,
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0] + 12),
+ v + scl * (v_extra + position[1] + 53),
+ ),
size=(scl * 36, scl * 18),
texture=self._lineup_tex,
color=eye_color,
- model_transparent=self._eyes_model)
+ model_transparent=self._eyes_model,
+ )
elif i == 1:
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0]),
- v + scl * (v_extra + position[1])),
- size=(scl * 45, scl * 90),
- color=color,
- texture=self._lineup_tex,
- model_transparent=self._lineup_2_transparent_model)
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0] + 5),
- v + scl * (v_extra + position[1] + 67)),
- size=(scl * 32, scl * 16),
- texture=self._lineup_tex,
- color=eye_color,
- model_transparent=self._eyes_model)
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0]),
+ v + scl * (v_extra + position[1]),
+ ),
+ size=(scl * 45, scl * 90),
+ color=color,
+ texture=self._lineup_tex,
+ model_transparent=self._lineup_2_transparent_model,
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0] + 5),
+ v + scl * (v_extra + position[1] + 67),
+ ),
+ size=(scl * 32, scl * 16),
+ texture=self._lineup_tex,
+ color=eye_color,
+ model_transparent=self._eyes_model,
+ )
elif i == 2:
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0]),
- v + scl * (v_extra + position[1])),
- size=(scl * 45, scl * 90),
- color=color,
- texture=self._lineup_tex,
- model_transparent=self._lineup_3_transparent_model)
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0] + 5),
- v + scl * (v_extra + position[1] + 59)),
- size=(scl * 34, scl * 17),
- texture=self._lineup_tex,
- color=eye_color,
- model_transparent=self._eyes_model)
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0]),
+ v + scl * (v_extra + position[1]),
+ ),
+ size=(scl * 45, scl * 90),
+ color=color,
+ texture=self._lineup_tex,
+ model_transparent=self._lineup_3_transparent_model,
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0] + 5),
+ v + scl * (v_extra + position[1] + 59),
+ ),
+ size=(scl * 34, scl * 17),
+ texture=self._lineup_tex,
+ color=eye_color,
+ model_transparent=self._eyes_model,
+ )
elif i == 3:
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0]),
- v + scl * (v_extra + position[1])),
- size=(scl * 48, scl * 96),
- color=color,
- texture=self._lineup_tex,
- model_transparent=self._lineup_4_transparent_model)
- ba.imagewidget(parent=self._root_widget,
- draw_controller=btn,
- position=(hoffs + scl * (h_extra + position[0] + 2),
- v + scl * (v_extra + position[1] + 62)),
- size=(scl * 38, scl * 19),
- texture=self._lineup_tex,
- color=eye_color,
- model_transparent=self._eyes_model)
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0]),
+ v + scl * (v_extra + position[1]),
+ ),
+ size=(scl * 48, scl * 96),
+ color=color,
+ texture=self._lineup_tex,
+ model_transparent=self._lineup_4_transparent_model,
+ )
+ ba.imagewidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ position=(
+ hoffs + scl * (h_extra + position[0] + 2),
+ v + scl * (v_extra + position[1] + 62),
+ ),
+ size=(scl * 38, scl * 19),
+ texture=self._lineup_tex,
+ color=eye_color,
+ model_transparent=self._eyes_model,
+ )
def _save_state(self) -> None:
try:
@@ -586,8 +728,11 @@ class PlayWindow(ba.Window):
elif sel_name == 'Back':
sel = self._back_button
else:
- sel = (self._coop_button if self._coop_button is not None else
- self._teams_button)
+ sel = (
+ self._coop_button
+ if self._coop_button is not None
+ else self._teams_button
+ )
ba.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
ba.print_exception(f'Error restoring state for {self}.')
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/__init__.py b/assets/src/ba_data/python/bastd/ui/playlist/__init__.py
index 5aeadf67..163487a2 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/__init__.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/__init__.py
@@ -17,37 +17,48 @@ class PlaylistTypeVars:
"""Defines values for a playlist type (config names to use, etc)."""
def __init__(self, sessiontype: type[ba.Session]):
- from ba.internal import (get_default_teams_playlist,
- get_default_free_for_all_playlist)
+ from ba.internal import (
+ get_default_teams_playlist,
+ get_default_free_for_all_playlist,
+ )
+
self.sessiontype: type[ba.Session]
if issubclass(sessiontype, ba.DualTeamSession):
- play_mode_name = ba.Lstr(resource='playModes.teamsText',
- fallback_resource='teamsText')
+ play_mode_name = ba.Lstr(
+ resource='playModes.teamsText', fallback_resource='teamsText'
+ )
self.get_default_list_call = get_default_teams_playlist
self.session_type_name = 'ba.DualTeamSession'
self.config_name = 'Team Tournament'
- self.window_title_name = ba.Lstr(resource='playModes.teamsText',
- fallback_resource='teamsText')
+ self.window_title_name = ba.Lstr(
+ resource='playModes.teamsText', fallback_resource='teamsText'
+ )
self.sessiontype = ba.DualTeamSession
elif issubclass(sessiontype, ba.FreeForAllSession):
- play_mode_name = ba.Lstr(resource='playModes.freeForAllText',
- fallback_resource='freeForAllText')
+ play_mode_name = ba.Lstr(
+ resource='playModes.freeForAllText',
+ fallback_resource='freeForAllText',
+ )
self.get_default_list_call = get_default_free_for_all_playlist
self.session_type_name = 'ba.FreeForAllSession'
self.config_name = 'Free-for-All'
self.window_title_name = ba.Lstr(
resource='playModes.freeForAllText',
- fallback_resource='freeForAllText')
+ fallback_resource='freeForAllText',
+ )
self.sessiontype = ba.FreeForAllSession
else:
raise RuntimeError(
- f'Playlist type vars undefined for sessiontype: {sessiontype}')
- self.default_list_name = ba.Lstr(resource='defaultGameListNameText',
- subs=[('${PLAYMODE}', play_mode_name)
- ])
+ f'Playlist type vars undefined for sessiontype: {sessiontype}'
+ )
+ self.default_list_name = ba.Lstr(
+ resource='defaultGameListNameText',
+ subs=[('${PLAYMODE}', play_mode_name)],
+ )
self.default_new_list_name = ba.Lstr(
resource='defaultNewGameListNameText',
- subs=[('${PLAYMODE}', play_mode_name)])
+ subs=[('${PLAYMODE}', play_mode_name)],
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/addgame.py b/assets/src/ba_data/python/bastd/ui/playlist/addgame.py
index 17af366d..5d1aad26 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/addgame.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/addgame.py
@@ -16,36 +16,52 @@ if TYPE_CHECKING:
class PlaylistAddGameWindow(ba.Window):
"""Window for selecting a game type to add to a playlist."""
- def __init__(self,
- editcontroller: PlaylistEditController,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ editcontroller: PlaylistEditController,
+ transition: str = 'in_right',
+ ):
self._editcontroller = editcontroller
self._r = 'addGameWindow'
uiscale = ba.app.ui.uiscale
self._width = 750 if uiscale is ba.UIScale.SMALL else 650
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
- self._height = (346 if uiscale is ba.UIScale.SMALL else
- 380 if uiscale is ba.UIScale.MEDIUM else 440)
+ self._height = (
+ 346
+ if uiscale is ba.UIScale.SMALL
+ else 380
+ if uiscale is ba.UIScale.MEDIUM
+ else 440
+ )
top_extra = 30 if uiscale is ba.UIScale.SMALL else 20
self._scroll_width = 210
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale=(2.17 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, 1) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ scale=(
+ 2.17
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, 1) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
- self._back_button = ba.buttonwidget(parent=self._root_widget,
- position=(58 + x_inset,
- self._height - 53),
- size=(165, 70),
- scale=0.75,
- text_scale=1.2,
- label=ba.Lstr(resource='backText'),
- autoselect=True,
- button_type='back',
- on_activate_call=self._back)
+ self._back_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(58 + x_inset, self._height - 53),
+ size=(165, 70),
+ scale=0.75,
+ text_scale=1.2,
+ label=ba.Lstr(resource='backText'),
+ autoselect=True,
+ button_type='back',
+ on_activate_call=self._back,
+ )
self._select_button = select_button = ba.buttonwidget(
parent=self._root_widget,
position=(self._width - (172 + x_inset), self._height - 50),
@@ -54,22 +70,26 @@ class PlaylistAddGameWindow(ba.Window):
scale=0.75,
text_scale=1.2,
label=ba.Lstr(resource='selectText'),
- on_activate_call=self._add)
+ on_activate_call=self._add,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=select_button,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 28),
- size=(0, 0),
- scale=1.0,
- text=ba.Lstr(resource=self._r + '.titleText'),
- h_align='center',
- color=ba.app.ui.title_color,
- maxwidth=250,
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 28),
+ size=(0, 0),
+ scale=1.0,
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ h_align='center',
+ color=ba.app.ui.title_color,
+ maxwidth=250,
+ v_align='center',
+ )
v = self._height - 64
self._selected_title_text = ba.textwidget(
@@ -80,7 +100,8 @@ class PlaylistAddGameWindow(ba.Window):
color=(0.7, 1.0, 0.7, 1.0),
maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
h_align='left',
- v_align='center')
+ v_align='center',
+ )
v -= 30
self._selected_description_text = ba.textwidget(
@@ -90,46 +111,55 @@ class PlaylistAddGameWindow(ba.Window):
scale=0.7,
color=(0.5, 0.8, 0.5, 1.0),
maxwidth=self._width - self._scroll_width - 150 - x_inset * 2,
- h_align='left')
+ h_align='left',
+ )
scroll_height = self._height - 100
v = self._height - 60
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(x_inset + 61,
- v - scroll_height),
- size=(self._scroll_width,
- scroll_height),
- highlight=False)
- ba.widget(edit=self._scrollwidget,
- up_widget=self._back_button,
- left_widget=self._back_button,
- right_widget=select_button)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(x_inset + 61, v - scroll_height),
+ size=(self._scroll_width, scroll_height),
+ highlight=False,
+ )
+ ba.widget(
+ edit=self._scrollwidget,
+ up_widget=self._back_button,
+ left_widget=self._back_button,
+ right_widget=select_button,
+ )
self._column: ba.Widget | None = None
v -= 35
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button,
- start_button=select_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._back_button,
+ start_button=select_button,
+ )
self._selected_game_type: type[ba.GameActivity] | None = None
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
self._game_types: list[type[ba.GameActivity]] = []
# Get actual games loading in the bg.
- ba.app.meta.load_exported_classes(ba.GameActivity,
- self._on_game_types_loaded,
- completion_cb_in_bg_thread=True)
+ ba.app.meta.load_exported_classes(
+ ba.GameActivity,
+ self._on_game_types_loaded,
+ completion_cb_in_bg_thread=True,
+ )
# Refresh with our initial empty list. We'll refresh again once
# game loading is complete.
self._refresh()
- def _on_game_types_loaded(self,
- gametypes: list[type[ba.GameActivity]]) -> None:
+ def _on_game_types_loaded(
+ self, gametypes: list[type[ba.GameActivity]]
+ ) -> None:
from ba.internal import get_unowned_game_types
# We asked for a bg thread completion cb so we can do some
@@ -138,7 +168,8 @@ class PlaylistAddGameWindow(ba.Window):
sessiontype = self._editcontroller.get_session_type()
unowned = get_unowned_game_types()
self._game_types = [
- gt for gt in gametypes
+ gt
+ for gt in gametypes
if gt not in unowned and gt.supports_session_type(sessiontype)
]
@@ -154,31 +185,34 @@ class PlaylistAddGameWindow(ba.Window):
if self._column is not None:
self._column.delete()
- self._column = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0)
+ self._column = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0
+ )
for i, gametype in enumerate(self._game_types):
def _doit() -> None:
if self._select_button:
- ba.timer(0.1,
- self._select_button.activate,
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1,
+ self._select_button.activate,
+ timetype=ba.TimeType.REAL,
+ )
- txt = ba.textwidget(parent=self._column,
- position=(0, 0),
- size=(self._width - 88, 24),
- text=gametype.get_display_string(),
- h_align='left',
- v_align='center',
- color=(0.8, 0.8, 0.8, 1.0),
- maxwidth=self._scroll_width * 0.8,
- on_select_call=ba.Call(
- self._set_selected_game_type, gametype),
- always_highlight=True,
- selectable=True,
- on_activate_call=_doit)
+ txt = ba.textwidget(
+ parent=self._column,
+ position=(0, 0),
+ size=(self._width - 88, 24),
+ text=gametype.get_display_string(),
+ h_align='left',
+ v_align='center',
+ color=(0.8, 0.8, 0.8, 1.0),
+ maxwidth=self._scroll_width * 0.8,
+ on_select_call=ba.Call(self._set_selected_game_type, gametype),
+ always_highlight=True,
+ selectable=True,
+ on_activate_call=_doit,
+ )
if i == 0:
ba.widget(edit=txt, up_widget=self._back_button)
@@ -189,22 +223,28 @@ class PlaylistAddGameWindow(ba.Window):
color=(0.54, 0.52, 0.67),
textcolor=(0.7, 0.65, 0.7),
on_activate_call=self._on_get_more_games_press,
- size=(178, 50))
+ size=(178, 50),
+ )
if select_get_more_games_button:
- ba.containerwidget(edit=self._column,
- selected_child=self._get_more_games_button,
- visible_child=self._get_more_games_button)
+ ba.containerwidget(
+ edit=self._column,
+ selected_child=self._get_more_games_button,
+ visible_child=self._get_more_games_button,
+ )
def _on_get_more_games_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
- StoreBrowserWindow(modal=True,
- show_tab=StoreBrowserWindow.TabID.MINIGAMES,
- on_close_call=self._on_store_close,
- origin_widget=self._get_more_games_button)
+ StoreBrowserWindow(
+ modal=True,
+ show_tab=StoreBrowserWindow.TabID.MINIGAMES,
+ on_close_call=self._on_store_close,
+ origin_widget=self._get_more_games_button,
+ )
def _on_store_close(self) -> None:
self._refresh(select_get_more_games_button=True)
@@ -217,11 +257,15 @@ class PlaylistAddGameWindow(ba.Window):
def _set_selected_game_type(self, gametype: type[ba.GameActivity]) -> None:
self._selected_game_type = gametype
- ba.textwidget(edit=self._selected_title_text,
- text=gametype.get_display_string())
- ba.textwidget(edit=self._selected_description_text,
- text=gametype.get_description_display_string(
- self._editcontroller.get_session_type()))
+ ba.textwidget(
+ edit=self._selected_title_text, text=gametype.get_display_string()
+ )
+ ba.textwidget(
+ edit=self._selected_description_text,
+ text=gametype.get_description_display_string(
+ self._editcontroller.get_session_type()
+ ),
+ )
def _back(self) -> None:
self._editcontroller.add_game_cancelled()
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/browser.py b/assets/src/ba_data/python/bastd/ui/playlist/browser.py
index 8a621261..1ee3f119 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/browser.py
@@ -18,10 +18,12 @@ if TYPE_CHECKING:
class PlaylistBrowserWindow(ba.Window):
"""Window for starting teams games."""
- def __init__(self,
- sessiontype: type[ba.Session],
- transition: str | None = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ sessiontype: type[ba.Session],
+ transition: str | None = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bastd.ui.playlist import PlaylistTypeVars
@@ -56,25 +58,41 @@ class PlaylistBrowserWindow(ba.Window):
self._ensure_standard_playlists_exist()
# Get the current selection (if any).
- self._selected_playlist = ba.app.config.get(self._pvars.config_name +
- ' Playlist Selection')
+ self._selected_playlist = ba.app.config.get(
+ self._pvars.config_name + ' Playlist Selection'
+ )
uiscale = ba.app.ui.uiscale
self._width = 900 if uiscale is ba.UIScale.SMALL else 800
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
- self._height = (480 if uiscale is ba.UIScale.SMALL else
- 510 if uiscale is ba.UIScale.MEDIUM else 580)
+ self._height = (
+ 480
+ if uiscale is ba.UIScale.SMALL
+ else 510
+ if uiscale is ba.UIScale.MEDIUM
+ else 580
+ )
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
- scale=(1.69 if uiscale is ba.UIScale.SMALL else
- 1.05 if uiscale is ba.UIScale.MEDIUM else 0.9),
- stack_offset=(0, -26) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ toolbar_visibility='menu_full',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.69
+ if uiscale is ba.UIScale.SMALL
+ else 1.05
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.9
+ ),
+ stack_offset=(0, -26)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._back_button: ba.Widget | None = ba.buttonwidget(
parent=self._root_widget,
@@ -84,9 +102,11 @@ class PlaylistBrowserWindow(ba.Window):
on_activate_call=self._on_back_press,
autoselect=True,
label=ba.Lstr(resource='backText'),
- button_type='back')
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button)
+ button_type='back',
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
txt = self._title_text = ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 41),
@@ -96,34 +116,43 @@ class PlaylistBrowserWindow(ba.Window):
res_scale=1.5,
color=ba.app.ui.heading_color,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.textwidget(edit=txt, text='')
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 54),
- position=(59 + x_inset, self._height - 67),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 54),
+ position=(59 + x_inset, self._height - 67),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
self._back_button.delete()
self._back_button = None
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._on_back_press)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._on_back_press
+ )
scroll_offs = 33
else:
scroll_offs = 0
self._scroll_width = self._width - (100 + 2 * x_inset)
- self._scroll_height = (self._height -
- (146 if uiscale is ba.UIScale.SMALL
- and ba.app.ui.use_toolbars else 136))
+ self._scroll_height = self._height - (
+ 146
+ if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars
+ else 136
+ )
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
size=(self._scroll_width, self._scroll_height),
- position=((self._width - self._scroll_width) * 0.5,
- 65 + scroll_offs))
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ 65 + scroll_offs,
+ ),
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._subcontainer: ba.Widget | None = None
self._config_name_full = self._pvars.config_name + ' Playlists'
@@ -132,154 +161,168 @@ class PlaylistBrowserWindow(ba.Window):
# Update now and once per second.
# (this should do our initial refresh)
self._update()
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
def _ensure_standard_playlists_exist(self) -> None:
# On new installations, go ahead and create a few playlists
# besides the hard-coded default one:
- if not ba.internal.get_v1_account_misc_val('madeStandardPlaylists',
- False):
- ba.internal.add_transaction({
- 'type':
- 'ADD_PLAYLIST',
- 'playlistType':
- 'Free-for-All',
- 'playlistName':
- ba.Lstr(resource='singleGamePlaylistNameText'
- ).evaluate().replace(
- '${GAME}',
- ba.Lstr(translate=('gameNames',
- 'Death Match')).evaluate()),
- 'playlist': [
- {
- 'type': 'bs_death_match.DeathMatchGame',
- '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',
- 'settings': {
- 'Epic Mode': False,
- 'Kills to Win Per Player': 10,
- 'Respawn Times': 1.0,
- 'Time Limit': 300,
- 'map': 'Crag Castle'
- }
- },
- ]
- })
- ba.internal.add_transaction({
- 'type':
- 'ADD_PLAYLIST',
- 'playlistType':
- 'Team Tournament',
- 'playlistName':
- ba.Lstr(
+ if not ba.internal.get_v1_account_misc_val(
+ 'madeStandardPlaylists', False
+ ):
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': 'Free-for-All',
+ 'playlistName': ba.Lstr(
resource='singleGamePlaylistNameText'
- ).evaluate().replace(
+ )
+ .evaluate()
+ .replace(
'${GAME}',
- ba.Lstr(translate=('gameNames',
- 'Capture the Flag')).evaluate()),
- 'playlist': [
- {
- 'type': 'bs_capture_the_flag.CTFGame',
- 'settings': {
- 'map': 'Bridgit',
- 'Score to Win': 3,
- 'Flag Idle Return Time': 30,
- 'Flag Touch Return Time': 0,
- 'Respawn Times': 1.0,
- 'Time Limit': 600,
- 'Epic Mode': False
+ ba.Lstr(
+ translate=('gameNames', 'Death Match')
+ ).evaluate(),
+ ),
+ 'playlist': [
+ {
+ 'type': 'bs_death_match.DeathMatchGame',
+ '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',
+ 'settings': {
+ 'Epic Mode': False,
+ 'Kills to Win Per Player': 10,
+ 'Respawn Times': 1.0,
+ 'Time Limit': 300,
+ 'map': 'Crag Castle',
+ },
+ },
+ ],
+ }
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': 'Team Tournament',
+ 'playlistName': ba.Lstr(
+ resource='singleGamePlaylistNameText'
+ )
+ .evaluate()
+ .replace(
+ '${GAME}',
+ ba.Lstr(
+ translate=('gameNames', 'Capture the Flag')
+ ).evaluate(),
+ ),
+ 'playlist': [
+ {
+ 'type': 'bs_capture_the_flag.CTFGame',
+ 'settings': {
+ 'map': 'Bridgit',
+ 'Score to Win': 3,
+ 'Flag Idle Return Time': 30,
+ 'Flag Touch Return Time': 0,
+ 'Respawn Times': 1.0,
+ 'Time Limit': 600,
+ 'Epic Mode': False,
+ },
+ },
+ {
+ 'type': 'bs_capture_the_flag.CTFGame',
+ 'settings': {
+ 'map': 'Roundabout',
+ 'Score to Win': 2,
+ 'Flag Idle Return Time': 30,
+ 'Flag Touch Return Time': 0,
+ 'Respawn Times': 1.0,
+ 'Time Limit': 600,
+ 'Epic Mode': False,
+ },
+ },
+ {
+ 'type': 'bs_capture_the_flag.CTFGame',
+ 'settings': {
+ 'map': 'Tip Top',
+ 'Score to Win': 2,
+ 'Flag Idle Return Time': 30,
+ 'Flag Touch Return Time': 3,
+ 'Respawn Times': 1.0,
+ 'Time Limit': 300,
+ 'Epic Mode': False,
+ },
+ },
+ ],
+ }
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': 'Team Tournament',
+ 'playlistName': ba.Lstr(
+ translate=('playlistNames', 'Just Sports')
+ ).evaluate(),
+ 'playlist': [
+ {
+ 'type': 'bs_hockey.HockeyGame',
+ 'settings': {
+ 'Time Limit': 0,
+ 'map': 'Hockey Stadium',
+ 'Score to Win': 1,
+ 'Respawn Times': 1.0,
+ },
+ },
+ {
+ 'type': 'bs_football.FootballTeamGame',
+ 'settings': {
+ 'Time Limit': 0,
+ 'map': 'Football Stadium',
+ 'Score to Win': 21,
+ 'Respawn Times': 1.0,
+ },
+ },
+ ],
+ }
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': 'Free-for-All',
+ 'playlistName': ba.Lstr(
+ translate=('playlistNames', 'Just Epic')
+ ).evaluate(),
+ 'playlist': [
+ {
+ 'type': 'bs_elimination.EliminationGame',
+ 'settings': {
+ 'Time Limit': 120,
+ 'map': 'Tip Top',
+ 'Respawn Times': 1.0,
+ 'Lives Per Player': 1,
+ 'Epic Mode': 1,
+ },
}
- },
- {
- 'type': 'bs_capture_the_flag.CTFGame',
- 'settings': {
- 'map': 'Roundabout',
- 'Score to Win': 2,
- 'Flag Idle Return Time': 30,
- 'Flag Touch Return Time': 0,
- 'Respawn Times': 1.0,
- 'Time Limit': 600,
- 'Epic Mode': False
- }
- },
- {
- 'type': 'bs_capture_the_flag.CTFGame',
- 'settings': {
- 'map': 'Tip Top',
- 'Score to Win': 2,
- 'Flag Idle Return Time': 30,
- 'Flag Touch Return Time': 3,
- 'Respawn Times': 1.0,
- 'Time Limit': 300,
- 'Epic Mode': False
- }
- },
- ]
- })
- ba.internal.add_transaction({
- 'type':
- 'ADD_PLAYLIST',
- 'playlistType':
- 'Team Tournament',
- 'playlistName':
- ba.Lstr(translate=('playlistNames', 'Just Sports')
- ).evaluate(),
- 'playlist': [
- {
- 'type': 'bs_hockey.HockeyGame',
- 'settings': {
- 'Time Limit': 0,
- 'map': 'Hockey Stadium',
- 'Score to Win': 1,
- 'Respawn Times': 1.0
- }
- },
- {
- 'type': 'bs_football.FootballTeamGame',
- 'settings': {
- 'Time Limit': 0,
- 'map': 'Football Stadium',
- 'Score to Win': 21,
- 'Respawn Times': 1.0
- }
- },
- ]
- })
- ba.internal.add_transaction({
- 'type':
- 'ADD_PLAYLIST',
- 'playlistType':
- 'Free-for-All',
- 'playlistName':
- ba.Lstr(translate=('playlistNames', 'Just Epic')
- ).evaluate(),
- 'playlist': [{
- 'type': 'bs_elimination.EliminationGame',
- 'settings': {
- 'Time Limit': 120,
- 'map': 'Tip Top',
- 'Respawn Times': 1.0,
- 'Lives Per Player': 1,
- 'Epic Mode': 1
- }
- }]
- })
- ba.internal.add_transaction({
- 'type': 'SET_MISC_VAL',
- 'name': 'madeStandardPlaylists',
- 'value': True
- })
+ ],
+ }
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'SET_MISC_VAL',
+ 'name': 'madeStandardPlaylists',
+ 'value': True,
+ }
+ )
ba.internal.run_transactions()
def _refresh(self) -> None:
@@ -290,6 +333,7 @@ class PlaylistBrowserWindow(ba.Window):
# pylint: disable=too-many-nested-blocks
from efro.util import asserttype
from ba.internal import get_map_class, filter_playlist
+
if not self._root_widget:
return
if self._subcontainer is not None:
@@ -303,8 +347,10 @@ class PlaylistBrowserWindow(ba.Window):
items = list(ba.app.config[self._config_name_full].items())
# Make sure everything is unicode.
- items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i
- for i in items]
+ items = [
+ (i[0].decode(), i[1]) if not isinstance(i[0], str) else i
+ for i in items
+ ]
items.sort(key=lambda x2: asserttype(x2[0], str).lower())
items = [['__default__', None]] + items # default is always first
@@ -318,28 +364,32 @@ class PlaylistBrowserWindow(ba.Window):
button_buffer_v = 0
self._sub_width = self._scroll_width
- self._sub_height = 40 + rows * (button_height +
- 2 * button_buffer_v) + 90
+ self._sub_height = (
+ 40 + rows * (button_height + 2 * button_buffer_v) + 90
+ )
assert self._sub_width is not None
assert self._sub_height is not None
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
children = self._subcontainer.get_children()
for child in children:
child.delete()
- ba.textwidget(parent=self._subcontainer,
- text=ba.Lstr(resource='playlistsText'),
- position=(40, self._sub_height - 26),
- size=(0, 0),
- scale=1.0,
- maxwidth=400,
- color=ba.app.ui.title_color,
- h_align='left',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ text=ba.Lstr(resource='playlistsText'),
+ position=(40, self._sub_height - 26),
+ size=(0, 0),
+ scale=1.0,
+ maxwidth=400,
+ color=ba.app.ui.title_color,
+ h_align='left',
+ v_align='center',
+ )
index = 0
appconfig = ba.app.config
@@ -356,37 +406,61 @@ class PlaylistBrowserWindow(ba.Window):
for x in range(columns):
name = items[index][0]
assert name is not None
- pos = (x * (button_width + 2 * button_buffer_h) +
- button_buffer_h + 8 + h_offs, self._sub_height - 47 -
- (y + 1) * (button_height + 2 * button_buffer_v))
- btn = ba.buttonwidget(parent=self._subcontainer,
- button_type='square',
- size=(button_width, button_height),
- autoselect=True,
- label='',
- position=pos)
+ pos = (
+ x * (button_width + 2 * button_buffer_h)
+ + button_buffer_h
+ + 8
+ + h_offs,
+ self._sub_height
+ - 47
+ - (y + 1) * (button_height + 2 * button_buffer_v),
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ button_type='square',
+ size=(button_width, button_height),
+ autoselect=True,
+ label='',
+ position=pos,
+ )
- if (x == 0 and ba.app.ui.use_toolbars
- and uiscale is ba.UIScale.SMALL):
- ba.widget(edit=btn,
- left_widget=ba.internal.get_special_widget(
- 'back_button'))
- if (x == columns - 1 and ba.app.ui.use_toolbars
- and uiscale is ba.UIScale.SMALL):
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ if (
+ x == 0
+ and ba.app.ui.use_toolbars
+ and uiscale is ba.UIScale.SMALL
+ ):
+ ba.widget(
+ edit=btn,
+ left_widget=ba.internal.get_special_widget(
+ 'back_button'
+ ),
+ )
+ if (
+ x == columns - 1
+ and ba.app.ui.use_toolbars
+ and uiscale is ba.UIScale.SMALL
+ ):
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget(
+ 'party_button'
+ ),
+ )
ba.buttonwidget(
edit=btn,
- on_activate_call=ba.Call(self._on_playlist_press, btn,
- name),
- on_select_call=ba.Call(self._on_playlist_select, name))
+ on_activate_call=ba.Call(
+ self._on_playlist_press, btn, name
+ ),
+ on_select_call=ba.Call(self._on_playlist_select, name),
+ )
ba.widget(edit=btn, show_buffer_top=50, show_buffer_bottom=50)
if self._selected_playlist == name:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=btn,
+ visible_child=btn,
+ )
if self._back_button is not None:
if y == 0:
@@ -399,16 +473,20 @@ class PlaylistBrowserWindow(ba.Window):
print_name = self._pvars.default_list_name
else:
print_name = name
- ba.textwidget(parent=self._subcontainer,
- text=print_name,
- position=(pos[0] + button_width * 0.5,
- pos[1] + button_height * 0.79),
- size=(0, 0),
- scale=button_width * 0.003,
- maxwidth=button_width * 0.7,
- draw_controller=btn,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ text=print_name,
+ position=(
+ pos[0] + button_width * 0.5,
+ pos[1] + button_height * 0.79,
+ ),
+ size=(0, 0),
+ scale=button_width * 0.003,
+ maxwidth=button_width * 0.7,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ )
# Poke into this playlist and see if we can display some of
# its maps.
@@ -419,19 +497,28 @@ class PlaylistBrowserWindow(ba.Window):
if name == '__default__':
playlist = self._pvars.get_default_list_call()
else:
- if name not in appconfig[self._pvars.config_name +
- ' Playlists']:
+ if (
+ name
+ not in appconfig[
+ self._pvars.config_name + ' Playlists'
+ ]
+ ):
print(
'NOT FOUND ERR',
- appconfig[self._pvars.config_name +
- ' Playlists'])
- playlist = appconfig[self._pvars.config_name +
- ' Playlists'][name]
- playlist = filter_playlist(playlist,
- self._sessiontype,
- remove_unowned=False,
- mark_unowned=True,
- name=name)
+ appconfig[
+ self._pvars.config_name + ' Playlists'
+ ],
+ )
+ playlist = appconfig[
+ self._pvars.config_name + ' Playlists'
+ ][name]
+ playlist = filter_playlist(
+ playlist,
+ self._sessiontype,
+ remove_unowned=False,
+ mark_unowned=True,
+ name=name,
+ )
for entry in playlist:
mapname = entry['settings']['map']
maptype: type[ba.Map] | None
@@ -479,10 +566,16 @@ class PlaylistBrowserWindow(ba.Window):
if tex_index < len(map_textures):
entry = map_texture_entries[tex_index]
- owned = not (('is_unowned_map' in entry
- and entry['is_unowned_map']) or
- ('is_unowned_game' in entry
- and entry['is_unowned_game']))
+ owned = not (
+ (
+ 'is_unowned_map' in entry
+ and entry['is_unowned_map']
+ )
+ or (
+ 'is_unowned_game' in entry
+ and entry['is_unowned_game']
+ )
+ )
tex_name = map_textures[tex_index]
h = pos[0] + h_offs_img + scl * 250 * col
@@ -497,14 +590,17 @@ class PlaylistBrowserWindow(ba.Window):
draw_controller=btn,
model_opaque=model_opaque,
model_transparent=model_transparent,
- mask_texture=mask_tex))
+ mask_texture=mask_tex,
+ )
+ )
if not owned:
ba.imagewidget(
parent=self._subcontainer,
size=(scl * 100.0, scl * 100.0),
position=(h + scl * 75, v + scl * 10),
texture=ba.gettexture('lock'),
- draw_controller=btn)
+ draw_controller=btn,
+ )
if v is not None:
v -= scl * 130.0
@@ -512,16 +608,20 @@ class PlaylistBrowserWindow(ba.Window):
ba.print_exception('Error listing playlist maps.')
if not map_images:
- ba.textwidget(parent=self._subcontainer,
- text='???',
- scale=1.5,
- size=(0, 0),
- color=(1, 1, 1, 0.5),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- position=(pos[0] + button_width * 0.5,
- pos[1] + button_height * 0.5))
+ ba.textwidget(
+ parent=self._subcontainer,
+ text='???',
+ scale=1.5,
+ size=(0, 0),
+ color=(1, 1, 1, 0.5),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ position=(
+ pos[0] + button_width * 0.5,
+ pos[1] + button_height * 0.5,
+ ),
+ )
index += 1
@@ -538,7 +638,8 @@ class PlaylistBrowserWindow(ba.Window):
on_activate_call=self._on_customize_press,
color=(0.54, 0.52, 0.67),
textcolor=(0.7, 0.65, 0.7),
- autoselect=True)
+ autoselect=True,
+ )
ba.widget(edit=btn, show_buffer_top=22, show_buffer_bottom=28)
self._restore_state()
@@ -562,34 +663,40 @@ class PlaylistBrowserWindow(ba.Window):
self._last_config = copy.deepcopy(cfg)
self._refresh()
- def _on_playlist_press(self, button: ba.Widget,
- playlist_name: str) -> None:
+ def _on_playlist_press(self, button: ba.Widget, playlist_name: str) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playoptions import PlayOptionsWindow
# Make sure the target playlist still exists.
- exists = (playlist_name == '__default__'
- or playlist_name in ba.app.config.get(
- self._config_name_full, {}))
+ exists = (
+ playlist_name == '__default__'
+ or playlist_name in ba.app.config.get(self._config_name_full, {})
+ )
if not exists:
return
self._save_state()
- PlayOptionsWindow(sessiontype=self._sessiontype,
- scale_origin=button.get_screen_space_center(),
- playlist=playlist_name,
- delegate=self)
+ PlayOptionsWindow(
+ sessiontype=self._sessiontype,
+ scale_origin=button.get_screen_space_center(),
+ playlist=playlist_name,
+ delegate=self,
+ )
def _on_customize_press(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist.customizebrowser import (
- PlaylistCustomizeBrowserWindow)
+ PlaylistCustomizeBrowserWindow,
+ )
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button,
- sessiontype=self._sessiontype).get_root_widget())
+ sessiontype=self._sessiontype,
+ ).get_root_widget()
+ )
def _on_back_press(self) -> None:
# pylint: disable=cyclic-import
@@ -597,19 +704,23 @@ class PlaylistBrowserWindow(ba.Window):
# Store our selected playlist if that's changed.
if self._selected_playlist is not None:
- prev_sel = ba.app.config.get(self._pvars.config_name +
- ' Playlist Selection')
+ prev_sel = ba.app.config.get(
+ self._pvars.config_name + ' Playlist Selection'
+ )
if self._selected_playlist != prev_sel:
cfg = ba.app.config
- cfg[self._pvars.config_name +
- ' Playlist Selection'] = self._selected_playlist
+ cfg[
+ self._pvars.config_name + ' Playlist Selection'
+ ] = self._selected_playlist
cfg.commit()
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- PlayWindow(transition='in_left').get_root_widget())
+ PlayWindow(transition='in_left').get_root_widget()
+ )
def _save_state(self) -> None:
try:
@@ -638,9 +749,11 @@ class PlaylistBrowserWindow(ba.Window):
sel = self._scrollwidget
elif sel_name == 'Customize':
sel = self._scrollwidget
- ba.containerwidget(edit=self._subcontainer,
- selected_child=self._customize_button,
- visible_child=self._customize_button)
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=self._customize_button,
+ visible_child=self._customize_button,
+ )
else:
sel = self._scrollwidget
ba.containerwidget(edit=self._root_widget, selected_child=sel)
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/customizebrowser.py b/assets/src/ba_data/python/bastd/ui/playlist/customizebrowser.py
index 2f99ad04..c8b78a53 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/customizebrowser.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/customizebrowser.py
@@ -18,16 +18,19 @@ if TYPE_CHECKING:
class PlaylistCustomizeBrowserWindow(ba.Window):
"""Window for viewing a playlist."""
- def __init__(self,
- sessiontype: type[ba.Session],
- transition: str = 'in_right',
- select_playlist: str | None = None,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ sessiontype: type[ba.Session],
+ transition: str = 'in_right',
+ select_playlist: str | None = None,
+ origin_widget: ba.Widget | None = None,
+ ):
# Yes this needs tidying.
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bastd.ui import playlist
+
scale_origin: tuple[float, float] | None
if origin_widget is not None:
self._transition_out = 'out_scale'
@@ -44,17 +47,32 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 750.0 if uiscale is ba.UIScale.SMALL else 650.0
x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
- self._height = (380.0 if uiscale is ba.UIScale.SMALL else
- 420.0 if uiscale is ba.UIScale.MEDIUM else 500.0)
+ self._height = (
+ 380.0
+ if uiscale is ba.UIScale.SMALL
+ else 420.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 500.0
+ )
top_extra = 20.0 if uiscale is ba.UIScale.SMALL else 0.0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- scale=(2.05 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.05
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -10)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._back_button = back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -64,23 +82,29 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
autoselect=True,
text_scale=1.3,
label=ba.Lstr(resource='backText'),
- button_type='back')
+ button_type='back',
+ )
- ba.textwidget(parent=self._root_widget,
- position=(0, self._height - 47),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.titleText',
- subs=[('${TYPE}',
- self._pvars.window_title_name)]),
- color=ba.app.ui.heading_color,
- maxwidth=290,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, self._height - 47),
+ size=(self._width, 25),
+ text=ba.Lstr(
+ resource=self._r + '.titleText',
+ subs=[('${TYPE}', self._pvars.window_title_name)],
+ ),
+ color=ba.app.ui.heading_color,
+ maxwidth=290,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v = self._height - 59.0
h = 41 + x_inset
@@ -89,8 +113,13 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
self._lock_images: list[ba.Widget] = []
lock_tex = ba.gettexture('lock')
- scl = (1.1 if uiscale is ba.UIScale.SMALL else
- 1.27 if uiscale is ba.UIScale.MEDIUM else 1.57)
+ scl = (
+ 1.1
+ if uiscale is ba.UIScale.SMALL
+ else 1.27
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.57
+ )
scl *= 0.63
v -= 65.0 * scl
new_button = btn = ba.buttonwidget(
@@ -103,14 +132,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
button_type='square',
textcolor=b_textcolor,
text_scale=0.7,
- label=ba.Lstr(resource='newText',
- fallback_resource=self._r + '.newText'))
+ label=ba.Lstr(
+ resource='newText', fallback_resource=self._r + '.newText'
+ ),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 58.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 58.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
v -= 65.0 * scl
self._edit_button = edit_button = btn = ba.buttonwidget(
@@ -123,14 +157,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
textcolor=b_textcolor,
button_type='square',
text_scale=0.7,
- label=ba.Lstr(resource='editText',
- fallback_resource=self._r + '.editText'))
+ label=ba.Lstr(
+ resource='editText', fallback_resource=self._r + '.editText'
+ ),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 58.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 58.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
v -= 65.0 * scl
duplicate_button = btn = ba.buttonwidget(
@@ -143,14 +182,20 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
textcolor=b_textcolor,
button_type='square',
text_scale=0.7,
- label=ba.Lstr(resource='duplicateText',
- fallback_resource=self._r + '.duplicateText'))
+ label=ba.Lstr(
+ resource='duplicateText',
+ fallback_resource=self._r + '.duplicateText',
+ ),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 58.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 58.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
v -= 65.0 * scl
delete_button = btn = ba.buttonwidget(
@@ -163,14 +208,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
textcolor=b_textcolor,
button_type='square',
text_scale=0.7,
- label=ba.Lstr(resource='deleteText',
- fallback_resource=self._r + '.deleteText'))
+ label=ba.Lstr(
+ resource='deleteText', fallback_resource=self._r + '.deleteText'
+ ),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 58.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 58.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
v -= 65.0 * scl
self._import_button = ba.buttonwidget(
parent=self._root_widget,
@@ -182,51 +232,61 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
textcolor=b_textcolor,
button_type='square',
text_scale=0.7,
- label=ba.Lstr(resource='importText'))
+ label=ba.Lstr(resource='importText'),
+ )
v -= 65.0 * scl
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(h, v),
- size=(90, 58.0 * scl),
- on_activate_call=self._share_playlist,
- color=b_color,
- autoselect=True,
- textcolor=b_textcolor,
- button_type='square',
- text_scale=0.7,
- label=ba.Lstr(resource='shareText'))
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(90, 58.0 * scl),
+ on_activate_call=self._share_playlist,
+ color=b_color,
+ autoselect=True,
+ textcolor=b_textcolor,
+ button_type='square',
+ text_scale=0.7,
+ label=ba.Lstr(resource='shareText'),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 58.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 58.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
v = self._height - 75
self._scroll_height = self._height - 119
- scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(140 + x_inset,
- v - self._scroll_height),
- size=(self._width - (180 + 2 * x_inset),
- self._scroll_height + 10),
- highlight=False)
+ scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(140 + x_inset, v - self._scroll_height),
+ size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10),
+ highlight=False,
+ )
ba.widget(edit=back_button, right_widget=scrollwidget)
- self._columnwidget = ba.columnwidget(parent=scrollwidget,
- border=2,
- margin=0)
+ self._columnwidget = ba.columnwidget(
+ parent=scrollwidget, border=2, margin=0
+ )
h = 145
self._do_randomize_val = ba.app.config.get(
- self._pvars.config_name + ' Playlist Randomize', 0)
+ self._pvars.config_name + ' Playlist Randomize', 0
+ )
h += 210
for btn in [new_button, delete_button, edit_button, duplicate_button]:
ba.widget(edit=btn, right_widget=scrollwidget)
- ba.widget(edit=scrollwidget,
- left_widget=new_button,
- right_widget=ba.internal.get_special_widget('party_button')
- if ba.app.ui.use_toolbars else None)
+ ba.widget(
+ edit=scrollwidget,
+ left_widget=new_button,
+ right_widget=ba.internal.get_special_widget('party_button')
+ if ba.app.ui.use_toolbars
+ else None,
+ )
# make sure config exists
self._config_name_full = self._pvars.config_name + ' Playlists'
@@ -246,10 +306,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, selected_child=scrollwidget)
# Keep our lock images up to date/etc.
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
def _update(self) -> None:
@@ -260,18 +322,22 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist import browser
+
if self._selected_playlist_name is not None:
cfg = ba.app.config
- cfg[self._pvars.config_name +
- ' Playlist Selection'] = self._selected_playlist_name
+ cfg[
+ self._pvars.config_name + ' Playlist Selection'
+ ] = self._selected_playlist_name
cfg.commit()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
browser.PlaylistBrowserWindow(
- transition='in_left',
- sessiontype=self._sessiontype).get_root_widget())
+ transition='in_left', sessiontype=self._sessiontype
+ ).get_root_widget()
+ )
def _select(self, name: str, index: int) -> None:
self._selected_playlist_name = name
@@ -284,6 +350,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
ba.internal.new_host_session(self._sessiontype)
except Exception:
from bastd import mainmenu
+
ba.print_exception(f'Error running session {self._sessiontype}.')
# Drop back into a main menu session.
@@ -299,12 +366,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
def _refresh(self, select_playlist: str | None = None) -> None:
from efro.util import asserttype
+
old_selection = self._selected_playlist_name
# If there was no prev selection, look in prefs.
if old_selection is None:
- old_selection = ba.app.config.get(self._pvars.config_name +
- ' Playlist Selection')
+ old_selection = ba.app.config.get(
+ self._pvars.config_name + ' Playlist Selection'
+ )
old_selection_index = self._selected_playlist_index
@@ -315,8 +384,10 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
items = list(ba.app.config[self._config_name_full].items())
# Make sure everything is unicode now.
- items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i
- for i in items]
+ items = [
+ (i[0].decode(), i[1]) if not isinstance(i[0], str) else i
+ for i in items
+ ]
items.sort(key=lambda x: asserttype(x[0], str).lower())
@@ -331,12 +402,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
text=self._get_playlist_display_name(pname),
h_align='left',
v_align='center',
- color=(0.6, 0.6, 0.7, 1.0) if pname == '__default__' else
- (0.85, 0.85, 0.85, 1),
+ color=(0.6, 0.6, 0.7, 1.0)
+ if pname == '__default__'
+ else (0.85, 0.85, 0.85, 1),
always_highlight=True,
on_select_call=ba.Call(self._select, pname, index),
on_activate_call=ba.Call(self._edit_button.activate),
- selectable=True)
+ selectable=True,
+ )
ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50)
# Hitting up from top widget should jump to 'back'
@@ -348,22 +421,28 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# Select this one if the user requested it.
if select_playlist is not None:
if pname == select_playlist:
- ba.columnwidget(edit=self._columnwidget,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._columnwidget,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
else:
# Select this one if it was previously selected.
# Go by index if there's one.
if old_selection_index is not None:
if index == old_selection_index:
- ba.columnwidget(edit=self._columnwidget,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._columnwidget,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
else: # Otherwise look by name.
if pname == old_selection:
- ba.columnwidget(edit=self._columnwidget,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._columnwidget,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
index += 1
@@ -373,16 +452,19 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# if we want and also lets us pass it to the game (since we reset
# the whole python environment that's not actually easy).
cfg = ba.app.config
- cfg[self._pvars.config_name +
- ' Playlist Selection'] = self._selected_playlist_name
- cfg[self._pvars.config_name +
- ' Playlist Randomize'] = self._do_randomize_val
+ cfg[
+ self._pvars.config_name + ' Playlist Selection'
+ ] = self._selected_playlist_name
+ cfg[
+ self._pvars.config_name + ' Playlist Randomize'
+ ] = self._do_randomize_val
cfg.commit()
def _new_playlist(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist.editcontroller import PlaylistEditController
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -390,9 +472,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# Clamp at our max playlist number.
if len(ba.app.config[self._config_name_full]) > self._max_playlists:
ba.screenmessage(
- ba.Lstr(translate=('serverResponses',
- 'Max number of playlists reached.')),
- color=(1, 0, 0))
+ ba.Lstr(
+ translate=(
+ 'serverResponses',
+ 'Max number of playlists reached.',
+ )
+ ),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -407,6 +494,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.playlist.editcontroller import PlaylistEditController
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -414,30 +502,34 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
return
if self._selected_playlist_name == '__default__':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.cantEditDefaultText'))
+ ba.screenmessage(ba.Lstr(resource=self._r + '.cantEditDefaultText'))
return
self._save_playlist_selection()
PlaylistEditController(
existing_playlist_name=self._selected_playlist_name,
- sessiontype=self._sessiontype)
+ sessiontype=self._sessiontype,
+ )
ba.containerwidget(edit=self._root_widget, transition='out_left')
def _do_delete_playlist(self) -> None:
- ba.internal.add_transaction({
- 'type': 'REMOVE_PLAYLIST',
- 'playlistType': self._pvars.config_name,
- 'playlistName': self._selected_playlist_name
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'REMOVE_PLAYLIST',
+ 'playlistType': self._pvars.config_name,
+ 'playlistName': self._selected_playlist_name,
+ }
+ )
ba.internal.run_transactions()
ba.playsound(ba.getsound('shieldDown'))
# (we don't use len()-1 here because the default list adds one)
assert self._selected_playlist_index is not None
if self._selected_playlist_index > len(
- ba.app.config[self._pvars.config_name + ' Playlists']):
+ ba.app.config[self._pvars.config_name + ' Playlists']
+ ):
self._selected_playlist_index = len(
- ba.app.config[self._pvars.config_name + ' Playlists'])
+ ba.app.config[self._pvars.config_name + ' Playlists']
+ )
self._refresh()
def _import_playlist(self) -> None:
@@ -446,14 +538,16 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# Gotta be signed in for this to work.
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
- share.SharePlaylistImportWindow(origin_widget=self._import_button,
- on_success_callback=ba.WeakCall(
- self._on_playlist_import_success))
+ share.SharePlaylistImportWindow(
+ origin_widget=self._import_button,
+ on_success_callback=ba.WeakCall(self._on_playlist_import_success),
+ )
def _on_playlist_import_success(self) -> None:
self._refresh()
@@ -461,10 +555,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
def _on_share_playlist_response(self, name: str, response: Any) -> None:
# pylint: disable=cyclic-import
from bastd.ui.playlist import share
+
if response is None:
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
share.SharePlaylistResultsWindow(name, response)
@@ -472,21 +568,24 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
def _share_playlist(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
# Gotta be signed in for this to work.
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
if self._selected_playlist_name == '__default__':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.cantShareDefaultText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.cantShareDefaultText'),
+ color=(1, 0, 0),
+ )
return
if self._selected_playlist_name is None:
@@ -497,10 +596,12 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
'type': 'SHARE_PLAYLIST',
'expire_time': time.time() + 5,
'playlistType': self._pvars.config_name,
- 'playlistName': self._selected_playlist_name
+ 'playlistName': self._selected_playlist_name,
},
- callback=ba.WeakCall(self._on_share_playlist_response,
- self._selected_playlist_name))
+ callback=ba.WeakCall(
+ self._on_share_playlist_response, self._selected_playlist_name
+ ),
+ )
ba.internal.run_transactions()
ba.screenmessage(ba.Lstr(resource='sharingText'))
@@ -508,6 +609,7 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.confirm import ConfirmWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -517,23 +619,33 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
if self._selected_playlist_name == '__default__':
ba.playsound(ba.getsound('error'))
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantDeleteDefaultText'))
+ ba.Lstr(resource=self._r + '.cantDeleteDefaultText')
+ )
else:
ConfirmWindow(
- ba.Lstr(resource=self._r + '.deleteConfirmText',
- subs=[('${LIST}', self._selected_playlist_name)]),
- self._do_delete_playlist, 450, 150)
+ ba.Lstr(
+ resource=self._r + '.deleteConfirmText',
+ subs=[('${LIST}', self._selected_playlist_name)],
+ ),
+ self._do_delete_playlist,
+ 450,
+ 150,
+ )
def _get_playlist_display_name(self, playlist: str) -> ba.Lstr:
if playlist == '__default__':
return self._pvars.default_list_name
- return playlist if isinstance(playlist, ba.Lstr) else ba.Lstr(
- value=playlist)
+ return (
+ playlist
+ if isinstance(playlist, ba.Lstr)
+ else ba.Lstr(value=playlist)
+ )
def _duplicate_playlist(self) -> None:
# pylint: disable=too-many-branches
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -544,7 +656,8 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
plst = self._pvars.get_default_list_call()
else:
plst = ba.app.config[self._config_name_full].get(
- self._selected_playlist_name)
+ self._selected_playlist_name
+ )
if plst is None:
ba.playsound(ba.getsound('error'))
return
@@ -552,9 +665,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
# clamp at our max playlist number
if len(ba.app.config[self._config_name_full]) > self._max_playlists:
ba.screenmessage(
- ba.Lstr(translate=('serverResponses',
- 'Max number of playlists reached.')),
- color=(1, 0, 0))
+ ba.Lstr(
+ translate=(
+ 'serverResponses',
+ 'Max number of playlists reached.',
+ )
+ ),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -565,7 +683,8 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
test_index = 1
base_name = self._get_playlist_display_name(
- self._selected_playlist_name).evaluate()
+ self._selected_playlist_name
+ ).evaluate()
# If it looks like a copy, strip digits and spaces off the end.
if copy_word in base_name:
@@ -582,12 +701,14 @@ class PlaylistCustomizeBrowserWindow(ba.Window):
break
test_index += 1
- ba.internal.add_transaction({
- 'type': 'ADD_PLAYLIST',
- 'playlistType': self._pvars.config_name,
- 'playlistName': test_name,
- 'playlist': copy.deepcopy(plst)
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': self._pvars.config_name,
+ 'playlistName': test_name,
+ 'playlist': copy.deepcopy(plst),
+ }
+ )
ba.internal.run_transactions()
ba.playsound(ba.getsound('gunCocking'))
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/edit.py b/assets/src/ba_data/python/bastd/ui/playlist/edit.py
index 7e2040b5..ed9b319f 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/edit.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/edit.py
@@ -16,9 +16,11 @@ if TYPE_CHECKING:
class PlaylistEditWindow(ba.Window):
"""Window for editing an individual game playlist."""
- def __init__(self,
- editcontroller: PlaylistEditController,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ editcontroller: PlaylistEditController,
+ transition: str = 'in_right',
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
prev_selection: str | None
@@ -29,24 +31,40 @@ class PlaylistEditWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 770 if uiscale is ba.UIScale.SMALL else 670
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
- self._height = (400 if uiscale is ba.UIScale.SMALL else
- 470 if uiscale is ba.UIScale.MEDIUM else 540)
+ self._height = (
+ 400
+ if uiscale is ba.UIScale.SMALL
+ else 470
+ if uiscale is ba.UIScale.MEDIUM
+ else 540
+ )
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -16) if uiscale is ba.UIScale.SMALL else (0, 0)))
- cancel_button = ba.buttonwidget(parent=self._root_widget,
- position=(35 + x_inset,
- self._height - 60),
- scale=0.8,
- size=(175, 60),
- autoselect=True,
- label=ba.Lstr(resource='cancelText'),
- text_scale=1.2)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -16)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
+ cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35 + x_inset, self._height - 60),
+ scale=0.8,
+ size=(175, 60),
+ autoselect=True,
+ label=ba.Lstr(resource='cancelText'),
+ text_scale=1.2,
+ )
save_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(self._width - (195 + x_inset), self._height - 60),
@@ -55,40 +73,48 @@ class PlaylistEditWindow(ba.Window):
autoselect=True,
left_widget=cancel_button,
label=ba.Lstr(resource='saveText'),
- text_scale=1.2)
+ text_scale=1.2,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=btn,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
- ba.widget(edit=cancel_button,
- left_widget=cancel_button,
- right_widget=save_button)
+ ba.widget(
+ edit=cancel_button,
+ left_widget=cancel_button,
+ right_widget=save_button,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(-10, self._height - 50),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- scale=1.05,
- h_align='center',
- v_align='center',
- maxwidth=270)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(-10, self._height - 50),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ scale=1.05,
+ h_align='center',
+ v_align='center',
+ maxwidth=270,
+ )
v = self._height - 115.0
self._scroll_width = self._width - (205 + 2 * x_inset)
- ba.textwidget(parent=self._root_widget,
- text=ba.Lstr(resource=self._r + '.listNameText'),
- position=(196 + x_inset, v + 31),
- maxwidth=150,
- color=(0.8, 0.8, 0.8, 0.5),
- size=(0, 0),
- scale=0.75,
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ text=ba.Lstr(resource=self._r + '.listNameText'),
+ position=(196 + x_inset, v + 31),
+ maxwidth=150,
+ color=(0.8, 0.8, 0.8, 0.5),
+ size=(0, 0),
+ scale=0.75,
+ h_align='right',
+ v_align='center',
+ )
self._text_field = ba.textwidget(
parent=self._root_widget,
@@ -103,7 +129,8 @@ class PlaylistEditWindow(ba.Window):
description=ba.Lstr(resource=self._r + '.listNameText'),
editable=True,
padding=4,
- on_return_press_call=self._save_press_with_sound)
+ on_return_press_call=self._save_press_with_sound,
+ )
ba.widget(edit=cancel_button, down_widget=self._text_field)
self._list_widgets: list[ba.Widget] = []
@@ -117,8 +144,13 @@ class PlaylistEditWindow(ba.Window):
v -= 2.0
v += 63
- scl = (1.03 if uiscale is ba.UIScale.SMALL else
- 1.36 if uiscale is ba.UIScale.MEDIUM else 1.74)
+ scl = (
+ 1.03
+ if uiscale is ba.UIScale.SMALL
+ else 1.36
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.74
+ )
v -= 63.0 * scl
add_game_button = ba.buttonwidget(
@@ -132,7 +164,8 @@ class PlaylistEditWindow(ba.Window):
color=b_color,
textcolor=b_textcolor,
text_scale=0.8,
- label=ba.Lstr(resource=self._r + '.addGameText'))
+ label=ba.Lstr(resource=self._r + '.addGameText'),
+ )
ba.widget(edit=add_game_button, up_widget=self._text_field)
v -= 63.0 * scl
@@ -147,43 +180,49 @@ class PlaylistEditWindow(ba.Window):
color=b_color,
textcolor=b_textcolor,
text_scale=0.8,
- label=ba.Lstr(resource=self._r + '.editGameText'))
+ label=ba.Lstr(resource=self._r + '.editGameText'),
+ )
v -= 63.0 * scl
- remove_game_button = ba.buttonwidget(parent=self._root_widget,
- position=(h, v),
- size=(110, 61.0 * scl),
- text_scale=0.8,
- on_activate_call=self._remove,
- autoselect=True,
- button_type='square',
- color=b_color,
- textcolor=b_textcolor,
- label=ba.Lstr(resource=self._r +
- '.removeGameText'))
+ remove_game_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(110, 61.0 * scl),
+ text_scale=0.8,
+ on_activate_call=self._remove,
+ autoselect=True,
+ button_type='square',
+ color=b_color,
+ textcolor=b_textcolor,
+ label=ba.Lstr(resource=self._r + '.removeGameText'),
+ )
v -= 40
h += 9
- ba.buttonwidget(parent=self._root_widget,
- position=(h, v),
- size=(42, 35),
- on_activate_call=self._move_up,
- label=ba.charstr(ba.SpecialChar.UP_ARROW),
- button_type='square',
- color=b_color,
- textcolor=b_textcolor,
- autoselect=True,
- repeat=True)
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(42, 35),
+ on_activate_call=self._move_up,
+ label=ba.charstr(ba.SpecialChar.UP_ARROW),
+ button_type='square',
+ color=b_color,
+ textcolor=b_textcolor,
+ autoselect=True,
+ repeat=True,
+ )
h += 52
- ba.buttonwidget(parent=self._root_widget,
- position=(h, v),
- size=(42, 35),
- on_activate_call=self._move_down,
- autoselect=True,
- button_type='square',
- color=b_color,
- textcolor=b_textcolor,
- label=ba.charstr(ba.SpecialChar.DOWN_ARROW),
- repeat=True)
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(42, 35),
+ on_activate_call=self._move_down,
+ autoselect=True,
+ button_type='square',
+ color=b_color,
+ textcolor=b_textcolor,
+ label=ba.charstr(ba.SpecialChar.DOWN_ARROW),
+ repeat=True,
+ )
v = self._height - 100
scroll_height = self._height - 155
@@ -192,77 +231,98 @@ class PlaylistEditWindow(ba.Window):
position=(160 + x_inset, v - scroll_height),
highlight=False,
on_select_call=ba.Call(self._set_ui_selection, 'gameList'),
- size=(self._scroll_width, (scroll_height - 15)))
- ba.widget(edit=scrollwidget,
- left_widget=add_game_button,
- right_widget=scrollwidget)
- self._columnwidget = ba.columnwidget(parent=scrollwidget,
- border=2,
- margin=0)
+ size=(self._scroll_width, (scroll_height - 15)),
+ )
+ ba.widget(
+ edit=scrollwidget,
+ left_widget=add_game_button,
+ right_widget=scrollwidget,
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=scrollwidget, border=2, margin=0
+ )
ba.widget(edit=self._columnwidget, up_widget=self._text_field)
for button in [add_game_button, edit_game_button, remove_game_button]:
- ba.widget(edit=button,
- left_widget=button,
- right_widget=scrollwidget)
+ ba.widget(
+ edit=button, left_widget=button, right_widget=scrollwidget
+ )
self._refresh()
ba.buttonwidget(edit=cancel_button, on_activate_call=self._cancel)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=cancel_button,
- selected_child=scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=cancel_button,
+ selected_child=scrollwidget,
+ )
ba.buttonwidget(edit=save_button, on_activate_call=self._save_press)
ba.containerwidget(edit=self._root_widget, start_button=save_button)
if prev_selection == 'add_button':
- ba.containerwidget(edit=self._root_widget,
- selected_child=add_game_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=add_game_button
+ )
elif prev_selection == 'editButton':
- ba.containerwidget(edit=self._root_widget,
- selected_child=edit_game_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=edit_game_button
+ )
elif prev_selection == 'gameList':
- ba.containerwidget(edit=self._root_widget,
- selected_child=scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=scrollwidget
+ )
def _set_ui_selection(self, selection: str) -> None:
self._editcontroller.set_edit_ui_selection(selection)
def _cancel(self) -> None:
from bastd.ui.playlist.customizebrowser import (
- PlaylistCustomizeBrowserWindow)
+ PlaylistCustomizeBrowserWindow,
+ )
+
ba.playsound(ba.getsound('powerdown01'))
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
PlaylistCustomizeBrowserWindow(
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
- select_playlist=self._editcontroller.
- get_existing_playlist_name()).get_root_widget())
+ select_playlist=(
+ self._editcontroller.get_existing_playlist_name()
+ ),
+ ).get_root_widget()
+ )
def _add(self) -> None:
# Store list name then tell the session to perform an add.
self._editcontroller.setname(
- cast(str, ba.textwidget(query=self._text_field)))
+ cast(str, ba.textwidget(query=self._text_field))
+ )
self._editcontroller.add_game_pressed()
def _edit(self) -> None:
# Store list name then tell the session to perform an add.
self._editcontroller.setname(
- cast(str, ba.textwidget(query=self._text_field)))
+ cast(str, ba.textwidget(query=self._text_field))
+ )
self._editcontroller.edit_game_pressed()
def _save_press(self) -> None:
from bastd.ui.playlist.customizebrowser import (
- PlaylistCustomizeBrowserWindow)
+ PlaylistCustomizeBrowserWindow,
+ )
+
new_name = cast(str, ba.textwidget(query=self._text_field))
- if (new_name != self._editcontroller.get_existing_playlist_name()
- and new_name
- in ba.app.config[self._editcontroller.get_config_name() +
- ' Playlists']):
+ if (
+ new_name != self._editcontroller.get_existing_playlist_name()
+ and new_name
+ in ba.app.config[
+ self._editcontroller.get_config_name() + ' Playlists'
+ ]
+ ):
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText'))
+ ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
+ )
ba.playsound(ba.getsound('error'))
return
if not new_name:
@@ -270,7 +330,8 @@ class PlaylistEditWindow(ba.Window):
return
if not self._editcontroller.get_playlist():
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantSaveEmptyListText'))
+ ba.Lstr(resource=self._r + '.cantSaveEmptyListText')
+ )
ba.playsound(ba.getsound('error'))
return
@@ -278,27 +339,31 @@ class PlaylistEditWindow(ba.Window):
# using its exact name to avoid confusion.
if new_name == self._editcontroller.get_default_list_name().evaluate():
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantOverwriteDefaultText'))
+ ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')
+ )
ba.playsound(ba.getsound('error'))
return
# If we had an old one, delete it.
if self._editcontroller.get_existing_playlist_name() is not None:
- ba.internal.add_transaction({
- 'type':
- 'REMOVE_PLAYLIST',
- 'playlistType':
- self._editcontroller.get_config_name(),
- 'playlistName':
- self._editcontroller.get_existing_playlist_name()
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'REMOVE_PLAYLIST',
+ 'playlistType': self._editcontroller.get_config_name(),
+ 'playlistName': (
+ self._editcontroller.get_existing_playlist_name()
+ ),
+ }
+ )
- ba.internal.add_transaction({
- 'type': 'ADD_PLAYLIST',
- 'playlistType': self._editcontroller.get_config_name(),
- 'playlistName': new_name,
- 'playlist': self._editcontroller.get_playlist()
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYLIST',
+ 'playlistType': self._editcontroller.get_config_name(),
+ 'playlistName': new_name,
+ 'playlist': self._editcontroller.get_playlist(),
+ }
+ )
ba.internal.run_transactions()
ba.containerwidget(edit=self._root_widget, transition='out_right')
@@ -307,7 +372,9 @@ class PlaylistEditWindow(ba.Window):
PlaylistCustomizeBrowserWindow(
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
- select_playlist=new_name).get_root_widget())
+ select_playlist=new_name,
+ ).get_root_widget()
+ )
def _save_press_with_sound(self) -> None:
ba.playsound(ba.getsound('swish'))
@@ -334,17 +401,19 @@ class PlaylistEditWindow(ba.Window):
ba.print_exception()
desc = "(invalid: '" + pentry['type'] + "')"
- txtw = ba.textwidget(parent=self._columnwidget,
- size=(self._width - 80, 30),
- on_select_call=ba.Call(self._select, index),
- always_highlight=True,
- color=(0.8, 0.8, 0.8, 1.0),
- padding=0,
- maxwidth=self._scroll_width * 0.93,
- text=desc,
- on_activate_call=self._edit_button.activate,
- v_align='center',
- selectable=True)
+ txtw = ba.textwidget(
+ parent=self._columnwidget,
+ size=(self._width - 80, 30),
+ on_select_call=ba.Call(self._select, index),
+ always_highlight=True,
+ color=(0.8, 0.8, 0.8, 1.0),
+ padding=0,
+ maxwidth=self._scroll_width * 0.93,
+ text=desc,
+ on_activate_call=self._edit_button.activate,
+ v_align='center',
+ selectable=True,
+ )
ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50)
# Wanna be able to jump up to the text field from the top one.
@@ -352,9 +421,11 @@ class PlaylistEditWindow(ba.Window):
ba.widget(edit=txtw, up_widget=self._text_field)
self._list_widgets.append(txtw)
if old_selection_index == index:
- ba.columnwidget(edit=self._columnwidget,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._columnwidget,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
def _move_down(self) -> None:
playlist = self._editcontroller.get_playlist()
@@ -375,7 +446,7 @@ class PlaylistEditWindow(ba.Window):
if index < 1:
return
tmp = playlist[index]
- playlist[index] = (playlist[index - 1])
+ playlist[index] = playlist[index - 1]
playlist[index - 1] = tmp
index -= 1
self._editcontroller.set_playlist(playlist)
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/editcontroller.py b/assets/src/ba_data/python/bastd/ui/playlist/editcontroller.py
index 664f6499..9f978a36 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/editcontroller.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/editcontroller.py
@@ -16,12 +16,14 @@ if TYPE_CHECKING:
class PlaylistEditController:
"""Coordinates various UIs involved in playlist editing."""
- def __init__(self,
- sessiontype: type[ba.Session],
- existing_playlist_name: str | None = None,
- transition: str = 'in_right',
- playlist: list[dict[str, Any]] | None = None,
- playlist_name: str | None = None):
+ def __init__(
+ self,
+ sessiontype: type[ba.Session],
+ existing_playlist_name: str | None = None,
+ transition: str = 'in_right',
+ playlist: list[dict[str, Any]] | None = None,
+ playlist_name: str | None = None,
+ ):
from ba.internal import preload_map_preview_media, filter_playlist
from bastd.ui.playlist import PlaylistTypeVars
from bastd.ui.playlist.edit import PlaylistEditWindow
@@ -49,11 +51,13 @@ class PlaylistEditController:
# Filter out invalid games.
self._playlist = filter_playlist(
- appconfig[self._pvars.config_name +
- ' Playlists'][existing_playlist_name],
+ appconfig[self._pvars.config_name + ' Playlists'][
+ existing_playlist_name
+ ],
sessiontype=sessiontype,
remove_unowned=False,
- name=existing_playlist_name)
+ name=existing_playlist_name,
+ )
self._edit_ui_selection = None
else:
if playlist is not None:
@@ -68,10 +72,13 @@ class PlaylistEditController:
i = 1
while True:
self._name = (
- self._pvars.default_new_list_name.evaluate() +
- ((' ' + str(i)) if i > 1 else ''))
- if self._name not in appconfig[self._pvars.config_name +
- ' Playlists']:
+ self._pvars.default_new_list_name.evaluate()
+ + ((' ' + str(i)) if i > 1 else '')
+ )
+ if (
+ self._name
+ not in appconfig[self._pvars.config_name + ' Playlists']
+ ):
break
i += 1
@@ -80,8 +87,10 @@ class PlaylistEditController:
self._edit_ui_selection = 'add_button'
ba.app.ui.set_main_menu_window(
- PlaylistEditWindow(editcontroller=self,
- transition=transition).get_root_widget())
+ PlaylistEditWindow(
+ editcontroller=self, transition=transition
+ ).get_root_widget()
+ )
def get_config_name(self) -> str:
"""(internal)"""
@@ -134,35 +143,46 @@ class PlaylistEditController:
def add_game_pressed(self) -> None:
"""(internal)"""
from bastd.ui.playlist.addgame import PlaylistAddGameWindow
+
ba.app.ui.clear_main_menu_window(transition='out_left')
ba.app.ui.set_main_menu_window(
- PlaylistAddGameWindow(editcontroller=self).get_root_widget())
+ PlaylistAddGameWindow(editcontroller=self).get_root_widget()
+ )
def edit_game_pressed(self) -> None:
"""Should be called by supplemental UIs when a game is to be edited."""
from ba.internal import getclass
+
if not self._playlist:
return
- self._show_edit_ui(gametype=getclass(
- self._playlist[self._selected_index]['type'],
- subclassof=ba.GameActivity),
- settings=self._playlist[self._selected_index])
+ self._show_edit_ui(
+ gametype=getclass(
+ self._playlist[self._selected_index]['type'],
+ subclassof=ba.GameActivity,
+ ),
+ settings=self._playlist[self._selected_index],
+ )
def add_game_cancelled(self) -> None:
"""(internal)"""
from bastd.ui.playlist.edit import PlaylistEditWindow
+
ba.app.ui.clear_main_menu_window(transition='out_right')
ba.app.ui.set_main_menu_window(
- PlaylistEditWindow(editcontroller=self,
- transition='in_left').get_root_widget())
+ PlaylistEditWindow(
+ editcontroller=self, transition='in_left'
+ ).get_root_widget()
+ )
- def _show_edit_ui(self, gametype: type[ba.GameActivity],
- settings: dict[str, Any] | None) -> None:
- self._editing_game = (settings is not None)
+ def _show_edit_ui(
+ self, gametype: type[ba.GameActivity], settings: dict[str, Any] | None
+ ) -> None:
+ self._editing_game = settings is not None
self._editing_game_type = gametype
assert self._sessiontype is not None
- gametype.create_settings_ui(self._sessiontype, copy.deepcopy(settings),
- self._edit_game_done)
+ gametype.create_settings_ui(
+ self._sessiontype, copy.deepcopy(settings), self._edit_game_done
+ )
def add_game_type_selected(self, gametype: type[ba.GameActivity]) -> None:
"""(internal)"""
@@ -172,22 +192,26 @@ class PlaylistEditController:
from bastd.ui.playlist.edit import PlaylistEditWindow
from bastd.ui.playlist.addgame import PlaylistAddGameWindow
from ba.internal import get_type_name
+
if config is None:
# If we were editing, go back to our list.
if self._editing_game:
ba.playsound(ba.getsound('powerdown01'))
ba.app.ui.clear_main_menu_window(transition='out_right')
ba.app.ui.set_main_menu_window(
- PlaylistEditWindow(editcontroller=self,
- transition='in_left').get_root_widget())
+ PlaylistEditWindow(
+ editcontroller=self, transition='in_left'
+ ).get_root_widget()
+ )
# Otherwise we were adding; go back to the add type choice list.
else:
ba.app.ui.clear_main_menu_window(transition='out_right')
ba.app.ui.set_main_menu_window(
PlaylistAddGameWindow(
- editcontroller=self,
- transition='in_left').get_root_widget())
+ editcontroller=self, transition='in_left'
+ ).get_root_widget()
+ )
else:
# Make sure type is in there.
assert self._editing_game_type is not None
@@ -197,13 +221,16 @@ class PlaylistEditController:
self._playlist[self._selected_index] = copy.deepcopy(config)
else:
# Add a new entry to the playlist.
- insert_index = min(len(self._playlist),
- self._selected_index + 1)
+ insert_index = min(
+ len(self._playlist), self._selected_index + 1
+ )
self._playlist.insert(insert_index, copy.deepcopy(config))
self._selected_index = insert_index
ba.playsound(ba.getsound('gunCocking'))
ba.app.ui.clear_main_menu_window(transition='out_right')
ba.app.ui.set_main_menu_window(
- PlaylistEditWindow(editcontroller=self,
- transition='in_left').get_root_widget())
+ PlaylistEditWindow(
+ editcontroller=self, transition='in_left'
+ ).get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/editgame.py b/assets/src/ba_data/python/bastd/ui/playlist/editgame.py
index ece7304b..ee3a2cd5 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/editgame.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/editgame.py
@@ -18,19 +18,26 @@ if TYPE_CHECKING:
class PlaylistEditGameWindow(ba.Window):
"""Window for editing a game config."""
- def __init__(self,
- gametype: type[ba.GameActivity],
- sessiontype: type[ba.Session],
- config: dict[str, Any] | None,
- completion_call: Callable[[dict[str, Any] | None], Any],
- default_selection: str | None = None,
- transition: str = 'in_right',
- edit_info: dict[str, Any] | None = None):
+ def __init__(
+ self,
+ gametype: type[ba.GameActivity],
+ sessiontype: type[ba.Session],
+ config: dict[str, Any] | None,
+ completion_call: Callable[[dict[str, Any] | None], Any],
+ default_selection: str | None = None,
+ transition: str = 'in_right',
+ edit_info: dict[str, Any] | None = None,
+ ):
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
- from ba.internal import (get_unowned_maps, get_filtered_map_name,
- get_map_class, get_map_display_string)
+ from ba.internal import (
+ get_unowned_maps,
+ get_filtered_map_name,
+ get_map_class,
+ get_map_display_string,
+ )
+
self._gametype = gametype
self._sessiontype = sessiontype
@@ -68,14 +75,18 @@ class PlaylistEditGameWindow(ba.Window):
else:
self._map = valid_maps[random.randrange(len(valid_maps))]
- is_add = (self._edit_info['editType'] == 'add')
+ is_add = self._edit_info['editType'] == 'add'
# If there's a valid map name in the existing config, use that.
try:
- if (config is not None and 'settings' in config
- and 'map' in config['settings']):
+ if (
+ config is not None
+ and 'settings' in config
+ and 'map' in config['settings']
+ ):
filtered_map_name = get_filtered_map_name(
- config['settings']['map'])
+ config['settings']['map']
+ )
if filtered_map_name in valid_maps:
self._map = filtered_map_name
except Exception:
@@ -91,36 +102,53 @@ class PlaylistEditGameWindow(ba.Window):
uiscale = ba.app.ui.uiscale
width = 720 if uiscale is ba.UIScale.SMALL else 620
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
- height = (365 if uiscale is ba.UIScale.SMALL else
- 460 if uiscale is ba.UIScale.MEDIUM else 550)
+ height = (
+ 365
+ if uiscale is ba.UIScale.SMALL
+ else 460
+ if uiscale is ba.UIScale.MEDIUM
+ else 550
+ )
spacing = 52
y_extra = 15
y_extra2 = 21
- map_tex_name = (get_map_class(self._map).get_preview_texture_name())
+ map_tex_name = get_map_class(self._map).get_preview_texture_name()
if map_tex_name is None:
raise Exception('no map preview tex found for' + self._map)
map_tex = ba.gettexture(map_tex_name)
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height + top_extra),
- transition=transition,
- scale=(2.19 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -17) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height + top_extra),
+ transition=transition,
+ scale=(
+ 2.19
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -17)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
btn = ba.buttonwidget(
parent=self._root_widget,
position=(45 + x_inset, height - 82 + y_extra2),
size=(180, 70) if is_add else (180, 65),
- label=ba.Lstr(resource='backText') if is_add else ba.Lstr(
- resource='cancelText'),
+ label=ba.Lstr(resource='backText')
+ if is_add
+ else ba.Lstr(resource='cancelText'),
button_type='back' if is_add else None,
autoselect=True,
scale=0.75,
text_scale=1.3,
- on_activate_call=ba.Call(self._cancel))
+ on_activate_call=ba.Call(self._cancel),
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
add_button = ba.buttonwidget(
@@ -129,23 +157,26 @@ class PlaylistEditGameWindow(ba.Window):
size=(200, 65),
scale=0.75,
text_scale=1.3,
- label=ba.Lstr(resource=self._r +
- '.addGameText') if is_add else ba.Lstr(
- resource='doneText'))
+ label=ba.Lstr(resource=self._r + '.addGameText')
+ if is_add
+ else ba.Lstr(resource='doneText'),
+ )
if ba.app.ui.use_toolbars:
pbtn = ba.internal.get_special_widget('party_button')
ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn)
- ba.textwidget(parent=self._root_widget,
- position=(-8, height - 70 + y_extra2),
- size=(width, 25),
- text=gametype.get_display_string(),
- color=ba.app.ui.title_color,
- maxwidth=235,
- scale=1.1,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(-8, height - 70 + y_extra2),
+ size=(width, 25),
+ text=gametype.get_display_string(),
+ color=ba.app.ui.title_color,
+ maxwidth=235,
+ scale=1.1,
+ h_align='center',
+ v_align='center',
+ )
map_height = 100
@@ -155,21 +186,23 @@ class PlaylistEditGameWindow(ba.Window):
scroll_height += spacing * len(self._settings_defs)
scroll_width = width - (86 + 2 * x_inset)
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(44 + x_inset,
- 35 + y_extra),
- size=(scroll_width, height - 116),
- highlight=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(scroll_width,
- scroll_height),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(44 + x_inset, 35 + y_extra),
+ size=(scroll_width, height - 116),
+ highlight=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(scroll_width, scroll_height),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
v = scroll_height - 5
h = -40
@@ -179,14 +212,16 @@ class PlaylistEditGameWindow(ba.Window):
widget_column: list[list[ba.Widget]] = []
# Map select button.
- ba.textwidget(parent=self._subcontainer,
- position=(h + 49, v - 63),
- size=(100, 30),
- maxwidth=110,
- text=ba.Lstr(resource='mapText'),
- h_align='left',
- color=(0.8, 0.8, 0.8, 1.0),
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 49, v - 63),
+ size=(100, 30),
+ maxwidth=110,
+ text=ba.Lstr(resource='mapText'),
+ h_align='left',
+ color=(0.8, 0.8, 0.8, 1.0),
+ v_align='center',
+ )
ba.imagewidget(
parent=self._subcontainer,
@@ -195,27 +230,31 @@ class PlaylistEditGameWindow(ba.Window):
texture=map_tex,
model_opaque=ba.getmodel('level_select_button_opaque'),
model_transparent=ba.getmodel('level_select_button_transparent'),
- mask_texture=ba.gettexture('mapPreviewMask'))
+ mask_texture=ba.gettexture('mapPreviewMask'),
+ )
map_button = btn = ba.buttonwidget(
parent=self._subcontainer,
size=(140, 60),
position=(h + 448, v - 72),
on_activate_call=ba.Call(self._select_map),
scale=0.7,
- label=ba.Lstr(resource='mapSelectText'))
+ label=ba.Lstr(resource='mapSelectText'),
+ )
widget_column.append([btn])
- ba.textwidget(parent=self._subcontainer,
- position=(h + 363 - 123, v - 114),
- size=(100, 30),
- flatness=1.0,
- shadow=1.0,
- scale=0.55,
- maxwidth=256 * 0.7 * 0.8,
- text=get_map_display_string(self._map),
- h_align='center',
- color=(0.6, 1.0, 0.6, 1.0),
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 363 - 123, v - 114),
+ size=(100, 30),
+ flatness=1.0,
+ shadow=1.0,
+ scale=0.55,
+ maxwidth=256 * 0.7 * 0.8,
+ text=get_map_display_string(self._map),
+ h_align='center',
+ color=(0.6, 1.0, 0.6, 1.0),
+ v_align='center',
+ )
v -= map_height
for setting in self._settings_defs:
@@ -225,8 +264,11 @@ class PlaylistEditGameWindow(ba.Window):
# Now, if there's an existing value for it in the config,
# override with that.
try:
- if (config is not None and 'settings' in config
- and setting.name in config['settings']):
+ if (
+ config is not None
+ and 'settings' in config
+ and setting.name in config['settings']
+ ):
value = value_type(config['settings'][setting.name])
except Exception:
ba.print_exception()
@@ -244,20 +286,26 @@ class PlaylistEditGameWindow(ba.Window):
for choice in setting.choices:
if len(choice) != 2:
raise ValueError(
- "Expected 2-member tuples for 'choices'; got: " +
- repr(choice))
+ "Expected 2-member tuples for 'choices'; got: "
+ + repr(choice)
+ )
if not isinstance(choice[0], str):
raise TypeError(
'First value for choice tuple must be a str; got: '
- + repr(choice))
+ + repr(choice)
+ )
if not isinstance(choice[1], value_type):
raise TypeError(
'Choice type does not match default value; choice:'
- + repr(choice) + '; setting:' + repr(setting))
+ + repr(choice)
+ + '; setting:'
+ + repr(setting)
+ )
if value_type not in (int, float):
raise TypeError(
'Choice type setting must have int or float default; '
- 'got: ' + repr(setting))
+ 'got: ' + repr(setting)
+ )
# Start at the choice corresponding to the default if possible.
self._choice_selections[setting.name] = 0
@@ -267,44 +315,54 @@ class PlaylistEditGameWindow(ba.Window):
break
v -= spacing
- ba.textwidget(parent=self._subcontainer,
- position=(h + 50, v),
- size=(100, 30),
- maxwidth=mw1,
- text=name_translated,
- h_align='left',
- color=(0.8, 0.8, 0.8, 1.0),
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 50, v),
+ size=(100, 30),
+ maxwidth=mw1,
+ text=name_translated,
+ h_align='left',
+ color=(0.8, 0.8, 0.8, 1.0),
+ v_align='center',
+ )
txt = ba.textwidget(
parent=self._subcontainer,
position=(h + 509 - 95, v),
size=(0, 28),
- text=self._get_localized_setting_name(setting.choices[
- self._choice_selections[setting.name]][0]),
+ text=self._get_localized_setting_name(
+ setting.choices[self._choice_selections[setting.name]][
+ 0
+ ]
+ ),
editable=False,
color=(0.6, 1.0, 0.6, 1.0),
maxwidth=mw2,
h_align='right',
v_align='center',
- padding=2)
- btn1 = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 509 - 50 - 1, v),
- size=(28, 28),
- label='<',
- autoselect=True,
- on_activate_call=ba.Call(
- self._choice_inc, setting.name, txt,
- setting, -1),
- repeat=True)
- btn2 = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 509 + 5, v),
- size=(28, 28),
- label='>',
- autoselect=True,
- on_activate_call=ba.Call(
- self._choice_inc, setting.name, txt,
- setting, 1),
- repeat=True)
+ padding=2,
+ )
+ btn1 = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 509 - 50 - 1, v),
+ size=(28, 28),
+ label='<',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._choice_inc, setting.name, txt, setting, -1
+ ),
+ repeat=True,
+ )
+ btn2 = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 509 + 5, v),
+ size=(28, 28),
+ label='>',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._choice_inc, setting.name, txt, setting, 1
+ ),
+ repeat=True,
+ )
widget_column.append([btn1, btn2])
elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)):
@@ -312,78 +370,102 @@ class PlaylistEditGameWindow(ba.Window):
min_value = setting.min_value
max_value = setting.max_value
increment = setting.increment
- ba.textwidget(parent=self._subcontainer,
- position=(h + 50, v),
- size=(100, 30),
- text=name_translated,
- h_align='left',
- color=(0.8, 0.8, 0.8, 1.0),
- v_align='center',
- maxwidth=mw1)
- txt = ba.textwidget(parent=self._subcontainer,
- position=(h + 509 - 95, v),
- size=(0, 28),
- text=str(value),
- editable=False,
- color=(0.6, 1.0, 0.6, 1.0),
- maxwidth=mw2,
- h_align='right',
- v_align='center',
- padding=2)
- btn1 = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 509 - 50 - 1, v),
- size=(28, 28),
- label='-',
- autoselect=True,
- on_activate_call=ba.Call(
- self._inc, txt, min_value,
- max_value, -increment, value_type,
- setting.name),
- repeat=True)
- btn2 = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 509 + 5, v),
- size=(28, 28),
- label='+',
- autoselect=True,
- on_activate_call=ba.Call(
- self._inc, txt, min_value,
- max_value, increment, value_type,
- setting.name),
- repeat=True)
- widget_column.append([btn1, btn2])
-
- elif value_type == bool:
- v -= spacing
- ba.textwidget(parent=self._subcontainer,
- position=(h + 50, v),
- size=(100, 30),
- text=name_translated,
- h_align='left',
- color=(0.8, 0.8, 0.8, 1.0),
- v_align='center',
- maxwidth=mw1)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 50, v),
+ size=(100, 30),
+ text=name_translated,
+ h_align='left',
+ color=(0.8, 0.8, 0.8, 1.0),
+ v_align='center',
+ maxwidth=mw1,
+ )
txt = ba.textwidget(
parent=self._subcontainer,
position=(h + 509 - 95, v),
size=(0, 28),
- text=ba.Lstr(resource='onText') if value else ba.Lstr(
- resource='offText'),
+ text=str(value),
editable=False,
color=(0.6, 1.0, 0.6, 1.0),
maxwidth=mw2,
h_align='right',
v_align='center',
- padding=2)
- cbw = ba.checkboxwidget(parent=self._subcontainer,
- text='',
- position=(h + 505 - 50 - 5, v - 2),
- size=(200, 30),
- autoselect=True,
- textcolor=(0.8, 0.8, 0.8),
- value=value,
- on_value_change_call=ba.Call(
- self._check_value_change,
- setting.name, txt))
+ padding=2,
+ )
+ btn1 = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 509 - 50 - 1, v),
+ size=(28, 28),
+ label='-',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._inc,
+ txt,
+ min_value,
+ max_value,
+ -increment,
+ value_type,
+ setting.name,
+ ),
+ repeat=True,
+ )
+ btn2 = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 509 + 5, v),
+ size=(28, 28),
+ label='+',
+ autoselect=True,
+ on_activate_call=ba.Call(
+ self._inc,
+ txt,
+ min_value,
+ max_value,
+ increment,
+ value_type,
+ setting.name,
+ ),
+ repeat=True,
+ )
+ widget_column.append([btn1, btn2])
+
+ elif value_type == bool:
+ v -= spacing
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 50, v),
+ size=(100, 30),
+ text=name_translated,
+ h_align='left',
+ color=(0.8, 0.8, 0.8, 1.0),
+ v_align='center',
+ maxwidth=mw1,
+ )
+ txt = ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 509 - 95, v),
+ size=(0, 28),
+ text=ba.Lstr(resource='onText')
+ if value
+ else ba.Lstr(resource='offText'),
+ editable=False,
+ color=(0.6, 1.0, 0.6, 1.0),
+ maxwidth=mw2,
+ h_align='right',
+ v_align='center',
+ padding=2,
+ )
+ cbw = ba.checkboxwidget(
+ parent=self._subcontainer,
+ text='',
+ position=(h + 505 - 50 - 5, v - 2),
+ size=(200, 30),
+ autoselect=True,
+ textcolor=(0.8, 0.8, 0.8),
+ value=value,
+ on_value_change_call=ba.Call(
+ self._check_value_change, setting.name, txt
+ ),
+ )
widget_column.append([cbw])
else:
@@ -405,18 +487,23 @@ class PlaylistEditGameWindow(ba.Window):
prev_widgets = cwdg
except Exception:
ba.print_exception(
- 'Error wiring up game-settings-select widget column.')
+ 'Error wiring up game-settings-select widget column.'
+ )
ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add))
- ba.containerwidget(edit=self._root_widget,
- selected_child=add_button,
- start_button=add_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ selected_child=add_button,
+ start_button=add_button,
+ )
if default_selection == 'map':
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
- ba.containerwidget(edit=self._subcontainer,
- selected_child=map_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
+ ba.containerwidget(
+ edit=self._subcontainer, selected_child=map_button
+ )
def _get_localized_setting_name(self, name: str) -> ba.Lstr:
return ba.Lstr(translate=('settingNames', name))
@@ -428,34 +515,53 @@ class PlaylistEditGameWindow(ba.Window):
# Replace ourself with the map-select UI.
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- PlaylistMapSelectWindow(self._gametype, self._sessiontype,
- copy.deepcopy(self._getconfig()),
- self._edit_info,
- self._completion_call).get_root_widget())
+ PlaylistMapSelectWindow(
+ self._gametype,
+ self._sessiontype,
+ copy.deepcopy(self._getconfig()),
+ self._edit_info,
+ self._completion_call,
+ ).get_root_widget()
+ )
- def _choice_inc(self, setting_name: str, widget: ba.Widget,
- setting: ba.ChoiceSetting, increment: int) -> None:
+ def _choice_inc(
+ self,
+ setting_name: str,
+ widget: ba.Widget,
+ setting: ba.ChoiceSetting,
+ increment: int,
+ ) -> None:
choices = setting.choices
if increment > 0:
self._choice_selections[setting_name] = min(
- len(choices) - 1, self._choice_selections[setting_name] + 1)
+ len(choices) - 1, self._choice_selections[setting_name] + 1
+ )
else:
self._choice_selections[setting_name] = max(
- 0, self._choice_selections[setting_name] - 1)
- ba.textwidget(edit=widget,
- text=self._get_localized_setting_name(
- choices[self._choice_selections[setting_name]][0]))
+ 0, self._choice_selections[setting_name] - 1
+ )
+ ba.textwidget(
+ edit=widget,
+ text=self._get_localized_setting_name(
+ choices[self._choice_selections[setting_name]][0]
+ ),
+ )
self._settings[setting_name] = choices[
- self._choice_selections[setting_name]][1]
+ self._choice_selections[setting_name]
+ ][1]
def _cancel(self) -> None:
self._completion_call(None)
- def _check_value_change(self, setting_name: str, widget: ba.Widget,
- value: int) -> None:
- ba.textwidget(edit=widget,
- text=ba.Lstr(resource='onText') if value else ba.Lstr(
- resource='offText'))
+ def _check_value_change(
+ self, setting_name: str, widget: ba.Widget, value: int
+ ) -> None:
+ ba.textwidget(
+ edit=widget,
+ text=ba.Lstr(resource='onText')
+ if value
+ else ba.Lstr(resource='offText'),
+ )
self._settings[setting_name] = value
def _getconfig(self) -> dict[str, Any]:
@@ -466,9 +572,15 @@ class PlaylistEditGameWindow(ba.Window):
def _add(self) -> None:
self._completion_call(copy.deepcopy(self._getconfig()))
- def _inc(self, ctrl: ba.Widget, min_val: int | float, max_val: int | float,
- increment: int | float, setting_type: type,
- setting_name: str) -> None:
+ def _inc(
+ self,
+ ctrl: ba.Widget,
+ min_val: int | float,
+ max_val: int | float,
+ increment: int | float,
+ setting_type: type,
+ setting_name: str,
+ ) -> None:
if setting_type == float:
val = float(cast(str, ba.textwidget(query=ctrl)))
else:
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py b/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py
index 7e23d6a6..5ced52af 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py
@@ -17,14 +17,17 @@ if TYPE_CHECKING:
class PlaylistMapSelectWindow(ba.Window):
"""Window to select a map."""
- def __init__(self,
- gametype: type[ba.GameActivity],
- sessiontype: type[ba.Session],
- config: dict[str, Any],
- edit_info: dict[str, Any],
- completion_call: Callable[[dict[str, Any] | None], Any],
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ gametype: type[ba.GameActivity],
+ sessiontype: type[ba.Session],
+ config: dict[str, Any],
+ edit_info: dict[str, Any],
+ completion_call: Callable[[dict[str, Any] | None], Any],
+ transition: str = 'in_right',
+ ):
from ba.internal import get_filtered_map_name
+
self._gametype = gametype
self._sessiontype = sessiontype
self._config = config
@@ -33,23 +36,39 @@ class PlaylistMapSelectWindow(ba.Window):
self._maps: list[tuple[str, ba.Texture]] = []
try:
self._previous_map = get_filtered_map_name(
- config['settings']['map'])
+ config['settings']['map']
+ )
except Exception:
self._previous_map = ''
uiscale = ba.app.ui.uiscale
width = 715 if uiscale is ba.UIScale.SMALL else 615
x_inset = 50 if uiscale is ba.UIScale.SMALL else 0
- height = (400 if uiscale is ba.UIScale.SMALL else
- 480 if uiscale is ba.UIScale.MEDIUM else 600)
+ height = (
+ 400
+ if uiscale is ba.UIScale.SMALL
+ else 480
+ if uiscale is ba.UIScale.MEDIUM
+ else 600
+ )
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height + top_extra),
- transition=transition,
- scale=(2.17 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -27) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height + top_extra),
+ transition=transition,
+ scale=(
+ 2.17
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -27)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._cancel_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -59,21 +78,24 @@ class PlaylistMapSelectWindow(ba.Window):
text_scale=1.0,
autoselect=True,
label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._cancel)
+ on_activate_call=self._cancel,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height - 46),
- size=(0, 0),
- maxwidth=260,
- scale=1.1,
- text=ba.Lstr(resource='mapSelectTitleText',
- subs=[('${GAME}',
- self._gametype.get_display_string())
- ]),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height - 46),
+ size=(0, 0),
+ maxwidth=260,
+ scale=1.1,
+ text=ba.Lstr(
+ resource='mapSelectTitleText',
+ subs=[('${GAME}', self._gametype.get_display_string())],
+ ),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ )
v = height - 70
self._scroll_width = width - (80 + 2 * x_inset)
self._scroll_height = height - 140
@@ -81,9 +103,11 @@ class PlaylistMapSelectWindow(ba.Window):
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
position=(40 + x_inset, v - self._scroll_height),
- size=(self._scroll_width, self._scroll_height))
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ size=(self._scroll_width, self._scroll_height),
+ )
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._subcontainer: ba.Widget | None = None
@@ -93,8 +117,11 @@ class PlaylistMapSelectWindow(ba.Window):
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
- from ba.internal import (get_unowned_maps, get_map_class,
- get_map_display_string)
+ from ba.internal import (
+ get_unowned_maps,
+ get_map_class,
+ get_map_display_string,
+ )
# Kill old.
if self._subcontainer is not None:
@@ -114,7 +141,7 @@ class PlaylistMapSelectWindow(ba.Window):
# Disallow ones we don't own.
if mapname in unowned_maps:
continue
- map_tex_name = (get_map_class(mapname).get_preview_texture_name())
+ map_tex_name = get_map_class(mapname).get_preview_texture_name()
if map_tex_name is not None:
try:
map_tex = ba.gettexture(map_tex_name)
@@ -132,60 +159,75 @@ class PlaylistMapSelectWindow(ba.Window):
button_buffer_h = 16
button_buffer_v = 19
self._sub_width = self._scroll_width * 0.95
- self._sub_height = 5 + rows * (button_height +
- 2 * button_buffer_v) + 100
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._sub_height = (
+ 5 + rows * (button_height + 2 * button_buffer_v) + 100
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
index = 0
mask_texture = ba.gettexture('mapPreviewMask')
h_offs = 130 if len(self._maps) == 1 else 0
for y in range(rows):
for x in range(columns):
- pos = (x * (button_width + 2 * button_buffer_h) +
- button_buffer_h + h_offs, self._sub_height - (y + 1) *
- (button_height + 2 * button_buffer_v) + 12)
- btn = ba.buttonwidget(parent=self._subcontainer,
- button_type='square',
- size=(button_width, button_height),
- autoselect=True,
- texture=self._maps[index][1],
- mask_texture=mask_texture,
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- label='',
- color=(1, 1, 1),
- on_activate_call=ba.Call(
- self._select_with_delay,
- self._maps[index][0]),
- position=pos)
+ pos = (
+ x * (button_width + 2 * button_buffer_h)
+ + button_buffer_h
+ + h_offs,
+ self._sub_height
+ - (y + 1) * (button_height + 2 * button_buffer_v)
+ + 12,
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ button_type='square',
+ size=(button_width, button_height),
+ autoselect=True,
+ texture=self._maps[index][1],
+ mask_texture=mask_texture,
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ label='',
+ color=(1, 1, 1),
+ on_activate_call=ba.Call(
+ self._select_with_delay, self._maps[index][0]
+ ),
+ position=pos,
+ )
if x == 0:
ba.widget(edit=btn, left_widget=self._cancel_button)
if y == 0:
ba.widget(edit=btn, up_widget=self._cancel_button)
if x == columns - 1 and ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget(
+ 'party_button'
+ ),
+ )
ba.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
if self._maps[index][0] == self._previous_map:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=btn,
+ visible_child=btn,
+ )
name = get_map_display_string(self._maps[index][0])
- ba.textwidget(parent=self._subcontainer,
- text=name,
- position=(pos[0] + button_width * 0.5,
- pos[1] - 12),
- size=(0, 0),
- scale=0.5,
- maxwidth=button_width,
- draw_controller=btn,
- h_align='center',
- v_align='center',
- color=(0.8, 0.8, 0.8, 0.8))
+ ba.textwidget(
+ parent=self._subcontainer,
+ text=name,
+ position=(pos[0] + button_width * 0.5, pos[1] - 12),
+ size=(0, 0),
+ scale=0.5,
+ maxwidth=button_width,
+ draw_controller=btn,
+ h_align='center',
+ v_align='center',
+ color=(0.8, 0.8, 0.8, 0.8),
+ )
index += 1
if index >= count:
@@ -200,29 +242,34 @@ class PlaylistMapSelectWindow(ba.Window):
on_activate_call=self._on_store_press,
color=(0.6, 0.53, 0.63),
textcolor=(0.75, 0.7, 0.8),
- autoselect=True)
+ autoselect=True,
+ )
ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30)
if select_get_more_maps_button:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=btn,
- visible_child=btn)
+ ba.containerwidget(
+ edit=self._subcontainer, selected_child=btn, visible_child=btn
+ )
def _on_store_press(self) -> None:
from bastd.ui import account
from bastd.ui.store.browser import StoreBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
- StoreBrowserWindow(modal=True,
- show_tab=StoreBrowserWindow.TabID.MAPS,
- on_close_call=self._on_store_close,
- origin_widget=self._get_more_maps_button)
+ StoreBrowserWindow(
+ modal=True,
+ show_tab=StoreBrowserWindow.TabID.MAPS,
+ on_close_call=self._on_store_close,
+ origin_widget=self._get_more_maps_button,
+ )
def _on_store_close(self) -> None:
self._refresh(select_get_more_maps_button=True)
def _select(self, map_name: str) -> None:
from bastd.ui.playlist.editgame import PlaylistEditGameWindow
+
self._config['settings']['map'] = map_name
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
@@ -233,17 +280,20 @@ class PlaylistMapSelectWindow(ba.Window):
self._completion_call,
default_selection='map',
transition='in_left',
- edit_info=self._edit_info).get_root_widget())
+ edit_info=self._edit_info,
+ ).get_root_widget()
+ )
def _select_with_delay(self, map_name: str) -> None:
ba.internal.lock_all_input()
ba.timer(0.1, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
- ba.timer(0.1,
- ba.WeakCall(self._select, map_name),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1, ba.WeakCall(self._select, map_name), timetype=ba.TimeType.REAL
+ )
def _cancel(self) -> None:
from bastd.ui.playlist.editgame import PlaylistEditGameWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
PlaylistEditGameWindow(
@@ -253,4 +303,6 @@ class PlaylistMapSelectWindow(ba.Window):
self._completion_call,
default_selection='map',
transition='in_left',
- edit_info=self._edit_info).get_root_widget())
+ edit_info=self._edit_info,
+ ).get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/share.py b/assets/src/ba_data/python/bastd/ui/playlist/share.py
index 9726545e..2118c536 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/share.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/share.py
@@ -18,12 +18,14 @@ if TYPE_CHECKING:
class SharePlaylistImportWindow(promocode.PromoCodeWindow):
"""Window for importing a shared playlist."""
- def __init__(self,
- origin_widget: ba.Widget | None = None,
- on_success_callback: Callable[[], Any] | None = None):
- promocode.PromoCodeWindow.__init__(self,
- modal=True,
- origin_widget=origin_widget)
+ def __init__(
+ self,
+ origin_widget: ba.Widget | None = None,
+ on_success_callback: Callable[[], Any] | None = None,
+ ):
+ promocode.PromoCodeWindow.__init__(
+ self, modal=True, origin_widget=origin_widget
+ )
self._on_success_callback = on_success_callback
def _on_import_response(self, response: dict[str, Any] | None) -> None:
@@ -39,24 +41,32 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow):
else:
playlist_type_name = ba.Lstr(value=response['playlistType'])
- ba.screenmessage(ba.Lstr(resource='importPlaylistSuccessText',
- subs=[('${TYPE}', playlist_type_name),
- ('${NAME}', response['playlistName'])]),
- color=(0, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='importPlaylistSuccessText',
+ subs=[
+ ('${TYPE}', playlist_type_name),
+ ('${NAME}', response['playlistName']),
+ ],
+ ),
+ color=(0, 1, 0),
+ )
ba.playsound(ba.getsound('gunCocking'))
if self._on_success_callback is not None:
self._on_success_callback()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
def _do_enter(self) -> None:
ba.internal.add_transaction(
{
'type': 'IMPORT_PLAYLIST',
'expire_time': time.time() + 5,
- 'code': ba.textwidget(query=self._text_field)
+ 'code': ba.textwidget(query=self._text_field),
},
- callback=ba.WeakCall(self._on_import_response))
+ callback=ba.WeakCall(self._on_import_response),
+ )
ba.internal.run_transactions()
ba.screenmessage(ba.Lstr(resource='importingText'))
@@ -64,47 +74,60 @@ class SharePlaylistImportWindow(promocode.PromoCodeWindow):
class SharePlaylistResultsWindow(ba.Window):
"""Window for sharing playlists."""
- def __init__(self,
- name: str,
- data: str,
- origin: tuple[float, float] = (0.0, 0.0)):
+ def __init__(
+ self, name: str, data: str, origin: tuple[float, float] = (0.0, 0.0)
+ ):
del origin # unused arg
self._width = 450
self._height = 300
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- color=(0.45, 0.63, 0.15),
- transition='in_scale',
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ color=(0.45, 0.63, 0.15),
+ transition='in_scale',
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
ba.playsound(ba.getsound('cashRegister'))
ba.playsound(ba.getsound('swish'))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.7,
- position=(40, self._height - 40),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.45, 0.63, 0.15),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.7,
+ position=(40, self._height - 40),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.45, 0.63, 0.15),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.745),
- size=(0, 0),
- color=ba.app.ui.infotextcolor,
- scale=1.0,
- flatness=1.0,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='exportSuccessText',
- subs=[('${NAME}', name)]),
- maxwidth=self._width * 0.85)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.745),
+ size=(0, 0),
+ color=ba.app.ui.infotextcolor,
+ scale=1.0,
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(
+ resource='exportSuccessText', subs=[('${NAME}', name)]
+ ),
+ maxwidth=self._width * 0.85,
+ )
ba.textwidget(
parent=self._root_widget,
@@ -116,17 +139,20 @@ class SharePlaylistResultsWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource='importPlaylistCodeInstructionsText'),
- maxwidth=self._width * 0.85)
+ maxwidth=self._width * 0.85,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.4),
- size=(0, 0),
- color=(1.0, 3.0, 1.0),
- scale=2.3,
- h_align='center',
- v_align='center',
- text=data,
- maxwidth=self._width * 0.85)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.4),
+ size=(0, 0),
+ color=(1.0, 3.0, 1.0),
+ scale=2.3,
+ h_align='center',
+ v_align='center',
+ text=data,
+ maxwidth=self._width * 0.85,
+ )
def close(self) -> None:
"""Close the window."""
diff --git a/assets/src/ba_data/python/bastd/ui/playoptions.py b/assets/src/ba_data/python/bastd/ui/playoptions.py
index 8dfb4aaf..507550bc 100644
--- a/assets/src/ba_data/python/bastd/ui/playoptions.py
+++ b/assets/src/ba_data/python/bastd/ui/playoptions.py
@@ -17,11 +17,13 @@ if TYPE_CHECKING:
class PlayOptionsWindow(popup.PopupWindow):
"""A popup window for configuring play options."""
- def __init__(self,
- sessiontype: type[ba.Session],
- playlist: str,
- scale_origin: tuple[float, float],
- delegate: Any = None):
+ def __init__(
+ self,
+ sessiontype: type[ba.Session],
+ playlist: str,
+ scale_origin: tuple[float, float],
+ delegate: Any = None,
+ ):
# FIXME: Tidy this up.
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -38,8 +40,9 @@ class PlayOptionsWindow(popup.PopupWindow):
# vs starting a game directly (should make this more elegant).
self._selecting_mode = ba.app.ui.selecting_private_party_playlist
- self._do_randomize_val = (ba.app.config.get(
- self._pvars.config_name + ' Playlist Randomize', 0))
+ self._do_randomize_val = ba.app.config.get(
+ self._pvars.config_name + ' Playlist Randomize', 0
+ )
self._sessiontype = sessiontype
self._playlist = playlist
@@ -73,21 +76,30 @@ class PlayOptionsWindow(popup.PopupWindow):
plst = self._pvars.get_default_list_call()
else:
try:
- plst = ba.app.config[self._pvars.config_name +
- ' Playlists'][name]
+ plst = ba.app.config[
+ self._pvars.config_name + ' Playlists'
+ ][name]
except Exception:
- print('ERROR INFO: self._config_name is:',
- self._pvars.config_name)
+ print(
+ 'ERROR INFO: self._config_name is:',
+ self._pvars.config_name,
+ )
print(
'ERROR INFO: playlist names are:',
- list(ba.app.config[self._pvars.config_name +
- ' Playlists'].keys()))
+ list(
+ ba.app.config[
+ self._pvars.config_name + ' Playlists'
+ ].keys()
+ ),
+ )
raise
- plst = filter_playlist(plst,
- self._sessiontype,
- remove_unowned=False,
- mark_unowned=True,
- name=name)
+ plst = filter_playlist(
+ plst,
+ self._sessiontype,
+ remove_unowned=False,
+ mark_unowned=True,
+ name=name,
+ )
game_count = len(plst)
for entry in plst:
mapname = entry['settings']['map']
@@ -127,25 +139,33 @@ class PlayOptionsWindow(popup.PopupWindow):
# Creates our _root_widget.
uiscale = ba.app.ui.uiscale
- scale = (1.69 if uiscale is ba.UIScale.SMALL else
- 1.1 if uiscale is ba.UIScale.MEDIUM else 0.85)
- super().__init__(position=scale_origin,
- size=(self._width, self._height),
- scale=scale)
+ scale = (
+ 1.69
+ if uiscale is ba.UIScale.SMALL
+ else 1.1
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.85
+ )
+ super().__init__(
+ position=scale_origin, size=(self._width, self._height), scale=scale
+ )
- playlist_name: str | ba.Lstr = (self._pvars.default_list_name
- if playlist == '__default__' else
- playlist)
- self._title_text = ba.textwidget(parent=self.root_widget,
- position=(self._width * 0.5,
- self._height - 89 + 51),
- size=(0, 0),
- text=playlist_name,
- scale=1.4,
- color=(1, 1, 1),
- maxwidth=self._width * 0.7,
- h_align='center',
- v_align='center')
+ playlist_name: str | ba.Lstr = (
+ self._pvars.default_list_name
+ if playlist == '__default__'
+ else playlist
+ )
+ self._title_text = ba.textwidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5, self._height - 89 + 51),
+ size=(0, 0),
+ text=playlist_name,
+ scale=1.4,
+ color=(1, 1, 1),
+ maxwidth=self._width * 0.7,
+ h_align='center',
+ v_align='center',
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
@@ -157,7 +177,8 @@ class PlayOptionsWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
h_offs_img = self._width * 0.5 - c_width_total * 0.5
v_offs_img = self._height - 118 - scl * 125.0 + 50
@@ -172,27 +193,34 @@ class PlayOptionsWindow(popup.PopupWindow):
h = h_offs_img + scl * 250 * col
v = v_offs_img - self._row_height * row
entry = map_texture_entries[tex_index]
- owned = not (('is_unowned_map' in entry
- and entry['is_unowned_map']) or
- ('is_unowned_game' in entry
- and entry['is_unowned_game']))
+ owned = not (
+ ('is_unowned_map' in entry and entry['is_unowned_map'])
+ or (
+ 'is_unowned_game' in entry
+ and entry['is_unowned_game']
+ )
+ )
if owned:
self._have_at_least_one_owned = True
try:
- desc = getclass(entry['type'],
- subclassof=ba.GameActivity
- ).get_settings_display_string(entry)
+ desc = getclass(
+ entry['type'], subclassof=ba.GameActivity
+ ).get_settings_display_string(entry)
if not owned:
desc = ba.Lstr(
value='${DESC}\n${UNLOCK}',
subs=[
('${DESC}', desc),
- ('${UNLOCK}',
- ba.Lstr(
- resource='unlockThisInTheStoreText'))
- ])
+ (
+ '${UNLOCK}',
+ ba.Lstr(
+ resource='unlockThisInTheStoreText'
+ ),
+ ),
+ ],
+ )
desc_color = (0, 1, 0) if owned else (1, 0, 0)
except Exception:
desc = ba.Lstr(value='(invalid)')
@@ -204,14 +232,16 @@ class PlayOptionsWindow(popup.PopupWindow):
position=(h, v),
texture=ba.gettexture(tex_name if owned else 'empty'),
model_opaque=model_opaque if owned else None,
- on_activate_call=ba.Call(ba.screenmessage, desc,
- desc_color),
+ on_activate_call=ba.Call(
+ ba.screenmessage, desc, desc_color
+ ),
label='',
color=(1, 1, 1),
autoselect=True,
extra_touch_border_scale=0.0,
model_transparent=model_transparent if owned else None,
- mask_texture=mask_tex if owned else None)
+ mask_texture=mask_tex if owned else None,
+ )
if row == 0 and col == 0:
ba.widget(edit=self._cancel_button, down_widget=btn)
if row == rows - 1:
@@ -221,23 +251,26 @@ class PlayOptionsWindow(popup.PopupWindow):
# Ewww; buttons don't currently have alpha so in this
# case we draw an image over our button with an empty
# texture on it.
- ba.imagewidget(parent=self.root_widget,
- size=(scl * 260.0, scl * 130.0),
- position=(h - 10.0 * scl,
- v - 4.0 * scl),
- draw_controller=btn,
- color=(1, 1, 1),
- texture=ba.gettexture(tex_name),
- model_opaque=model_opaque,
- opacity=0.25,
- model_transparent=model_transparent,
- mask_texture=mask_tex)
+ ba.imagewidget(
+ parent=self.root_widget,
+ size=(scl * 260.0, scl * 130.0),
+ position=(h - 10.0 * scl, v - 4.0 * scl),
+ draw_controller=btn,
+ color=(1, 1, 1),
+ texture=ba.gettexture(tex_name),
+ model_opaque=model_opaque,
+ opacity=0.25,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ )
- ba.imagewidget(parent=self.root_widget,
- size=(scl * 100, scl * 100),
- draw_controller=btn,
- position=(h + scl * 70, v + scl * 10),
- texture=ba.gettexture('lock'))
+ ba.imagewidget(
+ parent=self.root_widget,
+ size=(scl * 100, scl * 100),
+ draw_controller=btn,
+ position=(h + scl * 70, v + scl * 10),
+ texture=ba.gettexture('lock'),
+ )
# Team names/colors.
self._custom_colors_names_button: ba.Widget | None
@@ -250,14 +283,16 @@ class PlayOptionsWindow(popup.PopupWindow):
on_activate_call=ba.WeakCall(self._custom_colors_names_press),
autoselect=True,
textcolor=(0.8, 0.8, 0.8),
- label=ba.Lstr(resource='teamNamesColorText'))
+ label=ba.Lstr(resource='teamNamesColorText'),
+ )
if not ba.app.accounts_v1.have_pro():
ba.imagewidget(
parent=self.root_widget,
size=(30, 30),
position=(95, 202 + y_offs),
texture=ba.gettexture('lock'),
- draw_controller=self._custom_colors_names_button)
+ draw_controller=self._custom_colors_names_button,
+ )
else:
self._custom_colors_names_button = None
@@ -265,8 +300,9 @@ class PlayOptionsWindow(popup.PopupWindow):
def _cb_callback(val: bool) -> None:
self._do_randomize_val = val
cfg = ba.app.config
- cfg[self._pvars.config_name +
- ' Playlist Randomize'] = self._do_randomize_val
+ cfg[
+ self._pvars.config_name + ' Playlist Randomize'
+ ] = self._do_randomize_val
cfg.commit()
if show_shuffle_check_box:
@@ -280,7 +316,8 @@ class PlayOptionsWindow(popup.PopupWindow):
maxwidth=300,
textcolor=(0.8, 0.8, 0.8),
value=self._do_randomize_val,
- on_value_change_call=_cb_callback)
+ on_value_change_call=_cb_callback,
+ )
# Show tutorial.
show_tutorial = bool(ba.app.config.get('Show Tutorial', True))
@@ -300,24 +337,34 @@ class PlayOptionsWindow(popup.PopupWindow):
maxwidth=300,
textcolor=(0.8, 0.8, 0.8),
value=show_tutorial,
- on_value_change_call=_cb_callback_2)
+ on_value_change_call=_cb_callback_2,
+ )
# Grumble: current autoselect doesn't do a very good job
# with checkboxes.
if self._custom_colors_names_button is not None:
for btn in bottom_row_buttons:
- ba.widget(edit=btn,
- down_widget=self._custom_colors_names_button)
+ ba.widget(
+ edit=btn, down_widget=self._custom_colors_names_button
+ )
if show_shuffle_check_box:
- ba.widget(edit=self._custom_colors_names_button,
- down_widget=self._shuffle_check_box)
- ba.widget(edit=self._shuffle_check_box,
- up_widget=self._custom_colors_names_button)
+ ba.widget(
+ edit=self._custom_colors_names_button,
+ down_widget=self._shuffle_check_box,
+ )
+ ba.widget(
+ edit=self._shuffle_check_box,
+ up_widget=self._custom_colors_names_button,
+ )
else:
- ba.widget(edit=self._custom_colors_names_button,
- down_widget=self._show_tutorial_check_box)
- ba.widget(edit=self._show_tutorial_check_box,
- up_widget=self._custom_colors_names_button)
+ ba.widget(
+ edit=self._custom_colors_names_button,
+ down_widget=self._show_tutorial_check_box,
+ )
+ ba.widget(
+ edit=self._show_tutorial_check_box,
+ up_widget=self._custom_colors_names_button,
+ )
self._ok_button = ba.buttonwidget(
parent=self.root_widget,
@@ -328,27 +375,33 @@ class PlayOptionsWindow(popup.PopupWindow):
on_activate_call=self._on_ok_press,
autoselect=True,
label=ba.Lstr(
- resource='okText' if self._selecting_mode else 'playText'))
+ resource='okText' if self._selecting_mode else 'playText'
+ ),
+ )
- ba.widget(edit=self._ok_button,
- up_widget=self._show_tutorial_check_box)
+ ba.widget(edit=self._ok_button, up_widget=self._show_tutorial_check_box)
- ba.containerwidget(edit=self.root_widget,
- start_button=self._ok_button,
- cancel_button=self._cancel_button,
- selected_child=self._ok_button)
+ ba.containerwidget(
+ edit=self.root_widget,
+ start_button=self._ok_button,
+ cancel_button=self._cancel_button,
+ selected_child=self._ok_button,
+ )
# Update now and once per second.
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
def _custom_colors_names_press(self) -> None:
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.teamnamescolors import TeamNamesColorsWindow
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro():
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
@@ -357,14 +410,18 @@ class PlayOptionsWindow(popup.PopupWindow):
self._transition_out()
return
assert self._custom_colors_names_button
- TeamNamesColorsWindow(scale_origin=self._custom_colors_names_button.
- get_screen_space_center())
+ TeamNamesColorsWindow(
+ scale_origin=(
+ self._custom_colors_names_button.get_screen_space_center()
+ )
+ )
def _does_target_playlist_exist(self) -> bool:
if self._playlist == '__default__':
return True
return self._playlist in ba.app.config.get(
- self._pvars.config_name + ' Playlists', {})
+ self._pvars.config_name + ' Playlists', {}
+ )
def _update(self) -> None:
# All we do here is make sure our targeted playlist still exists,
@@ -393,8 +450,10 @@ class PlayOptionsWindow(popup.PopupWindow):
# Disallow if we have no unlocked games.
if not self._have_at_least_one_owned:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='playlistNoValidGamesErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='playlistNoValidGamesErrorText'),
+ color=(1, 0, 0),
+ )
return
cfg = ba.app.config
@@ -404,6 +463,7 @@ class PlayOptionsWindow(popup.PopupWindow):
# or start the game in regular mode.
if self._selecting_mode:
from bastd.ui.gather import GatherWindow
+
if self._sessiontype is ba.FreeForAllSession:
typename = 'ffa'
elif self._sessiontype is ba.DualTeamSession:
@@ -413,7 +473,8 @@ class PlayOptionsWindow(popup.PopupWindow):
cfg['Private Party Host Session Type'] = typename
ba.playsound(ba.getsound('gunCocking'))
ba.app.ui.set_main_menu_window(
- GatherWindow(transition='in_right').get_root_widget())
+ GatherWindow(transition='in_right').get_root_widget()
+ )
self._transition_out(transition='out_left')
if self._delegate is not None:
self._delegate.on_play_options_window_run_game()
@@ -432,6 +493,7 @@ class PlayOptionsWindow(popup.PopupWindow):
ba.internal.new_host_session(self._sessiontype)
except Exception:
from bastd import mainmenu
+
ba.print_exception('exception running session', self._sessiontype)
# Drop back into a main menu session.
diff --git a/assets/src/ba_data/python/bastd/ui/popup.py b/assets/src/ba_data/python/bastd/ui/popup.py
index 8f918fe8..79d3de5d 100644
--- a/assets/src/ba_data/python/bastd/ui/popup.py
+++ b/assets/src/ba_data/python/bastd/ui/popup.py
@@ -19,15 +19,17 @@ class PopupWindow:
Category: UI Classes"""
- def __init__(self,
- position: tuple[float, float],
- size: tuple[float, float],
- scale: float = 1.0,
- offset: tuple[float, float] = (0, 0),
- bg_color: tuple[float, float, float] = (0.35, 0.55, 0.15),
- focus_position: tuple[float, float] = (0, 0),
- focus_size: tuple[float, float] | None = None,
- toolbar_visibility: str = 'menu_minimal_no_back'):
+ def __init__(
+ self,
+ position: tuple[float, float],
+ size: tuple[float, float],
+ scale: float = 1.0,
+ offset: tuple[float, float] = (0, 0),
+ bg_color: tuple[float, float, float] = (0.35, 0.55, 0.15),
+ focus_position: tuple[float, float] = (0, 0),
+ focus_size: tuple[float, float] | None = None,
+ toolbar_visibility: str = 'menu_minimal_no_back',
+ ):
# pylint: disable=too-many-locals
if focus_size is None:
focus_size = size
@@ -45,17 +47,17 @@ class PopupWindow:
# need be and clamping it to the UI bounds.
bounds = ba.app.ui_bounds
edge_buffer = 15
- bounds_width = (bounds[1] - bounds[0] - edge_buffer * 2)
- bounds_height = (bounds[3] - bounds[2] - edge_buffer * 2)
+ bounds_width = bounds[1] - bounds[0] - edge_buffer * 2
+ bounds_height = bounds[3] - bounds[2] - edge_buffer * 2
fin_width = width * scale
fin_height = height * scale
if fin_width > bounds_width:
- scale /= (fin_width / bounds_width)
+ scale /= fin_width / bounds_width
fin_width = width * scale
fin_height = height * scale
if fin_height > bounds_height:
- scale /= (fin_height / bounds_height)
+ scale /= fin_height / bounds_height
fin_width = width * scale
fin_height = height * scale
@@ -71,10 +73,12 @@ class PopupWindow:
# focus area. ..now calc the difference between the center of our
# focus area and the center of our window to come up with the
# offset we'll need to plug in to the window
- x_offs = ((focus_position[0] + focus_size[0] * 0.5) -
- (size[0] * 0.5)) * scale
- y_offs = ((focus_position[1] + focus_size[1] * 0.5) -
- (size[1] * 0.5)) * scale
+ x_offs = (
+ (focus_position[0] + focus_size[0] * 0.5) - (size[0] * 0.5)
+ ) * scale
+ y_offs = (
+ (focus_position[1] + focus_size[1] * 0.5) - (size[1] * 0.5)
+ ) * scale
self.root_widget = ba.containerwidget(
transition='in_scale',
@@ -87,7 +91,8 @@ class PopupWindow:
on_outside_click_call=self.on_popup_cancel,
claim_outside_clicks=True,
color=bg_color,
- on_cancel_call=self.on_popup_cancel)
+ on_cancel_call=self.on_popup_cancel,
+ )
# complain if we outlive our root widget
ba.uicleanupcheck(self, self.root_widget)
@@ -102,16 +107,18 @@ class PopupWindow:
class PopupMenuWindow(PopupWindow):
"""A menu built using popup-window functionality."""
- def __init__(self,
- position: tuple[float, float],
- choices: Sequence[str],
- current_choice: str,
- delegate: Any = None,
- width: float = 230.0,
- maxwidth: float | None = None,
- scale: float = 1.0,
- choices_disabled: Sequence[str] | None = None,
- choices_display: Sequence[ba.Lstr] | None = None):
+ def __init__(
+ self,
+ position: tuple[float, float],
+ choices: Sequence[str],
+ current_choice: str,
+ delegate: Any = None,
+ width: float = 230.0,
+ maxwidth: float | None = None,
+ scale: float = 1.0,
+ choices_disabled: Sequence[str] | None = None,
+ choices_display: Sequence[ba.Lstr] | None = None,
+ ):
# FIXME: Clean up a bit.
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
@@ -160,65 +167,81 @@ class PopupMenuWindow(PopupWindow):
min(
maxwidth,
ba.internal.get_string_width(
- choice_display_name, suppress_warning=True)) + 75)
+ choice_display_name, suppress_warning=True
+ ),
+ )
+ + 75,
+ )
else:
self._width = max(
self._width,
min(
maxwidth,
ba.internal.get_string_width(
- choice_display_name, suppress_warning=True)) + 60)
+ choice_display_name, suppress_warning=True
+ ),
+ )
+ + 60,
+ )
# init parent class - this will rescale and reposition things as
# needed and create our root widget
- PopupWindow.__init__(self,
- position,
- size=(self._width, self._height),
- scale=self._scale)
+ PopupWindow.__init__(
+ self, position, size=(self._width, self._height), scale=self._scale
+ )
if self._use_scroll:
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- position=(20, 20),
- highlight=False,
- color=(0.35, 0.55, 0.15),
- size=(self._width - 40,
- self._height - 40))
- self._columnwidget = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ position=(20, 20),
+ highlight=False,
+ color=(0.35, 0.55, 0.15),
+ size=(self._width - 40, self._height - 40),
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0
+ )
else:
- self._offset_widget = ba.containerwidget(parent=self.root_widget,
- position=(30, 15),
- size=(self._width - 40,
- self._height),
- background=False)
- self._columnwidget = ba.columnwidget(parent=self._offset_widget,
- border=2,
- margin=0)
+ self._offset_widget = ba.containerwidget(
+ parent=self.root_widget,
+ position=(30, 15),
+ size=(self._width - 40, self._height),
+ background=False,
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=self._offset_widget, border=2, margin=0
+ )
for index, choice in enumerate(choices):
if len(choices_display_fin) == len(choices):
choice_display_name = choices_display_fin[index]
else:
choice_display_name = choice
- inactive = (choice in self._choices_disabled)
- wdg = ba.textwidget(parent=self._columnwidget,
- size=(self._width - 40, 28),
- on_select_call=ba.Call(self._select, index),
- click_activate=True,
- color=(0.5, 0.5, 0.5, 0.5) if inactive else
- ((0.5, 1, 0.5,
- 1) if choice == self._current_choice else
- (0.8, 0.8, 0.8, 1.0)),
- padding=0,
- maxwidth=maxwidth,
- text=choice_display_name,
- on_activate_call=self._activate,
- v_align='center',
- selectable=(not inactive))
+ inactive = choice in self._choices_disabled
+ wdg = ba.textwidget(
+ parent=self._columnwidget,
+ size=(self._width - 40, 28),
+ on_select_call=ba.Call(self._select, index),
+ click_activate=True,
+ color=(0.5, 0.5, 0.5, 0.5)
+ if inactive
+ else (
+ (0.5, 1, 0.5, 1)
+ if choice == self._current_choice
+ else (0.8, 0.8, 0.8, 1.0)
+ ),
+ padding=0,
+ maxwidth=maxwidth,
+ text=choice_display_name,
+ on_activate_call=self._activate,
+ v_align='center',
+ selectable=(not inactive),
+ )
if choice == self._current_choice:
- ba.containerwidget(edit=self._columnwidget,
- selected_child=wdg,
- visible_child=wdg)
+ ba.containerwidget(
+ edit=self._columnwidget,
+ selected_child=wdg,
+ visible_child=wdg,
+ )
# ok from now on our delegate can be called
self._delegate = weakref.ref(delegate)
@@ -235,8 +258,9 @@ class PopupMenuWindow(PopupWindow):
if delegate is not None:
# Call this in a timer so it doesn't interfere with us killing
# our widgets and whatnot.
- call = ba.Call(delegate.popup_menu_selected_choice, self,
- self._current_choice)
+ call = ba.Call(
+ delegate.popup_menu_selected_choice, self, self._current_choice
+ )
ba.timer(0, call, timetype=ba.TimeType.REAL)
def _getdelegate(self) -> Any:
@@ -264,21 +288,23 @@ class PopupMenu:
This creates a button and wrangles its pop-up menu.
"""
- def __init__(self,
- parent: ba.Widget,
- position: tuple[float, float],
- choices: Sequence[str],
- current_choice: str | None = None,
- on_value_change_call: Callable[[str], Any] | None = None,
- opening_call: Callable[[], Any] | None = None,
- closing_call: Callable[[], Any] | None = None,
- width: float = 230.0,
- maxwidth: float | None = None,
- scale: float | None = None,
- choices_disabled: Sequence[str] | None = None,
- choices_display: Sequence[ba.Lstr] | None = None,
- button_size: tuple[float, float] = (160.0, 50.0),
- autoselect: bool = True):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: tuple[float, float],
+ choices: Sequence[str],
+ current_choice: str | None = None,
+ on_value_change_call: Callable[[str], Any] | None = None,
+ opening_call: Callable[[], Any] | None = None,
+ closing_call: Callable[[], Any] | None = None,
+ width: float = 230.0,
+ maxwidth: float | None = None,
+ scale: float | None = None,
+ choices_disabled: Sequence[str] | None = None,
+ choices_display: Sequence[ba.Lstr] | None = None,
+ button_size: tuple[float, float] = (160.0, 50.0),
+ autoselect: bool = True,
+ ):
# pylint: disable=too-many-locals
if choices_disabled is None:
choices_disabled = []
@@ -286,8 +312,13 @@ class PopupMenu:
choices_display = []
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
if current_choice not in choices:
current_choice = None
self._choices = list(choices)
@@ -298,8 +329,9 @@ class PopupMenu:
self._width = width
self._maxwidth = maxwidth
self._scale = scale
- self._current_choice = (current_choice if current_choice is not None
- else self._choices[0])
+ self._current_choice = (
+ current_choice if current_choice is not None else self._choices[0]
+ )
self._position = position
self._parent = parent
if not choices:
@@ -315,7 +347,9 @@ class PopupMenu:
scale=1.0,
label='',
on_activate_call=lambda: ba.timer(
- 0, self._make_popup, timetype=ba.TimeType.REAL))
+ 0, self._make_popup, timetype=ba.TimeType.REAL
+ ),
+ )
self._on_value_change_call = None # Don't wanna call for initial set.
self._opening_call = opening_call
self._autoselect = autoselect
@@ -341,7 +375,8 @@ class PopupMenu:
choices=self._choices,
current_choice=self._current_choice,
choices_disabled=self._choices_disabled,
- choices_display=self._choices_display).root_widget
+ choices_display=self._choices_display,
+ ).root_widget
def get_button(self) -> ba.Widget:
"""Return the menu's button widget."""
@@ -351,8 +386,9 @@ class PopupMenu:
"""Return the menu's window widget (or None if nonexistent)."""
return self._window_widget
- def popup_menu_selected_choice(self, popup_window: PopupWindow,
- choice: str) -> None:
+ def popup_menu_selected_choice(
+ self, popup_window: PopupWindow, choice: str
+ ) -> None:
"""Called when a choice is selected."""
del popup_window # Unused here.
self.set_choice(choice)
diff --git a/assets/src/ba_data/python/bastd/ui/profile/browser.py b/assets/src/ba_data/python/bastd/ui/profile/browser.py
index ff8c6111..350e303d 100644
--- a/assets/src/ba_data/python/bastd/ui/profile/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/profile/browser.py
@@ -16,11 +16,13 @@ if TYPE_CHECKING:
class ProfileBrowserWindow(ba.Window):
"""Window for browsing player profiles."""
- def __init__(self,
- transition: str = 'in_right',
- in_main_menu: bool = True,
- selected_profile: str | None = None,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ in_main_menu: bool = True,
+ selected_profile: str | None = None,
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
self._in_main_menu = in_main_menu
@@ -31,8 +33,13 @@ class ProfileBrowserWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0
x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
- self._height = (360.0 if uiscale is ba.UIScale.SMALL else
- 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0)
+ self._height = (
+ 360.0
+ if uiscale is ba.UIScale.SMALL
+ else 385.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 410.0
+ )
# If we're being called up standalone, handle pause/resume ourself.
if not self._in_main_menu:
@@ -55,13 +62,23 @@ class ProfileBrowserWindow(ba.Window):
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- scale=(2.2 if uiscale is ba.UIScale.SMALL else
- 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.2
+ if uiscale is ba.UIScale.SMALL
+ else 1.6
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -14)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -71,24 +88,29 @@ class ProfileBrowserWindow(ba.Window):
label=back_label,
button_type='back' if self._in_main_menu else None,
autoselect=True,
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 36),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=300,
- color=ba.app.ui.title_color,
- scale=0.9,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 36),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=300,
+ color=ba.app.ui.title_color,
+ scale=0.9,
+ h_align='center',
+ v_align='center',
+ )
if self._in_main_menu:
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
scroll_height = self._height - 140.0
self._scroll_width = self._width - (188 + x_inset * 2)
@@ -96,20 +118,26 @@ class ProfileBrowserWindow(ba.Window):
h = 50 + x_inset
b_color = (0.6, 0.53, 0.63)
- scl = (1.055 if uiscale is ba.UIScale.SMALL else
- 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3)
+ scl = (
+ 1.055
+ if uiscale is ba.UIScale.SMALL
+ else 1.18
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.3
+ )
v -= 70.0 * scl
- self._new_button = ba.buttonwidget(parent=self._root_widget,
- position=(h, v),
- size=(80, 66.0 * scl),
- on_activate_call=self._new_profile,
- color=b_color,
- button_type='square',
- autoselect=True,
- textcolor=(0.75, 0.7, 0.8),
- text_scale=0.7,
- label=ba.Lstr(resource=self._r +
- '.newButtonText'))
+ self._new_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(h, v),
+ size=(80, 66.0 * scl),
+ on_activate_call=self._new_profile,
+ color=b_color,
+ button_type='square',
+ autoselect=True,
+ textcolor=(0.75, 0.7, 0.8),
+ text_scale=0.7,
+ label=ba.Lstr(resource=self._r + '.newButtonText'),
+ )
v -= 70.0 * scl
self._edit_button = ba.buttonwidget(
parent=self._root_widget,
@@ -121,7 +149,8 @@ class ProfileBrowserWindow(ba.Window):
autoselect=True,
textcolor=(0.75, 0.7, 0.8),
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.editButtonText'))
+ label=ba.Lstr(resource=self._r + '.editButtonText'),
+ )
v -= 70.0 * scl
self._delete_button = ba.buttonwidget(
parent=self._root_widget,
@@ -133,34 +162,40 @@ class ProfileBrowserWindow(ba.Window):
autoselect=True,
textcolor=(0.75, 0.7, 0.8),
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.deleteButtonText'))
+ label=ba.Lstr(resource=self._r + '.deleteButtonText'),
+ )
v = self._height - 87
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 71),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.explanationText'),
- color=ba.app.ui.infotextcolor,
- maxwidth=self._width * 0.83,
- scale=0.6,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 71),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.explanationText'),
+ color=ba.app.ui.infotextcolor,
+ maxwidth=self._width * 0.83,
+ scale=0.6,
+ h_align='center',
+ v_align='center',
+ )
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- highlight=False,
- position=(140 + x_inset,
- v - scroll_height),
- size=(self._scroll_width,
- scroll_height))
- ba.widget(edit=self._scrollwidget,
- autoselect=True,
- left_widget=self._new_button)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
- self._columnwidget = ba.columnwidget(parent=self._scrollwidget,
- border=2,
- margin=0)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ highlight=False,
+ position=(140 + x_inset, v - scroll_height),
+ size=(self._scroll_width, scroll_height),
+ )
+ ba.widget(
+ edit=self._scrollwidget,
+ autoselect=True,
+ left_widget=self._new_button,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
+ self._columnwidget = ba.columnwidget(
+ parent=self._scrollwidget, border=2, margin=0
+ )
v -= 255
self._profiles: dict[str, dict[str, Any]] | None = None
self._selected_profile = selected_profile
@@ -175,23 +210,34 @@ class ProfileBrowserWindow(ba.Window):
# Limit to a handful profiles if they don't have pro-options.
max_non_pro_profiles = ba.internal.get_v1_account_misc_read_val(
- 'mnpp', 5)
+ 'mnpp', 5
+ )
assert self._profiles is not None
- if (not ba.app.accounts_v1.have_pro_options()
- and len(self._profiles) >= max_non_pro_profiles):
- PurchaseWindow(items=['pro'],
- header_text=ba.Lstr(
- resource='unlockThisProfilesText',
- subs=[('${NUM}', str(max_non_pro_profiles))]))
+ if (
+ not ba.app.accounts_v1.have_pro_options()
+ and len(self._profiles) >= max_non_pro_profiles
+ ):
+ PurchaseWindow(
+ items=['pro'],
+ header_text=ba.Lstr(
+ resource='unlockThisProfilesText',
+ subs=[('${NUM}', str(max_non_pro_profiles))],
+ ),
+ )
return
# Clamp at 100 profiles (otherwise the server will and that's less
# elegant looking).
if len(self._profiles) > 100:
ba.screenmessage(
- ba.Lstr(translate=('serverResponses',
- 'Max number of profiles reached.')),
- color=(1, 0, 0))
+ ba.Lstr(
+ translate=(
+ 'serverResponses',
+ 'Max number of profiles reached.',
+ )
+ ),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
return
@@ -199,55 +245,66 @@ class ProfileBrowserWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
EditProfileWindow(
- existing_profile=None,
- in_main_menu=self._in_main_menu).get_root_widget())
+ existing_profile=None, in_main_menu=self._in_main_menu
+ ).get_root_widget()
+ )
def _delete_profile(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui import confirm
+
if self._selected_profile is None:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
+ )
return
if self._selected_profile == '__account__':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.cantDeleteAccountProfileText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.cantDeleteAccountProfileText'),
+ color=(1, 0, 0),
+ )
return
confirm.ConfirmWindow(
- ba.Lstr(resource=self._r + '.deleteConfirmText',
- subs=[('${PROFILE}', self._selected_profile)]),
- self._do_delete_profile, 350)
+ ba.Lstr(
+ resource=self._r + '.deleteConfirmText',
+ subs=[('${PROFILE}', self._selected_profile)],
+ ),
+ self._do_delete_profile,
+ 350,
+ )
def _do_delete_profile(self) -> None:
- ba.internal.add_transaction({
- 'type': 'REMOVE_PLAYER_PROFILE',
- 'name': self._selected_profile
- })
+ ba.internal.add_transaction(
+ {'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile}
+ )
ba.internal.run_transactions()
ba.playsound(ba.getsound('shieldDown'))
self._refresh()
# Select profile list.
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
def _edit_profile(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.profile.edit import EditProfileWindow
+
if self._selected_profile is None:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
+ )
return
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
EditProfileWindow(
- self._selected_profile,
- in_main_menu=self._in_main_menu).get_root_widget())
+ self._selected_profile, in_main_menu=self._in_main_menu
+ ).get_root_widget()
+ )
def _select(self, name: str, index: int) -> None:
del index # Unused.
@@ -256,12 +313,15 @@ class ProfileBrowserWindow(ba.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account.settings import AccountSettingsWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if self._in_main_menu:
ba.app.ui.set_main_menu_window(
- AccountSettingsWindow(transition='in_left').get_root_widget())
+ AccountSettingsWindow(transition='in_left').get_root_widget()
+ )
# If we're being called up standalone, handle pause/resume ourself.
else:
@@ -270,9 +330,12 @@ class ProfileBrowserWindow(ba.Window):
def _refresh(self) -> None:
# pylint: disable=too-many-locals
from efro.util import asserttype
- from ba.internal import (PlayerProfilesChangedMessage,
- get_player_profile_colors,
- get_player_profile_icon)
+ from ba.internal import (
+ PlayerProfilesChangedMessage,
+ get_player_profile_colors,
+ get_player_profile_icon,
+ )
+
old_selection = self._selected_profile
# Delete old.
@@ -294,8 +357,11 @@ class ProfileBrowserWindow(ba.Window):
continue
color, _highlight = get_player_profile_colors(p_name)
scl = 1.1
- tval = (account_name if p_name == '__account__' else
- get_player_profile_icon(p_name) + p_name)
+ tval = (
+ account_name
+ if p_name == '__account__'
+ else get_player_profile_icon(p_name) + p_name
+ )
assert isinstance(tval, str)
txtw = ba.textwidget(
parent=self._columnwidget,
@@ -310,7 +376,8 @@ class ProfileBrowserWindow(ba.Window):
color=ba.safecolor(color, 0.4),
always_highlight=True,
on_activate_call=ba.Call(self._edit_button.activate),
- selectable=True)
+ selectable=True,
+ )
if index == 0:
ba.widget(edit=txtw, up_widget=self._back_button)
ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40)
@@ -325,9 +392,11 @@ class ProfileBrowserWindow(ba.Window):
index += 1
if widget_to_select is not None:
- ba.columnwidget(edit=self._columnwidget,
- selected_child=widget_to_select,
- visible_child=widget_to_select)
+ ba.columnwidget(
+ edit=self._columnwidget,
+ selected_child=widget_to_select,
+ visible_child=widget_to_select,
+ )
# If there's a team-chooser in existence, tell it the profile-list
# has probably changed.
diff --git a/assets/src/ba_data/python/bastd/ui/profile/edit.py b/assets/src/ba_data/python/bastd/ui/profile/edit.py
index 3304d1cd..2ebb1620 100644
--- a/assets/src/ba_data/python/bastd/ui/profile/edit.py
+++ b/assets/src/ba_data/python/bastd/ui/profile/edit.py
@@ -22,18 +22,23 @@ class EditProfileWindow(ba.Window):
"""Transitions out and recreates ourself."""
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- EditProfileWindow(self.getname(),
- self._in_main_menu).get_root_widget())
+ EditProfileWindow(
+ self.getname(), self._in_main_menu
+ ).get_root_widget()
+ )
- def __init__(self,
- existing_profile: str | None,
- in_main_menu: bool,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ existing_profile: str | None,
+ in_main_menu: bool,
+ transition: str = 'in_right',
+ ):
# FIXME: Tidy this up a bit.
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from ba.internal import get_player_profile_colors
+
self._in_main_menu = in_main_menu
self._existing_profile = existing_profile
self._r = 'editProfileWindow'
@@ -43,22 +48,35 @@ class EditProfileWindow(ba.Window):
# Grab profile colors or pick random ones.
self._color, self._highlight = get_player_profile_colors(
- existing_profile)
+ existing_profile
+ )
uiscale = ba.app.ui.uiscale
self._width = width = 780.0 if uiscale is ba.UIScale.SMALL else 680.0
self._x_inset = x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0
self._height = height = (
- 350.0 if uiscale is ba.UIScale.SMALL else
- 400.0 if uiscale is ba.UIScale.MEDIUM else 450.0)
+ 350.0
+ if uiscale is ba.UIScale.SMALL
+ else 400.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 450.0
+ )
spacing = 40
- self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)
+ self._base_scale = (
+ 2.05
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ )
top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height + top_extra),
- transition=transition,
- scale=self._base_scale,
- stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height + top_extra),
+ transition=transition,
+ scale=self._base_scale,
+ stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
cancel_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(52 + x_inset, height - 60),
@@ -66,34 +84,41 @@ class EditProfileWindow(ba.Window):
scale=0.8,
autoselect=True,
label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._cancel)
+ on_activate_call=self._cancel,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- save_button = btn = ba.buttonwidget(parent=self._root_widget,
- position=(width - (177 + x_inset),
- height - 60),
- size=(155, 60),
- autoselect=True,
- scale=0.8,
- label=ba.Lstr(resource='saveText'))
+ save_button = btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(width - (177 + x_inset), height - 60),
+ size=(155, 60),
+ autoselect=True,
+ scale=0.8,
+ label=ba.Lstr(resource='saveText'),
+ )
ba.widget(edit=save_button, left_widget=cancel_button)
ba.widget(edit=cancel_button, right_widget=save_button)
ba.containerwidget(edit=self._root_widget, start_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, height - 38),
- size=(0, 0),
- text=(ba.Lstr(resource=self._r + '.titleNewText')
- if existing_profile is None else ba.Lstr(
- resource=self._r + '.titleEditText')),
- color=ba.app.ui.title_color,
- maxwidth=290,
- scale=1.0,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, height - 38),
+ size=(0, 0),
+ text=(
+ ba.Lstr(resource=self._r + '.titleNewText')
+ if existing_profile is None
+ else ba.Lstr(resource=self._r + '.titleEditText')
+ ),
+ color=ba.app.ui.title_color,
+ maxwidth=290,
+ scale=1.0,
+ h_align='center',
+ v_align='center',
+ )
# Make a list of spaz icons.
self.refresh_characters()
- profile = ba.app.config.get('Player Profiles',
- {}).get(self._existing_profile, {})
+ profile = ba.app.config.get('Player Profiles', {}).get(
+ self._existing_profile, {}
+ )
if 'global' in profile:
self._global = profile['global']
@@ -126,19 +151,22 @@ class EditProfileWindow(ba.Window):
ba.buttonwidget(edit=save_button, on_activate_call=self.save)
v = height - 115.0
- self._name = ('' if self._existing_profile is None else
- self._existing_profile)
- self._is_account_profile = (self._name == '__account__')
+ self._name = (
+ '' if self._existing_profile is None else self._existing_profile
+ )
+ self._is_account_profile = self._name == '__account__'
# If we just picked a random character, see if it has specific
# colors/highlights associated with it and assign them if so.
if assigned_random_char:
clr = ba.app.spaz_appearances[
- self._spazzes[icon_index]].default_color
+ self._spazzes[icon_index]
+ ].default_color
if clr is not None:
self._color = clr
highlight = ba.app.spaz_appearances[
- self._spazzes[icon_index]].default_highlight
+ self._spazzes[icon_index]
+ ].default_highlight
if highlight is not None:
self._highlight = highlight
@@ -147,28 +175,31 @@ class EditProfileWindow(ba.Window):
names = ba.internal.get_random_names()
self._name = names[random.randrange(len(names))]
- self._clipped_name_text = ba.textwidget(parent=self._root_widget,
- text='',
- position=(540 + x_inset,
- v - 8),
- flatness=1.0,
- shadow=0.0,
- scale=0.55,
- size=(0, 0),
- maxwidth=100,
- h_align='center',
- v_align='center',
- color=(1, 1, 0, 0.5))
+ self._clipped_name_text = ba.textwidget(
+ parent=self._root_widget,
+ text='',
+ position=(540 + x_inset, v - 8),
+ flatness=1.0,
+ shadow=0.0,
+ scale=0.55,
+ size=(0, 0),
+ maxwidth=100,
+ h_align='center',
+ v_align='center',
+ color=(1, 1, 0, 0.5),
+ )
if not self._is_account_profile and not self._global:
- ba.textwidget(parent=self._root_widget,
- text=ba.Lstr(resource=self._r + '.nameText'),
- position=(200 + x_inset, v - 6),
- size=(0, 0),
- h_align='right',
- v_align='center',
- color=(1, 1, 1, 0.5),
- scale=0.9)
+ ba.textwidget(
+ parent=self._root_widget,
+ text=ba.Lstr(resource=self._r + '.nameText'),
+ position=(200 + x_inset, v - 6),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ color=(1, 1, 1, 0.5),
+ scale=0.9,
+ )
self._upgrade_button = None
if self._is_account_profile:
@@ -176,29 +207,34 @@ class EditProfileWindow(ba.Window):
sval = ba.internal.get_v1_account_display_string()
else:
sval = '??'
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v - 7),
- size=(0, 0),
- scale=1.2,
- text=sval,
- maxwidth=270,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v - 7),
+ size=(0, 0),
+ scale=1.2,
+ text=sval,
+ maxwidth=270,
+ h_align='center',
+ v_align='center',
+ )
txtl = ba.Lstr(
- resource='editProfileWindow.accountProfileText').evaluate()
+ resource='editProfileWindow.accountProfileText'
+ ).evaluate()
b_width = min(
270.0,
- ba.internal.get_string_width(txtl, suppress_warning=True) *
- 0.6)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v - 39),
- size=(0, 0),
- scale=0.6,
- color=ba.app.ui.infotextcolor,
- text=txtl,
- maxwidth=270,
- h_align='center',
- v_align='center')
+ ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v - 39),
+ size=(0, 0),
+ scale=0.6,
+ color=ba.app.ui.infotextcolor,
+ text=txtl,
+ maxwidth=270,
+ h_align='center',
+ v_align='center',
+ )
self._account_type_info_button = ba.buttonwidget(
parent=self._root_widget,
label='?',
@@ -208,7 +244,8 @@ class EditProfileWindow(ba.Window):
button_type='square',
color=(0.6, 0.5, 0.65),
autoselect=True,
- on_activate_call=self.show_account_profile_info)
+ on_activate_call=self.show_account_profile_info,
+ )
elif self._global:
b_size = 60
@@ -221,7 +258,8 @@ class EditProfileWindow(ba.Window):
label='',
button_type='square',
text_scale=1.2,
- on_activate_call=self._on_icon_press)
+ on_activate_call=self._on_icon_press,
+ )
self._icon_button_label = ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5 - 160, v - 35),
@@ -231,45 +269,53 @@ class EditProfileWindow(ba.Window):
size=(0, 0),
color=(1, 1, 1),
text='',
- scale=2.0)
+ scale=2.0,
+ )
- ba.textwidget(parent=self._root_widget,
- h_align='center',
- v_align='center',
- position=(self._width * 0.5 - 160, v - 55 - 15),
- size=(0, 0),
- draw_controller=btn,
- text=ba.Lstr(resource=self._r + '.iconText'),
- scale=0.7,
- color=ba.app.ui.title_color,
- maxwidth=120)
+ ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ v_align='center',
+ position=(self._width * 0.5 - 160, v - 55 - 15),
+ size=(0, 0),
+ draw_controller=btn,
+ text=ba.Lstr(resource=self._r + '.iconText'),
+ scale=0.7,
+ color=ba.app.ui.title_color,
+ maxwidth=120,
+ )
self._update_icon()
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v - 7),
- size=(0, 0),
- scale=1.2,
- text=self._name,
- maxwidth=240,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v - 7),
+ size=(0, 0),
+ scale=1.2,
+ text=self._name,
+ maxwidth=240,
+ h_align='center',
+ v_align='center',
+ )
# FIXME hard coded strings are bad
txtl = ba.Lstr(
- resource='editProfileWindow.globalProfileText').evaluate()
+ resource='editProfileWindow.globalProfileText'
+ ).evaluate()
b_width = min(
240.0,
- ba.internal.get_string_width(txtl, suppress_warning=True) *
- 0.6)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v - 39),
- size=(0, 0),
- scale=0.6,
- color=ba.app.ui.infotextcolor,
- text=txtl,
- maxwidth=240,
- h_align='center',
- v_align='center')
+ ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v - 39),
+ size=(0, 0),
+ scale=0.6,
+ color=ba.app.ui.infotextcolor,
+ text=txtl,
+ maxwidth=240,
+ h_align='center',
+ v_align='center',
+ )
self._account_type_info_button = ba.buttonwidget(
parent=self._root_widget,
label='?',
@@ -279,7 +325,8 @@ class EditProfileWindow(ba.Window):
button_type='square',
color=(0.6, 0.5, 0.65),
autoselect=True,
- on_activate_call=self.show_global_profile_info)
+ on_activate_call=self.show_global_profile_info,
+ )
else:
self._text_field = ba.textwidget(
parent=self._root_widget,
@@ -294,24 +341,28 @@ class EditProfileWindow(ba.Window):
editable=True,
padding=4,
color=(0.9, 0.9, 0.9, 1.0),
- on_return_press_call=ba.Call(save_button.activate))
+ on_return_press_call=ba.Call(save_button.activate),
+ )
# FIXME hard coded strings are bad
txtl = ba.Lstr(
- resource='editProfileWindow.localProfileText').evaluate()
+ resource='editProfileWindow.localProfileText'
+ ).evaluate()
b_width = min(
270.0,
- ba.internal.get_string_width(txtl, suppress_warning=True) *
- 0.6)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v - 43),
- size=(0, 0),
- scale=0.6,
- color=ba.app.ui.infotextcolor,
- text=txtl,
- maxwidth=270,
- h_align='center',
- v_align='center')
+ ba.internal.get_string_width(txtl, suppress_warning=True) * 0.6,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v - 43),
+ size=(0, 0),
+ scale=0.6,
+ color=ba.app.ui.infotextcolor,
+ text=txtl,
+ maxwidth=270,
+ h_align='center',
+ v_align='center',
+ )
self._account_type_info_button = ba.buttonwidget(
parent=self._root_widget,
label='?',
@@ -321,7 +372,8 @@ class EditProfileWindow(ba.Window):
button_type='square',
color=(0.6, 0.5, 0.65),
autoselect=True,
- on_activate_call=self.show_local_profile_info)
+ on_activate_call=self.show_local_profile_info,
+ )
self._upgrade_button = ba.buttonwidget(
parent=self._root_widget,
label=ba.Lstr(resource='upgradeText'),
@@ -331,14 +383,16 @@ class EditProfileWindow(ba.Window):
position=(self._width * 0.5 + b_width * 0.5 + 13 + 43, v - 51),
color=(0.6, 0.5, 0.65),
autoselect=True,
- on_activate_call=self.upgrade_profile)
+ on_activate_call=self.upgrade_profile,
+ )
self._update_clipped_name()
- self._clipped_name_timer = ba.Timer(0.333,
- ba.WeakCall(
- self._update_clipped_name),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._clipped_name_timer = ba.Timer(
+ 0.333,
+ ba.WeakCall(self._update_clipped_name),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
v -= spacing * 3.0
b_size = 80
@@ -351,21 +405,25 @@ class EditProfileWindow(ba.Window):
size=(b_size, b_size),
color=self._color,
label='',
- button_type='square')
+ button_type='square',
+ )
origin = self._color_button.get_screen_space_center()
- ba.buttonwidget(edit=self._color_button,
- on_activate_call=ba.WeakCall(self._make_picker,
- 'color', origin))
- ba.textwidget(parent=self._root_widget,
- h_align='center',
- v_align='center',
- position=(self._width * 0.5 - b_offs, v - 65),
- size=(0, 0),
- draw_controller=btn,
- text=ba.Lstr(resource=self._r + '.colorText'),
- scale=0.7,
- color=ba.app.ui.title_color,
- maxwidth=120)
+ ba.buttonwidget(
+ edit=self._color_button,
+ on_activate_call=ba.WeakCall(self._make_picker, 'color', origin),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ v_align='center',
+ position=(self._width * 0.5 - b_offs, v - 65),
+ size=(0, 0),
+ draw_controller=btn,
+ text=ba.Lstr(resource=self._r + '.colorText'),
+ scale=0.7,
+ color=ba.app.ui.title_color,
+ maxwidth=120,
+ )
self._character_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -376,59 +434,73 @@ class EditProfileWindow(ba.Window):
size=(b_size_2, b_size_2),
label='',
color=(1, 1, 1),
- mask_texture=ba.gettexture('characterIconMask'))
+ mask_texture=ba.gettexture('characterIconMask'),
+ )
if not self._is_account_profile and not self._global:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._text_field)
- ba.textwidget(parent=self._root_widget,
- h_align='center',
- v_align='center',
- position=(self._width * 0.5, v - 80),
- size=(0, 0),
- draw_controller=btn,
- text=ba.Lstr(resource=self._r + '.characterText'),
- scale=0.7,
- color=ba.app.ui.title_color,
- maxwidth=130)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._text_field
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ v_align='center',
+ position=(self._width * 0.5, v - 80),
+ size=(0, 0),
+ draw_controller=btn,
+ text=ba.Lstr(resource=self._r + '.characterText'),
+ scale=0.7,
+ color=ba.app.ui.title_color,
+ maxwidth=130,
+ )
self._highlight_button = btn = ba.buttonwidget(
parent=self._root_widget,
autoselect=True,
position=(self._width * 0.5 + b_offs - b_size * 0.5, v - 50),
- up_widget=self._upgrade_button if self._upgrade_button is not None
+ up_widget=self._upgrade_button
+ if self._upgrade_button is not None
else self._account_type_info_button,
size=(b_size, b_size),
color=self._highlight,
label='',
- button_type='square')
+ button_type='square',
+ )
if not self._is_account_profile and not self._global:
ba.widget(edit=cancel_button, down_widget=self._text_field)
ba.widget(edit=save_button, down_widget=self._text_field)
ba.widget(edit=self._color_button, up_widget=self._text_field)
- ba.widget(edit=self._account_type_info_button,
- down_widget=self._character_button)
+ ba.widget(
+ edit=self._account_type_info_button,
+ down_widget=self._character_button,
+ )
origin = self._highlight_button.get_screen_space_center()
- ba.buttonwidget(edit=self._highlight_button,
- on_activate_call=ba.WeakCall(self._make_picker,
- 'highlight', origin))
- ba.textwidget(parent=self._root_widget,
- h_align='center',
- v_align='center',
- position=(self._width * 0.5 + b_offs, v - 65),
- size=(0, 0),
- draw_controller=btn,
- text=ba.Lstr(resource=self._r + '.highlightText'),
- scale=0.7,
- color=ba.app.ui.title_color,
- maxwidth=120)
+ ba.buttonwidget(
+ edit=self._highlight_button,
+ on_activate_call=ba.WeakCall(
+ self._make_picker, 'highlight', origin
+ ),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ v_align='center',
+ position=(self._width * 0.5 + b_offs, v - 65),
+ size=(0, 0),
+ draw_controller=btn,
+ text=ba.Lstr(resource=self._r + '.highlightText'),
+ scale=0.7,
+ color=ba.app.ui.title_color,
+ maxwidth=120,
+ )
self._update_character()
def upgrade_profile(self) -> None:
"""Attempt to ugrade the profile to global."""
from bastd.ui import account
from bastd.ui.profile import upgrade as pupgrade
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
@@ -438,46 +510,64 @@ class EditProfileWindow(ba.Window):
def show_account_profile_info(self) -> None:
"""Show an explanation of account profiles."""
from bastd.ui.confirm import ConfirmWindow
- icons_str = ' '.join([
- ba.charstr(n) for n in [
- ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
- ba.SpecialChar.GAME_CENTER_LOGO,
- ba.SpecialChar.GAME_CIRCLE_LOGO, ba.SpecialChar.OUYA_LOGO,
- ba.SpecialChar.LOCAL_ACCOUNT, ba.SpecialChar.ALIBABA_LOGO,
- ba.SpecialChar.OCULUS_LOGO, ba.SpecialChar.NVIDIA_LOGO
+
+ icons_str = ' '.join(
+ [
+ ba.charstr(n)
+ for n in [
+ ba.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
+ ba.SpecialChar.GAME_CENTER_LOGO,
+ ba.SpecialChar.GAME_CIRCLE_LOGO,
+ ba.SpecialChar.OUYA_LOGO,
+ ba.SpecialChar.LOCAL_ACCOUNT,
+ ba.SpecialChar.ALIBABA_LOGO,
+ ba.SpecialChar.OCULUS_LOGO,
+ ba.SpecialChar.NVIDIA_LOGO,
+ ]
]
- ])
- txtl = ba.Lstr(resource='editProfileWindow.accountProfileInfoText',
- subs=[('${ICONS}', icons_str)])
- ConfirmWindow(txtl,
- cancel_button=False,
- width=500,
- height=300,
- origin_widget=self._account_type_info_button)
+ )
+ txtl = ba.Lstr(
+ resource='editProfileWindow.accountProfileInfoText',
+ subs=[('${ICONS}', icons_str)],
+ )
+ ConfirmWindow(
+ txtl,
+ cancel_button=False,
+ width=500,
+ height=300,
+ origin_widget=self._account_type_info_button,
+ )
def show_local_profile_info(self) -> None:
"""Show an explanation of local profiles."""
from bastd.ui.confirm import ConfirmWindow
+
txtl = ba.Lstr(resource='editProfileWindow.localProfileInfoText')
- ConfirmWindow(txtl,
- cancel_button=False,
- width=600,
- height=250,
- origin_widget=self._account_type_info_button)
+ ConfirmWindow(
+ txtl,
+ cancel_button=False,
+ width=600,
+ height=250,
+ origin_widget=self._account_type_info_button,
+ )
def show_global_profile_info(self) -> None:
"""Show an explanation of global profiles."""
from bastd.ui.confirm import ConfirmWindow
+
txtl = ba.Lstr(resource='editProfileWindow.globalProfileInfoText')
- ConfirmWindow(txtl,
- cancel_button=False,
- width=600,
- height=250,
- origin_widget=self._account_type_info_button)
+ ConfirmWindow(
+ txtl,
+ cancel_button=False,
+ width=600,
+ height=250,
+ origin_widget=self._account_type_info_button,
+ )
def refresh_characters(self) -> None:
"""Refresh available characters/icons."""
from bastd.actor import spazappearance
+
self._spazzes = spazappearance.get_appearances()
self._spazzes.sort()
self._icon_textures = [
@@ -501,33 +591,40 @@ class EditProfileWindow(ba.Window):
# The player could have bought a new one while the picker was up.
self.refresh_characters()
- self._icon_index = self._spazzes.index(
- character) if character in self._spazzes else 0
+ self._icon_index = (
+ self._spazzes.index(character) if character in self._spazzes else 0
+ )
self._update_character()
def _on_character_press(self) -> None:
from bastd.ui import characterpicker
+
characterpicker.CharacterPicker(
parent=self._root_widget,
position=self._character_button.get_screen_space_center(),
selected_character=self._spazzes[self._icon_index],
delegate=self,
tint_color=self._color,
- tint2_color=self._highlight)
+ tint2_color=self._highlight,
+ )
def _on_icon_press(self) -> None:
from bastd.ui import iconpicker
+
iconpicker.IconPicker(
parent=self._root_widget,
position=self._icon_button.get_screen_space_center(),
selected_icon=self._icon,
delegate=self,
tint_color=self._color,
- tint2_color=self._highlight)
+ tint2_color=self._highlight,
+ )
- def _make_picker(self, picker_type: str, origin: tuple[float,
- float]) -> None:
+ def _make_picker(
+ self, picker_type: str, origin: tuple[float, float]
+ ) -> None:
from bastd.ui import colorpicker
+
if picker_type == 'color':
initial_color = self._color
elif picker_type == 'highlight':
@@ -537,20 +634,26 @@ class EditProfileWindow(ba.Window):
colorpicker.ColorPicker(
parent=self._root_widget,
position=origin,
- offset=(self._base_scale *
- (-100 if picker_type == 'color' else 100), 0),
+ offset=(
+ self._base_scale * (-100 if picker_type == 'color' else 100),
+ 0,
+ ),
initial_color=initial_color,
delegate=self,
- tag=picker_type)
+ tag=picker_type,
+ )
def _cancel(self) -> None:
from bastd.ui.profile.browser import ProfileBrowserWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
ProfileBrowserWindow(
'in_left',
selected_profile=self._existing_profile,
- in_main_menu=self._in_main_menu).get_root_widget())
+ in_main_menu=self._in_main_menu,
+ ).get_root_widget()
+ )
def _set_color(self, color: tuple[float, float, float]) -> None:
self._color = color
@@ -568,16 +671,19 @@ class EditProfileWindow(ba.Window):
return
tag = picker.get_tag()
if tag == 'color':
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._color_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._color_button
+ )
elif tag == 'highlight':
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._highlight_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._highlight_button
+ )
else:
print('color_picker_closing got unknown tag ' + str(tag))
- def color_picker_selected_color(self, picker: ColorPicker,
- color: tuple[float, float, float]) -> None:
+ def color_picker_selected_color(
+ self, picker: ColorPicker, color: tuple[float, float, float]
+ ) -> None:
"""Called when a color is selected in a color picker."""
if not self._root_widget:
return
@@ -595,13 +701,19 @@ class EditProfileWindow(ba.Window):
return
name = self.getname()
if name == '__account__':
- name = (ba.internal.get_v1_account_name()
- if ba.internal.get_v1_account_state() == 'signed_in' else
- '???')
+ name = (
+ ba.internal.get_v1_account_name()
+ if ba.internal.get_v1_account_state() == 'signed_in'
+ else '???'
+ )
if len(name) > 10 and not (self._global or self._is_account_profile):
- ba.textwidget(edit=self._clipped_name_text,
- text=ba.Lstr(resource='inGameClippedNameText',
- subs=[('${NAME}', name[:10] + '...')]))
+ ba.textwidget(
+ edit=self._clipped_name_text,
+ text=ba.Lstr(
+ resource='inGameClippedNameText',
+ subs=[('${NAME}', name[:10] + '...')],
+ ),
+ )
else:
ba.textwidget(edit=self._clipped_name_text, text='')
@@ -613,7 +725,8 @@ class EditProfileWindow(ba.Window):
texture=self._icon_textures[self._icon_index],
tint_texture=self._icon_tint_textures[self._icon_index],
tint_color=self._color,
- tint2_color=self._highlight)
+ tint2_color=self._highlight,
+ )
def _update_icon(self) -> None:
if self._icon_button_label:
@@ -632,6 +745,7 @@ class EditProfileWindow(ba.Window):
def save(self, transition_out: bool = True) -> bool:
"""Save has been selected."""
from bastd.ui.profile.browser import ProfileBrowserWindow
+
new_name = self.getname().strip()
if not new_name:
@@ -644,26 +758,30 @@ class EditProfileWindow(ba.Window):
# Delete old in case we're renaming.
if self._existing_profile and self._existing_profile != new_name:
- ba.internal.add_transaction({
- 'type': 'REMOVE_PLAYER_PROFILE',
- 'name': self._existing_profile
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'REMOVE_PLAYER_PROFILE',
+ 'name': self._existing_profile,
+ }
+ )
# Also lets be aware we're no longer global if we're taking a
# new name (will need to re-request it).
self._global = False
- ba.internal.add_transaction({
- 'type': 'ADD_PLAYER_PROFILE',
- 'name': new_name,
- 'profile': {
- 'character': self._spazzes[self._icon_index],
- 'color': list(self._color),
- 'global': self._global,
- 'icon': self._icon,
- 'highlight': list(self._highlight)
+ ba.internal.add_transaction(
+ {
+ 'type': 'ADD_PLAYER_PROFILE',
+ 'name': new_name,
+ 'profile': {
+ 'character': self._spazzes[self._icon_index],
+ 'color': list(self._color),
+ 'global': self._global,
+ 'icon': self._icon,
+ 'highlight': list(self._highlight),
+ },
}
- })
+ )
if transition_out:
ba.internal.run_transactions()
@@ -672,5 +790,7 @@ class EditProfileWindow(ba.Window):
ProfileBrowserWindow(
'in_left',
selected_profile=new_name,
- in_main_menu=self._in_main_menu).get_root_widget())
+ in_main_menu=self._in_main_menu,
+ ).get_root_widget()
+ )
return True
diff --git a/assets/src/ba_data/python/bastd/ui/profile/upgrade.py b/assets/src/ba_data/python/bastd/ui/profile/upgrade.py
index ffce43ee..e4a30f62 100644
--- a/assets/src/ba_data/python/bastd/ui/profile/upgrade.py
+++ b/assets/src/ba_data/python/bastd/ui/profile/upgrade.py
@@ -19,35 +19,48 @@ if TYPE_CHECKING:
class ProfileUpgradeWindow(ba.Window):
"""Window for player profile upgrades to global."""
- def __init__(self,
- edit_profile_window: EditProfileWindow,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ edit_profile_window: EditProfileWindow,
+ transition: str = 'in_right',
+ ):
from ba.internal import master_server_get
+
self._r = 'editProfileWindow'
self._width = 680
self._height = 350
uiscale = ba.app.ui.uiscale
- self._base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.2)
+ self._base_scale = (
+ 2.05
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.2
+ )
self._upgrade_start_time: float | None = None
self._name = edit_profile_window.getname()
self._edit_profile_window = weakref.ref(edit_profile_window)
top_extra = 15 if uiscale is ba.UIScale.SMALL else 15
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- toolbar_visibility='menu_currency',
- transition=transition,
- scale=self._base_scale,
- stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0)))
- cancel_button = ba.buttonwidget(parent=self._root_widget,
- position=(52, 30),
- size=(155, 60),
- scale=0.8,
- autoselect=True,
- label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._cancel)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ toolbar_visibility='menu_currency',
+ transition=transition,
+ scale=self._base_scale,
+ stack_offset=(0, 15) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
+ cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(52, 30),
+ size=(155, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(resource='cancelText'),
+ on_activate_call=self._cancel,
+ )
self._upgrade_button = ba.buttonwidget(
parent=self._root_widget,
position=(self._width - 190, 30),
@@ -55,56 +68,65 @@ class ProfileUpgradeWindow(ba.Window):
scale=0.8,
autoselect=True,
label=ba.Lstr(resource='upgradeText'),
- on_activate_call=self._on_upgrade_press)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=cancel_button,
- start_button=self._upgrade_button,
- selected_child=self._upgrade_button)
+ on_activate_call=self._on_upgrade_press,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=cancel_button,
+ start_button=self._upgrade_button,
+ selected_child=self._upgrade_button,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 38),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.upgradeToGlobalProfileText'),
- color=ba.app.ui.title_color,
- maxwidth=self._width * 0.45,
- scale=1.0,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 38),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.upgradeToGlobalProfileText'),
+ color=ba.app.ui.title_color,
+ maxwidth=self._width * 0.45,
+ scale=1.0,
+ h_align='center',
+ v_align='center',
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 100),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.upgradeProfileInfoText'),
- color=ba.app.ui.infotextcolor,
- maxwidth=self._width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 100),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.upgradeProfileInfoText'),
+ color=ba.app.ui.infotextcolor,
+ maxwidth=self._width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
self._status_text = ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 160),
size=(0, 0),
- text=ba.Lstr(resource=self._r + '.checkingAvailabilityText',
- subs=[('${NAME}', self._name)]),
+ text=ba.Lstr(
+ resource=self._r + '.checkingAvailabilityText',
+ subs=[('${NAME}', self._name)],
+ ),
color=(0.8, 0.4, 0.0),
maxwidth=self._width * 0.8,
scale=0.65,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
- self._price_text = ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- self._height - 230),
- size=(0, 0),
- text='',
- color=(0.2, 1, 0.2),
- maxwidth=self._width * 0.8,
- scale=1.5,
- h_align='center',
- v_align='center')
+ self._price_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 230),
+ size=(0, 0),
+ text='',
+ color=(0.2, 1, 0.2),
+ maxwidth=self._width * 0.8,
+ scale=1.5,
+ h_align='center',
+ v_align='center',
+ )
self._tickets_text: ba.Widget | None
if not ba.app.ui.use_toolbars:
@@ -117,22 +139,26 @@ class ProfileUpgradeWindow(ba.Window):
maxwidth=100,
scale=0.5,
h_align='right',
- v_align='center')
+ v_align='center',
+ )
else:
self._tickets_text = None
- master_server_get('bsGlobalProfileCheck', {
- 'name': self._name,
- 'b': ba.app.build_number
- },
- callback=ba.WeakCall(self._profile_check_result))
+ master_server_get(
+ 'bsGlobalProfileCheck',
+ {'name': self._name, 'b': ba.app.build_number},
+ callback=ba.WeakCall(self._profile_check_result),
+ )
self._cost = ba.internal.get_v1_account_misc_read_val(
- 'price.global_profile', 500)
+ 'price.global_profile', 500
+ )
self._status: str | None = 'waiting'
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
def _profile_check_result(self, result: dict[str, Any] | None) -> None:
@@ -140,34 +166,48 @@ class ProfileUpgradeWindow(ba.Window):
ba.textwidget(
edit=self._status_text,
text=ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
self._status = 'error'
- ba.buttonwidget(edit=self._upgrade_button,
- color=(0.4, 0.4, 0.4),
- textcolor=(0.5, 0.5, 0.5))
+ ba.buttonwidget(
+ edit=self._upgrade_button,
+ color=(0.4, 0.4, 0.4),
+ textcolor=(0.5, 0.5, 0.5),
+ )
else:
if result['available']:
- ba.textwidget(edit=self._status_text,
- text=ba.Lstr(resource=self._r + '.availableText',
- subs=[('${NAME}', self._name)]),
- color=(0, 1, 0))
- ba.textwidget(edit=self._price_text,
- text=ba.charstr(ba.SpecialChar.TICKET) +
- str(self._cost))
+ ba.textwidget(
+ edit=self._status_text,
+ text=ba.Lstr(
+ resource=self._r + '.availableText',
+ subs=[('${NAME}', self._name)],
+ ),
+ color=(0, 1, 0),
+ )
+ ba.textwidget(
+ edit=self._price_text,
+ text=ba.charstr(ba.SpecialChar.TICKET) + str(self._cost),
+ )
self._status = None
else:
- ba.textwidget(edit=self._status_text,
- text=ba.Lstr(resource=self._r +
- '.unavailableText',
- subs=[('${NAME}', self._name)]),
- color=(1, 0, 0))
+ ba.textwidget(
+ edit=self._status_text,
+ text=ba.Lstr(
+ resource=self._r + '.unavailableText',
+ subs=[('${NAME}', self._name)],
+ ),
+ color=(1, 0, 0),
+ )
self._status = 'unavailable'
- ba.buttonwidget(edit=self._upgrade_button,
- color=(0.4, 0.4, 0.4),
- textcolor=(0.5, 0.5, 0.5))
+ ba.buttonwidget(
+ edit=self._upgrade_button,
+ color=(0.4, 0.4, 0.4),
+ textcolor=(0.5, 0.5, 0.5),
+ )
def _on_upgrade_press(self) -> None:
from bastd.ui import getcurrency
+
if self._status is None:
# If it appears we don't have enough tickets, offer to buy more.
tickets = ba.internal.get_v1_account_ticket_count()
@@ -175,8 +215,9 @@ class ProfileUpgradeWindow(ba.Window):
ba.playsound(ba.getsound('error'))
getcurrency.show_get_tickets_prompt()
return
- ba.screenmessage(ba.Lstr(resource='purchasingText'),
- color=(0, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='purchasingText'), color=(0, 1, 0)
+ )
self._status = 'pre_upgrading'
# Now we tell the original editor to save the profile, add an
@@ -189,14 +230,12 @@ class ProfileUpgradeWindow(ba.Window):
success = edit_profile_window.save(transition_out=False)
if not success:
print('profile upgrade: error occurred saving profile')
- ba.screenmessage(ba.Lstr(resource='errorText'),
- color=(1, 0, 0))
+ ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0))
ba.playsound(ba.getsound('error'))
return
- ba.internal.add_transaction({
- 'type': 'UPGRADE_PROFILE',
- 'name': self._name
- })
+ ba.internal.add_transaction(
+ {'type': 'UPGRADE_PROFILE', 'name': self._name}
+ )
ba.internal.run_transactions()
self._status = 'upgrading'
self._upgrade_start_time = time.time()
@@ -209,23 +248,30 @@ class ProfileUpgradeWindow(ba.Window):
except Exception:
t_str = '?'
if self._tickets_text is not None:
- ba.textwidget(edit=self._tickets_text,
- text=ba.Lstr(
- resource='getTicketsWindow.youHaveShortText',
- subs=[('${COUNT}',
- ba.charstr(ba.SpecialChar.TICKET) + t_str)
- ]))
+ ba.textwidget(
+ edit=self._tickets_text,
+ text=ba.Lstr(
+ resource='getTicketsWindow.youHaveShortText',
+ subs=[
+ ('${COUNT}', ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ ],
+ ),
+ )
# Once we've kicked off an upgrade attempt and all transactions go
# through, we're done.
- if (self._status == 'upgrading'
- and not ba.internal.have_outstanding_transactions()):
+ if (
+ self._status == 'upgrading'
+ and not ba.internal.have_outstanding_transactions()
+ ):
self._status = 'exiting'
ba.containerwidget(edit=self._root_widget, transition='out_right')
edit_profile_window = self._edit_profile_window()
if edit_profile_window is None:
- print('profile upgrade transition out:'
- ' original edit window gone')
+ print(
+ 'profile upgrade transition out:'
+ ' original edit window gone'
+ )
return
ba.playsound(ba.getsound('gunCocking'))
edit_profile_window.reload_window()
@@ -233,8 +279,10 @@ class ProfileUpgradeWindow(ba.Window):
def _cancel(self) -> None:
# If we recently sent out an upgrade request, disallow canceling
# for a bit.
- if (self._upgrade_start_time is not None
- and time.time() - self._upgrade_start_time < 10.0):
+ if (
+ self._upgrade_start_time is not None
+ and time.time() - self._upgrade_start_time < 10.0
+ ):
ba.playsound(ba.getsound('error'))
return
ba.containerwidget(edit=self._root_widget, transition='out_right')
diff --git a/assets/src/ba_data/python/bastd/ui/promocode.py b/assets/src/ba_data/python/bastd/ui/promocode.py
index 78de4978..1096ec12 100644
--- a/assets/src/ba_data/python/bastd/ui/promocode.py
+++ b/assets/src/ba_data/python/bastd/ui/promocode.py
@@ -17,9 +17,9 @@ if TYPE_CHECKING:
class PromoCodeWindow(ba.Window):
"""Window for entering promo codes."""
- def __init__(self,
- modal: bool = False,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self, modal: bool = False, origin_widget: ba.Widget | None = None
+ ):
scale_origin: tuple[float, float] | None
if origin_widget is not None:
@@ -38,31 +38,43 @@ class PromoCodeWindow(ba.Window):
self._r = 'promoCodeWindow'
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal_no_back',
- scale_origin_stack_offset=scale_origin,
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ toolbar_visibility='menu_minimal_no_back',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
- btn = ba.buttonwidget(parent=self._root_widget,
- scale=0.5,
- position=(40, height - 40),
- size=(60, 60),
- label='',
- on_activate_call=self._do_back,
- autoselect=True,
- color=(0.55, 0.5, 0.6),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.5,
+ position=(40, height - 40),
+ size=(60, 60),
+ label='',
+ on_activate_call=self._do_back,
+ autoselect=True,
+ color=(0.55, 0.5, 0.6),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
- ba.textwidget(parent=self._root_widget,
- text=ba.Lstr(resource=self._r + '.codeText'),
- position=(22, height - 113),
- color=(0.8, 0.8, 0.8, 1.0),
- size=(90, 30),
- h_align='right')
+ ba.textwidget(
+ parent=self._root_widget,
+ text=ba.Lstr(resource=self._r + '.codeText'),
+ position=(22, height - 113),
+ color=(0.8, 0.8, 0.8, 1.0),
+ size=(90, 30),
+ h_align='right',
+ )
self._text_field = ba.textwidget(
parent=self._root_widget,
position=(125, height - 121),
@@ -75,7 +87,8 @@ class PromoCodeWindow(ba.Window):
description=ba.Lstr(resource=self._r + '.codeText'),
editable=True,
padding=4,
- on_return_press_call=self._activate_enter_button)
+ on_return_press_call=self._activate_enter_button,
+ )
ba.widget(edit=btn, down_widget=self._text_field)
b_width = 200
@@ -84,22 +97,29 @@ class PromoCodeWindow(ba.Window):
position=(width * 0.5 - b_width * 0.5, height - 200),
size=(b_width, 60),
scale=1.0,
- label=ba.Lstr(resource='submitText',
- fallback_resource=self._r + '.enterText'),
- on_activate_call=self._do_enter)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=btn,
- start_button=btn2,
- selected_child=self._text_field)
+ label=ba.Lstr(
+ resource='submitText', fallback_resource=self._r + '.enterText'
+ ),
+ on_activate_call=self._do_enter,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=btn,
+ start_button=btn2,
+ selected_child=self._text_field,
+ )
def _do_back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
ba.app.ui.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget())
+ AdvancedSettingsWindow(transition='in_left').get_root_widget()
+ )
def _activate_enter_button(self) -> None:
self._enter_button.activate()
@@ -107,14 +127,19 @@ class PromoCodeWindow(ba.Window):
def _do_enter(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
ba.app.ui.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget())
- ba.internal.add_transaction({
- 'type': 'PROMO_CODE',
- 'expire_time': time.time() + 5,
- 'code': ba.textwidget(query=self._text_field)
- })
+ AdvancedSettingsWindow(transition='in_left').get_root_widget()
+ )
+ ba.internal.add_transaction(
+ {
+ 'type': 'PROMO_CODE',
+ 'expire_time': time.time() + 5,
+ 'code': ba.textwidget(query=self._text_field),
+ }
+ )
ba.internal.run_transactions()
diff --git a/assets/src/ba_data/python/bastd/ui/purchase.py b/assets/src/ba_data/python/bastd/ui/purchase.py
index d28fa5ac..8bcdd097 100644
--- a/assets/src/ba_data/python/bastd/ui/purchase.py
+++ b/assets/src/ba_data/python/bastd/ui/purchase.py
@@ -16,52 +16,75 @@ if TYPE_CHECKING:
class PurchaseWindow(ba.Window):
"""Window for purchasing one or more items."""
- def __init__(self,
- items: list[str],
- transition: str = 'in_right',
- header_text: ba.Lstr | None = None):
+ def __init__(
+ self,
+ items: list[str],
+ transition: str = 'in_right',
+ header_text: ba.Lstr | None = None,
+ ):
from ba.internal import get_store_item_display_size
from bastd.ui.store import item as storeitemui
+
if header_text is None:
- header_text = ba.Lstr(resource='unlockThisText',
- fallback_resource='unlockThisInTheStoreText')
+ header_text = ba.Lstr(
+ resource='unlockThisText',
+ fallback_resource='unlockThisInTheStoreText',
+ )
if len(items) != 1:
raise ValueError('expected exactly 1 item')
self._items = list(items)
self._width = 580
self._height = 520
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- toolbar_visibility='menu_currency',
- scale=(1.2 if uiscale is ba.UIScale.SMALL else
- 1.1 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ toolbar_visibility='menu_currency',
+ scale=(
+ 1.2
+ if uiscale is ba.UIScale.SMALL
+ else 1.1
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -15)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._is_double = False
- self._title_text = ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- self._height - 30),
- size=(0, 0),
- text=header_text,
- h_align='center',
- v_align='center',
- maxwidth=self._width * 0.9 - 120,
- scale=1.2,
- color=(1, 0.8, 0.3, 1))
+ self._title_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 30),
+ size=(0, 0),
+ text=header_text,
+ h_align='center',
+ v_align='center',
+ maxwidth=self._width * 0.9 - 120,
+ scale=1.2,
+ color=(1, 0.8, 0.3, 1),
+ )
size = get_store_item_display_size(items[0])
display: dict[str, Any] = {}
storeitemui.instantiate_store_item_display(
items[0],
display,
parent_widget=self._root_widget,
- b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 -
- ((size[0] * 0.5 + 30) if self._is_double else 0),
- self._height * 0.5 - size[1] * 0.5 + 30 +
- (20 if self._is_double else 0)),
+ b_pos=(
+ self._width * 0.5
+ - size[0] * 0.5
+ + 10
+ - ((size[0] * 0.5 + 30) if self._is_double else 0),
+ self._height * 0.5
+ - size[1] * 0.5
+ + 30
+ + (20 if self._is_double else 0),
+ ),
b_width=size[0],
b_height=size[1],
- button=False)
+ button=False,
+ )
# Wire up the parts we need.
if self._is_double:
@@ -73,23 +96,27 @@ class PurchaseWindow(ba.Window):
else:
pyoffs = 0
price = self._price = ba.internal.get_v1_account_misc_read_val(
- 'price.' + str(items[0]), -1)
+ 'price.' + str(items[0]), -1
+ )
price_str = ba.charstr(ba.SpecialChar.TICKET) + str(price)
- self._price_text = ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- 150 + pyoffs),
- size=(0, 0),
- text=price_str,
- h_align='center',
- v_align='center',
- maxwidth=self._width * 0.9,
- scale=1.4,
- color=(0.2, 1, 0.2))
+ self._price_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, 150 + pyoffs),
+ size=(0, 0),
+ text=price_str,
+ h_align='center',
+ v_align='center',
+ maxwidth=self._width * 0.9,
+ scale=1.4,
+ color=(0.2, 1, 0.2),
+ )
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._cancel_button = ba.buttonwidget(
parent=self._root_widget,
@@ -98,7 +125,8 @@ class PurchaseWindow(ba.Window):
scale=1.0,
on_activate_call=self._cancel,
autoselect=True,
- label=ba.Lstr(resource='cancelText'))
+ label=ba.Lstr(resource='cancelText'),
+ )
self._purchase_button = ba.buttonwidget(
parent=self._root_widget,
position=(self._width - 200, 40),
@@ -106,12 +134,15 @@ class PurchaseWindow(ba.Window):
scale=1.0,
on_activate_call=self._purchase,
autoselect=True,
- label=ba.Lstr(resource='store.purchaseText'))
+ label=ba.Lstr(resource='store.purchaseText'),
+ )
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button,
- start_button=self._purchase_button,
- selected_child=self._purchase_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._cancel_button,
+ start_button=self._purchase_button,
+ selected_child=self._purchase_button,
+ )
def _update(self) -> None:
can_die = False
@@ -129,6 +160,7 @@ class PurchaseWindow(ba.Window):
def _purchase(self) -> None:
from bastd.ui import getcurrency
+
if self._items == ['pro']:
ba.internal.purchase('pro')
else:
diff --git a/assets/src/ba_data/python/bastd/ui/qrcode.py b/assets/src/ba_data/python/bastd/ui/qrcode.py
index 16efd5db..76e5de26 100644
--- a/assets/src/ba_data/python/bastd/ui/qrcode.py
+++ b/assets/src/ba_data/python/bastd/ui/qrcode.py
@@ -14,17 +14,24 @@ class QRCodeWindow(popup.PopupWindow):
position = origin_widget.get_screen_space_center()
uiscale = ba.app.ui.uiscale
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 450
self._height = 400
bg_color = (0.5, 0.4, 0.6)
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color)
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
position=(50, self._height - 30),
@@ -35,12 +42,14 @@ class QRCodeWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.imagewidget(parent=self.root_widget,
- position=(self._width * 0.5 - 150,
- self._height * 0.5 - 150),
- size=(300, 300),
- texture=qr_tex)
+ iconscale=1.2,
+ )
+ ba.imagewidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5 - 150, self._height * 0.5 - 150),
+ size=(300, 300),
+ texture=qr_tex,
+ )
def _on_cancel_press(self) -> None:
self._transition_out()
diff --git a/assets/src/ba_data/python/bastd/ui/radiogroup.py b/assets/src/ba_data/python/bastd/ui/radiogroup.py
index 39f2aff7..1cc8301f 100644
--- a/assets/src/ba_data/python/bastd/ui/radiogroup.py
+++ b/assets/src/ba_data/python/bastd/ui/radiogroup.py
@@ -12,22 +12,30 @@ if TYPE_CHECKING:
from typing import Any, Callable, Sequence
-def make_radio_group(check_boxes: Sequence[ba.Widget],
- value_names: Sequence[str], value: str,
- value_change_call: Callable[[str], Any]) -> None:
+def make_radio_group(
+ check_boxes: Sequence[ba.Widget],
+ value_names: Sequence[str],
+ value: str,
+ value_change_call: Callable[[str], Any],
+) -> None:
"""Link the provided check_boxes together into a radio group."""
- def _radio_press(check_string: str, other_check_boxes: list[ba.Widget],
- val: int) -> None:
+ def _radio_press(
+ check_string: str, other_check_boxes: list[ba.Widget], val: int
+ ) -> None:
if val == 1:
value_change_call(check_string)
for cbx in other_check_boxes:
ba.checkboxwidget(edit=cbx, value=False)
for i, check_box in enumerate(check_boxes):
- ba.checkboxwidget(edit=check_box,
- value=(value == value_names[i]),
- is_radio_button=True,
- on_value_change_call=ba.Call(
- _radio_press, value_names[i],
- [c for c in check_boxes if c != check_box]))
+ ba.checkboxwidget(
+ edit=check_box,
+ value=(value == value_names[i]),
+ is_radio_button=True,
+ on_value_change_call=ba.Call(
+ _radio_press,
+ value_names[i],
+ [c for c in check_boxes if c != check_box],
+ ),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/report.py b/assets/src/ba_data/python/bastd/ui/report.py
index 1f322ca4..1a760434 100644
--- a/assets/src/ba_data/python/bastd/ui/report.py
+++ b/assets/src/ba_data/python/bastd/ui/report.py
@@ -20,71 +20,102 @@ class ReportPlayerWindow(ba.Window):
overlay_stack = ba.internal.get_special_widget('overlay_stack')
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- parent=overlay_stack,
- transition='in_scale',
- scale_origin_stack_offset=scale_origin,
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
- self._cancel_button = ba.buttonwidget(parent=self._root_widget,
- scale=0.7,
- position=(40, self._height - 50),
- size=(50, 50),
- label='',
- on_activate_call=self.close,
- autoselect=True,
- color=(0.4, 0.4, 0.5),
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.64),
- size=(0, 0),
- color=(1, 1, 1, 0.8),
- scale=1.2,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='reportThisPlayerReasonText'),
- maxwidth=self._width * 0.85)
- ba.buttonwidget(parent=self._root_widget,
- size=(235, 60),
- position=(20, 30),
- label=ba.Lstr(resource='reportThisPlayerLanguageText'),
- on_activate_call=self._on_language_press,
- autoselect=True)
- ba.buttonwidget(parent=self._root_widget,
- size=(235, 60),
- position=(self._width - 255, 30),
- label=ba.Lstr(resource='reportThisPlayerCheatingText'),
- on_activate_call=self._on_cheating_press,
- autoselect=True)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ parent=overlay_stack,
+ transition='in_scale',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
+ self._cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ scale=0.7,
+ position=(40, self._height - 50),
+ size=(50, 50),
+ label='',
+ on_activate_call=self.close,
+ autoselect=True,
+ color=(0.4, 0.4, 0.5),
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.64),
+ size=(0, 0),
+ color=(1, 1, 1, 0.8),
+ scale=1.2,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='reportThisPlayerReasonText'),
+ maxwidth=self._width * 0.85,
+ )
+ ba.buttonwidget(
+ parent=self._root_widget,
+ size=(235, 60),
+ position=(20, 30),
+ label=ba.Lstr(resource='reportThisPlayerLanguageText'),
+ on_activate_call=self._on_language_press,
+ autoselect=True,
+ )
+ ba.buttonwidget(
+ parent=self._root_widget,
+ size=(235, 60),
+ position=(self._width - 255, 30),
+ label=ba.Lstr(resource='reportThisPlayerCheatingText'),
+ on_activate_call=self._on_cheating_press,
+ autoselect=True,
+ )
def _on_language_press(self) -> None:
from urllib import parse
- ba.internal.add_transaction({
- 'type': 'REPORT_ACCOUNT',
- 'reason': 'language',
- 'account': self._account_id
- })
+
+ ba.internal.add_transaction(
+ {
+ 'type': 'REPORT_ACCOUNT',
+ 'reason': 'language',
+ 'account': self._account_id,
+ }
+ )
body = ba.Lstr(resource='reportPlayerExplanationText').evaluate()
- ba.open_url('mailto:support@froemling.net'
- f'?subject={ba.internal.appnameupper()} Player Report: ' +
- self._account_id + '&body=' + parse.quote(body))
+ ba.open_url(
+ 'mailto:support@froemling.net'
+ f'?subject={ba.internal.appnameupper()} Player Report: '
+ + self._account_id
+ + '&body='
+ + parse.quote(body)
+ )
self.close()
def _on_cheating_press(self) -> None:
from urllib import parse
- ba.internal.add_transaction({
- 'type': 'REPORT_ACCOUNT',
- 'reason': 'cheating',
- 'account': self._account_id
- })
+
+ ba.internal.add_transaction(
+ {
+ 'type': 'REPORT_ACCOUNT',
+ 'reason': 'cheating',
+ 'account': self._account_id,
+ }
+ )
body = ba.Lstr(resource='reportPlayerExplanationText').evaluate()
- ba.open_url('mailto:support@froemling.net'
- f'?subject={ba.internal.appnameupper()} Player Report: ' +
- self._account_id + '&body=' + parse.quote(body))
+ ba.open_url(
+ 'mailto:support@froemling.net'
+ f'?subject={ba.internal.appnameupper()} Player Report: '
+ + self._account_id
+ + '&body='
+ + parse.quote(body)
+ )
self.close()
def close(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/resourcetypeinfo.py b/assets/src/ba_data/python/bastd/ui/resourcetypeinfo.py
index 2013f5c4..2bd829bd 100644
--- a/assets/src/ba_data/python/bastd/ui/resourcetypeinfo.py
+++ b/assets/src/ba_data/python/bastd/ui/resourcetypeinfo.py
@@ -13,8 +13,13 @@ class ResourceTypeInfoWindow(popup.PopupWindow):
def __init__(self, origin_widget: ba.Widget):
uiscale = ba.app.ui.uiscale
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 570
self._height = 350
@@ -25,7 +30,8 @@ class ResourceTypeInfoWindow(popup.PopupWindow):
toolbar_visibility='inherit',
scale=scale,
bg_color=bg_color,
- position=origin_widget.get_screen_space_center())
+ position=origin_widget.get_screen_space_center(),
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
position=(50, self._height - 30),
@@ -36,7 +42,8 @@ class ResourceTypeInfoWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
def _on_cancel_press(self) -> None:
self._transition_out()
diff --git a/assets/src/ba_data/python/bastd/ui/serverdialog.py b/assets/src/ba_data/python/bastd/ui/serverdialog.py
index 86099822..6f3ec3c8 100644
--- a/assets/src/ba_data/python/bastd/ui/serverdialog.py
+++ b/assets/src/ba_data/python/bastd/ui/serverdialog.py
@@ -21,10 +21,12 @@ if TYPE_CHECKING:
@dataclass
class ServerDialogData:
"""Data for ServerDialog."""
+
dialog_id: Annotated[str, IOAttrs('dialogID')]
text: Annotated[str, IOAttrs('text')]
- subs: Annotated[list[tuple[str, str]],
- IOAttrs('subs')] = field(default_factory=list)
+ subs: Annotated[list[tuple[str, str]], IOAttrs('subs')] = field(
+ default_factory=list
+ )
show_cancel: Annotated[bool, IOAttrs('showCancel')] = True
copy_text: Annotated[str | None, IOAttrs('copyText')] = None
@@ -34,35 +36,46 @@ class ServerDialogWindow(ba.Window):
def __init__(self, data: ServerDialogData):
self._data = data
- txt = ba.Lstr(translate=('serverResponses', data.text),
- subs=data.subs).evaluate()
+ txt = ba.Lstr(
+ translate=('serverResponses', data.text), subs=data.subs
+ ).evaluate()
txt = txt.strip()
txt_scale = 1.5
txt_height = (
- ba.internal.get_string_height(txt, suppress_warning=True) *
- txt_scale)
+ ba.internal.get_string_height(txt, suppress_warning=True)
+ * txt_scale
+ )
self._width = 500
self._height = 160 + min(200, txt_height)
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_scale',
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_scale',
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
self._starttime = ba.time(ba.TimeType.REAL)
ba.playsound(ba.getsound('swish'))
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- 70 + (self._height - 70) * 0.5),
- size=(0, 0),
- color=(1.0, 3.0, 1.0),
- scale=txt_scale,
- h_align='center',
- v_align='center',
- text=txt,
- maxwidth=self._width * 0.85,
- max_height=(self._height - 110))
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, 70 + (self._height - 70) * 0.5),
+ size=(0, 0),
+ color=(1.0, 3.0, 1.0),
+ scale=txt_scale,
+ h_align='center',
+ v_align='center',
+ text=txt,
+ maxwidth=self._width * 0.85,
+ max_height=(self._height - 110),
+ )
show_copy = data.copy_text is not None and ba.clipboard_is_supported()
@@ -70,41 +83,58 @@ class ServerDialogWindow(ba.Window):
# spot. Cancel wins in this case since it is important functionality
# and copy is just for convenience (and not even always available).
if show_copy and data.show_cancel:
- logging.warning('serverdialog requesting both copy and cancel;'
- ' copy will not be shown.')
+ logging.warning(
+ 'serverdialog requesting both copy and cancel;'
+ ' copy will not be shown.'
+ )
show_copy = False
- self._cancel_button = (None
- if not data.show_cancel else ba.buttonwidget(
- parent=self._root_widget,
- position=(30, 30),
- size=(160, 60),
- autoselect=True,
- label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._cancel_press))
+ self._cancel_button = (
+ None
+ if not data.show_cancel
+ else ba.buttonwidget(
+ parent=self._root_widget,
+ position=(30, 30),
+ size=(160, 60),
+ autoselect=True,
+ label=ba.Lstr(resource='cancelText'),
+ on_activate_call=self._cancel_press,
+ )
+ )
- self._copy_button = None if not show_copy else ba.buttonwidget(
- parent=self._root_widget,
- position=(30, 30),
- size=(160, 60),
- autoselect=True,
- label=ba.Lstr(resource='copyText'),
- on_activate_call=self._copy_press)
+ self._copy_button = (
+ None
+ if not show_copy
+ else ba.buttonwidget(
+ parent=self._root_widget,
+ position=(30, 30),
+ size=(160, 60),
+ autoselect=True,
+ label=ba.Lstr(resource='copyText'),
+ on_activate_call=self._copy_press,
+ )
+ )
self._ok_button = ba.buttonwidget(
parent=self._root_widget,
- position=((self._width - 182) if
- (data.show_cancel or show_copy) else
- (self._width * 0.5 - 80), 30),
+ position=(
+ (self._width - 182)
+ if (data.show_cancel or show_copy)
+ else (self._width * 0.5 - 80),
+ 30,
+ ),
size=(160, 60),
autoselect=True,
label=ba.Lstr(resource='okText'),
- on_activate_call=self._ok_press)
+ on_activate_call=self._ok_press,
+ )
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button,
- start_button=self._ok_button,
- selected_child=self._ok_button)
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._cancel_button,
+ start_button=self._ok_button,
+ selected_child=self._ok_button,
+ )
def _copy_press(self) -> None:
assert self._data.copy_text is not None
@@ -115,20 +145,24 @@ class ServerDialogWindow(ba.Window):
if ba.time(ba.TimeType.REAL) - self._starttime < 1.0:
ba.playsound(ba.getsound('error'))
return
- ba.internal.add_transaction({
- 'type': 'DIALOG_RESPONSE',
- 'dialogID': self._data.dialog_id,
- 'response': 1
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'DIALOG_RESPONSE',
+ 'dialogID': self._data.dialog_id,
+ 'response': 1,
+ }
+ )
ba.containerwidget(edit=self._root_widget, transition='out_scale')
def _cancel_press(self) -> None:
if ba.time(ba.TimeType.REAL) - self._starttime < 1.0:
ba.playsound(ba.getsound('error'))
return
- ba.internal.add_transaction({
- 'type': 'DIALOG_RESPONSE',
- 'dialogID': self._data.dialog_id,
- 'response': 0
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'DIALOG_RESPONSE',
+ 'dialogID': self._data.dialog_id,
+ 'response': 0,
+ }
+ )
ba.containerwidget(edit=self._root_widget, transition='out_scale')
diff --git a/assets/src/ba_data/python/bastd/ui/settings/advanced.py b/assets/src/ba_data/python/bastd/ui/settings/advanced.py
index 8a85f12a..940cd161 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/advanced.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/advanced.py
@@ -17,9 +17,11 @@ if TYPE_CHECKING:
class AdvancedSettingsWindow(ba.Window):
"""Window for editing advanced game settings."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
from ba.internal import master_server_get
import threading
@@ -43,19 +45,34 @@ class AdvancedSettingsWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (390.0 if uiscale is ba.UIScale.SMALL else
- 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0)
+ self._height = (
+ 390.0
+ if uiscale is ba.UIScale.SMALL
+ else 450.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 520.0
+ )
self._spacing = 32
self._menu_open = False
top_extra = 10 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(2.06 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.06
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -25)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._prev_lang = ''
self._prev_lang_list: list[str] = []
@@ -65,8 +82,9 @@ class AdvancedSettingsWindow(ba.Window):
# In vr-mode, the internal keyboard is currently the *only* option,
# so no need to show this.
- self._show_always_use_internal_keyboard = (not app.vr_mode
- and not app.iircade_mode)
+ self._show_always_use_internal_keyboard = (
+ not app.vr_mode and not app.iircade_mode
+ )
self._scroll_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - 115.0
@@ -93,8 +111,9 @@ class AdvancedSettingsWindow(ba.Window):
self._r = 'settingsWindowAdvanced'
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._do_back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._do_back
+ )
self._back_button = None
else:
self._back_button = ba.buttonwidget(
@@ -105,50 +124,62 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button)
+ on_activate_call=self._do_back,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
- self._title_text = ba.textwidget(parent=self._root_widget,
- position=(0, self._height - 52),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r +
- '.titleText'),
- color=app.ui.title_color,
- h_align='center',
- v_align='top')
+ self._title_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(0, self._height - 52),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=app.ui.title_color,
+ h_align='center',
+ v_align='top',
+ )
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(50 + x_inset, 50),
- simple_culling_v=20.0,
- highlight=False,
- size=(self._scroll_width,
- self._scroll_height),
- selection_loops_to_parent=True)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(50 + x_inset, 50),
+ simple_culling_v=20.0,
+ highlight=False,
+ size=(self._scroll_width, self._scroll_height),
+ selection_loops_to_parent=True,
+ )
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False,
- selection_loops_to_parent=True)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ selection_loops_to_parent=True,
+ )
self._rebuild()
# Rebuild periodically to pick up language changes/additions/etc.
- self._rebuild_timer = ba.Timer(1.0,
- ba.WeakCall(self._rebuild),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._rebuild_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._rebuild),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
# Fetch the list of completed languages.
- master_server_get('bsLangGetCompleted', {'b': app.build_number},
- callback=ba.WeakCall(self._completed_langs_cb))
+ master_server_get(
+ 'bsLangGetCompleted',
+ {'b': app.build_number},
+ callback=ba.WeakCall(self._completed_langs_cb),
+ )
# noinspection PyUnresolvedReferences
@staticmethod
@@ -166,23 +197,32 @@ class AdvancedSettingsWindow(ba.Window):
def _update_lang_status(self) -> None:
if self._complete_langs_list is not None:
- up_to_date = (ba.app.lang.language in self._complete_langs_list)
+ up_to_date = ba.app.lang.language in self._complete_langs_list
ba.textwidget(
edit=self._lang_status_text,
- text='' if ba.app.lang.language == 'Test' else ba.Lstr(
- resource=self._r + '.translationNoUpdateNeededText')
- if up_to_date else ba.Lstr(resource=self._r +
- '.translationUpdateNeededText'),
- color=(0.2, 1.0, 0.2, 0.8) if up_to_date else
- (1.0, 0.2, 0.2, 0.8))
+ text=''
+ if ba.app.lang.language == 'Test'
+ else ba.Lstr(
+ resource=self._r + '.translationNoUpdateNeededText'
+ )
+ if up_to_date
+ else ba.Lstr(resource=self._r + '.translationUpdateNeededText'),
+ color=(0.2, 1.0, 0.2, 0.8)
+ if up_to_date
+ else (1.0, 0.2, 0.2, 0.8),
+ )
else:
ba.textwidget(
edit=self._lang_status_text,
text=ba.Lstr(resource=self._r + '.translationFetchErrorText')
- if self._complete_langs_error else ba.Lstr(
- resource=self._r + '.translationFetchingStatusText'),
- color=(1.0, 0.5, 0.2) if self._complete_langs_error else
- (0.7, 0.7, 0.7))
+ if self._complete_langs_error
+ else ba.Lstr(
+ resource=self._r + '.translationFetchingStatusText'
+ ),
+ color=(1.0, 0.5, 0.2)
+ if self._complete_langs_error
+ else (0.7, 0.7, 0.7),
+ )
def _rebuild(self) -> None:
# pylint: disable=too-many-statements
@@ -200,8 +240,10 @@ class AdvancedSettingsWindow(ba.Window):
# menu based on the language so still need this. ...however we could
# make this more limited to it only rebuilds that one menu instead
# of everything.
- if self._menu_open or (self._prev_lang == ba.app.config.get(
- 'Lang', None) and self._prev_lang_list == available_languages):
+ if self._menu_open or (
+ self._prev_lang == ba.app.config.get('Lang', None)
+ and self._prev_lang_list == available_languages
+ ):
return
self._prev_lang = ba.app.config.get('Lang', None)
self._prev_lang_list = available_languages
@@ -217,13 +259,16 @@ class AdvancedSettingsWindow(ba.Window):
# Update our existing back button and title.
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- label=ba.Lstr(resource='backText'))
- ba.buttonwidget(edit=self._back_button,
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button, label=ba.Lstr(resource='backText')
+ )
+ ba.buttonwidget(
+ edit=self._back_button, label=ba.charstr(ba.SpecialChar.BACK)
+ )
- ba.textwidget(edit=self._title_text,
- text=ba.Lstr(resource=self._r + '.titleText'))
+ ba.textwidget(
+ edit=self._title_text, text=ba.Lstr(resource=self._r + '.titleText')
+ )
this_button_width = 410
@@ -234,22 +279,27 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource=self._r + '.enterPromoCodeText'),
text_scale=1.0,
- on_activate_call=self._on_promo_code_press)
+ on_activate_call=self._on_promo_code_press,
+ )
if self._back_button is not None:
- ba.widget(edit=self._promo_code_button,
- up_widget=self._back_button,
- left_widget=self._back_button)
+ ba.widget(
+ edit=self._promo_code_button,
+ up_widget=self._back_button,
+ left_widget=self._back_button,
+ )
v -= self._extra_button_spacing * 0.8
- ba.textwidget(parent=self._subcontainer,
- position=(200, v + 10),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.languageText'),
- maxwidth=150,
- scale=0.95,
- color=ba.app.ui.title_color,
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(200, v + 10),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.languageText'),
+ maxwidth=150,
+ scale=0.95,
+ color=ba.app.ui.title_color,
+ h_align='right',
+ v_align='center',
+ )
languages = ba.app.lang.available_languages
cur_lang = ba.app.config.get('Lang', None)
@@ -260,10 +310,11 @@ class AdvancedSettingsWindow(ba.Window):
# so we don't have to go digging through each full language.
try:
import json
- with open('ba_data/data/langdata.json',
- encoding='utf-8') as infile:
- lang_names_translated = (json.loads(
- infile.read())['lang_names_translated'])
+
+ with open('ba_data/data/langdata.json', encoding='utf-8') as infile:
+ lang_names_translated = json.loads(infile.read())[
+ 'lang_names_translated'
+ ]
except Exception:
ba.print_exception('Error reading lang data.')
lang_names_translated = {}
@@ -278,8 +329,9 @@ class AdvancedSettingsWindow(ba.Window):
if langs_translated[lang] == lang_translated:
langs_full[lang] = lang_translated
else:
- langs_full[lang] = (langs_translated[lang] + ' (' +
- lang_translated + ')')
+ langs_full[lang] = (
+ langs_translated[lang] + ' (' + lang_translated + ')'
+ )
self._language_popup = popup_ui.PopupMenu(
parent=self._subcontainer,
@@ -291,52 +343,72 @@ class AdvancedSettingsWindow(ba.Window):
on_value_change_call=ba.WeakCall(self._on_menu_choice),
choices=['Auto'] + languages,
button_size=(250, 60),
- choices_display=([
- ba.Lstr(value=(ba.Lstr(resource='autoText').evaluate() + ' (' +
- ba.Lstr(translate=('languages',
- ba.app.lang.default_language
- )).evaluate() + ')'))
- ] + [ba.Lstr(value=langs_full[l]) for l in languages]),
- current_choice=cur_lang)
+ choices_display=(
+ [
+ ba.Lstr(
+ value=(
+ ba.Lstr(resource='autoText').evaluate()
+ + ' ('
+ + ba.Lstr(
+ translate=(
+ 'languages',
+ ba.app.lang.default_language,
+ )
+ ).evaluate()
+ + ')'
+ )
+ )
+ ]
+ + [ba.Lstr(value=langs_full[l]) for l in languages]
+ ),
+ current_choice=cur_lang,
+ )
v -= self._spacing * 1.8
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v + 10),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.helpTranslateText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]),
- maxwidth=self._sub_width * 0.9,
- max_height=55,
- flatness=1.0,
- scale=0.65,
- color=(0.4, 0.9, 0.4, 0.8),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v + 10),
+ size=(0, 0),
+ text=ba.Lstr(
+ resource=self._r + '.helpTranslateText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ maxwidth=self._sub_width * 0.9,
+ max_height=55,
+ flatness=1.0,
+ scale=0.65,
+ color=(0.4, 0.9, 0.4, 0.8),
+ h_align='center',
+ v_align='center',
+ )
v -= self._spacing * 1.9
this_button_width = 410
self._translation_editor_button = ba.buttonwidget(
parent=self._subcontainer,
position=(self._sub_width / 2 - this_button_width / 2, v - 24),
size=(this_button_width, 60),
- label=ba.Lstr(resource=self._r + '.translationEditorButtonText',
- subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
- ]),
+ label=ba.Lstr(
+ resource=self._r + '.translationEditorButtonText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
autoselect=True,
on_activate_call=ba.Call(
- ba.open_url, 'https://legacy.ballistica.net/translate'))
+ ba.open_url, 'https://legacy.ballistica.net/translate'
+ ),
+ )
- self._lang_status_text = ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5,
- v - 40),
- size=(0, 0),
- text='',
- flatness=1.0,
- scale=0.63,
- h_align='center',
- v_align='center',
- maxwidth=400.0)
+ self._lang_status_text = ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 40),
+ size=(0, 0),
+ text='',
+ flatness=1.0,
+ scale=0.63,
+ h_align='center',
+ v_align='center',
+ maxwidth=400.0,
+ )
self._update_lang_status()
v -= 40
@@ -351,12 +423,14 @@ class AdvancedSettingsWindow(ba.Window):
textcolor=(0.8, 0.8, 0.8),
value=lang_inform,
text=ba.Lstr(resource=self._r + '.translationInformMe'),
- on_value_change_call=ba.WeakCall(
- self._on_lang_inform_value_change))
+ on_value_change_call=ba.WeakCall(self._on_lang_inform_value_change),
+ )
- ba.widget(edit=self._translation_editor_button,
- down_widget=cbw,
- up_widget=self._language_popup.get_button())
+ ba.widget(
+ edit=self._translation_editor_button,
+ down_widget=cbw,
+ up_widget=self._language_popup.get_button(),
+ )
v -= self._spacing * 3.0
@@ -367,7 +441,8 @@ class AdvancedSettingsWindow(ba.Window):
configkey='Kick Idle Players',
displayname=ba.Lstr(resource=self._r + '.kickIdlePlayersText'),
scale=1.0,
- maxwidth=430)
+ maxwidth=430,
+ )
v -= 42
self._disable_camera_shake_check_box = ConfigCheckBox(
@@ -377,7 +452,8 @@ class AdvancedSettingsWindow(ba.Window):
configkey='Disable Camera Shake',
displayname=ba.Lstr(resource=self._r + '.disableCameraShakeText'),
scale=1.0,
- maxwidth=430)
+ maxwidth=430,
+ )
self._disable_gyro_check_box: ConfigCheckBox | None = None
if self._show_disable_gyro:
@@ -387,10 +463,12 @@ class AdvancedSettingsWindow(ba.Window):
position=(50, v),
size=(self._sub_width - 100, 30),
configkey='Disable Camera Gyro',
- displayname=ba.Lstr(resource=self._r +
- '.disableCameraGyroscopeMotionText'),
+ displayname=ba.Lstr(
+ resource=self._r + '.disableCameraGyroscopeMotionText'
+ ),
scale=1.0,
- maxwidth=430)
+ maxwidth=430,
+ )
self._always_use_internal_keyboard_check_box: ConfigCheckBox | None
if self._show_always_use_internal_keyboard:
@@ -401,22 +479,27 @@ class AdvancedSettingsWindow(ba.Window):
size=(self._sub_width - 100, 30),
configkey='Always Use Internal Keyboard',
autoselect=True,
- displayname=ba.Lstr(resource=self._r +
- '.alwaysUseInternalKeyboardText'),
+ displayname=ba.Lstr(
+ resource=self._r + '.alwaysUseInternalKeyboardText'
+ ),
scale=1.0,
- maxwidth=430)
+ maxwidth=430,
+ )
ba.textwidget(
parent=self._subcontainer,
position=(90, v - 10),
size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.alwaysUseInternalKeyboardDescriptionText'),
+ text=ba.Lstr(
+ resource=self._r
+ + '.alwaysUseInternalKeyboardDescriptionText'
+ ),
maxwidth=400,
flatness=1.0,
scale=0.65,
color=(0.4, 0.9, 0.4, 0.8),
h_align='left',
- v_align='center')
+ v_align='center',
+ )
v -= 20
else:
self._always_use_internal_keyboard_check_box = None
@@ -432,19 +515,28 @@ class AdvancedSettingsWindow(ba.Window):
label=ba.Lstr(resource=self._r + '.moddingGuideText'),
text_scale=1.0,
on_activate_call=ba.Call(
- ba.open_url, 'https://ballistica.net/wiki/modding-guide'))
+ ba.open_url, 'https://ballistica.net/wiki/modding-guide'
+ ),
+ )
if self._show_always_use_internal_keyboard:
assert self._always_use_internal_keyboard_check_box is not None
- ba.widget(edit=self._always_use_internal_keyboard_check_box.widget,
- down_widget=self._modding_guide_button)
+ ba.widget(
+ edit=self._always_use_internal_keyboard_check_box.widget,
+ down_widget=self._modding_guide_button,
+ )
ba.widget(
edit=self._modding_guide_button,
- up_widget=self._always_use_internal_keyboard_check_box.widget)
+ up_widget=self._always_use_internal_keyboard_check_box.widget,
+ )
else:
- ba.widget(edit=self._modding_guide_button,
- up_widget=self._kick_idle_players_check_box.widget)
- ba.widget(edit=self._kick_idle_players_check_box.widget,
- down_widget=self._modding_guide_button)
+ ba.widget(
+ edit=self._modding_guide_button,
+ up_widget=self._kick_idle_players_check_box.widget,
+ )
+ ba.widget(
+ edit=self._kick_idle_players_check_box.widget,
+ down_widget=self._modding_guide_button,
+ )
v -= self._spacing * 2.0
@@ -455,7 +547,8 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource=self._r + '.showUserModsText'),
text_scale=1.0,
- on_activate_call=show_user_scripts)
+ on_activate_call=show_user_scripts,
+ )
v -= self._spacing * 2.0
@@ -466,7 +559,8 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='pluginsText'),
text_scale=1.0,
- on_activate_call=self._on_plugins_button_press)
+ on_activate_call=self._on_plugins_button_press,
+ )
v -= self._spacing * 0.6
@@ -480,7 +574,8 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource=self._r + '.vrTestingText'),
text_scale=1.0,
- on_activate_call=self._on_vr_test_press)
+ on_activate_call=self._on_vr_test_press,
+ )
else:
self._vr_test_button = None
@@ -494,7 +589,8 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource=self._r + '.netTestingText'),
text_scale=1.0,
- on_activate_call=self._on_net_test_press)
+ on_activate_call=self._on_net_test_press,
+ )
else:
self._net_test_button = None
@@ -506,7 +602,8 @@ class AdvancedSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource=self._r + '.benchmarksText'),
text_scale=1.0,
- on_activate_call=self._on_benchmark_press)
+ on_activate_call=self._on_benchmark_press,
+ )
for child in self._subcontainer.get_children():
ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20)
@@ -517,48 +614,53 @@ class AdvancedSettingsWindow(ba.Window):
if self._back_button is None:
ba.widget(
edit=self._scrollwidget,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
self._restore_state()
def _show_restart_needed(self, value: Any) -> None:
del value # Unused.
- ba.screenmessage(ba.Lstr(resource=self._r + '.mustRestartText'),
- color=(1, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.mustRestartText'), color=(1, 1, 0)
+ )
def _on_lang_inform_value_change(self, val: bool) -> None:
- ba.internal.add_transaction({
- 'type': 'SET_MISC_VAL',
- 'name': 'langInform',
- 'value': val
- })
+ ba.internal.add_transaction(
+ {'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val}
+ )
ba.internal.run_transactions()
def _on_vr_test_press(self) -> None:
from bastd.ui.settings.vrtesting import VRTestingWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- VRTestingWindow(transition='in_right').get_root_widget())
+ VRTestingWindow(transition='in_right').get_root_widget()
+ )
def _on_net_test_press(self) -> None:
from bastd.ui.settings.nettesting import NetTestingWindow
# Net-testing requires a signed in v1 account.
if ba.internal.get_v1_account_state() != 'signed_in':
- ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- NetTestingWindow(transition='in_right').get_root_widget())
+ NetTestingWindow(transition='in_right').get_root_widget()
+ )
def _on_friend_promo_code_press(self) -> None:
from bastd.ui import appinvite
from bastd.ui import account
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
@@ -566,11 +668,14 @@ class AdvancedSettingsWindow(ba.Window):
def _on_plugins_button_press(self) -> None:
from bastd.ui.settings.plugins import PluginSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
PluginSettingsWindow(
- origin_widget=self._plugins_button).get_root_widget())
+ origin_widget=self._plugins_button
+ ).get_root_widget()
+ )
def _on_promo_code_press(self) -> None:
from bastd.ui.promocode import PromoCodeWindow
@@ -584,14 +689,18 @@ class AdvancedSettingsWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
PromoCodeWindow(
- origin_widget=self._promo_code_button).get_root_widget())
+ origin_widget=self._promo_code_button
+ ).get_root_widget()
+ )
def _on_benchmark_press(self) -> None:
from bastd.ui.debug import DebugWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- DebugWindow(transition='in_right').get_root_widget())
+ DebugWindow(transition='in_right').get_root_widget()
+ )
def _save_state(self) -> None:
# pylint: disable=too-many-branches
@@ -611,15 +720,21 @@ class AdvancedSettingsWindow(ba.Window):
sel_name = 'KickIdlePlayers'
elif sel == self._disable_camera_shake_check_box.widget:
sel_name = 'DisableCameraShake'
- elif (self._always_use_internal_keyboard_check_box is not None
- and sel
- == self._always_use_internal_keyboard_check_box.widget):
+ elif (
+ self._always_use_internal_keyboard_check_box is not None
+ and sel
+ == self._always_use_internal_keyboard_check_box.widget
+ ):
sel_name = 'AlwaysUseInternalKeyboard'
- elif (self._disable_gyro_check_box is not None
- and sel == self._disable_gyro_check_box.widget):
+ elif (
+ self._disable_gyro_check_box is not None
+ and sel == self._disable_gyro_check_box.widget
+ ):
sel_name = 'DisableGyro'
- elif (self._language_popup is not None
- and sel == self._language_popup.get_button()):
+ elif (
+ self._language_popup is not None
+ and sel == self._language_popup.get_button()
+ ):
sel_name = 'Languages'
elif sel == self._translation_editor_button:
sel_name = 'TranslationEditor'
@@ -644,13 +759,15 @@ class AdvancedSettingsWindow(ba.Window):
def _restore_state(self) -> None:
# pylint: disable=too-many-branches
try:
- sel_name = ba.app.ui.window_states.get(type(self),
- {}).get('sel_name')
+ sel_name = ba.app.ui.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
if sel_name == 'Back':
sel = self._back_button
else:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
if sel_name == 'VRTest':
sel = self._vr_test_button
elif sel_name == 'NetTest':
@@ -663,15 +780,19 @@ class AdvancedSettingsWindow(ba.Window):
sel = self._kick_idle_players_check_box.widget
elif sel_name == 'DisableCameraShake':
sel = self._disable_camera_shake_check_box.widget
- elif (sel_name == 'AlwaysUseInternalKeyboard'
- and self._always_use_internal_keyboard_check_box
- is not None):
+ elif (
+ sel_name == 'AlwaysUseInternalKeyboard'
+ and self._always_use_internal_keyboard_check_box is not None
+ ):
sel = self._always_use_internal_keyboard_check_box.widget
- elif (sel_name == 'DisableGyro'
- and self._disable_gyro_check_box is not None):
+ elif (
+ sel_name == 'DisableGyro'
+ and self._disable_gyro_check_box is not None
+ ):
sel = self._disable_gyro_check_box.widget
- elif (sel_name == 'Languages'
- and self._language_popup is not None):
+ elif (
+ sel_name == 'Languages' and self._language_popup is not None
+ ):
sel = self._language_popup.get_button()
elif sel_name == 'TranslationEditor':
sel = self._translation_editor_button
@@ -686,9 +807,11 @@ class AdvancedSettingsWindow(ba.Window):
else:
sel = None
if sel is not None:
- ba.containerwidget(edit=self._subcontainer,
- selected_child=sel,
- visible_child=sel)
+ ba.containerwidget(
+ edit=self._subcontainer,
+ selected_child=sel,
+ visible_child=sel,
+ )
except Exception:
ba.print_exception(f'Error restoring state for {self.__class__}')
@@ -710,14 +833,19 @@ class AdvancedSettingsWindow(ba.Window):
else:
self._complete_langs_list = None
self._complete_langs_error = True
- ba.timer(0.001,
- ba.WeakCall(self._update_lang_status),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.001,
+ ba.WeakCall(self._update_lang_status),
+ timetype=ba.TimeType.REAL,
+ )
def _do_back(self) -> None:
from bastd.ui.settings.allsettings import AllSettingsWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- AllSettingsWindow(transition='in_left').get_root_widget())
+ AllSettingsWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
index b1f08f0e..9882a93b 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
@@ -16,9 +16,11 @@ if TYPE_CHECKING:
class AllSettingsWindow(ba.Window):
"""Window for selecting a settings category."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
import threading
@@ -44,19 +46,28 @@ class AllSettingsWindow(ba.Window):
top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(1.75 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height + top_extra),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.75
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -8) if uiscale is ba.UIScale.SMALL else (0, 0),
+ )
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
self._back_button = None
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._do_back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._do_back
+ )
else:
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -67,47 +78,56 @@ class AllSettingsWindow(ba.Window):
text_scale=1.2,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back)
+ on_activate_call=self._do_back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(0, height - 44),
- size=(width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- maxwidth=130)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 44),
+ size=(width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ maxwidth=130,
+ )
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v = height - 80
v -= 145
basew = 280 if uiscale is ba.UIScale.SMALL else 230
baseh = 170
- x_offs = x_inset + (105 if uiscale is ba.UIScale.SMALL else
- 72) - basew # now unused
+ x_offs = (
+ x_inset + (105 if uiscale is ba.UIScale.SMALL else 72) - basew
+ ) # now unused
x_offs2 = x_offs + basew - 7
x_offs3 = x_offs + 2 * (basew - 7)
x_offs4 = x_offs2
x_offs5 = x_offs3
- def _b_title(x: float, y: float, button: ba.Widget,
- text: str | ba.Lstr) -> None:
- ba.textwidget(parent=self._root_widget,
- text=text,
- position=(x + basew * 0.47, y + baseh * 0.22),
- maxwidth=basew * 0.7,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=button,
- color=(0.7, 0.9, 0.7, 1.0))
+ def _b_title(
+ x: float, y: float, button: ba.Widget, text: str | ba.Lstr
+ ) -> None:
+ ba.textwidget(
+ parent=self._root_widget,
+ text=text,
+ position=(x + basew * 0.47, y + baseh * 0.22),
+ maxwidth=basew * 0.7,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=button,
+ color=(0.7, 0.9, 0.7, 1.0),
+ )
ctb = self._controllers_button = ba.buttonwidget(
parent=self._root_widget,
@@ -116,18 +136,22 @@ class AllSettingsWindow(ba.Window):
size=(basew, baseh),
button_type='square',
label='',
- on_activate_call=self._do_controllers)
+ on_activate_call=self._do_controllers,
+ )
if ba.app.ui.use_toolbars and self._back_button is None:
bbtn = ba.internal.get_special_widget('back_button')
ba.widget(edit=ctb, left_widget=bbtn)
- _b_title(x_offs2, v, ctb,
- ba.Lstr(resource=self._r + '.controllersText'))
+ _b_title(
+ x_offs2, v, ctb, ba.Lstr(resource=self._r + '.controllersText')
+ )
imgw = imgh = 130
- ba.imagewidget(parent=self._root_widget,
- position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35),
- size=(imgw, imgh),
- texture=ba.gettexture('controllerIcon'),
- draw_controller=ctb)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(x_offs2 + basew * 0.49 - imgw * 0.5, v + 35),
+ size=(imgw, imgh),
+ texture=ba.gettexture('controllerIcon'),
+ draw_controller=ctb,
+ )
gfxb = self._graphics_button = ba.buttonwidget(
parent=self._root_widget,
@@ -136,19 +160,22 @@ class AllSettingsWindow(ba.Window):
size=(basew, baseh),
button_type='square',
label='',
- on_activate_call=self._do_graphics)
+ on_activate_call=self._do_graphics,
+ )
if ba.app.ui.use_toolbars:
pbtn = ba.internal.get_special_widget('party_button')
ba.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
_b_title(x_offs3, v, gfxb, ba.Lstr(resource=self._r + '.graphicsText'))
imgw = imgh = 110
- ba.imagewidget(parent=self._root_widget,
- position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42),
- size=(imgw, imgh),
- texture=ba.gettexture('graphicsIcon'),
- draw_controller=gfxb)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(x_offs3 + basew * 0.49 - imgw * 0.5, v + 42),
+ size=(imgw, imgh),
+ texture=ba.gettexture('graphicsIcon'),
+ draw_controller=gfxb,
+ )
- v -= (baseh - 5)
+ v -= baseh - 5
abtn = self._audio_button = ba.buttonwidget(
parent=self._root_widget,
@@ -157,16 +184,18 @@ class AllSettingsWindow(ba.Window):
size=(basew, baseh),
button_type='square',
label='',
- on_activate_call=self._do_audio)
+ on_activate_call=self._do_audio,
+ )
_b_title(x_offs4, v, abtn, ba.Lstr(resource=self._r + '.audioText'))
imgw = imgh = 120
- ba.imagewidget(parent=self._root_widget,
- position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5,
- v + 35),
- size=(imgw, imgh),
- color=(1, 1, 0),
- texture=ba.gettexture('audioIcon'),
- draw_controller=abtn)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(x_offs4 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
+ size=(imgw, imgh),
+ color=(1, 1, 0),
+ texture=ba.gettexture('audioIcon'),
+ draw_controller=abtn,
+ )
avb = self._advanced_button = ba.buttonwidget(
parent=self._root_widget,
@@ -175,16 +204,18 @@ class AllSettingsWindow(ba.Window):
size=(basew, baseh),
button_type='square',
label='',
- on_activate_call=self._do_advanced)
+ on_activate_call=self._do_advanced,
+ )
_b_title(x_offs5, v, avb, ba.Lstr(resource=self._r + '.advancedText'))
imgw = imgh = 120
- ba.imagewidget(parent=self._root_widget,
- position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5,
- v + 35),
- size=(imgw, imgh),
- color=(0.8, 0.95, 1),
- texture=ba.gettexture('advancedIcon'),
- draw_controller=avb)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(x_offs5 + basew * 0.49 - imgw * 0.5 + 5, v + 35),
+ size=(imgw, imgh),
+ color=(0.8, 0.95, 1),
+ texture=ba.gettexture('advancedIcon'),
+ draw_controller=avb,
+ )
self._restore_state()
# noinspection PyUnresolvedReferences
@@ -200,47 +231,62 @@ class AllSettingsWindow(ba.Window):
def _do_back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
def _do_controllers(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.controls import ControlsSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
ControlsSettingsWindow(
- origin_widget=self._controllers_button).get_root_widget())
+ origin_widget=self._controllers_button
+ ).get_root_widget()
+ )
def _do_graphics(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.graphics import GraphicsSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
GraphicsSettingsWindow(
- origin_widget=self._graphics_button).get_root_widget())
+ origin_widget=self._graphics_button
+ ).get_root_widget()
+ )
def _do_audio(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.audio import AudioSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
AudioSettingsWindow(
- origin_widget=self._audio_button).get_root_widget())
+ origin_widget=self._audio_button
+ ).get_root_widget()
+ )
def _do_advanced(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
AdvancedSettingsWindow(
- origin_widget=self._advanced_button).get_root_widget())
+ origin_widget=self._advanced_button
+ ).get_root_widget()
+ )
def _save_state(self) -> None:
try:
@@ -263,8 +309,9 @@ class AllSettingsWindow(ba.Window):
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(type(self),
- {}).get('sel_name')
+ sel_name = ba.app.ui.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
sel: ba.Widget | None
if sel_name == 'Controllers':
sel = self._controllers_button
diff --git a/assets/src/ba_data/python/bastd/ui/settings/audio.py b/assets/src/ba_data/python/bastd/ui/settings/audio.py
index ad464c39..296cc0b0 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/audio.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/audio.py
@@ -16,9 +16,11 @@ if TYPE_CHECKING:
class AudioSettingsWindow(ba.Window):
"""Window for editing audio settings."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
@@ -57,16 +59,26 @@ class AudioSettingsWindow(ba.Window):
height += spacing * 2.0
uiscale = ba.app.ui.uiscale
- base_scale = (2.05 if uiscale is ba.UIScale.SMALL else
- 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0)
+ base_scale = (
+ 2.05
+ if uiscale is ba.UIScale.SMALL
+ else 1.6
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ )
popup_menu_scale = base_scale * 1.2
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- scale=base_scale,
- scale_origin_stack_offset=scale_origin,
- stack_offset=(0, -20) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ scale=base_scale,
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(0, -20)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._back_button = back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -77,23 +89,28 @@ class AudioSettingsWindow(ba.Window):
label=ba.Lstr(resource='backText'),
button_type='back',
on_activate_call=self._back,
- autoselect=True)
+ autoselect=True,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
v = height - 60
v -= spacing * 1.0
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height - 32),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- maxwidth=180,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height - 32),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ maxwidth=180,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._sound_volume_numedit = svne = ConfigNumberEdit(
parent=self._root_widget,
@@ -103,11 +120,13 @@ class AudioSettingsWindow(ba.Window):
displayname=ba.Lstr(resource=self._r + '.soundVolumeText'),
minval=0.0,
maxval=1.0,
- increment=0.1)
+ increment=0.1,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=svne.plusbutton,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
v -= spacing
self._music_volume_numedit = ConfigNumberEdit(
parent=self._root_widget,
@@ -119,22 +138,24 @@ class AudioSettingsWindow(ba.Window):
maxval=1.0,
increment=0.1,
callback=music.music_volume_changed,
- changesound=False)
+ changesound=False,
+ )
v -= 0.5 * spacing
self._vr_head_relative_audio_button: ba.Widget | None
if show_vr_head_relative_audio:
v -= 40
- ba.textwidget(parent=self._root_widget,
- position=(40, v + 24),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.headRelativeVRAudioText'),
- color=(0.8, 0.8, 0.8),
- maxwidth=230,
- h_align='left',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(40, v + 24),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.headRelativeVRAudioText'),
+ color=(0.8, 0.8, 0.8),
+ maxwidth=230,
+ h_align='left',
+ v_align='center',
+ )
popup = PopupMenu(
parent=self._root_widget,
@@ -146,22 +167,24 @@ class AudioSettingsWindow(ba.Window):
choices_display=[
ba.Lstr(resource='autoText'),
ba.Lstr(resource='onText'),
- ba.Lstr(resource='offText')
+ ba.Lstr(resource='offText'),
],
current_choice=ba.app.config.resolve('VR Head Relative Audio'),
- on_value_change_call=self._set_vr_head_relative_audio)
+ on_value_change_call=self._set_vr_head_relative_audio,
+ )
self._vr_head_relative_audio_button = popup.get_button()
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, v - 11),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.headRelativeVRAudioInfoText'),
- scale=0.5,
- color=(0.7, 0.8, 0.7),
- maxwidth=400,
- flatness=1.0,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, v - 11),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.headRelativeVRAudioInfoText'),
+ scale=0.5,
+ color=(0.7, 0.8, 0.7),
+ maxwidth=400,
+ flatness=1.0,
+ h_align='center',
+ v_align='center',
+ )
v -= 30
else:
self._vr_head_relative_audio_button = None
@@ -175,18 +198,20 @@ class AudioSettingsWindow(ba.Window):
size=(310, 50),
autoselect=True,
label=ba.Lstr(resource=self._r + '.soundtrackButtonText'),
- on_activate_call=self._do_soundtracks)
+ on_activate_call=self._do_soundtracks,
+ )
v -= spacing * 0.5
- ba.textwidget(parent=self._root_widget,
- position=(0, v),
- size=(width, 20),
- text=ba.Lstr(resource=self._r +
- '.soundtrackDescriptionText'),
- flatness=1.0,
- h_align='center',
- scale=0.5,
- color=(0.7, 0.8, 0.7, 1.0),
- maxwidth=400)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, v),
+ size=(width, 20),
+ text=ba.Lstr(resource=self._r + '.soundtrackDescriptionText'),
+ flatness=1.0,
+ h_align='center',
+ scale=0.5,
+ color=(0.7, 0.8, 0.7, 1.0),
+ maxwidth=400,
+ )
else:
self._soundtrack_button = None
@@ -211,29 +236,38 @@ class AudioSettingsWindow(ba.Window):
# if we don't have it, request it.
if not ba.internal.have_permission(ba.Permission.STORAGE):
ba.playsound(ba.getsound('ding'))
- ba.screenmessage(ba.Lstr(resource='storagePermissionAccessText'),
- color=(0.5, 1, 0.5))
- ba.timer(1.0,
- ba.Call(ba.internal.request_permission,
- ba.Permission.STORAGE),
- timetype=ba.TimeType.REAL)
+ ba.screenmessage(
+ ba.Lstr(resource='storagePermissionAccessText'),
+ color=(0.5, 1, 0.5),
+ )
+ ba.timer(
+ 1.0,
+ ba.Call(ba.internal.request_permission, ba.Permission.STORAGE),
+ timetype=ba.TimeType.REAL,
+ )
return
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
stb.SoundtrackBrowserWindow(
- origin_widget=self._soundtrack_button).get_root_widget())
+ origin_widget=self._soundtrack_button
+ ).get_root_widget()
+ )
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings import allsettings
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
allsettings.AllSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
def _save_state(self) -> None:
try:
diff --git a/assets/src/ba_data/python/bastd/ui/settings/controls.py b/assets/src/ba_data/python/bastd/ui/settings/controls.py
index 3e9566a1..73a2fe39 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/controls.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/controls.py
@@ -16,15 +16,18 @@ if TYPE_CHECKING:
class ControlsSettingsWindow(ba.Window):
"""Top level control settings window."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# FIXME: should tidy up here.
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
from bastd.ui import popup as popup_ui
+
self._have_selected_child = False
scale_origin: tuple[float, float] | None
@@ -56,8 +59,9 @@ class ControlsSettingsWindow(ba.Window):
show_gamepads = False
platform = app.platform
subplatform = app.subplatform
- non_vr_windows = (platform == 'windows'
- and (subplatform != 'oculus' or not app.vr_mode))
+ non_vr_windows = platform == 'windows' and (
+ subplatform != 'oculus' or not app.vr_mode
+ )
if platform in ('linux', 'android', 'mac') or non_vr_windows:
show_gamepads = True
height += spacing
@@ -73,8 +77,10 @@ class ControlsSettingsWindow(ba.Window):
height += space_height
show_keyboard = False
- if ba.internal.getinputdevice('Keyboard', '#1',
- doraise=False) is not None:
+ if (
+ ba.internal.getinputdevice('Keyboard', '#1', doraise=False)
+ is not None
+ ):
show_keyboard = True
height += spacing
show_keyboard_p2 = False if app.vr_mode else show_keyboard
@@ -112,14 +118,24 @@ class ControlsSettingsWindow(ba.Window):
height += spacing
uiscale = ba.app.ui.uiscale
- smallscale = (1.7 if show_keyboard else 2.2)
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- stack_offset=((0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)),
- scale=(smallscale if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ smallscale = 1.7 if show_keyboard else 2.2
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(
+ (0, -10) if uiscale is ba.UIScale.SMALL else (0, 0)
+ ),
+ scale=(
+ smallscale
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
position=(35, height - 60),
@@ -129,7 +145,8 @@ class ControlsSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
# We need these vars to exist even if the buttons don't.
@@ -139,17 +156,21 @@ class ControlsSettingsWindow(ba.Window):
self._keyboard_2_button: ba.Widget | None = None
self._idevices_button: ba.Widget | None = None
- ba.textwidget(parent=self._root_widget,
- position=(0, height - 49),
- size=(width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='top')
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 49),
+ size=(width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='top',
+ )
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v = height - 75
v -= spacing
@@ -161,16 +182,20 @@ class ControlsSettingsWindow(ba.Window):
size=(button_width, 43),
autoselect=True,
label=ba.Lstr(resource=self._r + '.configureTouchText'),
- on_activate_call=self._do_touchscreen)
+ on_activate_call=self._do_touchscreen,
+ )
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._touch_button)
- ba.widget(edit=self._back_button,
- down_widget=self._touch_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._touch_button
+ )
+ ba.widget(
+ edit=self._back_button, down_widget=self._touch_button
+ )
self._have_selected_child = True
v -= spacing
@@ -181,16 +206,20 @@ class ControlsSettingsWindow(ba.Window):
size=(button_width, 43),
autoselect=True,
label=ba.Lstr(resource=self._r + '.configureControllersText'),
- on_activate_call=self._do_gamepads)
+ on_activate_call=self._do_gamepads,
+ )
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._gamepads_button)
- ba.widget(edit=self._back_button,
- down_widget=self._gamepads_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._gamepads_button
+ )
+ ba.widget(
+ edit=self._back_button, down_widget=self._gamepads_button
+ )
self._have_selected_child = True
v -= spacing
else:
@@ -206,16 +235,20 @@ class ControlsSettingsWindow(ba.Window):
size=(button_width, 43),
autoselect=True,
label=ba.Lstr(resource=self._r + '.configureKeyboardText'),
- on_activate_call=self._config_keyboard)
+ on_activate_call=self._config_keyboard,
+ )
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._keyboard_button)
- ba.widget(edit=self._back_button,
- down_widget=self._keyboard_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._keyboard_button
+ )
+ ba.widget(
+ edit=self._back_button, down_widget=self._keyboard_button
+ )
self._have_selected_child = True
v -= spacing
if show_keyboard_p2:
@@ -225,7 +258,8 @@ class ControlsSettingsWindow(ba.Window):
size=(button_width, 43),
autoselect=True,
label=ba.Lstr(resource=self._r + '.configureKeyboard2Text'),
- on_activate_call=self._config_keyboard2)
+ on_activate_call=self._config_keyboard2,
+ )
v -= spacing
if show_space_2:
v -= space_height
@@ -236,16 +270,20 @@ class ControlsSettingsWindow(ba.Window):
size=(button_width, 43),
autoselect=True,
label=ba.Lstr(resource=self._r + '.configureMobileText'),
- on_activate_call=self._do_mobile_devices)
+ on_activate_call=self._do_mobile_devices,
+ )
if ba.app.ui.use_toolbars:
- ba.widget(edit=btn,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=btn,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._idevices_button)
- ba.widget(edit=self._back_button,
- down_widget=self._idevices_button)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._idevices_button
+ )
+ ba.widget(
+ edit=self._back_button, down_widget=self._idevices_button
+ )
self._have_selected_child = True
v -= spacing
@@ -254,21 +292,27 @@ class ControlsSettingsWindow(ba.Window):
def do_toggle(value: bool) -> None:
ba.screenmessage(
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
- color=(1, 1, 0))
+ color=(1, 1, 0),
+ )
ba.playsound(ba.getsound('gunCocking'))
- ba.internal.set_low_level_config_value('enablexinput',
- not value)
+ ba.internal.set_low_level_config_value(
+ 'enablexinput', not value
+ )
ba.checkboxwidget(
parent=self._root_widget,
position=(100, v + 3),
size=(120, 30),
- value=(not ba.internal.get_low_level_config_value(
- 'enablexinput', 1)),
+ value=(
+ not ba.internal.get_low_level_config_value(
+ 'enablexinput', 1
+ )
+ ),
maxwidth=200,
on_value_change_call=do_toggle,
text=ba.Lstr(resource='disableXInputText'),
- autoselect=True)
+ autoselect=True,
+ )
ba.textwidget(
parent=self._root_widget,
position=(width * 0.5, v - 5),
@@ -278,7 +322,8 @@ class ControlsSettingsWindow(ba.Window):
h_align='center',
v_align='center',
color=ba.app.ui.infotextcolor,
- maxwidth=width * 0.8)
+ maxwidth=width * 0.8,
+ )
v -= spacing
if show_mac_controller_subsystem:
popup_ui.PopupMenu(
@@ -291,11 +336,13 @@ class ControlsSettingsWindow(ba.Window):
choices_display=[
ba.Lstr(resource='macControllerSubsystemClassicText'),
ba.Lstr(resource='macControllerSubsystemMFiText'),
- ba.Lstr(resource='macControllerSubsystemBothText')
+ ba.Lstr(resource='macControllerSubsystemBothText'),
],
current_choice=ba.app.config.resolve(
- 'Mac Controller Subsystem'),
- on_value_change_call=self._set_mac_controller_subsystem)
+ 'Mac Controller Subsystem'
+ ),
+ on_value_change_call=self._set_mac_controller_subsystem,
+ )
ba.textwidget(
parent=self._root_widget,
position=(245, v + 13),
@@ -305,7 +352,8 @@ class ControlsSettingsWindow(ba.Window):
h_align='right',
v_align='center',
color=ba.app.ui.infotextcolor,
- maxwidth=180)
+ maxwidth=180,
+ )
ba.textwidget(
parent=self._root_widget,
position=(width * 0.5, v - 20),
@@ -315,7 +363,8 @@ class ControlsSettingsWindow(ba.Window):
h_align='center',
v_align='center',
color=ba.app.ui.infotextcolor,
- maxwidth=width * 0.8)
+ maxwidth=width * 0.8,
+ )
v -= spacing * 1.5
self._restore_state()
@@ -327,32 +376,41 @@ class ControlsSettingsWindow(ba.Window):
def _config_keyboard(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.keyboard import ConfigKeyboardWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- ConfigKeyboardWindow(ba.internal.getinputdevice(
- 'Keyboard', '#1')).get_root_widget())
+ ConfigKeyboardWindow(
+ ba.internal.getinputdevice('Keyboard', '#1')
+ ).get_root_widget()
+ )
def _config_keyboard2(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.keyboard import ConfigKeyboardWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- ConfigKeyboardWindow(ba.internal.getinputdevice(
- 'Keyboard', '#2')).get_root_widget())
+ ConfigKeyboardWindow(
+ ba.internal.getinputdevice('Keyboard', '#2')
+ ).get_root_widget()
+ )
def _do_mobile_devices(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.remoteapp import RemoteAppSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- RemoteAppSettingsWindow().get_root_widget())
+ RemoteAppSettingsWindow().get_root_widget()
+ )
def _do_gamepads(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.gamepadselect import GamepadSelectWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(GamepadSelectWindow().get_root_widget())
@@ -360,10 +418,12 @@ class ControlsSettingsWindow(ba.Window):
def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.touchscreen import TouchscreenSettingsWindow
+
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- TouchscreenSettingsWindow().get_root_widget())
+ TouchscreenSettingsWindow().get_root_widget()
+ )
def _save_state(self) -> None:
sel = self._root_widget.get_selected_child()
@@ -396,15 +456,21 @@ class ControlsSettingsWindow(ba.Window):
elif sel_name == 'Back':
sel = self._back_button
else:
- sel = (self._gamepads_button
- if self._gamepads_button is not None else self._back_button)
+ sel = (
+ self._gamepads_button
+ if self._gamepads_button is not None
+ else self._back_button
+ )
ba.containerwidget(edit=self._root_widget, selected_child=sel)
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.allsettings import AllSettingsWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- AllSettingsWindow(transition='in_left').get_root_widget())
+ AllSettingsWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/gamepad.py b/assets/src/ba_data/python/bastd/ui/settings/gamepad.py
index 4d5d9465..cbb58a73 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/gamepad.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/gamepad.py
@@ -16,12 +16,14 @@ if TYPE_CHECKING:
class GamepadSettingsWindow(ba.Window):
"""Window for configuring a gamepad."""
- def __init__(self,
- gamepad: ba.InputDevice,
- is_main_menu: bool = True,
- transition: str = 'in_right',
- transition_out: str = 'out_right',
- settings: dict | None = None):
+ def __init__(
+ self,
+ gamepad: ba.InputDevice,
+ is_main_menu: bool = True,
+ transition: str = 'in_right',
+ transition_out: str = 'out_right',
+ settings: dict | None = None,
+ ):
self._input = gamepad
# If our input-device went away, just return an empty zombie.
@@ -35,7 +37,7 @@ class GamepadSettingsWindow(ba.Window):
self._transition_out = transition_out
# We're a secondary gamepad if supplied with settings.
- self._is_secondary = (settings is not None)
+ self._is_secondary = settings is not None
self._ext = '_B' if self._is_secondary else ''
self._is_main_menu = is_main_menu
self._displayname = self._name
@@ -43,12 +45,22 @@ class GamepadSettingsWindow(ba.Window):
self._height = 440 if self._is_secondary else 450
self._spacing = 40
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- scale=(1.63 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(-20, -16) if uiscale is ba.UIScale.SMALL else (0, 0),
- transition=transition))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ scale=(
+ 1.63
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(-20, -16)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ transition=transition,
+ )
+ )
# Don't ask to config joysticks while we're in here.
self._rebuild_ui()
@@ -72,63 +84,63 @@ class GamepadSettingsWindow(ba.Window):
# secondary controls).
self._settings = {}
for skey in [
- 'buttonJump',
- 'buttonJump_B',
- 'buttonPunch',
- 'buttonPunch_B',
- 'buttonBomb',
- 'buttonBomb_B',
- 'buttonPickUp',
- 'buttonPickUp_B',
- 'buttonStart',
- 'buttonStart_B',
- 'buttonStart2',
- 'buttonStart2_B',
- 'buttonUp',
- 'buttonUp_B',
- 'buttonDown',
- 'buttonDown_B',
- 'buttonLeft',
- 'buttonLeft_B',
- 'buttonRight',
- 'buttonRight_B',
- 'buttonRun1',
- 'buttonRun1_B',
- 'buttonRun2',
- 'buttonRun2_B',
- 'triggerRun1',
- 'triggerRun1_B',
- 'triggerRun2',
- 'triggerRun2_B',
- 'buttonIgnored',
- 'buttonIgnored_B',
- 'buttonIgnored2',
- 'buttonIgnored2_B',
- 'buttonIgnored3',
- 'buttonIgnored3_B',
- 'buttonIgnored4',
- 'buttonIgnored4_B',
- 'buttonVRReorient',
- 'buttonVRReorient_B',
- 'analogStickDeadZone',
- 'analogStickDeadZone_B',
- 'dpad',
- 'dpad_B',
- 'unassignedButtonsRun',
- 'unassignedButtonsRun_B',
- 'startButtonActivatesDefaultWidget',
- 'startButtonActivatesDefaultWidget_B',
- 'uiOnly',
- 'uiOnly_B',
- 'ignoreCompletely',
- 'ignoreCompletely_B',
- 'autoRecalibrateAnalogStick',
- 'autoRecalibrateAnalogStick_B',
- 'analogStickLR',
- 'analogStickLR_B',
- 'analogStickUD',
- 'analogStickUD_B',
- 'enableSecondary',
+ 'buttonJump',
+ 'buttonJump_B',
+ 'buttonPunch',
+ 'buttonPunch_B',
+ 'buttonBomb',
+ 'buttonBomb_B',
+ 'buttonPickUp',
+ 'buttonPickUp_B',
+ 'buttonStart',
+ 'buttonStart_B',
+ 'buttonStart2',
+ 'buttonStart2_B',
+ 'buttonUp',
+ 'buttonUp_B',
+ 'buttonDown',
+ 'buttonDown_B',
+ 'buttonLeft',
+ 'buttonLeft_B',
+ 'buttonRight',
+ 'buttonRight_B',
+ 'buttonRun1',
+ 'buttonRun1_B',
+ 'buttonRun2',
+ 'buttonRun2_B',
+ 'triggerRun1',
+ 'triggerRun1_B',
+ 'triggerRun2',
+ 'triggerRun2_B',
+ 'buttonIgnored',
+ 'buttonIgnored_B',
+ 'buttonIgnored2',
+ 'buttonIgnored2_B',
+ 'buttonIgnored3',
+ 'buttonIgnored3_B',
+ 'buttonIgnored4',
+ 'buttonIgnored4_B',
+ 'buttonVRReorient',
+ 'buttonVRReorient_B',
+ 'analogStickDeadZone',
+ 'analogStickDeadZone_B',
+ 'dpad',
+ 'dpad_B',
+ 'unassignedButtonsRun',
+ 'unassignedButtonsRun_B',
+ 'startButtonActivatesDefaultWidget',
+ 'startButtonActivatesDefaultWidget_B',
+ 'uiOnly',
+ 'uiOnly_B',
+ 'ignoreCompletely',
+ 'ignoreCompletely_B',
+ 'autoRecalibrateAnalogStick',
+ 'autoRecalibrateAnalogStick_B',
+ 'analogStickLR',
+ 'analogStickLR_B',
+ 'analogStickUD',
+ 'analogStickUD_B',
+ 'enableSecondary',
]:
val = get_device_value(self._input, skey)
if val != -1:
@@ -137,17 +149,20 @@ class GamepadSettingsWindow(ba.Window):
back_button: ba.Widget | None
if self._is_secondary:
- back_button = ba.buttonwidget(parent=self._root_widget,
- position=(self._width - 180,
- self._height - 65),
- autoselect=True,
- size=(160, 60),
- label=ba.Lstr(resource='doneText'),
- scale=0.9,
- on_activate_call=self._save)
- ba.containerwidget(edit=self._root_widget,
- start_button=back_button,
- on_cancel_call=back_button.activate)
+ back_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width - 180, self._height - 65),
+ autoselect=True,
+ size=(160, 60),
+ label=ba.Lstr(resource='doneText'),
+ scale=0.9,
+ on_activate_call=self._save,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ start_button=back_button,
+ on_cancel_call=back_button.activate,
+ )
cancel_button = None
else:
cancel_button = ba.buttonwidget(
@@ -157,80 +172,95 @@ class GamepadSettingsWindow(ba.Window):
size=(160, 60),
label=ba.Lstr(resource='cancelText'),
scale=0.9,
- on_activate_call=self._cancel)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=cancel_button)
+ on_activate_call=self._cancel,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=cancel_button
+ )
save_button: ba.Widget | None
if not self._is_secondary:
save_button = ba.buttonwidget(
parent=self._root_widget,
- position=(self._width - (165 if self._is_secondary else 195),
- self._height - 65),
+ position=(
+ self._width - (165 if self._is_secondary else 195),
+ self._height - 65,
+ ),
size=((160 if self._is_secondary else 180), 60),
autoselect=True,
label=ba.Lstr(resource='doneText')
- if self._is_secondary else ba.Lstr(resource='saveText'),
+ if self._is_secondary
+ else ba.Lstr(resource='saveText'),
scale=0.9,
- on_activate_call=self._save)
- ba.containerwidget(edit=self._root_widget,
- start_button=save_button)
+ on_activate_call=self._save,
+ )
+ ba.containerwidget(edit=self._root_widget, start_button=save_button)
else:
save_button = None
if not self._is_secondary:
v = self._height - 59
- ba.textwidget(parent=self._root_widget,
- position=(0, v + 5),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- maxwidth=310,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, v + 5),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ maxwidth=310,
+ h_align='center',
+ v_align='center',
+ )
v -= 48
- ba.textwidget(parent=self._root_widget,
- position=(0, v + 3),
- size=(self._width, 25),
- text=self._name,
- color=ba.app.ui.infotextcolor,
- maxwidth=self._width * 0.9,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, v + 3),
+ size=(self._width, 25),
+ text=self._name,
+ color=ba.app.ui.infotextcolor,
+ maxwidth=self._width * 0.9,
+ h_align='center',
+ v_align='center',
+ )
v -= self._spacing * 1
- ba.textwidget(parent=self._root_widget,
- position=(50, v + 10),
- size=(self._width - 100, 30),
- text=ba.Lstr(resource=self._r + '.appliesToAllText'),
- maxwidth=330,
- scale=0.65,
- color=(0.5, 0.6, 0.5, 1.0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(50, v + 10),
+ size=(self._width - 100, 30),
+ text=ba.Lstr(resource=self._r + '.appliesToAllText'),
+ maxwidth=330,
+ scale=0.65,
+ color=(0.5, 0.6, 0.5, 1.0),
+ h_align='center',
+ v_align='center',
+ )
v -= 70
self._enable_check_box = None
else:
v = self._height - 49
- ba.textwidget(parent=self._root_widget,
- position=(0, v + 5),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.secondaryText'),
- color=ba.app.ui.title_color,
- maxwidth=300,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, v + 5),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.secondaryText'),
+ color=ba.app.ui.title_color,
+ maxwidth=300,
+ h_align='center',
+ v_align='center',
+ )
v -= self._spacing * 1
- ba.textwidget(parent=self._root_widget,
- position=(50, v + 10),
- size=(self._width - 100, 30),
- text=ba.Lstr(resource=self._r + '.secondHalfText'),
- maxwidth=300,
- scale=0.65,
- color=(0.6, 0.8, 0.6, 1.0),
- h_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(50, v + 10),
+ size=(self._width - 100, 30),
+ text=ba.Lstr(resource=self._r + '.secondHalfText'),
+ maxwidth=300,
+ scale=0.65,
+ color=(0.6, 0.8, 0.6, 1.0),
+ h_align='center',
+ )
self._enable_check_box = ba.checkboxwidget(
parent=self._root_widget,
position=(self._width * 0.5 - 80, v - 73),
@@ -239,7 +269,8 @@ class GamepadSettingsWindow(ba.Window):
on_value_change_call=self._enable_check_box_changed,
size=(200, 30),
text=ba.Lstr(resource=self._r + '.secondaryEnableText'),
- scale=1.2)
+ scale=1.2,
+ )
v = self._height - 205
h_offs = 160
@@ -249,74 +280,93 @@ class GamepadSettingsWindow(ba.Window):
scly = 0.98
dpm = ba.Lstr(resource=self._r + '.pressAnyButtonOrDpadText')
dpm2 = ba.Lstr(resource=self._r + '.ifNothingHappensTryAnalogText')
- self._capture_button(pos=(h_offs, v + scly * dist),
- color=d_color,
- button='buttonUp' + self._ext,
- texture=ba.gettexture('upButton'),
- scale=1.0,
- message=dpm,
- message2=dpm2)
- self._capture_button(pos=(h_offs - sclx * dist, v),
- color=d_color,
- button='buttonLeft' + self._ext,
- texture=ba.gettexture('leftButton'),
- scale=1.0,
- message=dpm,
- message2=dpm2)
- self._capture_button(pos=(h_offs + sclx * dist, v),
- color=d_color,
- button='buttonRight' + self._ext,
- texture=ba.gettexture('rightButton'),
- scale=1.0,
- message=dpm,
- message2=dpm2)
- self._capture_button(pos=(h_offs, v - scly * dist),
- color=d_color,
- button='buttonDown' + self._ext,
- texture=ba.gettexture('downButton'),
- scale=1.0,
- message=dpm,
- message2=dpm2)
+ self._capture_button(
+ pos=(h_offs, v + scly * dist),
+ color=d_color,
+ button='buttonUp' + self._ext,
+ texture=ba.gettexture('upButton'),
+ scale=1.0,
+ message=dpm,
+ message2=dpm2,
+ )
+ self._capture_button(
+ pos=(h_offs - sclx * dist, v),
+ color=d_color,
+ button='buttonLeft' + self._ext,
+ texture=ba.gettexture('leftButton'),
+ scale=1.0,
+ message=dpm,
+ message2=dpm2,
+ )
+ self._capture_button(
+ pos=(h_offs + sclx * dist, v),
+ color=d_color,
+ button='buttonRight' + self._ext,
+ texture=ba.gettexture('rightButton'),
+ scale=1.0,
+ message=dpm,
+ message2=dpm2,
+ )
+ self._capture_button(
+ pos=(h_offs, v - scly * dist),
+ color=d_color,
+ button='buttonDown' + self._ext,
+ texture=ba.gettexture('downButton'),
+ scale=1.0,
+ message=dpm,
+ message2=dpm2,
+ )
dpm3 = ba.Lstr(resource=self._r + '.ifNothingHappensTryDpadText')
- self._capture_button(pos=(h_offs + 130, v - 125),
- color=(0.4, 0.4, 0.6),
- button='analogStickLR' + self._ext,
- maxwidth=140,
- texture=ba.gettexture('analogStick'),
- scale=1.2,
- message=ba.Lstr(resource=self._r +
- '.pressLeftRightText'),
- message2=dpm3)
+ self._capture_button(
+ pos=(h_offs + 130, v - 125),
+ color=(0.4, 0.4, 0.6),
+ button='analogStickLR' + self._ext,
+ maxwidth=140,
+ texture=ba.gettexture('analogStick'),
+ scale=1.2,
+ message=ba.Lstr(resource=self._r + '.pressLeftRightText'),
+ message2=dpm3,
+ )
- self._capture_button(pos=(self._width * 0.5, v),
- color=(0.4, 0.4, 0.6),
- button='buttonStart' + self._ext,
- texture=ba.gettexture('startButton'),
- scale=0.7)
+ self._capture_button(
+ pos=(self._width * 0.5, v),
+ color=(0.4, 0.4, 0.6),
+ button='buttonStart' + self._ext,
+ texture=ba.gettexture('startButton'),
+ scale=0.7,
+ )
h_offs = self._width - 160
- self._capture_button(pos=(h_offs, v + scly * dist),
- color=(0.6, 0.4, 0.8),
- button='buttonPickUp' + self._ext,
- texture=ba.gettexture('buttonPickUp'),
- scale=1.0)
- self._capture_button(pos=(h_offs - sclx * dist, v),
- color=(0.7, 0.5, 0.1),
- button='buttonPunch' + self._ext,
- texture=ba.gettexture('buttonPunch'),
- scale=1.0)
- self._capture_button(pos=(h_offs + sclx * dist, v),
- color=(0.5, 0.2, 0.1),
- button='buttonBomb' + self._ext,
- texture=ba.gettexture('buttonBomb'),
- scale=1.0)
- self._capture_button(pos=(h_offs, v - scly * dist),
- color=(0.2, 0.5, 0.2),
- button='buttonJump' + self._ext,
- texture=ba.gettexture('buttonJump'),
- scale=1.0)
+ self._capture_button(
+ pos=(h_offs, v + scly * dist),
+ color=(0.6, 0.4, 0.8),
+ button='buttonPickUp' + self._ext,
+ texture=ba.gettexture('buttonPickUp'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs - sclx * dist, v),
+ color=(0.7, 0.5, 0.1),
+ button='buttonPunch' + self._ext,
+ texture=ba.gettexture('buttonPunch'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs + sclx * dist, v),
+ color=(0.5, 0.2, 0.1),
+ button='buttonBomb' + self._ext,
+ texture=ba.gettexture('buttonBomb'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs, v - scly * dist),
+ color=(0.2, 0.5, 0.2),
+ button='buttonJump' + self._ext,
+ texture=ba.gettexture('buttonJump'),
+ scale=1.0,
+ )
self._advanced_button = ba.buttonwidget(
parent=self._root_widget,
@@ -327,7 +377,8 @@ class GamepadSettingsWindow(ba.Window):
textcolor=(0.65, 0.6, 0.7),
position=(self._width - 300, 30),
size=(130, 40),
- on_activate_call=self._do_advanced)
+ on_activate_call=self._do_advanced,
+ )
try:
if cancel_button is not None and save_button is not None:
@@ -364,6 +415,7 @@ class GamepadSettingsWindow(ba.Window):
def _do_advanced(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings import gamepadadvanced
+
gamepadadvanced.GamepadAdvancedSettingsWindow(self)
def _enable_check_box_changed(self, value: bool) -> None:
@@ -396,8 +448,9 @@ class GamepadSettingsWindow(ba.Window):
assert self._settings is not None
return self._settings.get('startButtonActivatesDefaultWidget', True)
- def set_start_button_activates_default_widget_value(self,
- value: bool) -> None:
+ def set_start_button_activates_default_widget_value(
+ self, value: bool
+ ) -> None:
"""(internal)"""
assert self._settings is not None
if value:
@@ -465,11 +518,13 @@ class GamepadSettingsWindow(ba.Window):
def show_secondary_editor(self) -> None:
"""(internal)"""
- GamepadSettingsWindow(self._input,
- is_main_menu=False,
- settings=self._settings,
- transition='in_scale',
- transition_out='out_scale')
+ GamepadSettingsWindow(
+ self._input,
+ is_main_menu=False,
+ settings=self._settings,
+ transition='in_scale',
+ transition_out='out_scale',
+ )
def get_control_value_name(self, control: str) -> str | ba.Lstr:
"""(internal)"""
@@ -478,14 +533,25 @@ class GamepadSettingsWindow(ba.Window):
if control == 'analogStickLR' + self._ext:
# This actually shows both LR and UD.
- sval1 = (self._settings['analogStickLR' +
- self._ext] if 'analogStickLR' + self._ext
- in self._settings else 5 if self._is_secondary else 1)
- sval2 = (self._settings['analogStickUD' +
- self._ext] if 'analogStickUD' + self._ext
- in self._settings else 6 if self._is_secondary else 2)
- return self._input.get_axis_name(
- sval1) + ' / ' + self._input.get_axis_name(sval2)
+ sval1 = (
+ self._settings['analogStickLR' + self._ext]
+ if 'analogStickLR' + self._ext in self._settings
+ else 5
+ if self._is_secondary
+ else 1
+ )
+ sval2 = (
+ self._settings['analogStickUD' + self._ext]
+ if 'analogStickUD' + self._ext in self._settings
+ else 6
+ if self._is_secondary
+ else 2
+ )
+ return (
+ self._input.get_axis_name(sval1)
+ + ' / '
+ + self._input.get_axis_name(sval2)
+ )
# If they're looking for triggers.
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
@@ -502,8 +568,10 @@ class GamepadSettingsWindow(ba.Window):
# For dpad buttons: show individual buttons if any are set.
# Otherwise show whichever dpad is set (defaulting to 1).
dpad_buttons = [
- 'buttonLeft' + self._ext, 'buttonRight' + self._ext,
- 'buttonUp' + self._ext, 'buttonDown' + self._ext
+ 'buttonLeft' + self._ext,
+ 'buttonRight' + self._ext,
+ 'buttonUp' + self._ext,
+ 'buttonDown' + self._ext,
]
if control in dpad_buttons:
@@ -516,20 +584,32 @@ class GamepadSettingsWindow(ba.Window):
# No dpad buttons - show the dpad number for all 4.
return ba.Lstr(
value='${A} ${B}',
- subs=[('${A}', ba.Lstr(resource=self._r + '.dpadText')),
- ('${B}',
- str(self._settings['dpad' +
- self._ext] if 'dpad' + self._ext in
- self._settings else 2 if self._is_secondary else 1))
- ])
+ subs=[
+ ('${A}', ba.Lstr(resource=self._r + '.dpadText')),
+ (
+ '${B}',
+ str(
+ self._settings['dpad' + self._ext]
+ if 'dpad' + self._ext in self._settings
+ else 2
+ if self._is_secondary
+ else 1
+ ),
+ ),
+ ],
+ )
# other buttons..
if control in self._settings:
return self._input.get_button_name(self._settings[control])
return ba.Lstr(resource=self._r + '.unsetText')
- def _gamepad_event(self, control: str, event: dict[str, Any],
- dialog: AwaitGamepadInputWindow) -> None:
+ def _gamepad_event(
+ self,
+ control: str,
+ event: dict[str, Any],
+ dialog: AwaitGamepadInputWindow,
+ ) -> None:
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -539,8 +619,10 @@ class GamepadSettingsWindow(ba.Window):
# For our dpad-buttons we're looking for either a button-press or a
# hat-switch press.
if control in [
- 'buttonUp' + ext, 'buttonLeft' + ext, 'buttonDown' + ext,
- 'buttonRight' + ext
+ 'buttonUp' + ext,
+ 'buttonLeft' + ext,
+ 'buttonDown' + ext,
+ 'buttonRight' + ext,
]:
if event['type'] in ['BUTTONDOWN', 'HATMOTION']:
@@ -553,8 +635,10 @@ class GamepadSettingsWindow(ba.Window):
elif event['type'] == 'HATMOTION':
# clear out any set dir-buttons
for btn in [
- 'buttonUp' + ext, 'buttonLeft' + ext,
- 'buttonRight' + ext, 'buttonDown' + ext
+ 'buttonUp' + ext,
+ 'buttonLeft' + ext,
+ 'buttonRight' + ext,
+ 'buttonDown' + ext,
]:
if btn in self._settings:
del self._settings[btn]
@@ -567,18 +651,22 @@ class GamepadSettingsWindow(ba.Window):
self._settings['dpad' + ext] = event['hat']
# Update the 4 dpad button txt widgets.
- ba.textwidget(edit=self._textwidgets['buttonUp' + ext],
- text=self.get_control_value_name('buttonUp' +
- ext))
- ba.textwidget(edit=self._textwidgets['buttonLeft' + ext],
- text=self.get_control_value_name('buttonLeft' +
- ext))
- ba.textwidget(edit=self._textwidgets['buttonRight' + ext],
- text=self.get_control_value_name('buttonRight' +
- ext))
- ba.textwidget(edit=self._textwidgets['buttonDown' + ext],
- text=self.get_control_value_name('buttonDown' +
- ext))
+ ba.textwidget(
+ edit=self._textwidgets['buttonUp' + ext],
+ text=self.get_control_value_name('buttonUp' + ext),
+ )
+ ba.textwidget(
+ edit=self._textwidgets['buttonLeft' + ext],
+ text=self.get_control_value_name('buttonLeft' + ext),
+ )
+ ba.textwidget(
+ edit=self._textwidgets['buttonRight' + ext],
+ text=self.get_control_value_name('buttonRight' + ext),
+ )
+ ba.textwidget(
+ edit=self._textwidgets['buttonDown' + ext],
+ text=self.get_control_value_name('buttonDown' + ext),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
@@ -597,16 +685,18 @@ class GamepadSettingsWindow(ba.Window):
self._settings['analogStickLR' + ext] = axis
ba.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
- text=self.get_control_value_name('analogStickLR' +
- ext))
+ text=self.get_control_value_name('analogStickLR' + ext),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
# Now launch the up/down listener.
AwaitGamepadInputWindow(
- self._input, 'analogStickUD' + ext,
+ self._input,
+ 'analogStickUD' + ext,
self._gamepad_event,
- ba.Lstr(resource=self._r + '.pressUpDownText'))
+ ba.Lstr(resource=self._r + '.pressUpDownText'),
+ )
elif control == 'analogStickUD' + ext:
if event['type'] == 'AXISMOTION':
@@ -619,7 +709,7 @@ class GamepadSettingsWindow(ba.Window):
if 'analogStickLR' + ext in self._settings:
lr_axis = self._settings['analogStickLR' + ext]
else:
- lr_axis = (5 if self._is_secondary else 1)
+ lr_axis = 5 if self._is_secondary else 1
if axis != lr_axis:
if axis == (6 if self._is_secondary else 2):
@@ -630,8 +720,10 @@ class GamepadSettingsWindow(ba.Window):
self._settings['analogStickUD' + ext] = axis
ba.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
- text=self.get_control_value_name('analogStickLR' +
- ext))
+ text=self.get_control_value_name(
+ 'analogStickLR' + ext
+ ),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
else:
@@ -641,69 +733,93 @@ class GamepadSettingsWindow(ba.Window):
self._settings[control] = value
# Update the button's text widget.
- ba.textwidget(edit=self._textwidgets[control],
- text=self.get_control_value_name(control))
+ ba.textwidget(
+ edit=self._textwidgets[control],
+ text=self.get_control_value_name(control),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
- def _capture_button(self,
- pos: tuple[float, float],
- color: tuple[float, float, float],
- texture: ba.Texture,
- button: str,
- scale: float = 1.0,
- message: ba.Lstr | None = None,
- message2: ba.Lstr | None = None,
- maxwidth: float = 80.0) -> ba.Widget:
+ def _capture_button(
+ self,
+ pos: tuple[float, float],
+ color: tuple[float, float, float],
+ texture: ba.Texture,
+ button: str,
+ scale: float = 1.0,
+ message: ba.Lstr | None = None,
+ message2: ba.Lstr | None = None,
+ maxwidth: float = 80.0,
+ ) -> ba.Widget:
if message is None:
message = ba.Lstr(resource=self._r + '.pressAnyButtonText')
base_size = 79
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(pos[0] - base_size * 0.5 * scale,
- pos[1] - base_size * 0.5 * scale),
- autoselect=True,
- size=(base_size * scale, base_size * scale),
- texture=texture,
- label='',
- color=color)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ pos[0] - base_size * 0.5 * scale,
+ pos[1] - base_size * 0.5 * scale,
+ ),
+ autoselect=True,
+ size=(base_size * scale, base_size * scale),
+ texture=texture,
+ label='',
+ color=color,
+ )
# Make this in a timer so that it shows up on top of all other buttons.
def doit() -> None:
uiscale = 0.9 * scale
- txt = ba.textwidget(parent=self._root_widget,
- position=(pos[0] + 0.0 * scale,
- pos[1] - 58.0 * scale),
- color=(1, 1, 1, 0.3),
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=uiscale,
- text=self.get_control_value_name(button),
- maxwidth=maxwidth)
+ txt = ba.textwidget(
+ parent=self._root_widget,
+ position=(pos[0] + 0.0 * scale, pos[1] - 58.0 * scale),
+ color=(1, 1, 1, 0.3),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=uiscale,
+ text=self.get_control_value_name(button),
+ maxwidth=maxwidth,
+ )
self._textwidgets[button] = txt
- ba.buttonwidget(edit=btn,
- on_activate_call=ba.Call(AwaitGamepadInputWindow,
- self._input, button,
- self._gamepad_event,
- message, message2))
+ ba.buttonwidget(
+ edit=btn,
+ on_activate_call=ba.Call(
+ AwaitGamepadInputWindow,
+ self._input,
+ button,
+ self._gamepad_event,
+ message,
+ message2,
+ ),
+ )
ba.timer(0, doit, timetype=ba.TimeType.REAL)
return btn
def _cancel(self) -> None:
from bastd.ui.settings.controls import ControlsSettingsWindow
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if self._is_main_menu:
ba.app.ui.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget())
+ ControlsSettingsWindow(transition='in_left').get_root_widget()
+ )
def _save(self) -> None:
- from ba.internal import (master_server_post, get_input_device_config,
- get_input_map_hash, should_submit_debug_info)
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ from ba.internal import (
+ master_server_post,
+ get_input_device_config,
+ get_input_map_hash,
+ should_submit_debug_info,
+ )
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
# If we're a secondary editor we just go away (we were editing our
# parent's settings dict).
@@ -726,14 +842,16 @@ class GamepadSettingsWindow(ba.Window):
inputhash = get_input_map_hash(self._input)
if should_submit_debug_info():
master_server_post(
- 'controllerConfig', {
+ 'controllerConfig',
+ {
'ua': ba.app.user_agent_string,
'b': ba.app.build_number,
'name': self._name,
'inputMapHash': inputhash,
'config': dst2,
- 'v': 2
- })
+ 'v': 2,
+ },
+ )
ba.app.config.apply_and_commit()
ba.playsound(ba.getsound('gunCocking'))
else:
@@ -741,21 +859,23 @@ class GamepadSettingsWindow(ba.Window):
if self._is_main_menu:
from bastd.ui.settings.controls import ControlsSettingsWindow
+
ba.app.ui.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget())
+ ControlsSettingsWindow(transition='in_left').get_root_widget()
+ )
class AwaitGamepadInputWindow(ba.Window):
"""Window for capturing a gamepad button press."""
def __init__(
- self,
- gamepad: ba.InputDevice,
- button: str,
- callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow],
- Any],
- message: ba.Lstr | None = None,
- message2: ba.Lstr | None = None):
+ self,
+ gamepad: ba.InputDevice,
+ button: str,
+ callback: Callable[[str, dict[str, Any], AwaitGamepadInputWindow], Any],
+ message: ba.Lstr | None = None,
+ message2: ba.Lstr | None = None,
+ ):
if message is None:
print('AwaitGamepadInputWindow message is None!')
# Shouldn't get here.
@@ -766,41 +886,55 @@ class AwaitGamepadInputWindow(ba.Window):
width = 400
height = 150
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.9 if uiscale is ba.UIScale.MEDIUM else 1.0),
- size=(width, height),
- transition='in_scale'), )
- ba.textwidget(parent=self._root_widget,
- position=(0, (height - 60) if message2 is None else
- (height - 50)),
- size=(width, 25),
- text=message,
- maxwidth=width * 0.9,
- h_align='center',
- v_align='center')
+ super().__init__(
+ root_widget=ba.containerwidget(
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.9
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ size=(width, height),
+ transition='in_scale',
+ ),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, (height - 60) if message2 is None else (height - 50)),
+ size=(width, 25),
+ text=message,
+ maxwidth=width * 0.9,
+ h_align='center',
+ v_align='center',
+ )
if message2 is not None:
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height - 60),
- size=(0, 0),
- text=message2,
- maxwidth=width * 0.9,
- scale=0.47,
- color=(0.7, 1.0, 0.7, 0.6),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height - 60),
+ size=(0, 0),
+ text=message2,
+ maxwidth=width * 0.9,
+ scale=0.47,
+ color=(0.7, 1.0, 0.7, 0.6),
+ h_align='center',
+ v_align='center',
+ )
self._counter = 5
- self._count_down_text = ba.textwidget(parent=self._root_widget,
- h_align='center',
- position=(0, height - 110),
- size=(width, 25),
- color=(1, 1, 1, 0.3),
- text=str(self._counter))
+ self._count_down_text = ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ position=(0, height - 110),
+ size=(width, 25),
+ color=(1, 1, 1, 0.3),
+ text=str(self._counter),
+ )
self._decrement_timer: ba.Timer | None = ba.Timer(
1.0,
ba.Call(self._decrement),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
ba.internal.capture_gamepad_input(ba.WeakCall(self._event_callback))
def __del__(self) -> None:
@@ -820,16 +954,20 @@ class AwaitGamepadInputWindow(ba.Window):
assert isinstance(input_device, ba.InputDevice)
# Update - we now allow *any* input device of this type.
- if (self._input and input_device
- and input_device.name == self._input.name):
+ if (
+ self._input
+ and input_device
+ and input_device.name == self._input.name
+ ):
self._callback(self._capture_button, event, self)
def _decrement(self) -> None:
self._counter -= 1
if self._counter >= 1:
if self._count_down_text:
- ba.textwidget(edit=self._count_down_text,
- text=str(self._counter))
+ ba.textwidget(
+ edit=self._count_down_text, text=str(self._counter)
+ )
else:
ba.playsound(ba.getsound('error'))
self.die()
diff --git a/assets/src/ba_data/python/bastd/ui/settings/gamepadadvanced.py b/assets/src/ba_data/python/bastd/ui/settings/gamepadadvanced.py
index e97b3184..73265887 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/gamepadadvanced.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/gamepadadvanced.py
@@ -18,6 +18,7 @@ class GamepadAdvancedSettingsWindow(ba.Window):
def __init__(self, parent_window: gpsui.GamepadSettingsWindow):
# pylint: disable=too-many-statements
+ # pylint: disable=too-many-locals
self._parent_window = parent_window
app = ba.app
@@ -28,62 +29,88 @@ class GamepadAdvancedSettingsWindow(ba.Window):
self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
self._height = 402 if uiscale is ba.UIScale.SMALL else 512
self._textwidgets: dict[str, ba.Widget] = {}
- super().__init__(root_widget=ba.containerwidget(
- transition='in_scale',
- size=(self._width, self._height),
- scale=1.06 * (1.85 if uiscale is ba.UIScale.SMALL else
- 1.35 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0),
- scale_origin_stack_offset=(parent_window.get_advanced_button().
- get_screen_space_center())))
+ advb = parent_window.get_advanced_button()
+ super().__init__(
+ root_widget=ba.containerwidget(
+ transition='in_scale',
+ size=(self._width, self._height),
+ scale=1.06
+ * (
+ 1.85
+ if uiscale is ba.UIScale.SMALL
+ else 1.35
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -25)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ scale_origin_stack_offset=(advb.get_screen_space_center()),
+ )
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height -
- (40 if uiscale is ba.UIScale.SMALL else 34)),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.advancedTitleText'),
- maxwidth=320,
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(
+ self._width * 0.5,
+ self._height - (40 if uiscale is ba.UIScale.SMALL else 34),
+ ),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.advancedTitleText'),
+ maxwidth=320,
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ )
back_button = btn = ba.buttonwidget(
parent=self._root_widget,
autoselect=True,
- position=(self._width - (176 + x_inset), self._height -
- (60 if uiscale is ba.UIScale.SMALL else 55)),
+ position=(
+ self._width - (176 + x_inset),
+ self._height - (60 if uiscale is ba.UIScale.SMALL else 55),
+ ),
size=(120, 48),
text_scale=0.8,
label=ba.Lstr(resource='doneText'),
- on_activate_call=self._done)
- ba.containerwidget(edit=self._root_widget,
- start_button=btn,
- on_cancel_call=btn.activate)
+ on_activate_call=self._done,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ start_button=btn,
+ on_cancel_call=btn.activate,
+ )
self._scroll_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - 110
self._sub_width = self._scroll_width - 20
- self._sub_height = (940 if self._parent_window.get_is_secondary() else
- 1040)
+ self._sub_height = (
+ 940 if self._parent_window.get_is_secondary() else 1040
+ )
if app.vr_mode:
self._sub_height += 50
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - 65 - self._scroll_height),
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - 65 - self._scroll_height,
+ ),
size=(self._scroll_width, self._scroll_height),
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ selection_loops_to_parent=True,
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
h = 30
v = self._sub_height - 10
@@ -101,16 +128,19 @@ class GamepadAdvancedSettingsWindow(ba.Window):
textcolor=(0.8, 0.8, 0.8),
maxwidth=330,
scale=1.0,
- on_value_change_call=self._parent_window.
- set_unassigned_buttons_run_value,
+ on_value_change_call=(
+ self._parent_window.set_unassigned_buttons_run_value
+ ),
autoselect=True,
- value=self._parent_window.get_unassigned_buttons_run_value())
+ value=self._parent_window.get_unassigned_buttons_run_value(),
+ )
ba.widget(edit=cb1, up_widget=back_button)
v -= 60
capb = self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.runButton1Text'),
- control='buttonRun1' + self._parent_window.get_ext())
+ control='buttonRun1' + self._parent_window.get_ext(),
+ )
if self._parent_window.get_is_secondary():
for widget in capb:
ba.widget(edit=widget, up_widget=back_button)
@@ -118,17 +148,19 @@ class GamepadAdvancedSettingsWindow(ba.Window):
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.runButton2Text'),
- control='buttonRun2' + self._parent_window.get_ext())
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 24),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.runTriggerDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- maxwidth=self._sub_width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ control='buttonRun2' + self._parent_window.get_ext(),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 24),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.runTriggerDescriptionText'),
+ color=(0.7, 1, 0.7, 0.6),
+ maxwidth=self._sub_width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 85
@@ -136,13 +168,15 @@ class GamepadAdvancedSettingsWindow(ba.Window):
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.runTrigger1Text'),
control='triggerRun1' + self._parent_window.get_ext(),
- message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'))
+ message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
+ )
v -= 42
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.runTrigger2Text'),
control='triggerRun2' + self._parent_window.get_ext(),
- message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'))
+ message=ba.Lstr(resource=self._r + '.pressAnyAnalogTriggerText'),
+ )
# in vr mode, allow assigning a reset-view button
if app.vr_mode:
@@ -150,69 +184,80 @@ class GamepadAdvancedSettingsWindow(ba.Window):
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.vrReorientButtonText'),
- control='buttonVRReorient' + self._parent_window.get_ext())
+ control='buttonVRReorient' + self._parent_window.get_ext(),
+ )
v -= 60
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.extraStartButtonText'),
- control='buttonStart2' + self._parent_window.get_ext())
+ control='buttonStart2' + self._parent_window.get_ext(),
+ )
v -= 60
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.ignoredButton1Text'),
- control='buttonIgnored' + self._parent_window.get_ext())
+ control='buttonIgnored' + self._parent_window.get_ext(),
+ )
v -= 42
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.ignoredButton2Text'),
- control='buttonIgnored2' + self._parent_window.get_ext())
+ control='buttonIgnored2' + self._parent_window.get_ext(),
+ )
v -= 42
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.ignoredButton3Text'),
- control='buttonIgnored3' + self._parent_window.get_ext())
+ control='buttonIgnored3' + self._parent_window.get_ext(),
+ )
v -= 42
self._capture_button(
pos=(h2, v),
name=ba.Lstr(resource=self._r + '.ignoredButton4Text'),
- control='buttonIgnored4' + self._parent_window.get_ext())
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 14),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.ignoredButtonDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- scale=0.8,
- maxwidth=self._sub_width * 0.8,
- h_align='center',
- v_align='center')
+ control='buttonIgnored4' + self._parent_window.get_ext(),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 14),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.ignoredButtonDescriptionText'),
+ color=(0.7, 1, 0.7, 0.6),
+ scale=0.8,
+ maxwidth=self._sub_width * 0.8,
+ h_align='center',
+ v_align='center',
+ )
v -= 80
- ba.checkboxwidget(parent=self._subcontainer,
- autoselect=True,
- position=(h + 50, v),
- size=(400, 30),
- text=ba.Lstr(resource=self._r +
- '.startButtonActivatesDefaultText'),
- textcolor=(0.8, 0.8, 0.8),
- maxwidth=450,
- scale=0.9,
- on_value_change_call=self._parent_window.
- set_start_button_activates_default_widget_value,
- value=self._parent_window.
- get_start_button_activates_default_widget_value())
+ pwin = self._parent_window
+ ba.checkboxwidget(
+ parent=self._subcontainer,
+ autoselect=True,
+ position=(h + 50, v),
+ size=(400, 30),
+ text=ba.Lstr(resource=self._r + '.startButtonActivatesDefaultText'),
+ textcolor=(0.8, 0.8, 0.8),
+ maxwidth=450,
+ scale=0.9,
+ on_value_change_call=(
+ pwin.set_start_button_activates_default_widget_value
+ ),
+ value=pwin.get_start_button_activates_default_widget_value(),
+ )
ba.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5, v - 12),
size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.startButtonActivatesDefaultDescriptionText'),
+ text=ba.Lstr(
+ resource=self._r + '.startButtonActivatesDefaultDescriptionText'
+ ),
color=(0.7, 1, 0.7, 0.6),
maxwidth=self._sub_width * 0.8,
scale=0.7,
h_align='center',
- v_align='center')
+ v_align='center',
+ )
v -= 80
ba.checkboxwidget(
@@ -225,17 +270,19 @@ class GamepadAdvancedSettingsWindow(ba.Window):
maxwidth=450,
scale=0.9,
on_value_change_call=self._parent_window.set_ui_only_value,
- value=self._parent_window.get_ui_only_value())
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 12),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.uiOnlyDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- maxwidth=self._sub_width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ value=self._parent_window.get_ui_only_value(),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 12),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.uiOnlyDescriptionText'),
+ color=(0.7, 1, 0.7, 0.6),
+ maxwidth=self._sub_width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 80
ba.checkboxwidget(
@@ -247,19 +294,20 @@ class GamepadAdvancedSettingsWindow(ba.Window):
textcolor=(0.8, 0.8, 0.8),
maxwidth=450,
scale=0.9,
- on_value_change_call=self._parent_window.
- set_ignore_completely_value,
- value=self._parent_window.get_ignore_completely_value())
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 12),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.ignoreCompletelyDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- maxwidth=self._sub_width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ on_value_change_call=pwin.set_ignore_completely_value,
+ value=self._parent_window.get_ignore_completely_value(),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 12),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.ignoreCompletelyDescriptionText'),
+ color=(0.7, 1, 0.7, 0.6),
+ maxwidth=self._sub_width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 80
@@ -272,20 +320,20 @@ class GamepadAdvancedSettingsWindow(ba.Window):
textcolor=(0.8, 0.8, 0.8),
maxwidth=450,
scale=0.9,
- on_value_change_call=self._parent_window.
- set_auto_recalibrate_analog_stick_value,
- value=self._parent_window.get_auto_recalibrate_analog_stick_value(
- ))
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 12),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.autoRecalibrateDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- maxwidth=self._sub_width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ on_value_change_call=pwin.set_auto_recalibrate_analog_stick_value,
+ value=self._parent_window.get_auto_recalibrate_analog_stick_value(),
+ )
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 12),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.autoRecalibrateDescriptionText'),
+ color=(0.7, 1, 0.7, 0.6),
+ maxwidth=self._sub_width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 80
buttons = self._config_value_editor(
@@ -295,20 +343,24 @@ class GamepadAdvancedSettingsWindow(ba.Window):
min_val=0,
max_val=10.0,
increment=0.1,
- x_offset=100)
+ x_offset=100,
+ )
ba.widget(edit=buttons[0], left_widget=cb1, up_widget=cb1)
ba.widget(edit=cb1, right_widget=buttons[0], down_widget=buttons[0])
- ba.textwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5, v - 12),
- size=(0, 0),
- text=ba.Lstr(resource=self._r +
- '.analogStickDeadZoneDescriptionText'),
- color=(0.7, 1, 0.7, 0.6),
- maxwidth=self._sub_width * 0.8,
- scale=0.7,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5, v - 12),
+ size=(0, 0),
+ text=ba.Lstr(
+ resource=self._r + '.analogStickDeadZoneDescriptionText'
+ ),
+ color=(0.7, 1, 0.7, 0.6),
+ maxwidth=self._sub_width * 0.8,
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 100
# child joysticks cant have child joysticks.. that's just
@@ -321,7 +373,8 @@ class GamepadAdvancedSettingsWindow(ba.Window):
position=(40, v),
size=(self._sub_width - 80, 50),
on_activate_call=self._parent_window.show_secondary_editor,
- up_widget=buttons[0])
+ up_widget=buttons[0],
+ )
# set a bigger bottom show-buffer for the widgets we just made
# so we can see the text below them when navigating with
@@ -330,31 +383,36 @@ class GamepadAdvancedSettingsWindow(ba.Window):
ba.widget(edit=child, show_buffer_bottom=30, show_buffer_top=30)
def _capture_button(
- self,
- pos: tuple[float, float],
- name: ba.Lstr,
- control: str,
- message: ba.Lstr | None = None) -> tuple[ba.Widget, ba.Widget]:
+ self,
+ pos: tuple[float, float],
+ name: ba.Lstr,
+ control: str,
+ message: ba.Lstr | None = None,
+ ) -> tuple[ba.Widget, ba.Widget]:
if message is None:
- message = ba.Lstr(resource=self._parent_window.get_r() +
- '.pressAnyButtonText')
- btn = ba.buttonwidget(parent=self._subcontainer,
- autoselect=True,
- position=(pos[0], pos[1]),
- label=name,
- size=(250, 60),
- scale=0.7)
- btn2 = ba.buttonwidget(parent=self._subcontainer,
- autoselect=True,
- position=(pos[0] + 400, pos[1] + 2),
- left_widget=btn,
- color=(0.45, 0.4, 0.5),
- textcolor=(0.65, 0.6, 0.7),
- label=ba.Lstr(resource=self._r + '.clearText'),
- size=(110, 50),
- scale=0.7,
- on_activate_call=ba.Call(
- self._clear_control, control))
+ message = ba.Lstr(
+ resource=self._parent_window.get_r() + '.pressAnyButtonText'
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ autoselect=True,
+ position=(pos[0], pos[1]),
+ label=name,
+ size=(250, 60),
+ scale=0.7,
+ )
+ btn2 = ba.buttonwidget(
+ parent=self._subcontainer,
+ autoselect=True,
+ position=(pos[0] + 400, pos[1] + 2),
+ left_widget=btn,
+ color=(0.45, 0.4, 0.5),
+ textcolor=(0.65, 0.6, 0.7),
+ label=ba.Lstr(resource=self._r + '.clearText'),
+ size=(110, 50),
+ scale=0.7,
+ on_activate_call=ba.Call(self._clear_control, control),
+ )
ba.widget(edit=btn, right_widget=btn2)
# make this in a timer so that it shows up on top of all
@@ -362,6 +420,7 @@ class GamepadAdvancedSettingsWindow(ba.Window):
def doit() -> None:
from bastd.ui.settings import gamepad
+
txt = ba.textwidget(
parent=self._subcontainer,
position=(pos[0] + 285, pos[1] + 20),
@@ -371,19 +430,26 @@ class GamepadAdvancedSettingsWindow(ba.Window):
v_align='center',
scale=0.7,
text=self._parent_window.get_control_value_name(control),
- maxwidth=200)
+ maxwidth=200,
+ )
self._textwidgets[control] = txt
- ba.buttonwidget(edit=btn,
- on_activate_call=ba.Call(
- gamepad.AwaitGamepadInputWindow,
- self._parent_window.get_input(), control,
- self._gamepad_event, message))
+ ba.buttonwidget(
+ edit=btn,
+ on_activate_call=ba.Call(
+ gamepad.AwaitGamepadInputWindow,
+ self._parent_window.get_input(),
+ control,
+ self._gamepad_event,
+ message,
+ ),
+ )
ba.timer(0, doit, timetype=ba.TimeType.REAL)
return btn, btn2
- def _inc(self, control: str, min_val: float, max_val: float,
- inc: float) -> None:
+ def _inc(
+ self, control: str, min_val: float, max_val: float, inc: float
+ ) -> None:
val = self._parent_window.get_settings().get(control, 1.0)
val = min(max_val, max(min_val, val + inc))
if abs(1.0 - val) < 0.001:
@@ -391,32 +457,37 @@ class GamepadAdvancedSettingsWindow(ba.Window):
del self._parent_window.get_settings()[control]
else:
self._parent_window.get_settings()[control] = round(val, 1)
- ba.textwidget(edit=self._textwidgets[control],
- text=self._parent_window.get_control_value_name(control))
+ ba.textwidget(
+ edit=self._textwidgets[control],
+ text=self._parent_window.get_control_value_name(control),
+ )
def _config_value_editor(
- self,
- name: ba.Lstr,
- control: str,
- position: tuple[float, float],
- min_val: float = 0.0,
- max_val: float = 100.0,
- increment: float = 1.0,
- change_sound: bool = True,
- x_offset: float = 0.0,
- displayname: ba.Lstr | None = None) -> tuple[ba.Widget, ba.Widget]:
+ self,
+ name: ba.Lstr,
+ control: str,
+ position: tuple[float, float],
+ min_val: float = 0.0,
+ max_val: float = 100.0,
+ increment: float = 1.0,
+ change_sound: bool = True,
+ x_offset: float = 0.0,
+ displayname: ba.Lstr | None = None,
+ ) -> tuple[ba.Widget, ba.Widget]:
if displayname is None:
displayname = name
- ba.textwidget(parent=self._subcontainer,
- position=position,
- size=(100, 30),
- text=displayname,
- color=(0.8, 0.8, 0.8, 1.0),
- h_align='left',
- v_align='center',
- scale=1.0,
- maxwidth=280)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=position,
+ size=(100, 30),
+ text=displayname,
+ color=(0.8, 0.8, 0.8, 1.0),
+ h_align='left',
+ v_align='center',
+ scale=1.0,
+ maxwidth=280,
+ )
self._textwidgets[control] = ba.textwidget(
parent=self._subcontainer,
position=(246.0 + x_offset, position[1]),
@@ -426,51 +497,63 @@ class GamepadAdvancedSettingsWindow(ba.Window):
h_align='right',
v_align='center',
text=self._parent_window.get_control_value_name(control),
- padding=2)
- btn = ba.buttonwidget(parent=self._subcontainer,
- autoselect=True,
- position=(330 + x_offset, position[1] + 4),
- size=(28, 28),
- label='-',
- on_activate_call=ba.Call(self._inc, control,
- min_val, max_val,
- -increment),
- repeat=True,
- enable_sound=(change_sound is True))
- btn2 = ba.buttonwidget(parent=self._subcontainer,
- autoselect=True,
- position=(380 + x_offset, position[1] + 4),
- size=(28, 28),
- label='+',
- on_activate_call=ba.Call(
- self._inc, control, min_val, max_val,
- increment),
- repeat=True,
- enable_sound=(change_sound is True))
+ padding=2,
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ autoselect=True,
+ position=(330 + x_offset, position[1] + 4),
+ size=(28, 28),
+ label='-',
+ on_activate_call=ba.Call(
+ self._inc, control, min_val, max_val, -increment
+ ),
+ repeat=True,
+ enable_sound=(change_sound is True),
+ )
+ btn2 = ba.buttonwidget(
+ parent=self._subcontainer,
+ autoselect=True,
+ position=(380 + x_offset, position[1] + 4),
+ size=(28, 28),
+ label='+',
+ on_activate_call=ba.Call(
+ self._inc, control, min_val, max_val, increment
+ ),
+ repeat=True,
+ enable_sound=(change_sound is True),
+ )
return btn, btn2
def _clear_control(self, control: str) -> None:
if control in self._parent_window.get_settings():
del self._parent_window.get_settings()[control]
- ba.textwidget(edit=self._textwidgets[control],
- text=self._parent_window.get_control_value_name(control))
+ ba.textwidget(
+ edit=self._textwidgets[control],
+ text=self._parent_window.get_control_value_name(control),
+ )
- def _gamepad_event(self, control: str, event: dict[str, Any],
- dialog: gpsui.AwaitGamepadInputWindow) -> None:
+ def _gamepad_event(
+ self,
+ control: str,
+ event: dict[str, Any],
+ dialog: gpsui.AwaitGamepadInputWindow,
+ ) -> None:
ext = self._parent_window.get_ext()
if control in ['triggerRun1' + ext, 'triggerRun2' + ext]:
if event['type'] == 'AXISMOTION':
# ignore small values or else we might get triggered
# by noise
if abs(event['value']) > 0.5:
- self._parent_window.get_settings()[control] = (
- event['axis'])
+ self._parent_window.get_settings()[control] = event['axis']
# update the button's text widget
if self._textwidgets[control]:
ba.textwidget(
edit=self._textwidgets[control],
text=self._parent_window.get_control_value_name(
- control))
+ control
+ ),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
else:
@@ -482,7 +565,9 @@ class GamepadAdvancedSettingsWindow(ba.Window):
ba.textwidget(
edit=self._textwidgets[control],
text=self._parent_window.get_control_value_name(
- control))
+ control
+ ),
+ )
ba.playsound(ba.getsound('gunCocking'))
dialog.die()
diff --git a/assets/src/ba_data/python/bastd/ui/settings/gamepadselect.py b/assets/src/ba_data/python/bastd/ui/settings/gamepadselect.py
index 65bdf267..c719985b 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/gamepadselect.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/gamepadselect.py
@@ -32,45 +32,63 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
assert isinstance(inputdevice, ba.InputDevice)
if inputdevice.allows_configuring:
ba.app.ui.set_main_menu_window(
- gamepad.GamepadSettingsWindow(inputdevice).get_root_widget())
+ gamepad.GamepadSettingsWindow(inputdevice).get_root_widget()
+ )
else:
width = 700
height = 200
button_width = 100
uiscale = ba.app.ui.uiscale
- dlg = (ba.containerwidget(
- scale=(1.7 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
+ dlg = ba.containerwidget(
+ scale=(
+ 1.7
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
size=(width, height),
- transition='in_right'))
+ transition='in_right',
+ )
ba.app.ui.set_main_menu_window(dlg)
device_name = inputdevice.name
if device_name == 'iDevice':
- msg = ba.Lstr(resource='bsRemoteConfigureInAppText',
- subs=[('${REMOTE_APP_NAME}', get_remote_app_name())])
+ msg = ba.Lstr(
+ resource='bsRemoteConfigureInAppText',
+ subs=[('${REMOTE_APP_NAME}', get_remote_app_name())],
+ )
else:
- msg = ba.Lstr(resource='cantConfigureDeviceText',
- subs=[('${DEVICE}', device_name)])
- ba.textwidget(parent=dlg,
- position=(0, height - 80),
- size=(width, 25),
- text=msg,
- scale=0.8,
- h_align='center',
- v_align='top')
+ msg = ba.Lstr(
+ resource='cantConfigureDeviceText',
+ subs=[('${DEVICE}', device_name)],
+ )
+ ba.textwidget(
+ parent=dlg,
+ position=(0, height - 80),
+ size=(width, 25),
+ text=msg,
+ scale=0.8,
+ h_align='center',
+ v_align='top',
+ )
def _ok() -> None:
from bastd.ui.settings import controls
+
ba.containerwidget(edit=dlg, transition='out_right')
ba.app.ui.set_main_menu_window(
controls.ControlsSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
- ba.buttonwidget(parent=dlg,
- position=((width - button_width) / 2, 20),
- size=(button_width, 60),
- label=ba.Lstr(resource='okText'),
- on_activate_call=_ok)
+ ba.buttonwidget(
+ parent=dlg,
+ position=((width - button_width) / 2, 20),
+ size=(button_width, 60),
+ label=ba.Lstr(resource='okText'),
+ on_activate_call=_ok,
+ )
class GamepadSelectWindow(ba.Window):
@@ -78,74 +96,97 @@ class GamepadSelectWindow(ba.Window):
def __init__(self) -> None:
from typing import cast
+
width = 480
height = 170
spacing = 40
self._r = 'configGamepadSelectWindow'
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- scale=(2.3 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- size=(width, height),
- transition='in_right',
- ))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ scale=(
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ size=(width, height),
+ transition='in_right',
+ )
+ )
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(20, height - 60),
- size=(130, 60),
- label=ba.Lstr(resource='backText'),
- button_type='back',
- scale=0.8,
- on_activate_call=self._back)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(20, height - 60),
+ size=(130, 60),
+ label=ba.Lstr(resource='backText'),
+ button_type='back',
+ scale=0.8,
+ on_activate_call=self._back,
+ )
# Let's not have anything selected by default; its misleading looking
# for the controller getting configured.
- ba.containerwidget(edit=self._root_widget,
- cancel_button=btn,
- selected_child=cast(ba.Widget, 0))
- ba.textwidget(parent=self._root_widget,
- position=(20, height - 50),
- size=(width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=250,
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center')
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=btn,
+ selected_child=cast(ba.Widget, 0),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(20, height - 50),
+ size=(width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=250,
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v: float = height - 60
v -= spacing
- ba.textwidget(parent=self._root_widget,
- position=(15, v),
- size=(width - 30, 30),
- scale=0.8,
- text=ba.Lstr(resource=self._r + '.pressAnyButtonText'),
- maxwidth=width * 0.95,
- color=ba.app.ui.infotextcolor,
- h_align='center',
- v_align='top')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(15, v),
+ size=(width - 30, 30),
+ scale=0.8,
+ text=ba.Lstr(resource=self._r + '.pressAnyButtonText'),
+ maxwidth=width * 0.95,
+ color=ba.app.ui.infotextcolor,
+ h_align='center',
+ v_align='top',
+ )
v -= spacing * 1.24
if ba.app.platform == 'android':
- ba.textwidget(parent=self._root_widget,
- position=(15, v),
- size=(width - 30, 30),
- scale=0.46,
- text=ba.Lstr(resource=self._r + '.androidNoteText'),
- maxwidth=width * 0.95,
- color=(0.7, 0.9, 0.7, 0.5),
- h_align='center',
- v_align='top')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(15, v),
+ size=(width - 30, 30),
+ scale=0.46,
+ text=ba.Lstr(resource=self._r + '.androidNoteText'),
+ maxwidth=width * 0.95,
+ color=(0.7, 0.9, 0.7, 0.5),
+ h_align='center',
+ v_align='top',
+ )
ba.internal.capture_gamepad_input(gamepad_configure_callback)
def _back(self) -> None:
from bastd.ui.settings import controls
+
ba.internal.release_gamepad_input()
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
controls.ControlsSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/graphics.py b/assets/src/ba_data/python/bastd/ui/settings/graphics.py
index 09eb1f20..a771f6e0 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/graphics.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/graphics.py
@@ -16,14 +16,17 @@ if TYPE_CHECKING:
class GraphicsSettingsWindow(ba.Window):
"""Window for graphics settings."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
from bastd.ui import popup
from bastd.ui.config import ConfigCheckBox, ConfigNumberEdit
+
# if they provided an origin-widget, scale up from that
scale_origin: tuple[float, float] | None
if origin_widget is not None:
@@ -62,46 +65,63 @@ class GraphicsSettingsWindow(ba.Window):
show_resolution = True
if app.vr_mode:
- show_resolution = (app.platform == 'android'
- and app.subplatform == 'cardboard')
+ show_resolution = (
+ app.platform == 'android' and app.subplatform == 'cardboard'
+ )
uiscale = ba.app.ui.uiscale
- base_scale = (2.4 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)
+ base_scale = (
+ 2.4
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ )
popup_menu_scale = base_scale * 1.2
v = height - 50
v -= spacing * 1.15
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- scale=base_scale,
- stack_offset=(0, -30) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ scale=base_scale,
+ stack_offset=(0, -30)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(35, height - 50),
- size=(120, 60),
- scale=0.8,
- text_scale=1.2,
- autoselect=True,
- label=ba.Lstr(resource='backText'),
- button_type='back',
- on_activate_call=self._back)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35, height - 50),
+ size=(120, 60),
+ scale=0.8,
+ text_scale=1.2,
+ autoselect=True,
+ label=ba.Lstr(resource='backText'),
+ button_type='back',
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(0, height - 44),
- size=(width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='top')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 44),
+ size=(width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='top',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._fullscreen_checkbox: ba.Widget | None
if self._show_fullscreen:
@@ -112,12 +132,20 @@ class GraphicsSettingsWindow(ba.Window):
maxwidth=200,
size=(300, 30),
configkey='Fullscreen',
- displayname=ba.Lstr(resource=self._r +
- ('.fullScreenCmdText' if app.platform ==
- 'mac' else '.fullScreenCtrlText'))).widget
+ displayname=ba.Lstr(
+ resource=self._r
+ + (
+ '.fullScreenCmdText'
+ if app.platform == 'mac'
+ else '.fullScreenCtrlText'
+ )
+ ),
+ ).widget
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._fullscreen_checkbox)
+ ba.containerwidget(
+ edit=self._root_widget,
+ selected_child=self._fullscreen_checkbox,
+ )
self._have_selected_child = True
v -= fullscreen_spacing
else:
@@ -134,14 +162,17 @@ class GraphicsSettingsWindow(ba.Window):
maxval=2.0,
increment=0.1,
xoffset=-70,
- textscale=0.85)
+ textscale=0.85,
+ )
if ba.app.ui.use_toolbars:
- ba.widget(edit=gmc.plusbutton,
- right_widget=ba.internal.get_special_widget(
- 'party_button'))
+ ba.widget(
+ edit=gmc.plusbutton,
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if not self._have_selected_child:
- ba.containerwidget(edit=self._root_widget,
- selected_child=gmc.minusbutton)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=gmc.minusbutton
+ )
self._have_selected_child = True
v -= gamma_spacing
else:
@@ -151,15 +182,17 @@ class GraphicsSettingsWindow(ba.Window):
self._unselected_color = (0.7, 0.7, 0.7, 1)
# quality
- ba.textwidget(parent=self._root_widget,
- position=(60, v),
- size=(160, 25),
- text=ba.Lstr(resource=self._r + '.visualsText'),
- color=ba.app.ui.heading_color,
- scale=0.65,
- maxwidth=150,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(60, v),
+ size=(160, 25),
+ text=ba.Lstr(resource=self._r + '.visualsText'),
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ maxwidth=150,
+ h_align='center',
+ v_align='center',
+ )
popup.PopupMenu(
parent=self._root_widget,
position=(60, v - 50),
@@ -167,27 +200,31 @@ class GraphicsSettingsWindow(ba.Window):
scale=popup_menu_scale,
choices=['Auto', 'Higher', 'High', 'Medium', 'Low'],
choices_disabled=['Higher', 'High']
- if ba.internal.get_max_graphics_quality() == 'Medium' else [],
+ if ba.internal.get_max_graphics_quality() == 'Medium'
+ else [],
choices_display=[
ba.Lstr(resource='autoText'),
ba.Lstr(resource=self._r + '.higherText'),
ba.Lstr(resource=self._r + '.highText'),
ba.Lstr(resource=self._r + '.mediumText'),
- ba.Lstr(resource=self._r + '.lowText')
+ ba.Lstr(resource=self._r + '.lowText'),
],
current_choice=ba.app.config.resolve('Graphics Quality'),
- on_value_change_call=self._set_quality)
+ on_value_change_call=self._set_quality,
+ )
# texture controls
- ba.textwidget(parent=self._root_widget,
- position=(230, v),
- size=(160, 25),
- text=ba.Lstr(resource=self._r + '.texturesText'),
- color=ba.app.ui.heading_color,
- scale=0.65,
- maxwidth=150,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(230, v),
+ size=(160, 25),
+ text=ba.Lstr(resource=self._r + '.texturesText'),
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ maxwidth=150,
+ h_align='center',
+ v_align='center',
+ )
textures_popup = popup.PopupMenu(
parent=self._root_widget,
position=(230, v - 50),
@@ -198,29 +235,33 @@ class GraphicsSettingsWindow(ba.Window):
ba.Lstr(resource='autoText'),
ba.Lstr(resource=self._r + '.highText'),
ba.Lstr(resource=self._r + '.mediumText'),
- ba.Lstr(resource=self._r + '.lowText')
+ ba.Lstr(resource=self._r + '.lowText'),
],
current_choice=ba.app.config.resolve('Texture Quality'),
- on_value_change_call=self._set_textures)
+ on_value_change_call=self._set_textures,
+ )
if ba.app.ui.use_toolbars:
ba.widget(
edit=textures_popup.get_button(),
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
v -= 80
h_offs = 0
if show_resolution:
# resolution
- ba.textwidget(parent=self._root_widget,
- position=(h_offs + 60, v),
- size=(160, 25),
- text=ba.Lstr(resource=self._r + '.resolutionText'),
- color=ba.app.ui.heading_color,
- scale=0.65,
- maxwidth=150,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(h_offs + 60, v),
+ size=(160, 25),
+ text=ba.Lstr(resource=self._r + '.resolutionText'),
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ maxwidth=150,
+ h_align='center',
+ v_align='center',
+ )
# on standard android we have 'Auto', 'Native', and a few
# HD standards
@@ -228,9 +269,25 @@ class GraphicsSettingsWindow(ba.Window):
# on cardboard/daydream android we have a few
# render-target-scale options
if app.subplatform == 'cardboard':
- current_res_cardboard = (str(min(100, max(10, int(round(
- ba.app.config.resolve('GVR Render Target Scale')
- * 100.0))))) + '%') # yapf: disable
+ current_res_cardboard = (
+ str(
+ min(
+ 100,
+ max(
+ 10,
+ int(
+ round(
+ ba.app.config.resolve(
+ 'GVR Render Target Scale'
+ )
+ * 100.0
+ )
+ ),
+ ),
+ )
+ )
+ + '%'
+ ) # yapf: disable
popup.PopupMenu(
parent=self._root_widget,
position=(h_offs + 60, v - 50),
@@ -238,14 +295,15 @@ class GraphicsSettingsWindow(ba.Window):
scale=popup_menu_scale,
choices=['100%', '75%', '50%', '35%'],
current_choice=current_res_cardboard,
- on_value_change_call=self._set_gvr_render_target_scale)
+ on_value_change_call=self._set_gvr_render_target_scale,
+ )
else:
native_res = ba.internal.get_display_resolution()
assert native_res is not None
choices = ['Auto', 'Native']
choices_display = [
ba.Lstr(resource='autoText'),
- ba.Lstr(resource='nativeText')
+ ba.Lstr(resource='nativeText'),
]
for res in [1440, 1080, 960, 720, 480]:
# nav bar is 72px so lets allow for that in what
@@ -255,23 +313,42 @@ class GraphicsSettingsWindow(ba.Window):
choices.append(res_str)
choices_display.append(ba.Lstr(value=res_str))
current_res_android = ba.app.config.resolve(
- 'Resolution (Android)')
- popup.PopupMenu(parent=self._root_widget,
- position=(h_offs + 60, v - 50),
- width=120,
- scale=popup_menu_scale,
- choices=choices,
- choices_display=choices_display,
- current_choice=current_res_android,
- on_value_change_call=self._set_android_res)
+ 'Resolution (Android)'
+ )
+ popup.PopupMenu(
+ parent=self._root_widget,
+ position=(h_offs + 60, v - 50),
+ width=120,
+ scale=popup_menu_scale,
+ choices=choices,
+ choices_display=choices_display,
+ current_choice=current_res_android,
+ on_value_change_call=self._set_android_res,
+ )
else:
# if we're on a system that doesn't allow setting resolution,
# set pixel-scale instead
current_res = ba.internal.get_display_resolution()
if current_res is None:
- current_res2 = (str(min(100, max(10, int(round(
- ba.app.config.resolve('Screen Pixel Scale')
- * 100.0))))) + '%') # yapf: disable
+ current_res2 = (
+ str(
+ min(
+ 100,
+ max(
+ 10,
+ int(
+ round(
+ ba.app.config.resolve(
+ 'Screen Pixel Scale'
+ )
+ * 100.0
+ )
+ ),
+ ),
+ )
+ )
+ + '%'
+ ) # yapf: disable
popup.PopupMenu(
parent=self._root_widget,
position=(h_offs + 60, v - 50),
@@ -279,22 +356,27 @@ class GraphicsSettingsWindow(ba.Window):
scale=popup_menu_scale,
choices=['100%', '88%', '75%', '63%', '50%'],
current_choice=current_res2,
- on_value_change_call=self._set_pixel_scale)
+ on_value_change_call=self._set_pixel_scale,
+ )
else:
- raise Exception('obsolete path; discrete resolutions'
- ' no longer supported')
+ raise Exception(
+ 'obsolete path; discrete resolutions'
+ ' no longer supported'
+ )
# vsync
if show_vsync:
- ba.textwidget(parent=self._root_widget,
- position=(230, v),
- size=(160, 25),
- text=ba.Lstr(resource=self._r + '.verticalSyncText'),
- color=ba.app.ui.heading_color,
- scale=0.65,
- maxwidth=150,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(230, v),
+ size=(160, 25),
+ text=ba.Lstr(resource=self._r + '.verticalSyncText'),
+ color=ba.app.ui.heading_color,
+ scale=0.65,
+ maxwidth=150,
+ h_align='center',
+ v_align='center',
+ )
popup.PopupMenu(
parent=self._root_widget,
@@ -305,31 +387,34 @@ class GraphicsSettingsWindow(ba.Window):
choices_display=[
ba.Lstr(resource='autoText'),
ba.Lstr(resource=self._r + '.alwaysText'),
- ba.Lstr(resource=self._r + '.neverText')
+ ba.Lstr(resource=self._r + '.neverText'),
],
current_choice=ba.app.config.resolve('Vertical Sync'),
- on_value_change_call=self._set_vsync)
+ on_value_change_call=self._set_vsync,
+ )
v -= 90
- fpsc = ConfigCheckBox(parent=self._root_widget,
- position=(69, v - 6),
- size=(210, 30),
- scale=0.86,
- configkey='Show FPS',
- displayname=ba.Lstr(resource=self._r +
- '.showFPSText'),
- maxwidth=130)
+ fpsc = ConfigCheckBox(
+ parent=self._root_widget,
+ position=(69, v - 6),
+ size=(210, 30),
+ scale=0.86,
+ configkey='Show FPS',
+ displayname=ba.Lstr(resource=self._r + '.showFPSText'),
+ maxwidth=130,
+ )
# (tv mode doesnt apply to vr)
if not ba.app.vr_mode:
- tvc = ConfigCheckBox(parent=self._root_widget,
- position=(240, v - 6),
- size=(210, 30),
- scale=0.86,
- configkey='TV Border',
- displayname=ba.Lstr(resource=self._r +
- '.tvBorderText'),
- maxwidth=130)
+ tvc = ConfigCheckBox(
+ parent=self._root_widget,
+ position=(240, v - 6),
+ size=(210, 30),
+ scale=0.86,
+ configkey='TV Border',
+ displayname=ba.Lstr(resource=self._r + '.tvBorderText'),
+ maxwidth=130,
+ )
# grumble..
ba.widget(edit=fpsc.widget, right_widget=tvc.widget)
try:
@@ -342,18 +427,24 @@ class GraphicsSettingsWindow(ba.Window):
# make a timer to update our controls in case the config changes
# under us
- self._update_timer = ba.Timer(0.25,
- ba.WeakCall(self._update_controls),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 0.25,
+ ba.WeakCall(self._update_controls),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
def _back(self) -> None:
from bastd.ui.settings import allsettings
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
allsettings.AllSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
def _set_quality(self, quality: str) -> None:
cfg = ba.app.config
@@ -387,5 +478,7 @@ class GraphicsSettingsWindow(ba.Window):
def _update_controls(self) -> None:
if self._show_fullscreen:
- ba.checkboxwidget(edit=self._fullscreen_checkbox,
- value=ba.app.config.resolve('Fullscreen'))
+ ba.checkboxwidget(
+ edit=self._fullscreen_checkbox,
+ value=ba.app.config.resolve('Fullscreen'),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/keyboard.py b/assets/src/ba_data/python/bastd/ui/settings/keyboard.py
index 4a2d056d..a40b0d0a 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/keyboard.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/keyboard.py
@@ -32,79 +32,105 @@ class ConfigKeyboardWindow(ba.Window):
self._height = 375
self._spacing = 40
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- scale=(1.6 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, 5) if uiscale is ba.UIScale.SMALL else (0, 0),
- transition=transition))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ scale=(
+ 1.6
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, 5) if uiscale is ba.UIScale.SMALL else (0, 0),
+ transition=transition,
+ )
+ )
self._rebuild_ui()
def _rebuild_ui(self) -> None:
from ba.internal import get_device_value
+
for widget in self._root_widget.get_children():
widget.delete()
# Fill our temp config with present values.
self._settings: dict[str, int] = {}
for button in [
- 'buttonJump', 'buttonPunch', 'buttonBomb', 'buttonPickUp',
- 'buttonStart', 'buttonStart2', 'buttonUp', 'buttonDown',
- 'buttonLeft', 'buttonRight'
+ 'buttonJump',
+ 'buttonPunch',
+ 'buttonBomb',
+ 'buttonPickUp',
+ 'buttonStart',
+ 'buttonStart2',
+ 'buttonUp',
+ 'buttonDown',
+ 'buttonLeft',
+ 'buttonRight',
]:
self._settings[button] = get_device_value(self._input, button)
- cancel_button = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- position=(38, self._height - 85),
- size=(170, 60),
- label=ba.Lstr(resource='cancelText'),
- scale=0.9,
- on_activate_call=self._cancel)
- save_button = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- position=(self._width - 190,
- self._height - 85),
- size=(180, 60),
- label=ba.Lstr(resource='saveText'),
- scale=0.9,
- text_scale=0.9,
- on_activate_call=self._save)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=cancel_button,
- start_button=save_button)
+ cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ position=(38, self._height - 85),
+ size=(170, 60),
+ label=ba.Lstr(resource='cancelText'),
+ scale=0.9,
+ on_activate_call=self._cancel,
+ )
+ save_button = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ position=(self._width - 190, self._height - 85),
+ size=(180, 60),
+ label=ba.Lstr(resource='saveText'),
+ scale=0.9,
+ text_scale=0.9,
+ on_activate_call=self._save,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=cancel_button,
+ start_button=save_button,
+ )
ba.widget(edit=cancel_button, right_widget=save_button)
ba.widget(edit=save_button, left_widget=cancel_button)
v = self._height - 74.0
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, v + 15),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.configuringText',
- subs=[('${DEVICE}', self._displayname)]),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- maxwidth=270,
- scale=0.83)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, v + 15),
+ size=(0, 0),
+ text=ba.Lstr(
+ resource=self._r + '.configuringText',
+ subs=[('${DEVICE}', self._displayname)],
+ ),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ maxwidth=270,
+ scale=0.83,
+ )
v -= 20
if self._unique_id != '#1':
v -= 20
v -= self._spacing
- ba.textwidget(parent=self._root_widget,
- position=(0, v + 19),
- size=(self._width, 50),
- text=ba.Lstr(resource=self._r +
- '.keyboard2NoteText'),
- scale=0.7,
- maxwidth=self._width * 0.75,
- max_height=110,
- color=ba.app.ui.infotextcolor,
- h_align='center',
- v_align='top')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, v + 19),
+ size=(self._width, 50),
+ text=ba.Lstr(resource=self._r + '.keyboard2NoteText'),
+ scale=0.7,
+ maxwidth=self._width * 0.75,
+ max_height=110,
+ color=ba.app.ui.infotextcolor,
+ h_align='center',
+ v_align='top',
+ )
v -= 40
v -= 10
v -= self._spacing * 2.2
@@ -113,72 +139,96 @@ class ConfigKeyboardWindow(ba.Window):
h_offs = 160
dist = 70
d_color = (0.4, 0.4, 0.8)
- self._capture_button(pos=(h_offs, v + 0.95 * dist),
- color=d_color,
- button='buttonUp',
- texture=ba.gettexture('upButton'),
- scale=1.0)
- self._capture_button(pos=(h_offs - 1.2 * dist, v),
- color=d_color,
- button='buttonLeft',
- texture=ba.gettexture('leftButton'),
- scale=1.0)
- self._capture_button(pos=(h_offs + 1.2 * dist, v),
- color=d_color,
- button='buttonRight',
- texture=ba.gettexture('rightButton'),
- scale=1.0)
- self._capture_button(pos=(h_offs, v - 0.95 * dist),
- color=d_color,
- button='buttonDown',
- texture=ba.gettexture('downButton'),
- scale=1.0)
+ self._capture_button(
+ pos=(h_offs, v + 0.95 * dist),
+ color=d_color,
+ button='buttonUp',
+ texture=ba.gettexture('upButton'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs - 1.2 * dist, v),
+ color=d_color,
+ button='buttonLeft',
+ texture=ba.gettexture('leftButton'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs + 1.2 * dist, v),
+ color=d_color,
+ button='buttonRight',
+ texture=ba.gettexture('rightButton'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs, v - 0.95 * dist),
+ color=d_color,
+ button='buttonDown',
+ texture=ba.gettexture('downButton'),
+ scale=1.0,
+ )
if self._unique_id == '#2':
- self._capture_button(pos=(self._width * 0.5, v + 0.1 * dist),
- color=(0.4, 0.4, 0.6),
- button='buttonStart',
- texture=ba.gettexture('startButton'),
- scale=0.8)
+ self._capture_button(
+ pos=(self._width * 0.5, v + 0.1 * dist),
+ color=(0.4, 0.4, 0.6),
+ button='buttonStart',
+ texture=ba.gettexture('startButton'),
+ scale=0.8,
+ )
h_offs = self._width - 160
- self._capture_button(pos=(h_offs, v + 0.95 * dist),
- color=(0.6, 0.4, 0.8),
- button='buttonPickUp',
- texture=ba.gettexture('buttonPickUp'),
- scale=1.0)
- self._capture_button(pos=(h_offs - 1.2 * dist, v),
- color=(0.7, 0.5, 0.1),
- button='buttonPunch',
- texture=ba.gettexture('buttonPunch'),
- scale=1.0)
- self._capture_button(pos=(h_offs + 1.2 * dist, v),
- color=(0.5, 0.2, 0.1),
- button='buttonBomb',
- texture=ba.gettexture('buttonBomb'),
- scale=1.0)
- self._capture_button(pos=(h_offs, v - 0.95 * dist),
- color=(0.2, 0.5, 0.2),
- button='buttonJump',
- texture=ba.gettexture('buttonJump'),
- scale=1.0)
+ self._capture_button(
+ pos=(h_offs, v + 0.95 * dist),
+ color=(0.6, 0.4, 0.8),
+ button='buttonPickUp',
+ texture=ba.gettexture('buttonPickUp'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs - 1.2 * dist, v),
+ color=(0.7, 0.5, 0.1),
+ button='buttonPunch',
+ texture=ba.gettexture('buttonPunch'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs + 1.2 * dist, v),
+ color=(0.5, 0.2, 0.1),
+ button='buttonBomb',
+ texture=ba.gettexture('buttonBomb'),
+ scale=1.0,
+ )
+ self._capture_button(
+ pos=(h_offs, v - 0.95 * dist),
+ color=(0.2, 0.5, 0.2),
+ button='buttonJump',
+ texture=ba.gettexture('buttonJump'),
+ scale=1.0,
+ )
- def _capture_button(self,
- pos: tuple[float, float],
- color: tuple[float, float, float],
- texture: ba.Texture,
- button: str,
- scale: float = 1.0) -> None:
+ def _capture_button(
+ self,
+ pos: tuple[float, float],
+ color: tuple[float, float, float],
+ texture: ba.Texture,
+ button: str,
+ scale: float = 1.0,
+ ) -> None:
base_size = 79
- btn = ba.buttonwidget(parent=self._root_widget,
- autoselect=True,
- position=(pos[0] - base_size * 0.5 * scale,
- pos[1] - base_size * 0.5 * scale),
- size=(base_size * scale, base_size * scale),
- texture=texture,
- label='',
- color=color)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ position=(
+ pos[0] - base_size * 0.5 * scale,
+ pos[1] - base_size * 0.5 * scale,
+ ),
+ size=(base_size * scale, base_size * scale),
+ texture=texture,
+ label='',
+ color=color,
+ )
# Do this deferred so it shows up on top of other buttons. (ew.)
def doit() -> None:
@@ -186,35 +236,42 @@ class ConfigKeyboardWindow(ba.Window):
return
uiscale = 0.66 * scale * 2.0
maxwidth = 76.0 * scale
- txt = ba.textwidget(parent=self._root_widget,
- position=(pos[0] + 0.0 * scale,
- pos[1] - (57.0 - 18.0) * scale),
- color=(1, 1, 1, 0.3),
- size=(0, 0),
- h_align='center',
- v_align='top',
- scale=uiscale,
- maxwidth=maxwidth,
- text=self._input.get_button_name(
- self._settings[button]))
- ba.buttonwidget(edit=btn,
- autoselect=True,
- on_activate_call=ba.Call(AwaitKeyboardInputWindow,
- button, txt,
- self._settings))
+ txt = ba.textwidget(
+ parent=self._root_widget,
+ position=(pos[0] + 0.0 * scale, pos[1] - (57.0 - 18.0) * scale),
+ color=(1, 1, 1, 0.3),
+ size=(0, 0),
+ h_align='center',
+ v_align='top',
+ scale=uiscale,
+ maxwidth=maxwidth,
+ text=self._input.get_button_name(self._settings[button]),
+ )
+ ba.buttonwidget(
+ edit=btn,
+ autoselect=True,
+ on_activate_call=ba.Call(
+ AwaitKeyboardInputWindow, button, txt, self._settings
+ ),
+ )
ba.pushcall(doit)
def _cancel(self) -> None:
from bastd.ui.settings.controls import ControlsSettingsWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget())
+ ControlsSettingsWindow(transition='in_left').get_root_widget()
+ )
def _save(self) -> None:
from bastd.ui.settings.controls import ControlsSettingsWindow
- from ba.internal import (get_input_device_config,
- should_submit_debug_info, master_server_post)
+ from ba.internal import (
+ get_input_device_config,
+ should_submit_debug_info,
+ master_server_post,
+ )
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.playsound(ba.getsound('gunCocking'))
@@ -236,16 +293,19 @@ class ConfigKeyboardWindow(ba.Window):
# more defaults in the future.
if should_submit_debug_info():
master_server_post(
- 'controllerConfig', {
+ 'controllerConfig',
+ {
'ua': ba.app.user_agent_string,
'name': self._name,
'b': ba.app.build_number,
'config': dst2,
- 'v': 2
- })
+ 'v': 2,
+ },
+ )
ba.app.config.apply_and_commit()
ba.app.ui.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget())
+ ControlsSettingsWindow(transition='in_left').get_root_widget()
+ )
class AwaitKeyboardInputWindow(ba.Window):
@@ -260,27 +320,40 @@ class AwaitKeyboardInputWindow(ba.Window):
width = 400
height = 150
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition='in_right',
- scale=(2.0 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0)))
- ba.textwidget(parent=self._root_widget,
- position=(0, height - 60),
- size=(width, 25),
- text=ba.Lstr(resource='pressAnyKeyText'),
- h_align='center',
- v_align='top')
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition='in_right',
+ scale=(
+ 2.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 60),
+ size=(width, 25),
+ text=ba.Lstr(resource='pressAnyKeyText'),
+ h_align='center',
+ v_align='top',
+ )
self._counter = 5
- self._count_down_text = ba.textwidget(parent=self._root_widget,
- h_align='center',
- position=(0, height - 110),
- size=(width, 25),
- color=(1, 1, 1, 0.3),
- text=str(self._counter))
+ self._count_down_text = ba.textwidget(
+ parent=self._root_widget,
+ h_align='center',
+ position=(0, height - 110),
+ size=(width, 25),
+ color=(1, 1, 1, 0.3),
+ text=str(self._counter),
+ )
self._decrement_timer: ba.Timer | None = ba.Timer(
- 1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL)
+ 1.0, self._decrement, repeat=True, timetype=ba.TimeType.REAL
+ )
ba.internal.capture_keyboard_input(ba.WeakCall(self._button_callback))
def __del__(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
index 1f6032be..c50e4158 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/nettesting.py
@@ -31,28 +31,39 @@ class NetTestingWindow(ba.Window):
self._height = 500
self._printed_lines: list[str] = []
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- scale=(1.56 if uiscale is ba.UIScale.SMALL else
- 1.2 if uiscale is ba.UIScale.MEDIUM else 0.8),
- stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0),
- transition=transition))
- self._done_button = ba.buttonwidget(parent=self._root_widget,
- position=(40, self._height - 77),
- size=(120, 60),
- scale=0.8,
- autoselect=True,
- label=ba.Lstr(resource='doneText'),
- on_activate_call=self._done)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ scale=(
+ 1.56
+ if uiscale is ba.UIScale.SMALL
+ else 1.2
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ stack_offset=(0.0, -7 if uiscale is ba.UIScale.SMALL else 0.0),
+ transition=transition,
+ )
+ )
+ self._done_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(40, self._height - 77),
+ size=(120, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(resource='doneText'),
+ on_activate_call=self._done,
+ )
- self._copy_button = ba.buttonwidget(parent=self._root_widget,
- position=(self._width - 200,
- self._height - 77),
- size=(100, 60),
- scale=0.8,
- autoselect=True,
- label=ba.Lstr(resource='copyText'),
- on_activate_call=self._copy)
+ self._copy_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width - 200, self._height - 77),
+ size=(100, 60),
+ scale=0.8,
+ autoselect=True,
+ label=ba.Lstr(resource='copyText'),
+ on_activate_call=self._copy,
+ )
self._settings_button = ba.buttonwidget(
parent=self._root_widget,
@@ -61,7 +72,8 @@ class NetTestingWindow(ba.Window):
scale=0.8,
autoselect=True,
label=ba.Lstr(value='...'),
- on_activate_call=self._show_val_testing)
+ on_activate_call=self._show_val_testing,
+ )
twidth = self._width - 450
ba.textwidget(
@@ -72,18 +84,21 @@ class NetTestingWindow(ba.Window):
color=(0.8, 0.8, 0.8, 1.0),
h_align='center',
v_align='center',
- maxwidth=twidth)
+ maxwidth=twidth,
+ )
- self._scroll = ba.scrollwidget(parent=self._root_widget,
- position=(50, 50),
- size=(self._width - 100,
- self._height - 140),
- capture_arrows=True,
- autoselect=True)
+ self._scroll = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(50, 50),
+ size=(self._width - 100, self._height - 140),
+ capture_arrows=True,
+ autoselect=True,
+ )
self._rows = ba.columnwidget(parent=self._scroll)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._done_button)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._done_button
+ )
# Now kick off the tests.
# Pass a weak-ref to this window so we don't keep it alive
@@ -97,20 +112,23 @@ class NetTestingWindow(ba.Window):
def print(self, text: str, color: tuple[float, float, float]) -> None:
"""Print text to our console thingie."""
for line in text.splitlines():
- txt = ba.textwidget(parent=self._rows,
- color=color,
- text=line,
- scale=0.75,
- flatness=1.0,
- shadow=0.0,
- size=(0, 20))
+ txt = ba.textwidget(
+ parent=self._rows,
+ color=color,
+ text=line,
+ scale=0.75,
+ flatness=1.0,
+ shadow=0.0,
+ size=(0, 20),
+ )
ba.containerwidget(edit=self._rows, visible_child=txt)
self._printed_lines.append(line)
def _copy(self) -> None:
if not ba.clipboard_is_supported():
- ba.screenmessage('Clipboard not supported on this platform.',
- color=(1, 0, 0))
+ ba.screenmessage(
+ 'Clipboard not supported on this platform.', color=(1, 0, 0)
+ )
return
ba.clipboard_set_text('\n'.join(self._printed_lines))
ba.screenmessage(f'{len(self._printed_lines)} lines copied.')
@@ -122,8 +140,10 @@ class NetTestingWindow(ba.Window):
def _done(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
+
ba.app.ui.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget())
+ AdvancedSettingsWindow(transition='in_left').get_root_widget()
+ )
ba.containerwidget(edit=self._root_widget, transition='out_right')
@@ -136,9 +156,9 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
# We're running in a background thread but UI stuff needs to run
# in the logic thread; give ourself a way to pass stuff to it.
- def _print(text: str,
- color: tuple[float, float, float] | None = None) -> None:
-
+ def _print(
+ text: str, color: tuple[float, float, float] | None = None
+ ) -> None:
def _print_in_logic_thread() -> None:
win = weakwin()
if win is not None:
@@ -156,18 +176,24 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
return True
except Exception as exc:
import traceback
+
duration = time.monotonic() - starttime
- msg = (str(exc)
- if isinstance(exc, CleanError) else traceback.format_exc())
+ msg = (
+ str(exc)
+ if isinstance(exc, CleanError)
+ else traceback.format_exc()
+ )
_print(msg, color=(1.0, 1.0, 0.3))
_print(f'Failed in {duration:.2f}s.', color=(1, 0, 0))
have_error[0] = True
return False
try:
- _print(f'Running network diagnostics...\n'
- f'ua: {ba.app.user_agent_string}\n'
- f'time: {utc_now()}.')
+ _print(
+ f'Running network diagnostics...\n'
+ f'ua: {ba.app.user_agent_string}\n'
+ f'time: {utc_now()}.'
+ )
if bool(False):
_print('\nRunning dummy success test...')
@@ -185,14 +211,17 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
if v1worked:
_print('\nSkipping V1 master-server src1 test since src0 worked.')
else:
- baseaddr = ba.internal.get_master_server_address(source=1,
- version=1)
+ baseaddr = ba.internal.get_master_server_address(
+ source=1, version=1
+ )
_print(f'\nContacting V1 master-server src1 ({baseaddr})...')
_print_test_results(lambda: _test_fetch(baseaddr))
if 'none succeeded' in ba.app.net.v1_test_log:
- _print(f'\nV1-test-log failed: {ba.app.net.v1_test_log}',
- color=(1, 0, 0))
+ _print(
+ f'\nV1-test-log failed: {ba.app.net.v1_test_log}',
+ color=(1, 0, 0),
+ )
have_error[0] = True
else:
_print(f'\nV1-test-log ok: {ba.app.net.v1_test_log}')
@@ -217,8 +246,11 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
# Get V2 nearby zone
with ba.app.net.zone_pings_lock:
zone_pings = copy.deepcopy(ba.app.net.zone_pings)
- nearest_zone = (None if not zone_pings else sorted(
- zone_pings.items(), key=lambda i: i[1])[0])
+ nearest_zone = (
+ None
+ if not zone_pings
+ else sorted(zone_pings.items(), key=lambda i: i[1])[0]
+ )
if nearest_zone is not None:
nearstr = f'{nearest_zone[0]}: {nearest_zone[1]:.0f}ms'
@@ -231,18 +263,24 @@ def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
_print_test_results(_test_v2_cloud_message)
if have_error[0]:
- _print('\nDiagnostics complete. Some diagnostics failed.',
- color=(10, 0, 0))
+ _print(
+ '\nDiagnostics complete. Some diagnostics failed.',
+ color=(10, 0, 0),
+ )
else:
- _print('\nDiagnostics complete. Everything looks good!',
- color=(0, 1, 0))
+ _print(
+ '\nDiagnostics complete. Everything looks good!',
+ color=(0, 1, 0),
+ )
except Exception:
import traceback
+
_print(
f'An unexpected error occurred during testing;'
f' please report this.\n'
f'{traceback.format_exc()}',
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
def _dummy_success() -> None:
@@ -289,7 +327,8 @@ def _test_v1_transaction() -> None:
time.sleep(0.01)
if time.monotonic() - starttime > MAX_TEST_SECONDS:
raise RuntimeError(
- f'test timed out after {MAX_TEST_SECONDS} seconds')
+ f'test timed out after {MAX_TEST_SECONDS} seconds'
+ )
# If we got left a string, its an error.
if isinstance(results[0], str):
@@ -330,8 +369,10 @@ def _test_v2_cloud_message() -> None:
break
time.sleep(0.01)
if time.monotonic() - wait_start_time > MAX_TEST_SECONDS:
- raise RuntimeError(f'Timeout ({MAX_TEST_SECONDS} seconds)'
- f' waiting for cloud message response')
+ raise RuntimeError(
+ f'Timeout ({MAX_TEST_SECONDS} seconds)'
+ f' waiting for cloud message response'
+ )
if results.errstr is not None:
raise RuntimeError(results.errstr)
@@ -339,28 +380,34 @@ def _test_v2_cloud_message() -> None:
def _test_v2_time() -> None:
offset = ba.app.net.server_time_offset_hours
if offset is None:
- raise RuntimeError('no time offset found;'
- ' perhaps unable to communicate with v2 server?')
+ raise RuntimeError(
+ 'no time offset found;'
+ ' perhaps unable to communicate with v2 server?'
+ )
if abs(offset) >= 2.0:
raise CleanError(
f'Your device time is off from world time by {offset:.1f} hours.\n'
'This may cause network operations to fail due to your device\n'
' incorrectly treating SSL certificates as not-yet-valid, etc.\n'
- 'Check your device time and time-zone settings to fix this.\n')
+ 'Check your device time and time-zone settings to fix this.\n'
+ )
def _test_fetch(baseaddr: str) -> None:
# pylint: disable=consider-using-with
import urllib.request
+
response = urllib.request.urlopen(
- urllib.request.Request(f'{baseaddr}/ping', None,
- {'User-Agent': ba.app.user_agent_string}),
+ urllib.request.Request(
+ f'{baseaddr}/ping', None, {'User-Agent': ba.app.user_agent_string}
+ ),
context=ba.app.net.sslcontext,
timeout=MAX_TEST_SECONDS,
)
if response.getcode() != 200:
raise RuntimeError(
- f'Got unexpected response code {response.getcode()}.')
+ f'Got unexpected response code {response.getcode()}.'
+ )
data = response.read()
if data != b'pong':
raise RuntimeError('Got unexpected response data.')
@@ -380,29 +427,22 @@ class NetValTestingWindow(TestingWindow):
def __init__(self, transition: str = 'in_right'):
entries = [
- {
- 'name': 'bufferTime',
- 'label': 'Buffer Time',
- 'increment': 1.0
- },
+ {'name': 'bufferTime', 'label': 'Buffer Time', 'increment': 1.0},
{
'name': 'delaySampling',
'label': 'Delay Sampling',
- 'increment': 1.0
+ 'increment': 1.0,
},
{
'name': 'dynamicsSyncTime',
'label': 'Dynamics Sync Time',
- 'increment': 10
- },
- {
- 'name': 'showNetInfo',
- 'label': 'Show Net Info',
- 'increment': 1
+ 'increment': 10,
},
+ {'name': 'showNetInfo', 'label': 'Show Net Info', 'increment': 1},
]
super().__init__(
title=ba.Lstr(resource='settingsWindowAdvanced.netTestingText'),
entries=entries,
transition=transition,
- back_call=lambda: NetTestingWindow(transition='in_left'))
+ back_call=lambda: NetTestingWindow(transition='in_left'),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/plugins.py b/assets/src/ba_data/python/bastd/ui/settings/plugins.py
index c90b73d4..8213e42a 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/plugins.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/plugins.py
@@ -15,9 +15,11 @@ if TYPE_CHECKING:
class PluginSettingsWindow(ba.Window):
"""Window for configuring plugins."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
app = ba.app
@@ -34,17 +36,32 @@ class PluginSettingsWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 870.0 if uiscale is ba.UIScale.SMALL else 670.0
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (390.0 if uiscale is ba.UIScale.SMALL else
- 450.0 if uiscale is ba.UIScale.MEDIUM else 520.0)
+ self._height = (
+ 390.0
+ if uiscale is ba.UIScale.SMALL
+ else 450.0
+ if uiscale is ba.UIScale.MEDIUM
+ else 520.0
+ )
top_extra = 10 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(2.06 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -25) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + top_extra),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.06
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -25)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._scroll_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - 115.0
@@ -52,8 +69,9 @@ class PluginSettingsWindow(ba.Window):
self._sub_height = 724.0
if app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._do_back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._do_back
+ )
self._back_button = None
else:
self._back_button = ba.buttonwidget(
@@ -64,38 +82,47 @@ class PluginSettingsWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button)
+ on_activate_call=self._do_back,
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
- self._title_text = ba.textwidget(parent=self._root_widget,
- position=(0, self._height - 52),
- size=(self._width, 25),
- text=ba.Lstr(resource='pluginsText'),
- color=app.ui.title_color,
- h_align='center',
- v_align='top')
+ self._title_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(0, self._height - 52),
+ size=(self._width, 25),
+ text=ba.Lstr(resource='pluginsText'),
+ color=app.ui.title_color,
+ h_align='center',
+ v_align='top',
+ )
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(50 + x_inset, 50),
- simple_culling_v=20.0,
- highlight=False,
- size=(self._scroll_width,
- self._scroll_height),
- selection_loops_to_parent=True)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(50 + x_inset, 50),
+ simple_culling_v=20.0,
+ highlight=False,
+ size=(self._scroll_width, self._scroll_height),
+ selection_loops_to_parent=True,
+ )
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
- self._subcontainer = ba.columnwidget(parent=self._scrollwidget,
- selection_loops_to_parent=True)
+ self._subcontainer = ba.columnwidget(
+ parent=self._scrollwidget, selection_loops_to_parent=True
+ )
if ba.app.meta.scanresults is None:
- ba.screenmessage('Still scanning plugins; please try again.',
- color=(1, 0, 0))
+ ba.screenmessage(
+ 'Still scanning plugins; please try again.', color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
pluglist = ba.app.plugins.potential_plugins
plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {})
@@ -112,10 +139,17 @@ class PluginSettingsWindow(ba.Window):
value=checked,
maxwidth=self._scroll_width - 100,
size=(self._scroll_width - 40, 50),
- on_value_change_call=ba.Call(self._check_value_changed,
- availplug),
- textcolor=((0.8, 0.3, 0.3) if not availplug.available else
- (0, 1, 0) if active else (0.6, 0.6, 0.6)))
+ on_value_change_call=ba.Call(
+ self._check_value_changed, availplug
+ ),
+ textcolor=(
+ (0.8, 0.3, 0.3)
+ if not availplug.available
+ else (0, 1, 0)
+ if active
+ else (0.6, 0.6, 0.6)
+ ),
+ )
# Make sure we scroll all the way to the end when using
# keyboard/button nav.
@@ -124,16 +158,19 @@ class PluginSettingsWindow(ba.Window):
# Keep last from looping to back button when down is pressed.
if i == len(pluglist) - 1:
ba.widget(edit=check, down_widget=check)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
self._restore_state()
- def _check_value_changed(self, plug: ba.PotentialPlugin,
- value: bool) -> None:
+ def _check_value_changed(
+ self, plug: ba.PotentialPlugin, value: bool
+ ) -> None:
ba.screenmessage(
ba.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
- color=(1.0, 0.5, 0.0))
+ color=(1.0, 0.5, 0.0),
+ )
plugstates: dict[str, dict] = ba.app.config.setdefault('Plugins', {})
assert isinstance(plugstates, dict)
plugstate = plugstates.setdefault(plug.class_path, {})
@@ -149,8 +186,11 @@ class PluginSettingsWindow(ba.Window):
def _do_back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget())
+ AdvancedSettingsWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/remoteapp.py b/assets/src/ba_data/python/bastd/ui/settings/remoteapp.py
index dd81689f..c0e8b2e2 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/remoteapp.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/remoteapp.py
@@ -12,87 +12,112 @@ class RemoteAppSettingsWindow(ba.Window):
def __init__(self) -> None:
from ba.internal import get_remote_app_name
+
self._r = 'connectMobileDevicesWindow'
width = 700
height = 390
spacing = 40
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition='in_right',
- scale=(1.85 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(-10, 0) if uiscale is ba.UIScale.SMALL else (0, 0)))
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(40, height - 67),
- size=(140, 65),
- scale=0.8,
- label=ba.Lstr(resource='backText'),
- button_type='back',
- text_scale=1.1,
- autoselect=True,
- on_activate_call=self._back)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition='in_right',
+ scale=(
+ 1.85
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(-10, 0)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(40, height - 67),
+ size=(140, 65),
+ scale=0.8,
+ label=ba.Lstr(resource='backText'),
+ button_type='back',
+ text_scale=1.1,
+ autoselect=True,
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height - 42),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=370,
- color=ba.app.ui.title_color,
- scale=0.8,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height - 42),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=370,
+ color=ba.app.ui.title_color,
+ scale=0.8,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v = height - 70.0
v -= spacing * 1.2
- ba.textwidget(parent=self._root_widget,
- position=(15, v - 26),
- size=(width - 30, 30),
- maxwidth=width * 0.95,
- color=(0.7, 0.9, 0.7, 1.0),
- scale=0.8,
- text=ba.Lstr(resource=self._r + '.explanationText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText')),
- ('${REMOTE_APP_NAME}',
- get_remote_app_name())]),
- max_height=100,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(15, v - 26),
+ size=(width - 30, 30),
+ maxwidth=width * 0.95,
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=0.8,
+ text=ba.Lstr(
+ resource=self._r + '.explanationText',
+ subs=[
+ ('${APP_NAME}', ba.Lstr(resource='titleText')),
+ ('${REMOTE_APP_NAME}', get_remote_app_name()),
+ ],
+ ),
+ max_height=100,
+ h_align='center',
+ v_align='center',
+ )
v -= 90
# hmm the itms:// version doesnt bounce through safari but is kinda
# apple-specific-ish
# Update: now we just show link to the remote webpage.
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, v + 5),
- size=(0, 0),
- color=(0.7, 0.9, 0.7, 1.0),
- scale=1.4,
- text='bombsquadgame.com/remote',
- maxwidth=width * 0.95,
- max_height=60,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, v + 5),
+ size=(0, 0),
+ color=(0.7, 0.9, 0.7, 1.0),
+ scale=1.4,
+ text='bombsquadgame.com/remote',
+ maxwidth=width * 0.95,
+ max_height=60,
+ h_align='center',
+ v_align='center',
+ )
v -= 30
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, v - 35),
- size=(0, 0),
- color=(0.7, 0.9, 0.7, 0.8),
- scale=0.65,
- text=ba.Lstr(resource=self._r + '.bestResultsText'),
- maxwidth=width * 0.95,
- max_height=height * 0.19,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, v - 35),
+ size=(0, 0),
+ color=(0.7, 0.9, 0.7, 0.8),
+ scale=0.65,
+ text=ba.Lstr(resource=self._r + '.bestResultsText'),
+ maxwidth=width * 0.95,
+ max_height=height * 0.19,
+ h_align='center',
+ v_align='center',
+ )
ba.checkboxwidget(
parent=self._root_widget,
@@ -103,7 +128,8 @@ class RemoteAppSettingsWindow(ba.Window):
value=not ba.app.config.resolve('Enable Remote App'),
autoselect=True,
text=ba.Lstr(resource='disableRemoteAppConnectionsText'),
- on_value_change_call=self._on_check_changed)
+ on_value_change_call=self._on_check_changed,
+ )
def _on_check_changed(self, value: bool) -> None:
cfg = ba.app.config
@@ -112,7 +138,10 @@ class RemoteAppSettingsWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.settings import controls
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
controls.ControlsSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/testing.py b/assets/src/ba_data/python/bastd/ui/settings/testing.py
index 53b63afb..739ba417 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/testing.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/testing.py
@@ -17,22 +17,34 @@ if TYPE_CHECKING:
class TestingWindow(ba.Window):
"""Window for conveniently testing various settings."""
- def __init__(self,
- title: ba.Lstr,
- entries: list[dict[str, Any]],
- transition: str = 'in_right',
- back_call: Callable[[], ba.Window] | None = None):
+ def __init__(
+ self,
+ title: ba.Lstr,
+ entries: list[dict[str, Any]],
+ transition: str = 'in_right',
+ back_call: Callable[[], ba.Window] | None = None,
+ ):
uiscale = ba.app.ui.uiscale
self._width = 600
self._height = 324 if uiscale is ba.UIScale.SMALL else 400
self._entries = copy.deepcopy(entries)
self._back_call = back_call
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=(2.5 if uiscale is ba.UIScale.SMALL else
- 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -28) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=(
+ 2.5
+ if uiscale is ba.UIScale.SMALL
+ else 1.2
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -28)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
autoselect=True,
@@ -42,20 +54,25 @@ class TestingWindow(ba.Window):
text_scale=1.2,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 35),
- size=(0, 0),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- maxwidth=245,
- text=title)
+ on_activate_call=self._do_back,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 35),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ maxwidth=245,
+ text=title,
+ )
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
ba.textwidget(
parent=self._root_widget,
@@ -65,7 +82,8 @@ class TestingWindow(ba.Window):
h_align='center',
v_align='center',
maxwidth=self._width * 0.75,
- text=ba.Lstr(resource='settingsWindowAdvanced.forTestingText'))
+ text=ba.Lstr(resource='settingsWindowAdvanced.forTestingText'),
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
self._scroll_width = self._width - 130
self._scroll_height = self._height - 140
@@ -73,17 +91,19 @@ class TestingWindow(ba.Window):
parent=self._root_widget,
size=(self._scroll_width, self._scroll_height),
highlight=False,
- position=((self._width - self._scroll_width) * 0.5, 40))
+ position=((self._width - self._scroll_width) * 0.5, 40),
+ )
ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._spacing = 50
self._sub_width = self._scroll_width * 0.95
self._sub_height = 50 + len(self._entries) * self._spacing + 60
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ )
h = 230
v = self._sub_height - 48
@@ -95,46 +115,52 @@ class TestingWindow(ba.Window):
# If we haven't yet, record the default value for this name so
# we can reset if we want..
if entry_name not in ba.app.value_test_defaults:
- ba.app.value_test_defaults[entry_name] = (
- ba.internal.value_test(entry_name))
+ ba.app.value_test_defaults[entry_name] = ba.internal.value_test(
+ entry_name
+ )
- ba.textwidget(parent=self._subcontainer,
- position=(h, v),
- size=(0, 0),
- h_align='right',
- v_align='center',
- maxwidth=200,
- text=entry['label'])
- btn = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 20, v - 19),
- size=(40, 40),
- autoselect=True,
- repeat=True,
- left_widget=self._back_button,
- button_type='square',
- label='-',
- on_activate_call=ba.Call(
- self._on_minus_press, entry['name']))
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ text=entry['label'],
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 20, v - 19),
+ size=(40, 40),
+ autoselect=True,
+ repeat=True,
+ left_widget=self._back_button,
+ button_type='square',
+ label='-',
+ on_activate_call=ba.Call(self._on_minus_press, entry['name']),
+ )
if i == 0:
ba.widget(edit=btn, up_widget=self._back_button)
# pylint: disable=consider-using-f-string
- entry['widget'] = ba.textwidget(parent=self._subcontainer,
- position=(h + 100, v),
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=60,
- text='%.4g' %
- ba.internal.value_test(entry_name))
- btn = ba.buttonwidget(parent=self._subcontainer,
- position=(h + 140, v - 19),
- size=(40, 40),
- autoselect=True,
- repeat=True,
- button_type='square',
- label='+',
- on_activate_call=ba.Call(
- self._on_plus_press, entry['name']))
+ entry['widget'] = ba.textwidget(
+ parent=self._subcontainer,
+ position=(h + 100, v),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=60,
+ text='%.4g' % ba.internal.value_test(entry_name),
+ )
+ btn = ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(h + 140, v - 19),
+ size=(40, 40),
+ autoselect=True,
+ repeat=True,
+ button_type='square',
+ label='+',
+ on_activate_call=ba.Call(self._on_plus_press, entry['name']),
+ )
if i == 0:
ba.widget(edit=btn, up_widget=self._back_button)
v -= self._spacing
@@ -146,7 +172,8 @@ class TestingWindow(ba.Window):
position=(self._sub_width * 0.5 - 100, v),
label=ba.Lstr(resource='settingsWindowAdvanced.resetText'),
right_widget=btn,
- on_activate_call=self._on_reset_press)
+ on_activate_call=self._on_reset_press,
+ )
def _get_entry(self, name: str) -> dict[str, Any]:
for entry in self._entries:
@@ -158,29 +185,40 @@ class TestingWindow(ba.Window):
for entry in self._entries:
ba.internal.value_test(
entry['name'],
- absolute=ba.app.value_test_defaults[entry['name']])
+ absolute=ba.app.value_test_defaults[entry['name']],
+ )
# pylint: disable=consider-using-f-string
- ba.textwidget(edit=entry['widget'],
- text='%.4g' % ba.internal.value_test(entry['name']))
+ ba.textwidget(
+ edit=entry['widget'],
+ text='%.4g' % ba.internal.value_test(entry['name']),
+ )
def _on_minus_press(self, entry_name: str) -> None:
entry = self._get_entry(entry_name)
ba.internal.value_test(entry['name'], change=-entry['increment'])
# pylint: disable=consider-using-f-string
- ba.textwidget(edit=entry['widget'],
- text='%.4g' % ba.internal.value_test(entry['name']))
+ ba.textwidget(
+ edit=entry['widget'],
+ text='%.4g' % ba.internal.value_test(entry['name']),
+ )
def _on_plus_press(self, entry_name: str) -> None:
entry = self._get_entry(entry_name)
ba.internal.value_test(entry['name'], change=entry['increment'])
# pylint: disable=consider-using-f-string
- ba.textwidget(edit=entry['widget'],
- text='%.4g' % ba.internal.value_test(entry['name']))
+ ba.textwidget(
+ edit=entry['widget'],
+ text='%.4g' % ba.internal.value_test(entry['name']),
+ )
def _do_back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings.advanced import AdvancedSettingsWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
- backwin = (self._back_call() if self._back_call is not None else
- AdvancedSettingsWindow(transition='in_left'))
+ backwin = (
+ self._back_call()
+ if self._back_call is not None
+ else AdvancedSettingsWindow(transition='in_left')
+ )
ba.app.ui.set_main_menu_window(backwin.get_root_widget())
diff --git a/assets/src/ba_data/python/bastd/ui/settings/touchscreen.py b/assets/src/ba_data/python/bastd/ui/settings/touchscreen.py
index f8d7979a..c1140ccb 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/touchscreen.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/touchscreen.py
@@ -28,34 +28,48 @@ class TouchscreenSettingsWindow(ba.Window):
ba.internal.set_touchscreen_editing(True)
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_right',
- scale=(1.9 if uiscale is ba.UIScale.SMALL else
- 1.55 if uiscale is ba.UIScale.MEDIUM else 1.2)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_right',
+ scale=(
+ 1.9
+ if uiscale is ba.UIScale.SMALL
+ else 1.55
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.2
+ ),
+ )
+ )
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(55, self._height - 60),
- size=(120, 60),
- label=ba.Lstr(resource='backText'),
- button_type='back',
- scale=0.8,
- on_activate_call=self._back)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(55, self._height - 60),
+ size=(120, 60),
+ label=ba.Lstr(resource='backText'),
+ button_type='back',
+ scale=0.8,
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(25, self._height - 50),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- maxwidth=280,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(25, self._height - 50),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ maxwidth=280,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
self._scroll_width = self._width - 100
self._scroll_height = self._height - 110
@@ -64,19 +78,23 @@ class TouchscreenSettingsWindow(ba.Window):
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - 65 - self._scroll_height),
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - 65 - self._scroll_height,
+ ),
size=(self._scroll_width, self._scroll_height),
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(self._sub_width,
- self._sub_height),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(self._sub_width, self._sub_height),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
self._build_gui()
def _build_gui(self) -> None:
@@ -91,116 +109,137 @@ class TouchscreenSettingsWindow(ba.Window):
v = self._sub_height - 85
clr = (0.8, 0.8, 0.8, 1.0)
clr2 = (0.8, 0.8, 0.8)
- ba.textwidget(parent=self._subcontainer,
- position=(-10, v + 43),
- size=(self._sub_width, 25),
- text=ba.Lstr(resource=self._r + '.swipeInfoText'),
- flatness=1.0,
- color=(0, 0.9, 0.1, 0.7),
- maxwidth=self._sub_width * 0.9,
- scale=0.55,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(-10, v + 43),
+ size=(self._sub_width, 25),
+ text=ba.Lstr(resource=self._r + '.swipeInfoText'),
+ flatness=1.0,
+ color=(0, 0.9, 0.1, 0.7),
+ maxwidth=self._sub_width * 0.9,
+ scale=0.55,
+ h_align='center',
+ v_align='center',
+ )
cur_val = ba.app.config.get('Touch Movement Control Type', 'swipe')
- ba.textwidget(parent=self._subcontainer,
- position=(h, v - 2),
- size=(0, 30),
- text=ba.Lstr(resource=self._r + '.movementText'),
- maxwidth=190,
- color=clr,
- v_align='center')
- cb1 = ba.checkboxwidget(parent=self._subcontainer,
- position=(h + 220, v),
- size=(170, 30),
- text=ba.Lstr(resource=self._r +
- '.joystickText'),
- maxwidth=100,
- textcolor=clr2,
- scale=0.9)
- cb2 = ba.checkboxwidget(parent=self._subcontainer,
- position=(h + 357, v),
- size=(170, 30),
- text=ba.Lstr(resource=self._r + '.swipeText'),
- maxwidth=100,
- textcolor=clr2,
- value=False,
- scale=0.9)
- make_radio_group((cb1, cb2), ('joystick', 'swipe'), cur_val,
- self._movement_changed)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v - 2),
+ size=(0, 30),
+ text=ba.Lstr(resource=self._r + '.movementText'),
+ maxwidth=190,
+ color=clr,
+ v_align='center',
+ )
+ cb1 = ba.checkboxwidget(
+ parent=self._subcontainer,
+ position=(h + 220, v),
+ size=(170, 30),
+ text=ba.Lstr(resource=self._r + '.joystickText'),
+ maxwidth=100,
+ textcolor=clr2,
+ scale=0.9,
+ )
+ cb2 = ba.checkboxwidget(
+ parent=self._subcontainer,
+ position=(h + 357, v),
+ size=(170, 30),
+ text=ba.Lstr(resource=self._r + '.swipeText'),
+ maxwidth=100,
+ textcolor=clr2,
+ value=False,
+ scale=0.9,
+ )
+ make_radio_group(
+ (cb1, cb2), ('joystick', 'swipe'), cur_val, self._movement_changed
+ )
v -= 50
- ConfigNumberEdit(parent=self._subcontainer,
- position=(h, v),
- xoffset=65,
- configkey='Touch Controls Scale Movement',
- displayname=ba.Lstr(resource=self._r +
- '.movementControlScaleText'),
- changesound=False,
- minval=0.1,
- maxval=4.0,
- increment=0.1)
+ ConfigNumberEdit(
+ parent=self._subcontainer,
+ position=(h, v),
+ xoffset=65,
+ configkey='Touch Controls Scale Movement',
+ displayname=ba.Lstr(resource=self._r + '.movementControlScaleText'),
+ changesound=False,
+ minval=0.1,
+ maxval=4.0,
+ increment=0.1,
+ )
v -= 50
cur_val = ba.app.config.get('Touch Action Control Type', 'buttons')
- ba.textwidget(parent=self._subcontainer,
- position=(h, v - 2),
- size=(0, 30),
- text=ba.Lstr(resource=self._r + '.actionsText'),
- maxwidth=190,
- color=clr,
- v_align='center')
- cb1 = ba.checkboxwidget(parent=self._subcontainer,
- position=(h + 220, v),
- size=(170, 30),
- text=ba.Lstr(resource=self._r +
- '.buttonsText'),
- maxwidth=100,
- textcolor=clr2,
- scale=0.9)
- cb2 = ba.checkboxwidget(parent=self._subcontainer,
- position=(h + 357, v),
- size=(170, 30),
- text=ba.Lstr(resource=self._r + '.swipeText'),
- maxwidth=100,
- textcolor=clr2,
- scale=0.9)
- make_radio_group((cb1, cb2), ('buttons', 'swipe'), cur_val,
- self._actions_changed)
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(h, v - 2),
+ size=(0, 30),
+ text=ba.Lstr(resource=self._r + '.actionsText'),
+ maxwidth=190,
+ color=clr,
+ v_align='center',
+ )
+ cb1 = ba.checkboxwidget(
+ parent=self._subcontainer,
+ position=(h + 220, v),
+ size=(170, 30),
+ text=ba.Lstr(resource=self._r + '.buttonsText'),
+ maxwidth=100,
+ textcolor=clr2,
+ scale=0.9,
+ )
+ cb2 = ba.checkboxwidget(
+ parent=self._subcontainer,
+ position=(h + 357, v),
+ size=(170, 30),
+ text=ba.Lstr(resource=self._r + '.swipeText'),
+ maxwidth=100,
+ textcolor=clr2,
+ scale=0.9,
+ )
+ make_radio_group(
+ (cb1, cb2), ('buttons', 'swipe'), cur_val, self._actions_changed
+ )
v -= 50
- ConfigNumberEdit(parent=self._subcontainer,
- position=(h, v),
- xoffset=65,
- configkey='Touch Controls Scale Actions',
- displayname=ba.Lstr(resource=self._r +
- '.actionControlScaleText'),
- changesound=False,
- minval=0.1,
- maxval=4.0,
- increment=0.1)
+ ConfigNumberEdit(
+ parent=self._subcontainer,
+ position=(h, v),
+ xoffset=65,
+ configkey='Touch Controls Scale Actions',
+ displayname=ba.Lstr(resource=self._r + '.actionControlScaleText'),
+ changesound=False,
+ minval=0.1,
+ maxval=4.0,
+ increment=0.1,
+ )
v -= 50
- ConfigCheckBox(parent=self._subcontainer,
- position=(h, v),
- size=(400, 30),
- maxwidth=400,
- configkey='Touch Controls Swipe Hidden',
- displayname=ba.Lstr(resource=self._r +
- '.swipeControlsHiddenText'))
+ ConfigCheckBox(
+ parent=self._subcontainer,
+ position=(h, v),
+ size=(400, 30),
+ maxwidth=400,
+ configkey='Touch Controls Swipe Hidden',
+ displayname=ba.Lstr(resource=self._r + '.swipeControlsHiddenText'),
+ )
v -= 65
- ba.buttonwidget(parent=self._subcontainer,
- position=(self._sub_width * 0.5 - 70, v),
- size=(170, 60),
- label=ba.Lstr(resource=self._r + '.resetText'),
- scale=0.75,
- on_activate_call=self._reset)
+ ba.buttonwidget(
+ parent=self._subcontainer,
+ position=(self._sub_width * 0.5 - 70, v),
+ size=(170, 60),
+ label=ba.Lstr(resource=self._r + '.resetText'),
+ scale=0.75,
+ on_activate_call=self._reset,
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, 38),
- size=(0, 0),
- h_align='center',
- text=ba.Lstr(resource=self._r + '.dragControlsText'),
- maxwidth=self._width * 0.8,
- scale=0.65,
- color=(1, 1, 1, 0.4))
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, 38),
+ size=(0, 0),
+ h_align='center',
+ text=ba.Lstr(resource=self._r + '.dragControlsText'),
+ maxwidth=self._width * 0.8,
+ scale=0.65,
+ color=(1, 1, 1, 0.4),
+ )
def _actions_changed(self, v: str) -> None:
cfg = ba.app.config
@@ -215,11 +254,16 @@ class TouchscreenSettingsWindow(ba.Window):
def _reset(self) -> None:
cfg = ba.app.config
cfgkeys = [
- 'Touch Movement Control Type', 'Touch Action Control Type',
- 'Touch Controls Scale', 'Touch Controls Scale Movement',
- 'Touch Controls Scale Actions', 'Touch Controls Swipe Hidden',
- 'Touch DPad X', 'Touch DPad Y', 'Touch Buttons X',
- 'Touch Buttons Y'
+ 'Touch Movement Control Type',
+ 'Touch Action Control Type',
+ 'Touch Controls Scale',
+ 'Touch Controls Scale Movement',
+ 'Touch Controls Scale Actions',
+ 'Touch Controls Swipe Hidden',
+ 'Touch DPad X',
+ 'Touch DPad Y',
+ 'Touch Buttons X',
+ 'Touch Buttons Y',
]
for cfgkey in cfgkeys:
if cfgkey in cfg:
@@ -229,8 +273,11 @@ class TouchscreenSettingsWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.settings import controls
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
controls.ControlsSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
ba.internal.set_touchscreen_editing(False)
diff --git a/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py b/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
index a6723f78..43f18155 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/vrtesting.py
@@ -26,17 +26,17 @@ class VRTestingWindow(TestingWindow):
{
'name': 'timeWarpDebug',
'label': 'Time Warp Debug',
- 'increment': 1.0
+ 'increment': 1.0,
},
{
'name': 'chromaticAberrationCorrection',
'label': 'Chromatic Aberration Correction',
- 'increment': 1.0
+ 'increment': 1.0,
},
{
'name': 'vrMinimumVSyncs',
'label': 'Minimum Vsyncs',
- 'increment': 1.0
+ 'increment': 1.0,
},
# {'name':'eyeOffsX','label':'Eye IPD','increment':0.001}
]
@@ -46,45 +46,45 @@ class VRTestingWindow(TestingWindow):
# {'name':'eyeOffsY','label':'Eye Offset Y','increment':0.01},
# {'name':'eyeOffsZ','label':'Eye Offset Z','increment':0.005}]
# everyone gets head-scale
- entries += [{
- 'name': 'headScale',
- 'label': 'Head Scale',
- 'increment': 1.0
- }]
+ entries += [
+ {'name': 'headScale', 'label': 'Head Scale', 'increment': 1.0}
+ ]
# and everyone gets all these..
entries += [
{
'name': 'vrCamOffsetY',
'label': 'In-Game Cam Offset Y',
- 'increment': 0.1
+ 'increment': 0.1,
},
{
'name': 'vrCamOffsetZ',
'label': 'In-Game Cam Offset Z',
- 'increment': 0.1
+ 'increment': 0.1,
},
{
'name': 'vrOverlayScale',
'label': 'Overlay Scale',
- 'increment': 0.025
+ 'increment': 0.025,
},
{
'name': 'allowCameraMovement',
'label': 'Allow Camera Movement',
- 'increment': 1.0
+ 'increment': 1.0,
},
{
'name': 'cameraPanSpeedScale',
'label': 'Camera Movement Speed',
- 'increment': 0.1
+ 'increment': 0.1,
},
{
'name': 'showOverlayBounds',
'label': 'Show Overlay Bounds',
- 'increment': 1
+ 'increment': 1,
},
]
super().__init__(
- ba.Lstr(resource='settingsWindowAdvanced.vrTestingText'), entries,
- transition)
+ ba.Lstr(resource='settingsWindowAdvanced.vrTestingText'),
+ entries,
+ transition,
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/settings/xbox360controller.py b/assets/src/ba_data/python/bastd/ui/settings/xbox360controller.py
index bdec91d3..546f8f9a 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/xbox360controller.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/xbox360controller.py
@@ -22,66 +22,83 @@ class XBox360ControllerSettingsWindow(ba.Window):
height = 300 if ba.internal.is_running_on_fire_tv() else 485
spacing = 40
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height),
- transition='in_right',
- scale=(1.4 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height),
+ transition='in_right',
+ scale=(
+ 1.4
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(35, height - 65),
- size=(120, 60),
- scale=0.84,
- label=ba.Lstr(resource='backText'),
- button_type='back',
- autoselect=True,
- on_activate_call=self._back)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35, height - 65),
+ size=(120, 60),
+ scale=0.84,
+ label=ba.Lstr(resource='backText'),
+ button_type='back',
+ autoselect=True,
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height - 42),
- size=(0, 0),
- scale=0.85,
- text=ba.Lstr(resource=self._r + '.titleText',
- subs=[('${APP_NAME}',
- ba.Lstr(resource='titleText'))]),
- color=ba.app.ui.title_color,
- maxwidth=400,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height - 42),
+ size=(0, 0),
+ scale=0.85,
+ text=ba.Lstr(
+ resource=self._r + '.titleText',
+ subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))],
+ ),
+ color=ba.app.ui.title_color,
+ maxwidth=400,
+ h_align='center',
+ v_align='center',
+ )
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
v = height - 70
v -= spacing
if ba.internal.is_running_on_fire_tv():
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, height * 0.47),
- size=(0, 0),
- color=(0.7, 0.9, 0.7, 1.0),
- maxwidth=width * 0.95,
- max_height=height * 0.75,
- scale=0.7,
- text=ba.Lstr(resource=self._r +
- '.ouyaInstructionsText'),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, height * 0.47),
+ size=(0, 0),
+ color=(0.7, 0.9, 0.7, 1.0),
+ maxwidth=width * 0.95,
+ max_height=height * 0.75,
+ scale=0.7,
+ text=ba.Lstr(resource=self._r + '.ouyaInstructionsText'),
+ h_align='center',
+ v_align='center',
+ )
else:
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, v - 1),
- size=(0, 0),
- color=(0.7, 0.9, 0.7, 1.0),
- maxwidth=width * 0.95,
- max_height=height * 0.22,
- text=ba.Lstr(resource=self._r +
- '.macInstructionsText'),
- scale=0.7,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, v - 1),
+ size=(0, 0),
+ color=(0.7, 0.9, 0.7, 1.0),
+ maxwidth=width * 0.95,
+ max_height=height * 0.22,
+ text=ba.Lstr(resource=self._r + '.macInstructionsText'),
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
v -= 90
b_width = 300
btn = ba.buttonwidget(
@@ -92,24 +109,30 @@ class XBox360ControllerSettingsWindow(ba.Window):
autoselect=True,
on_activate_call=ba.Call(
ba.open_url,
- 'https://github.com/360Controller/360Controller/releases'))
+ 'https://github.com/360Controller/360Controller/releases',
+ ),
+ )
ba.containerwidget(edit=self._root_widget, start_button=btn)
v -= 60
- ba.textwidget(parent=self._root_widget,
- position=(width * 0.5, v - 85),
- size=(0, 0),
- color=(0.7, 0.9, 0.7, 1.0),
- maxwidth=width * 0.95,
- max_height=height * 0.46,
- scale=0.7,
- text=ba.Lstr(resource=self._r +
- '.macInstructions2Text'),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(width * 0.5, v - 85),
+ size=(0, 0),
+ color=(0.7, 0.9, 0.7, 1.0),
+ maxwidth=width * 0.95,
+ max_height=height * 0.46,
+ scale=0.7,
+ text=ba.Lstr(resource=self._r + '.macInstructions2Text'),
+ h_align='center',
+ v_align='center',
+ )
def _back(self) -> None:
from bastd.ui.settings import controls
+
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
controls.ControlsSettingsWindow(
- transition='in_left').get_root_widget())
+ transition='in_left'
+ ).get_root_widget()
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py b/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
index a4bf0dde..457b441f 100644
--- a/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
@@ -17,9 +17,11 @@ if TYPE_CHECKING:
class SoundtrackBrowserWindow(ba.Window):
"""Window for browsing soundtracks."""
- def __init__(self,
- transition: str = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@@ -37,20 +39,35 @@ class SoundtrackBrowserWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 800 if uiscale is ba.UIScale.SMALL else 600
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (340 if uiscale is ba.UIScale.SMALL else
- 370 if uiscale is ba.UIScale.MEDIUM else 440)
+ self._height = (
+ 340
+ if uiscale is ba.UIScale.SMALL
+ else 370
+ if uiscale is ba.UIScale.MEDIUM
+ else 440
+ )
spacing = 40.0
v = self._height - 40.0
v -= spacing * 1.0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(2.3 if uiscale is ba.UIScale.SMALL else
- 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -18) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.6
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -18)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
if ba.app.ui.use_toolbars and uiscale is ba.UIScale.SMALL:
self._back_button = None
@@ -62,19 +79,24 @@ class SoundtrackBrowserWindow(ba.Window):
scale=0.8,
label=ba.Lstr(resource='backText'),
button_type='back',
- autoselect=True)
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 35),
- size=(0, 0),
- maxwidth=300,
- text=ba.Lstr(resource=self._r + '.titleText'),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center')
+ autoselect=True,
+ )
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 35),
+ size=(0, 0),
+ maxwidth=300,
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ )
h = 43 + x_inset
v = self._height - 60
@@ -83,8 +105,13 @@ class SoundtrackBrowserWindow(ba.Window):
lock_tex = ba.gettexture('lock')
self._lock_images: list[ba.Widget] = []
- scl = (1.0 if uiscale is ba.UIScale.SMALL else
- 1.13 if uiscale is ba.UIScale.MEDIUM else 1.4)
+ scl = (
+ 1.0
+ if uiscale is ba.UIScale.SMALL
+ else 1.13
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.4
+ )
v -= 60.0 * scl
self._new_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -96,18 +123,23 @@ class SoundtrackBrowserWindow(ba.Window):
autoselect=True,
textcolor=b_textcolor,
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.newText'))
+ label=ba.Lstr(resource=self._r + '.newText'),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 55.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 55.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
if self._back_button is None:
ba.widget(
edit=btn,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
v -= 60.0 * scl
self._edit_button = btn = ba.buttonwidget(
@@ -120,17 +152,22 @@ class SoundtrackBrowserWindow(ba.Window):
autoselect=True,
textcolor=b_textcolor,
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.editText'))
+ label=ba.Lstr(resource=self._r + '.editText'),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 55.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 55.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
if self._back_button is None:
ba.widget(
edit=btn,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
v -= 60.0 * scl
self._duplicate_button = btn = ba.buttonwidget(
@@ -143,17 +180,22 @@ class SoundtrackBrowserWindow(ba.Window):
color=b_color,
textcolor=b_textcolor,
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.duplicateText'))
+ label=ba.Lstr(resource=self._r + '.duplicateText'),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 55.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 55.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
if self._back_button is None:
ba.widget(
edit=btn,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
v -= 60.0 * scl
self._delete_button = btn = ba.buttonwidget(
@@ -166,23 +208,30 @@ class SoundtrackBrowserWindow(ba.Window):
autoselect=True,
textcolor=b_textcolor,
text_scale=0.7,
- label=ba.Lstr(resource=self._r + '.deleteText'))
+ label=ba.Lstr(resource=self._r + '.deleteText'),
+ )
self._lock_images.append(
- ba.imagewidget(parent=self._root_widget,
- size=(30, 30),
- draw_controller=btn,
- position=(h - 10, v + 55.0 * scl - 28),
- texture=lock_tex))
+ ba.imagewidget(
+ parent=self._root_widget,
+ size=(30, 30),
+ draw_controller=btn,
+ position=(h - 10, v + 55.0 * scl - 28),
+ texture=lock_tex,
+ )
+ )
if self._back_button is None:
ba.widget(
edit=btn,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
# Keep our lock images up to date/etc.
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update()
v = self._height - 65
@@ -192,11 +241,15 @@ class SoundtrackBrowserWindow(ba.Window):
parent=self._root_widget,
position=(152 + x_inset, v),
highlight=False,
- size=(self._width - (205 + 2 * x_inset), scroll_height))
- ba.widget(edit=self._scrollwidget,
- left_widget=self._new_button,
- right_widget=ba.internal.get_special_widget('party_button')
- if ba.app.ui.use_toolbars else self._scrollwidget)
+ size=(self._width - (205 + 2 * x_inset), scroll_height),
+ )
+ ba.widget(
+ edit=self._scrollwidget,
+ left_widget=self._new_button,
+ right_widget=ba.internal.get_special_widget('party_button')
+ if ba.app.ui.use_toolbars
+ else self._scrollwidget,
+ )
self._col = ba.columnwidget(parent=scrollwidget, border=2, margin=0)
self._soundtracks: dict[str, Any] | None = None
@@ -206,13 +259,14 @@ class SoundtrackBrowserWindow(ba.Window):
self._allow_changing_soundtracks = False
self._refresh()
if self._back_button is not None:
- ba.buttonwidget(edit=self._back_button,
- on_activate_call=self._back)
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._back_button)
+ ba.buttonwidget(edit=self._back_button, on_activate_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
else:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
def _update(self) -> None:
have = ba.app.accounts_v1.have_pro_options()
@@ -236,6 +290,7 @@ class SoundtrackBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.confirm import ConfirmWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -243,18 +298,25 @@ class SoundtrackBrowserWindow(ba.Window):
return
if self._selected_soundtrack == '__default__':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.cantDeleteDefaultText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.cantDeleteDefaultText'),
+ color=(1, 0, 0),
+ )
else:
ConfirmWindow(
- ba.Lstr(resource=self._r + '.deleteConfirmText',
- subs=[('${NAME}', self._selected_soundtrack)]),
- self._do_delete_soundtrack, 450, 150)
+ ba.Lstr(
+ resource=self._r + '.deleteConfirmText',
+ subs=[('${NAME}', self._selected_soundtrack)],
+ ),
+ self._do_delete_soundtrack,
+ 450,
+ 150,
+ )
def _duplicate_soundtrack(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -275,7 +337,8 @@ class SoundtrackBrowserWindow(ba.Window):
# Get just 'Copy' or whatnot.
copy_word = copy_text.replace('${NAME}', '').strip()
base_name = self._get_soundtrack_display_name(
- self._selected_soundtrack).evaluate()
+ self._selected_soundtrack
+ ).evaluate()
assert isinstance(base_name, str)
# If it looks like a copy, strip digits and spaces off the end.
@@ -317,15 +380,19 @@ class SoundtrackBrowserWindow(ba.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.settings import audio
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- audio.AudioSettingsWindow(transition='in_left').get_root_widget())
+ audio.AudioSettingsWindow(transition='in_left').get_root_widget()
+ )
def _edit_soundtrack_with_sound(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -336,6 +403,7 @@ class SoundtrackBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -343,16 +411,19 @@ class SoundtrackBrowserWindow(ba.Window):
return
if self._selected_soundtrack == '__default__':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.cantEditDefaultText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.cantEditDefaultText'),
+ color=(1, 0, 0),
+ )
return
self._save_state()
ba.containerwidget(edit=self._root_widget, transition='out_left')
ba.app.ui.set_main_menu_window(
- SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack
- ).get_root_widget())
+ SoundtrackEditWindow(
+ existing_soundtrack=self._selected_soundtrack
+ ).get_root_widget()
+ )
def _get_soundtrack_display_name(self, soundtrack: str) -> ba.Lstr:
if soundtrack == '__default__':
@@ -361,6 +432,7 @@ class SoundtrackBrowserWindow(ba.Window):
def _refresh(self, select_soundtrack: str | None = None) -> None:
from efro.util import asserttype
+
self._allow_changing_soundtracks = False
old_selection = self._selected_soundtrack
@@ -391,7 +463,8 @@ class SoundtrackBrowserWindow(ba.Window):
always_highlight=True,
on_select_call=ba.WeakCall(self._select, pname, index),
on_activate_call=self._edit_soundtrack_with_sound,
- selectable=True)
+ selectable=True,
+ )
if index == 0:
ba.widget(edit=txtw, up_widget=self._back_button)
self._soundtrack_widgets.append(txtw)
@@ -399,22 +472,26 @@ class SoundtrackBrowserWindow(ba.Window):
# Select this one if the user requested it
if select_soundtrack is not None:
if pname == select_soundtrack:
- ba.columnwidget(edit=self._col,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._col, selected_child=txtw, visible_child=txtw
+ )
else:
# Select this one if it was previously selected.
# Go by index if there's one.
if old_selection_index is not None:
if index == old_selection_index:
- ba.columnwidget(edit=self._col,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._col,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
else: # Otherwise look by name.
if pname == old_selection:
- ba.columnwidget(edit=self._col,
- selected_child=txtw,
- visible_child=txtw)
+ ba.columnwidget(
+ edit=self._col,
+ selected_child=txtw,
+ visible_child=txtw,
+ )
index += 1
# Explicitly run select callback on current one and re-enable
@@ -423,21 +500,23 @@ class SoundtrackBrowserWindow(ba.Window):
# Eww need to run this in a timer so it happens after our select
# callbacks. With a small-enough time sometimes it happens before
# anyway. Ew. need a way to just schedule a callable i guess.
- ba.timer(0.1,
- ba.WeakCall(self._set_allow_changing),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1,
+ ba.WeakCall(self._set_allow_changing),
+ timetype=ba.TimeType.REAL,
+ )
def _set_allow_changing(self) -> None:
self._allow_changing_soundtracks = True
assert self._selected_soundtrack is not None
assert self._selected_soundtrack_index is not None
- self._select(self._selected_soundtrack,
- self._selected_soundtrack_index)
+ self._select(self._selected_soundtrack, self._selected_soundtrack_index)
def _new_soundtrack(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.purchase import PurchaseWindow
from bastd.ui.soundtrack.edit import SoundtrackEditWindow
+
if not ba.app.accounts_v1.have_pro_options():
PurchaseWindow(items=['pro'])
return
diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/edit.py b/assets/src/ba_data/python/bastd/ui/soundtrack/edit.py
index 5ab08cac..cb8d27c4 100644
--- a/assets/src/ba_data/python/bastd/ui/soundtrack/edit.py
+++ b/assets/src/ba_data/python/bastd/ui/soundtrack/edit.py
@@ -17,9 +17,11 @@ if TYPE_CHECKING:
class SoundtrackEditWindow(ba.Window):
"""Window for editing a soundtrack."""
- def __init__(self,
- existing_soundtrack: str | dict[str, Any] | None,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ existing_soundtrack: str | dict[str, Any] | None,
+ transition: str = 'in_right',
+ ):
# pylint: disable=too-many-statements
appconfig = ba.app.config
self._r = 'editSoundtrackWindow'
@@ -28,42 +30,66 @@ class SoundtrackEditWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 848 if uiscale is ba.UIScale.SMALL else 648
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (395 if uiscale is ba.UIScale.SMALL else
- 450 if uiscale is ba.UIScale.MEDIUM else 560)
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=(2.08 if uiscale is ba.UIScale.SMALL else
- 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -48) if uiscale is ba.UIScale.SMALL else (
- 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
- cancel_button = ba.buttonwidget(parent=self._root_widget,
- position=(38 + x_inset,
- self._height - 60),
- size=(160, 60),
- autoselect=True,
- label=ba.Lstr(resource='cancelText'),
- scale=0.8)
- save_button = ba.buttonwidget(parent=self._root_widget,
- position=(self._width - (168 + x_inset),
- self._height - 60),
- autoselect=True,
- size=(160, 60),
- label=ba.Lstr(resource='saveText'),
- scale=0.8)
+ self._height = (
+ 395
+ if uiscale is ba.UIScale.SMALL
+ else 450
+ if uiscale is ba.UIScale.MEDIUM
+ else 560
+ )
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=(
+ 2.08
+ if uiscale is ba.UIScale.SMALL
+ else 1.5
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -48)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 15)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ )
+ )
+ cancel_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(38 + x_inset, self._height - 60),
+ size=(160, 60),
+ autoselect=True,
+ label=ba.Lstr(resource='cancelText'),
+ scale=0.8,
+ )
+ save_button = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width - (168 + x_inset), self._height - 60),
+ autoselect=True,
+ size=(160, 60),
+ label=ba.Lstr(resource='saveText'),
+ scale=0.8,
+ )
ba.widget(edit=save_button, left_widget=cancel_button)
ba.widget(edit=cancel_button, right_widget=save_button)
ba.textwidget(
parent=self._root_widget,
position=(0, self._height - 50),
size=(self._width, 25),
- text=ba.Lstr(resource=self._r +
- ('.editSoundtrackText' if existing_soundtrack
- is not None else '.newSoundtrackText')),
+ text=ba.Lstr(
+ resource=self._r
+ + (
+ '.editSoundtrackText'
+ if existing_soundtrack is not None
+ else '.newSoundtrackText'
+ )
+ ),
color=ba.app.ui.title_color,
h_align='center',
v_align='center',
- maxwidth=280)
+ maxwidth=280,
+ )
v = self._height - 110
if 'Soundtracks' not in appconfig:
appconfig['Soundtracks'] = {}
@@ -74,7 +100,8 @@ class SoundtrackEditWindow(ba.Window):
# if they passed just a name, pull info from that soundtrack
if isinstance(existing_soundtrack, str):
self._soundtrack = copy.deepcopy(
- appconfig['Soundtracks'][existing_soundtrack])
+ appconfig['Soundtracks'][existing_soundtrack]
+ )
self._soundtrack_name = existing_soundtrack
self._existing_soundtrack_name = existing_soundtrack
self._last_edited_song_type = None
@@ -82,37 +109,41 @@ class SoundtrackEditWindow(ba.Window):
# otherwise they can pass info on an in-progress edit
self._soundtrack = existing_soundtrack['soundtrack']
self._soundtrack_name = existing_soundtrack['name']
- self._existing_soundtrack_name = (
- existing_soundtrack['existing_name'])
- self._last_edited_song_type = (
- existing_soundtrack['last_edited_song_type'])
+ self._existing_soundtrack_name = existing_soundtrack[
+ 'existing_name'
+ ]
+ self._last_edited_song_type = existing_soundtrack[
+ 'last_edited_song_type'
+ ]
else:
self._soundtrack_name = None
self._existing_soundtrack_name = None
self._soundtrack = {}
self._last_edited_song_type = None
- ba.textwidget(parent=self._root_widget,
- text=ba.Lstr(resource=self._r + '.nameText'),
- maxwidth=80,
- scale=0.8,
- position=(105 + x_inset, v + 19),
- color=(0.8, 0.8, 0.8, 0.5),
- size=(0, 0),
- h_align='right',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ text=ba.Lstr(resource=self._r + '.nameText'),
+ maxwidth=80,
+ scale=0.8,
+ position=(105 + x_inset, v + 19),
+ color=(0.8, 0.8, 0.8, 0.5),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ )
# if there's no initial value, find a good initial unused name
if existing_soundtrack is None:
i = 1
- st_name_text = ba.Lstr(resource=self._r +
- '.newSoundtrackNameText').evaluate()
+ st_name_text = ba.Lstr(
+ resource=self._r + '.newSoundtrackNameText'
+ ).evaluate()
if '${COUNT}' not in st_name_text:
# make sure we insert number *somewhere*
st_name_text = st_name_text + ' ${COUNT}'
while True:
- self._soundtrack_name = st_name_text.replace(
- '${COUNT}', str(i))
+ self._soundtrack_name = st_name_text.replace('${COUNT}', str(i))
if self._soundtrack_name not in appconfig['Soundtracks']:
break
i += 1
@@ -129,7 +160,8 @@ class SoundtrackEditWindow(ba.Window):
description=ba.Lstr(resource=self._r + '.nameText'),
editable=True,
padding=4,
- on_return_press_call=self._do_it_with_sound)
+ on_return_press_call=self._do_it_with_sound,
+ )
scroll_height = self._height - 180
self._scrollwidget = scrollwidget = ba.scrollwidget(
@@ -140,12 +172,15 @@ class SoundtrackEditWindow(ba.Window):
simple_culling_v=10,
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
ba.widget(edit=self._text_field, down_widget=self._scrollwidget)
- self._col = ba.columnwidget(parent=scrollwidget,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ self._col = ba.columnwidget(
+ parent=scrollwidget,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
self._song_type_buttons: dict[str, ba.Widget] = {}
self._refresh()
@@ -189,20 +224,24 @@ class SoundtrackEditWindow(ba.Window):
prev_test_button: ba.Widget | None = None
for index, song_type in enumerate(types):
- row = ba.rowwidget(parent=self._col,
- size=(self._width - 40, 40),
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ row = ba.rowwidget(
+ parent=self._col,
+ size=(self._width - 40, 40),
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
type_name = type_names_translated.get(song_type, song_type)
- ba.textwidget(parent=row,
- size=(230, 25),
- always_highlight=True,
- text=type_name,
- scale=0.7,
- h_align='left',
- v_align='center',
- maxwidth=190)
+ ba.textwidget(
+ parent=row,
+ size=(230, 25),
+ always_highlight=True,
+ text=type_name,
+ scale=0.7,
+ h_align='left',
+ v_align='center',
+ maxwidth=190,
+ )
if song_type in self._soundtrack:
entry = self._soundtrack[song_type]
@@ -219,34 +258,48 @@ class SoundtrackEditWindow(ba.Window):
size=(230, 32),
label=self._get_entry_button_display_name(entry),
text_scale=0.6,
- on_activate_call=ba.Call(self._get_entry, song_type, entry,
- type_name),
- icon=(self._file_tex if icon_type == 'file' else
- self._folder_tex if icon_type == 'folder' else None),
- icon_color=(1.1, 0.8, 0.2) if icon_type == 'folder' else
- (1, 1, 1),
+ on_activate_call=ba.Call(
+ self._get_entry, song_type, entry, type_name
+ ),
+ icon=(
+ self._file_tex
+ if icon_type == 'file'
+ else self._folder_tex
+ if icon_type == 'folder'
+ else None
+ ),
+ icon_color=(1.1, 0.8, 0.2)
+ if icon_type == 'folder'
+ else (1, 1, 1),
left_widget=self._text_field,
iconscale=0.7,
autoselect=True,
- up_widget=prev_type_button)
+ up_widget=prev_type_button,
+ )
if index == 0:
ba.widget(edit=btn, up_widget=self._text_field)
ba.widget(edit=btn, down_widget=btn)
- if (self._last_edited_song_type is not None
- and song_type == self._last_edited_song_type):
- ba.containerwidget(edit=row,
- selected_child=btn,
- visible_child=btn)
- ba.containerwidget(edit=self._col,
- selected_child=row,
- visible_child=row)
- ba.containerwidget(edit=self._scrollwidget,
- selected_child=self._col,
- visible_child=self._col)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget,
- visible_child=self._scrollwidget)
+ if (
+ self._last_edited_song_type is not None
+ and song_type == self._last_edited_song_type
+ ):
+ ba.containerwidget(
+ edit=row, selected_child=btn, visible_child=btn
+ )
+ ba.containerwidget(
+ edit=self._col, selected_child=row, visible_child=row
+ )
+ ba.containerwidget(
+ edit=self._scrollwidget,
+ selected_child=self._col,
+ visible_child=self._col,
+ )
+ ba.containerwidget(
+ edit=self._root_widget,
+ selected_child=self._scrollwidget,
+ visible_child=self._scrollwidget,
+ )
if prev_type_button is not None:
ba.widget(edit=prev_type_button, down_widget=btn)
@@ -259,21 +312,25 @@ class SoundtrackEditWindow(ba.Window):
text_scale=0.6,
on_activate_call=ba.Call(self._test, ba.MusicType(song_type)),
up_widget=prev_test_button
- if prev_test_button is not None else self._text_field)
+ if prev_test_button is not None
+ else self._text_field,
+ )
if prev_test_button is not None:
ba.widget(edit=prev_test_button, down_widget=btn)
ba.widget(edit=btn, down_widget=btn, right_widget=btn)
prev_test_button = btn
@classmethod
- def _restore_editor(cls, state: dict[str, Any], musictype: str,
- entry: Any) -> None:
+ def _restore_editor(
+ cls, state: dict[str, Any], musictype: str, entry: Any
+ ) -> None:
music = ba.app.music
# Apply the change and recreate the window.
soundtrack = state['soundtrack']
- existing_entry = (None if musictype not in soundtrack else
- soundtrack[musictype])
+ existing_entry = (
+ None if musictype not in soundtrack else soundtrack[musictype]
+ )
if existing_entry != entry:
ba.playsound(ba.getsound('gunCocking'))
@@ -290,10 +347,12 @@ class SoundtrackEditWindow(ba.Window):
soundtrack[musictype] = entry
ba.app.ui.set_main_menu_window(
- cls(state, transition='in_left').get_root_widget())
+ cls(state, transition='in_left').get_root_widget()
+ )
- def _get_entry(self, song_type: str, entry: Any,
- selection_target_name: str) -> None:
+ def _get_entry(
+ self, song_type: str, entry: Any, selection_target_name: str
+ ) -> None:
music = ba.app.music
if selection_target_name != '':
selection_target_name = "'" + selection_target_name + "'"
@@ -301,12 +360,18 @@ class SoundtrackEditWindow(ba.Window):
'name': self._soundtrack_name,
'existing_name': self._existing_soundtrack_name,
'soundtrack': self._soundtrack,
- 'last_edited_song_type': song_type
+ 'last_edited_song_type': song_type,
}
ba.containerwidget(edit=self._root_widget, transition='out_left')
- ba.app.ui.set_main_menu_window(music.get_music_player().select_entry(
- ba.Call(self._restore_editor, state, song_type), entry,
- selection_target_name).get_root_widget())
+ ba.app.ui.set_main_menu_window(
+ music.get_music_player()
+ .select_entry(
+ ba.Call(self._restore_editor, state, song_type),
+ entry,
+ selection_target_name,
+ )
+ .get_root_widget()
+ )
def _test(self, song_type: ba.MusicType) -> None:
music = ba.app.music
@@ -314,13 +379,16 @@ class SoundtrackEditWindow(ba.Window):
# Warn if volume is zero.
if ba.app.config.resolve('Music Volume') < 0.01:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.musicVolumeZeroWarning'),
- color=(1, 0.5, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.musicVolumeZeroWarning'),
+ color=(1, 0.5, 0),
+ )
music.set_music_play_mode(ba.MusicPlayMode.TEST)
- music.do_play_music(song_type,
- mode=ba.MusicPlayMode.TEST,
- testsoundtrack=self._soundtrack)
+ music.do_play_music(
+ song_type,
+ mode=ba.MusicPlayMode.TEST,
+ testsoundtrack=self._soundtrack,
+ )
def _get_entry_button_display_name(self, entry: Any) -> str | ba.Lstr:
music = ba.app.music
@@ -345,33 +413,40 @@ class SoundtrackEditWindow(ba.Window):
def _cancel(self) -> None:
from bastd.ui.soundtrack import browser as stb
+
music = ba.app.music
# Resets music back to normal.
music.set_music_play_mode(ba.MusicPlayMode.REGULAR)
ba.containerwidget(edit=self._root_widget, transition='out_right')
ba.app.ui.set_main_menu_window(
- stb.SoundtrackBrowserWindow(
- transition='in_left').get_root_widget())
+ stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
+ )
def _do_it(self) -> None:
from bastd.ui.soundtrack import browser as stb
+
music = ba.app.music
cfg = ba.app.config
new_name = cast(str, ba.textwidget(query=self._text_field))
- if (new_name != self._soundtrack_name
- and new_name in cfg['Soundtracks']):
+ if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']:
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText'))
+ ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')
+ )
ba.playsound(ba.getsound('error'))
return
if not new_name:
ba.playsound(ba.getsound('error'))
return
- if new_name == ba.Lstr(resource=self._r +
- '.defaultSoundtrackNameText').evaluate():
+ if (
+ new_name
+ == ba.Lstr(
+ resource=self._r + '.defaultSoundtrackNameText'
+ ).evaluate()
+ ):
ba.screenmessage(
- ba.Lstr(resource=self._r + '.cantOverwriteDefaultText'))
+ ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')
+ )
ba.playsound(ba.getsound('error'))
return
@@ -380,8 +455,10 @@ class SoundtrackEditWindow(ba.Window):
cfg['Soundtracks'] = {}
# If we had an old one, delete it.
- if (self._existing_soundtrack_name is not None
- and self._existing_soundtrack_name in cfg['Soundtracks']):
+ if (
+ self._existing_soundtrack_name is not None
+ and self._existing_soundtrack_name in cfg['Soundtracks']
+ ):
del cfg['Soundtracks'][self._existing_soundtrack_name]
cfg['Soundtracks'][new_name] = self._soundtrack
cfg['Soundtrack'] = new_name
@@ -394,8 +471,8 @@ class SoundtrackEditWindow(ba.Window):
music.set_music_play_mode(ba.MusicPlayMode.REGULAR, force_restart=True)
ba.app.ui.set_main_menu_window(
- stb.SoundtrackBrowserWindow(
- transition='in_left').get_root_widget())
+ stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
+ )
def _do_it_with_sound(self) -> None:
ba.playsound(ba.getsound('swish'))
diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py b/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py
index e009b251..21e6137f 100644
--- a/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py
+++ b/assets/src/ba_data/python/bastd/ui/soundtrack/entrytypeselect.py
@@ -16,11 +16,13 @@ if TYPE_CHECKING:
class SoundtrackEntryTypeSelectWindow(ba.Window):
"""Window for selecting a soundtrack entry type."""
- def __init__(self,
- callback: Callable[[Any], Any],
- current_entry: Any,
- selection_target_name: str,
- transition: str = 'in_right'):
+ def __init__(
+ self,
+ callback: Callable[[Any], Any],
+ current_entry: Any,
+ selection_target_name: str,
+ transition: str = 'in_right',
+ ):
music = ba.app.music
self._r = 'editSoundtrackWindow'
@@ -35,7 +37,8 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
# themselves here.
do_default = True
do_mac_music_app_playlist = music.supports_soundtrack_entry_type(
- 'iTunesPlaylist')
+ 'iTunesPlaylist'
+ )
do_music_file = music.supports_soundtrack_entry_type('musicFile')
do_music_folder = music.supports_soundtrack_entry_type('musicFolder')
@@ -53,50 +56,65 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
# standard UI-cleanup-check complains that something is holding on
# to our instance after its ui is gone. Should restructure in a
# cleaner way, but just disabling that check for now.
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=(1.7 if uiscale is ba.UIScale.SMALL else
- 1.4 if uiscale is ba.UIScale.MEDIUM else 1.0)),
- cleanupcheck=False)
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(35, self._height - 65),
- size=(160, 60),
- scale=0.8,
- text_scale=1.2,
- label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._on_cancel_press)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=(
+ 1.7
+ if uiscale is ba.UIScale.SMALL
+ else 1.4
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ ),
+ cleanupcheck=False,
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35, self._height - 65),
+ size=(160, 60),
+ scale=0.8,
+ text_scale=1.2,
+ label=ba.Lstr(resource='cancelText'),
+ on_activate_call=self._on_cancel_press,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 32),
- size=(0, 0),
- text=ba.Lstr(resource=self._r + '.selectASourceText'),
- color=ba.app.ui.title_color,
- maxwidth=230,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 32),
+ size=(0, 0),
+ text=ba.Lstr(resource=self._r + '.selectASourceText'),
+ color=ba.app.ui.title_color,
+ maxwidth=230,
+ h_align='center',
+ v_align='center',
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 56),
- size=(0, 0),
- text=selection_target_name,
- color=ba.app.ui.infotextcolor,
- scale=0.7,
- maxwidth=230,
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 56),
+ size=(0, 0),
+ text=selection_target_name,
+ color=ba.app.ui.infotextcolor,
+ scale=0.7,
+ maxwidth=230,
+ h_align='center',
+ v_align='center',
+ )
v = self._height - 155
current_entry_type = music.get_soundtrack_entry_type(current_entry)
if do_default:
- btn = ba.buttonwidget(parent=self._root_widget,
- size=(self._width - 100, 60),
- position=(50, v),
- label=ba.Lstr(resource=self._r +
- '.useDefaultGameMusicText'),
- on_activate_call=self._on_default_press)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ size=(self._width - 100, 60),
+ position=(50, v),
+ label=ba.Lstr(resource=self._r + '.useDefaultGameMusicText'),
+ on_activate_call=self._on_default_press,
+ )
if current_entry_type == 'default':
ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing
@@ -108,32 +126,35 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
position=(50, v),
label=ba.Lstr(resource=self._r + '.useITunesPlaylistText'),
on_activate_call=self._on_mac_music_app_playlist_press,
- icon=None)
+ icon=None,
+ )
if current_entry_type == 'iTunesPlaylist':
ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing
if do_music_file:
- btn = ba.buttonwidget(parent=self._root_widget,
- size=(self._width - 100, 60),
- position=(50, v),
- label=ba.Lstr(resource=self._r +
- '.useMusicFileText'),
- on_activate_call=self._on_music_file_press,
- icon=ba.gettexture('file'))
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ size=(self._width - 100, 60),
+ position=(50, v),
+ label=ba.Lstr(resource=self._r + '.useMusicFileText'),
+ on_activate_call=self._on_music_file_press,
+ icon=ba.gettexture('file'),
+ )
if current_entry_type == 'musicFile':
ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing
if do_music_folder:
- btn = ba.buttonwidget(parent=self._root_widget,
- size=(self._width - 100, 60),
- position=(50, v),
- label=ba.Lstr(resource=self._r +
- '.useMusicFolderText'),
- on_activate_call=self._on_music_folder_press,
- icon=ba.gettexture('folder'),
- icon_color=(1.1, 0.8, 0.2))
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ size=(self._width - 100, 60),
+ position=(50, v),
+ label=ba.Lstr(resource=self._r + '.useMusicFolderText'),
+ on_activate_call=self._on_music_folder_press,
+ icon=ba.gettexture('folder'),
+ icon_color=(1.1, 0.8, 0.2),
+ )
if current_entry_type == 'musicFolder':
ba.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing
@@ -141,24 +162,31 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
def _on_mac_music_app_playlist_press(self) -> None:
music = ba.app.music
from bastd.ui.soundtrack.macmusicapp import (
- MacMusicAppPlaylistSelectWindow)
+ MacMusicAppPlaylistSelectWindow,
+ )
+
ba.containerwidget(edit=self._root_widget, transition='out_left')
current_playlist_entry: str | None
- if (music.get_soundtrack_entry_type(
- self._current_entry) == 'iTunesPlaylist'):
+ if (
+ music.get_soundtrack_entry_type(self._current_entry)
+ == 'iTunesPlaylist'
+ ):
current_playlist_entry = music.get_soundtrack_entry_name(
- self._current_entry)
+ self._current_entry
+ )
else:
current_playlist_entry = None
ba.app.ui.set_main_menu_window(
MacMusicAppPlaylistSelectWindow(
- self._callback, current_playlist_entry,
- self._current_entry).get_root_widget())
+ self._callback, current_playlist_entry, self._current_entry
+ ).get_root_widget()
+ )
def _on_music_file_press(self) -> None:
from ba.osmusic import OSMusicPlayer
from bastd.ui.fileselector import FileSelectorWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_left')
base_path = ba.internal.android_get_external_files_dir()
ba.app.ui.set_main_menu_window(
@@ -167,19 +195,26 @@ class SoundtrackEntryTypeSelectWindow(ba.Window):
callback=self._music_file_selector_cb,
show_base_path=False,
valid_file_extensions=(
- OSMusicPlayer.get_valid_music_file_extensions()),
- allow_folders=False).get_root_widget())
+ OSMusicPlayer.get_valid_music_file_extensions()
+ ),
+ allow_folders=False,
+ ).get_root_widget()
+ )
def _on_music_folder_press(self) -> None:
from bastd.ui.fileselector import FileSelectorWindow
+
ba.containerwidget(edit=self._root_widget, transition='out_left')
base_path = ba.internal.android_get_external_files_dir()
ba.app.ui.set_main_menu_window(
- FileSelectorWindow(base_path,
- callback=self._music_folder_selector_cb,
- show_base_path=False,
- valid_file_extensions=[],
- allow_folders=True).get_root_widget())
+ FileSelectorWindow(
+ base_path,
+ callback=self._music_folder_selector_cb,
+ show_base_path=False,
+ valid_file_extensions=[],
+ allow_folders=True,
+ ).get_root_widget()
+ )
def _music_file_selector_cb(self, result: str | None) -> None:
if result is None:
diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/macmusicapp.py b/assets/src/ba_data/python/bastd/ui/soundtrack/macmusicapp.py
index d5b030a9..c564018b 100644
--- a/assets/src/ba_data/python/bastd/ui/soundtrack/macmusicapp.py
+++ b/assets/src/ba_data/python/bastd/ui/soundtrack/macmusicapp.py
@@ -16,9 +16,14 @@ if TYPE_CHECKING:
class MacMusicAppPlaylistSelectWindow(ba.Window):
"""Window for selecting an iTunes playlist."""
- def __init__(self, callback: Callable[[Any], Any],
- existing_playlist: str | None, existing_entry: Any):
+ def __init__(
+ self,
+ callback: Callable[[Any], Any],
+ existing_playlist: str | None,
+ existing_entry: Any,
+ ):
from ba.macmusicapp import MacMusicAppMusicPlayer
+
self._r = 'editSoundtrackWindow'
self._callback = callback
self._existing_playlist = existing_playlist
@@ -28,63 +33,78 @@ class MacMusicAppPlaylistSelectWindow(ba.Window):
self._spacing = 45.0
v = self._height - 90.0
v -= self._spacing * 1.0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height), transition='in_right'))
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(35, self._height - 65),
- size=(130, 50),
- label=ba.Lstr(resource='cancelText'),
- on_activate_call=self._back,
- autoselect=True)
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height), transition='in_right'
+ )
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(35, self._height - 65),
+ size=(130, 50),
+ label=ba.Lstr(resource='cancelText'),
+ on_activate_call=self._back,
+ autoselect=True,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.textwidget(parent=self._root_widget,
- position=(20, self._height - 54),
- size=(self._width, 25),
- text=ba.Lstr(resource=self._r + '.selectAPlaylistText'),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- maxwidth=200)
- self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
- position=(40, v - 340),
- size=(self._width - 80, 400),
- claims_tab=True,
- selection_loops_to_parent=True)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(20, self._height - 54),
+ size=(self._width, 25),
+ text=ba.Lstr(resource=self._r + '.selectAPlaylistText'),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ maxwidth=200,
+ )
+ self._scrollwidget = ba.scrollwidget(
+ parent=self._root_widget,
+ position=(40, v - 340),
+ size=(self._width - 80, 400),
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
ba.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
- self._column = ba.columnwidget(parent=self._scrollwidget,
- claims_tab=True,
- selection_loops_to_parent=True)
+ self._column = ba.columnwidget(
+ parent=self._scrollwidget,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
- ba.textwidget(parent=self._column,
- size=(self._width - 80, 22),
- text=ba.Lstr(resource=self._r + '.fetchingITunesText'),
- color=(0.6, 0.9, 0.6, 1.0),
- scale=0.8)
+ ba.textwidget(
+ parent=self._column,
+ size=(self._width - 80, 22),
+ text=ba.Lstr(resource=self._r + '.fetchingITunesText'),
+ color=(0.6, 0.9, 0.6, 1.0),
+ scale=0.8,
+ )
musicplayer = ba.app.music.get_music_player()
assert isinstance(musicplayer, MacMusicAppMusicPlayer)
musicplayer.get_playlists(self._playlists_cb)
- ba.containerwidget(edit=self._root_widget,
- selected_child=self._scrollwidget)
+ ba.containerwidget(
+ edit=self._root_widget, selected_child=self._scrollwidget
+ )
def _playlists_cb(self, playlists: list[str]) -> None:
if self._column:
for widget in self._column.get_children():
widget.delete()
for i, playlist in enumerate(playlists):
- txt = ba.textwidget(parent=self._column,
- size=(self._width - 80, 30),
- text=playlist,
- v_align='center',
- maxwidth=self._width - 110,
- selectable=True,
- on_activate_call=ba.Call(
- self._sel, playlist),
- click_activate=True)
+ txt = ba.textwidget(
+ parent=self._column,
+ size=(self._width - 80, 30),
+ text=playlist,
+ v_align='center',
+ maxwidth=self._width - 110,
+ selectable=True,
+ on_activate_call=ba.Call(self._sel, playlist),
+ click_activate=True,
+ )
ba.widget(edit=txt, show_buffer_top=40, show_buffer_bottom=40)
if playlist == self._existing_playlist:
- ba.columnwidget(edit=self._column,
- selected_child=txt,
- visible_child=txt)
+ ba.columnwidget(
+ edit=self._column, selected_child=txt, visible_child=txt
+ )
if i == len(playlists) - 1:
ba.widget(edit=txt, down_widget=txt)
diff --git a/assets/src/ba_data/python/bastd/ui/specialoffer.py b/assets/src/ba_data/python/bastd/ui/specialoffer.py
index 1935f3b3..b2f864ad 100644
--- a/assets/src/ba_data/python/bastd/ui/specialoffer.py
+++ b/assets/src/ba_data/python/bastd/ui/specialoffer.py
@@ -21,9 +21,10 @@ class SpecialOfferWindow(ba.Window):
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
- from ba.internal import (get_store_item_display_size, get_clean_price)
+ from ba.internal import get_store_item_display_size, get_clean_price
from ba import SpecialChar
from bastd.ui.store import item as storeitemui
+
self._cancel_delay = offer.get('cancelDelay', 0)
# First thing: if we're offering pro or an IAP, see if we have a
@@ -35,8 +36,9 @@ class SpecialOfferWindow(ba.Window):
# Misnomer: 'pro' actually means offer 'pro_sale'.
if offer['item'] in ['pro', 'pro_fullprice']:
- real_price = ba.internal.get_price('pro' if offer['item'] ==
- 'pro_fullprice' else 'pro_sale')
+ real_price = ba.internal.get_price(
+ 'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale'
+ )
if real_price is None and ba.app.debug_build:
print('NOTE: Faking prices for debug build.')
real_price = '$1.23'
@@ -67,19 +69,31 @@ class SpecialOfferWindow(ba.Window):
ba.internal.lock_all_input()
ba.timer(1.0, ba.internal.unlock_all_input, timetype=ba.TimeType.REAL)
ba.playsound(ba.getsound('ding'))
- ba.timer(0.3,
- lambda: ba.playsound(ba.getsound('ooh')),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.3,
+ lambda: ba.playsound(ba.getsound('ooh')),
+ timetype=ba.TimeType.REAL,
+ )
self._offer = copy.deepcopy(offer)
self._width = 580
self._height = 590
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition=transition,
- scale=(1.2 if uiscale is ba.UIScale.SMALL else
- 1.15 if uiscale is ba.UIScale.MEDIUM else 1.0),
- stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition=transition,
+ scale=(
+ 1.2
+ if uiscale is ba.UIScale.SMALL
+ else 1.15
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ stack_offset=(0, -15)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0),
+ )
+ )
self._is_bundle_sale = False
try:
if offer['item'] in ['pro', 'pro_fullprice']:
@@ -92,11 +106,14 @@ class SpecialOfferWindow(ba.Window):
percent_off_text = ''
else:
# If the offer includes bonus tickets, it's a bundle-sale.
- if ('bonusTickets' in offer
- and offer['bonusTickets'] is not None):
+ if (
+ 'bonusTickets' in offer
+ and offer['bonusTickets'] is not None
+ ):
self._is_bundle_sale = True
original_price = ba.internal.get_v1_account_misc_read_val(
- 'price.' + self._offer_item, 9999)
+ 'price.' + self._offer_item, 9999
+ )
# For pure ticket prices we can show a percent-off.
if isinstance(offer['price'], int):
@@ -105,11 +122,13 @@ class SpecialOfferWindow(ba.Window):
original_price_str = tchar + str(original_price)
new_price_str = tchar + str(new_price)
percent_off = int(
- round(100.0 -
- (float(new_price) / original_price) * 100.0))
+ round(
+ 100.0 - (float(new_price) / original_price) * 100.0
+ )
+ )
percent_off_text = ' ' + ba.Lstr(
- resource='store.salePercentText').evaluate().replace(
- '${PERCENT}', str(percent_off))
+ resource='store.salePercentText'
+ ).evaluate().replace('${PERCENT}', str(percent_off))
else:
original_price_str = new_price_str = '?'
percent_off_text = ''
@@ -122,41 +141,51 @@ class SpecialOfferWindow(ba.Window):
# If its a bundle sale, change the title.
if self._is_bundle_sale:
- sale_text = ba.Lstr(resource='store.saleBundleText',
- fallback_resource='store.saleText').evaluate()
+ sale_text = ba.Lstr(
+ resource='store.saleBundleText',
+ fallback_resource='store.saleText',
+ ).evaluate()
else:
# For full pro we say 'Upgrade?' since its not really a sale.
if offer['item'] == 'pro_fullprice':
sale_text = ba.Lstr(
resource='store.upgradeQuestionText',
- fallback_resource='store.saleExclaimText').evaluate()
+ fallback_resource='store.saleExclaimText',
+ ).evaluate()
else:
sale_text = ba.Lstr(
resource='store.saleExclaimText',
- fallback_resource='store.saleText').evaluate()
+ fallback_resource='store.saleText',
+ ).evaluate()
self._title_text = ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 40),
size=(0, 0),
- text=sale_text +
- ((' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate())
- if self._offer['oneTimeOnly'] else '') + percent_off_text,
+ text=sale_text
+ + (
+ (' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate())
+ if self._offer['oneTimeOnly']
+ else ''
+ )
+ + percent_off_text,
h_align='center',
v_align='center',
maxwidth=self._width * 0.9 - 220,
scale=1.4,
- color=(0.3, 1, 0.3))
+ color=(0.3, 1, 0.3),
+ )
self._flash_on = False
self._flashing_timer: ba.Timer | None = ba.Timer(
0.05,
ba.WeakCall(self._flash_cycle),
repeat=True,
- timetype=ba.TimeType.REAL)
- ba.timer(0.6,
- ba.WeakCall(self._stop_flashing),
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
+ ba.timer(
+ 0.6, ba.WeakCall(self._stop_flashing), timetype=ba.TimeType.REAL
+ )
size = get_store_item_display_size(self._offer_item)
display: dict[str, Any] = {}
@@ -164,56 +193,69 @@ class SpecialOfferWindow(ba.Window):
self._offer_item,
display,
parent_widget=self._root_widget,
- b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 -
- ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0),
- self._height * 0.5 - size[1] * 0.5 + 20 +
- (20 if self._is_bundle_sale else 0)),
+ b_pos=(
+ self._width * 0.5
+ - size[0] * 0.5
+ + 10
+ - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0),
+ self._height * 0.5
+ - size[1] * 0.5
+ + 20
+ + (20 if self._is_bundle_sale else 0),
+ ),
b_width=size[0],
b_height=size[1],
- button=not self._is_bundle_sale)
+ button=not self._is_bundle_sale,
+ )
# Wire up the parts we need.
if self._is_bundle_sale:
- self._plus_text = ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- self._height * 0.5 + 50),
- size=(0, 0),
- text='+',
- h_align='center',
- v_align='center',
- maxwidth=self._width * 0.9,
- scale=1.4,
- color=(0.5, 0.5, 0.5))
+ self._plus_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.5 + 50),
+ size=(0, 0),
+ text='+',
+ h_align='center',
+ v_align='center',
+ maxwidth=self._width * 0.9,
+ scale=1.4,
+ color=(0.5, 0.5, 0.5),
+ )
self._plus_tickets = ba.textwidget(
parent=self._root_widget,
position=(self._width * 0.5 + 120, self._height * 0.5 + 50),
size=(0, 0),
- text=ba.charstr(SpecialChar.TICKET_BACKING) +
- str(offer['bonusTickets']),
+ text=ba.charstr(SpecialChar.TICKET_BACKING)
+ + str(offer['bonusTickets']),
h_align='center',
v_align='center',
maxwidth=self._width * 0.9,
scale=2.5,
- color=(0.2, 1, 0.2))
- self._price_text = ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, 150),
- size=(0, 0),
- text=real_price,
- h_align='center',
- v_align='center',
- maxwidth=self._width * 0.9,
- scale=1.4,
- color=(0.2, 1, 0.2))
+ color=(0.2, 1, 0.2),
+ )
+ self._price_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, 150),
+ size=(0, 0),
+ text=real_price,
+ h_align='center',
+ v_align='center',
+ maxwidth=self._width * 0.9,
+ scale=1.4,
+ color=(0.2, 1, 0.2),
+ )
# Total-value if they supplied it.
total_worth_item = offer.get('valueItem', None)
if total_worth_item is not None:
price = ba.internal.get_price(total_worth_item)
- total_worth_price = (get_clean_price(price)
- if price is not None else None)
+ total_worth_price = (
+ get_clean_price(price) if price is not None else None
+ )
if total_worth_price is not None:
- total_worth_text = ba.Lstr(resource='store.totalWorthText',
- subs=[('${TOTAL_WORTH}',
- total_worth_price)])
+ total_worth_text = ba.Lstr(
+ resource='store.totalWorthText',
+ subs=[('${TOTAL_WORTH}', total_worth_price)],
+ )
self._total_worth_text = ba.textwidget(
parent=self._root_widget,
text=total_worth_text,
@@ -225,22 +267,27 @@ class SpecialOfferWindow(ba.Window):
v_align='center',
shadow=1.0,
flatness=1.0,
- color=(0.3, 1, 1))
+ color=(0.3, 1, 1),
+ )
elif offer['item'] == 'pro_fullprice':
# for full-price pro we simply show full price
ba.textwidget(edit=display['price_widget'], text=real_price)
- ba.buttonwidget(edit=display['button'],
- on_activate_call=self._purchase)
+ ba.buttonwidget(
+ edit=display['button'], on_activate_call=self._purchase
+ )
else:
# Show old/new prices otherwise (for pro sale).
- ba.buttonwidget(edit=display['button'],
- on_activate_call=self._purchase)
+ ba.buttonwidget(
+ edit=display['button'], on_activate_call=self._purchase
+ )
ba.imagewidget(edit=display['price_slash_widget'], opacity=1.0)
- ba.textwidget(edit=display['price_widget_left'],
- text=original_price_str)
- ba.textwidget(edit=display['price_widget_right'],
- text=new_price_str)
+ ba.textwidget(
+ edit=display['price_widget_left'], text=original_price_str
+ )
+ ba.textwidget(
+ edit=display['price_widget_right'], text=new_price_str
+ )
# Add ticket button only if this is ticket-purchasable.
if isinstance(offer.get('price'), int):
@@ -254,41 +301,49 @@ class SpecialOfferWindow(ba.Window):
textcolor=(0.2, 1, 0.2),
autoselect=True,
label=ba.Lstr(resource='getTicketsWindow.titleText'),
- on_activate_call=self._on_get_more_tickets_press)
+ on_activate_call=self._on_get_more_tickets_press,
+ )
self._ticket_text_update_timer = ba.Timer(
1.0,
ba.WeakCall(self._update_tickets_text),
timetype=ba.TimeType.REAL,
- repeat=True)
+ repeat=True,
+ )
self._update_tickets_text()
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._cancel_button = ba.buttonwidget(
parent=self._root_widget,
- position=(50, 40) if self._is_bundle_sale else
- (self._width * 0.5 - 75, 40),
+ position=(50, 40)
+ if self._is_bundle_sale
+ else (self._width * 0.5 - 75, 40),
size=(150, 60),
scale=1.0,
on_activate_call=self._cancel,
autoselect=True,
- label=ba.Lstr(resource='noThanksText'))
+ label=ba.Lstr(resource='noThanksText'),
+ )
self._cancel_countdown_text = ba.textwidget(
parent=self._root_widget,
text='',
- position=(50 + 150 + 20, 40 + 27) if self._is_bundle_sale else
- (self._width * 0.5 - 75 + 150 + 20, 40 + 27),
+ position=(50 + 150 + 20, 40 + 27)
+ if self._is_bundle_sale
+ else (self._width * 0.5 - 75 + 150 + 20, 40 + 27),
scale=1.1,
size=(0, 0),
h_align='left',
v_align='center',
shadow=1.0,
flatness=1.0,
- color=(0.6, 0.5, 0.5))
+ color=(0.6, 0.5, 0.5),
+ )
self._update_cancel_button_graphics()
if self._is_bundle_sale:
@@ -299,14 +354,19 @@ class SpecialOfferWindow(ba.Window):
scale=1.0,
on_activate_call=self._purchase,
autoselect=True,
- label=ba.Lstr(resource='store.purchaseText'))
+ label=ba.Lstr(resource='store.purchaseText'),
+ )
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button,
- start_button=self._purchase_button
- if self._is_bundle_sale else None,
- selected_child=self._purchase_button
- if self._is_bundle_sale else display['button'])
+ ba.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._cancel_button,
+ start_button=self._purchase_button
+ if self._is_bundle_sale
+ else None,
+ selected_child=self._purchase_button
+ if self._is_bundle_sale
+ else display['button'],
+ )
def _stop_flashing(self) -> None:
self._flashing_timer = None
@@ -316,19 +376,25 @@ class SpecialOfferWindow(ba.Window):
if not self._root_widget:
return
self._flash_on = not self._flash_on
- ba.textwidget(edit=self._title_text,
- color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0))
+ ba.textwidget(
+ edit=self._title_text,
+ color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0),
+ )
def _update_cancel_button_graphics(self) -> None:
- ba.buttonwidget(edit=self._cancel_button,
- color=(0.5, 0.5, 0.5) if self._cancel_delay > 0 else
- (0.7, 0.4, 0.34),
- textcolor=(0.5, 0.5,
- 0.5) if self._cancel_delay > 0 else
- (0.9, 0.9, 1.0))
+ ba.buttonwidget(
+ edit=self._cancel_button,
+ color=(0.5, 0.5, 0.5)
+ if self._cancel_delay > 0
+ else (0.7, 0.4, 0.34),
+ textcolor=(0.5, 0.5, 0.5)
+ if self._cancel_delay > 0
+ else (0.9, 0.9, 1.0),
+ )
ba.textwidget(
edit=self._cancel_countdown_text,
- text=str(self._cancel_delay) if self._cancel_delay > 0 else '')
+ text=str(self._cancel_delay) if self._cancel_delay > 0 else '',
+ )
def _update(self) -> None:
@@ -361,12 +427,14 @@ class SpecialOfferWindow(ba.Window):
def _update_tickets_text(self) -> None:
from ba import SpecialChar
+
if not self._root_widget:
return
sval: str | ba.Lstr
if ba.internal.get_v1_account_state() == 'signed_in':
- sval = (ba.charstr(SpecialChar.TICKET) +
- str(ba.internal.get_v1_account_ticket_count()))
+ sval = ba.charstr(SpecialChar.TICKET) + str(
+ ba.internal.get_v1_account_ticket_count()
+ )
else:
sval = ba.Lstr(resource='getTicketsWindow.titleText')
ba.buttonwidget(edit=self._get_tickets_button, label=sval)
@@ -374,6 +442,7 @@ class SpecialOfferWindow(ba.Window):
def _on_get_more_tickets_press(self) -> None:
from bastd.ui import account
from bastd.ui import getcurrency
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
@@ -383,6 +452,7 @@ class SpecialOfferWindow(ba.Window):
from ba.internal import get_store_item_name_translated
from bastd.ui import getcurrency
from bastd.ui import confirm
+
if self._offer['item'] == 'pro':
ba.internal.purchase('pro_sale')
elif self._offer['item'] == 'pro_fullprice':
@@ -396,27 +466,34 @@ class SpecialOfferWindow(ba.Window):
ticket_count = ba.internal.get_v1_account_ticket_count()
except Exception:
ticket_count = None
- if (ticket_count is not None
- and ticket_count < self._offer['price']):
+ if ticket_count is not None and ticket_count < self._offer['price']:
getcurrency.show_get_tickets_prompt()
ba.playsound(ba.getsound('error'))
return
def do_it() -> None:
- ba.internal.in_game_purchase('offer:' + str(self._offer['id']),
- self._offer['price'])
+ ba.internal.in_game_purchase(
+ 'offer:' + str(self._offer['id']), self._offer['price']
+ )
ba.playsound(ba.getsound('swish'))
- confirm.ConfirmWindow(ba.Lstr(
- resource='store.purchaseConfirmText',
- subs=[('${ITEM}',
- get_store_item_name_translated(self._offer['item']))]),
- width=400,
- height=120,
- action=do_it,
- ok_text=ba.Lstr(
- resource='store.purchaseText',
- fallback_resource='okText'))
+ confirm.ConfirmWindow(
+ ba.Lstr(
+ resource='store.purchaseConfirmText',
+ subs=[
+ (
+ '${ITEM}',
+ get_store_item_name_translated(self._offer['item']),
+ )
+ ],
+ ),
+ width=400,
+ height=120,
+ action=do_it,
+ ok_text=ba.Lstr(
+ resource='store.purchaseText', fallback_resource='okText'
+ ),
+ )
def _cancel(self) -> None:
if self._cancel_delay > 0:
@@ -429,14 +506,15 @@ def show_offer() -> bool:
"""(internal)"""
try:
from bastd.ui import feedback
+
app = ba.app
# Space things out a bit so we don't hit the poor user with an ad and
# then an in-game offer.
has_been_long_enough_since_ad = True
- if (app.ads.last_ad_completion_time is not None and
- (ba.time(ba.TimeType.REAL) - app.ads.last_ad_completion_time <
- 30.0)):
+ if app.ads.last_ad_completion_time is not None and (
+ ba.time(ba.TimeType.REAL) - app.ads.last_ad_completion_time < 30.0
+ ):
has_been_long_enough_since_ad = False
if app.special_offer is not None and has_been_long_enough_since_ad:
@@ -447,7 +525,7 @@ def show_offer() -> bool:
cfg = app.config
cfg['pendingSpecialOffer'] = {
'a': ba.internal.get_public_login_id(),
- 'o': app.special_offer
+ 'o': app.special_offer,
}
cfg.commit()
diff --git a/assets/src/ba_data/python/bastd/ui/store/browser.py b/assets/src/ba_data/python/bastd/ui/store/browser.py
index e2471b79..49a301c2 100644
--- a/assets/src/ba_data/python/bastd/ui/store/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/store/browser.py
@@ -22,19 +22,22 @@ class StoreBrowserWindow(ba.Window):
class TabID(Enum):
"""Our available tab types."""
+
EXTRAS = 'extras'
MAPS = 'maps'
MINIGAMES = 'minigames'
CHARACTERS = 'characters'
ICONS = 'icons'
- def __init__(self,
- transition: str = 'in_right',
- modal: bool = False,
- show_tab: StoreBrowserWindow.TabID | None = None,
- on_close_call: Callable[[], Any] | None = None,
- back_location: str | None = None,
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ modal: bool = False,
+ show_tab: StoreBrowserWindow.TabID | None = None,
+ on_close_call: Callable[[], Any] | None = None,
+ back_location: str | None = None,
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from bastd.ui.tabs import TabRow
@@ -66,8 +69,13 @@ class StoreBrowserWindow(ba.Window):
self._modal = modal
self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (578 if uiscale is ba.UIScale.SMALL else
- 645 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 578
+ if uiscale is ba.UIScale.SMALL
+ else 645
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
self._current_tab: StoreBrowserWindow.TabID | None = None
extra_top = 30 if uiscale is ba.UIScale.SMALL else 0
@@ -75,15 +83,28 @@ class StoreBrowserWindow(ba.Window):
self._r = 'store'
self._last_buy_time: float | None = None
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_full',
- scale=(1.3 if uiscale is ba.UIScale.SMALL else
- 0.9 if uiscale is ba.UIScale.MEDIUM else 0.8),
- scale_origin_stack_offset=scale_origin,
- stack_offset=((0, -5) if uiscale is ba.UIScale.SMALL else (
- 0, 0) if uiscale is ba.UIScale.MEDIUM else (0, 0))))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + extra_top),
+ transition=transition,
+ toolbar_visibility='menu_full',
+ scale=(
+ 1.3
+ if uiscale is ba.UIScale.SMALL
+ else 0.9
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ scale_origin_stack_offset=scale_origin,
+ stack_offset=(
+ (0, -5)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 0)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0)
+ ),
+ )
+ )
self._back_button = btn = ba.buttonwidget(
parent=self._root_widget,
@@ -93,7 +114,8 @@ class StoreBrowserWindow(ba.Window):
autoselect=True,
label=ba.Lstr(resource='doneText' if self._modal else 'backText'),
button_type=None if self._modal else 'back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
self._ticket_count_text: ba.Widget | None = None
@@ -110,13 +132,16 @@ class StoreBrowserWindow(ba.Window):
left_widget=self._back_button,
color=(0.7, 0.5, 0.85),
textcolor=(0.2, 1.0, 0.2),
- label=ba.Lstr(resource='getTicketsWindow.titleText'))
+ label=ba.Lstr(resource='getTicketsWindow.titleText'),
+ )
else:
- self._ticket_count_text = ba.textwidget(parent=self._root_widget,
- size=(210, 64),
- color=(0.2, 1.0, 0.2),
- h_align='center',
- v_align='center')
+ self._ticket_count_text = ba.textwidget(
+ parent=self._root_widget,
+ size=(210, 64),
+ color=(0.2, 1.0, 0.2),
+ h_align='center',
+ v_align='center',
+ )
# Move this dynamically to keep it out of the way of the party icon.
self._update_get_tickets_button_pos()
@@ -124,15 +149,18 @@ class StoreBrowserWindow(ba.Window):
1.0,
ba.WeakCall(self._update_get_tickets_button_pos),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
if self._get_tickets_button:
- ba.widget(edit=self._back_button,
- right_widget=self._get_tickets_button)
+ ba.widget(
+ edit=self._back_button, right_widget=self._get_tickets_button
+ )
self._ticket_text_update_timer = ba.Timer(
1.0,
ba.WeakCall(self._update_tickets_text),
timetype=ba.TimeType.REAL,
- repeat=True)
+ repeat=True,
+ )
self._update_tickets_text()
app = ba.app
@@ -146,24 +174,28 @@ class StoreBrowserWindow(ba.Window):
color=(0.35, 0.3, 0.4),
selectable=False,
textcolor=(0.55, 0.5, 0.6),
- label=ba.Lstr(
- resource='getTicketsWindow.restorePurchasesText'))
+ label=ba.Lstr(resource='getTicketsWindow.restorePurchasesText'),
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 44),
- size=(0, 0),
- color=app.ui.title_color,
- scale=1.5,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='storeText'),
- maxwidth=420)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 44),
+ size=(0, 0),
+ color=app.ui.title_color,
+ scale=1.5,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='storeText'),
+ maxwidth=420,
+ )
if not self._modal:
- ba.buttonwidget(edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(SpecialChar.BACK),
+ )
scroll_buffer_h = 130 + 2 * x_inset
tab_buffer_h = 250 + 2 * x_inset
@@ -171,21 +203,28 @@ class StoreBrowserWindow(ba.Window):
tabs_def = [
(self.TabID.EXTRAS, ba.Lstr(resource=self._r + '.extrasText')),
(self.TabID.MAPS, ba.Lstr(resource=self._r + '.mapsText')),
- (self.TabID.MINIGAMES,
- ba.Lstr(resource=self._r + '.miniGamesText')),
- (self.TabID.CHARACTERS,
- ba.Lstr(resource=self._r + '.charactersText')),
+ (
+ self.TabID.MINIGAMES,
+ ba.Lstr(resource=self._r + '.miniGamesText'),
+ ),
+ (
+ self.TabID.CHARACTERS,
+ ba.Lstr(resource=self._r + '.charactersText'),
+ ),
(self.TabID.ICONS, ba.Lstr(resource=self._r + '.iconsText')),
]
- self._tab_row = TabRow(self._root_widget,
- tabs_def,
- pos=(tab_buffer_h * 0.5, self._height - 130),
- size=(self._width - tab_buffer_h, 50),
- on_select_call=self._set_tab)
+ self._tab_row = TabRow(
+ self._root_widget,
+ tabs_def,
+ pos=(tab_buffer_h * 0.5, self._height - 130),
+ size=(self._width - tab_buffer_h, 50),
+ on_select_call=self._set_tab,
+ )
- self._purchasable_count_widgets: dict[StoreBrowserWindow.TabID,
- dict[str, Any]] = {}
+ self._purchasable_count_widgets: dict[
+ StoreBrowserWindow.TabID, dict[str, Any]
+ ] = {}
# Create our purchasable-items tags and have them update over time.
for tab_id, tab in self._tab_row.tabs.items():
@@ -194,73 +233,84 @@ class StoreBrowserWindow(ba.Window):
button = tab.button
rad = 10
center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1])
- img = ba.imagewidget(parent=self._root_widget,
- position=(center[0] - rad * 1.04,
- center[1] - rad * 1.15),
- size=(rad * 2.2, rad * 2.2),
- texture=ba.gettexture('circleShadow'),
- color=(1, 0, 0))
- txt = ba.textwidget(parent=self._root_widget,
- position=center,
- size=(0, 0),
- h_align='center',
- v_align='center',
- maxwidth=1.4 * rad,
- scale=0.6,
- shadow=1.0,
- flatness=1.0)
+ img = ba.imagewidget(
+ parent=self._root_widget,
+ position=(center[0] - rad * 1.04, center[1] - rad * 1.15),
+ size=(rad * 2.2, rad * 2.2),
+ texture=ba.gettexture('circleShadow'),
+ color=(1, 0, 0),
+ )
+ txt = ba.textwidget(
+ parent=self._root_widget,
+ position=center,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=1.4 * rad,
+ scale=0.6,
+ shadow=1.0,
+ flatness=1.0,
+ )
rad = 20
- sale_img = ba.imagewidget(parent=self._root_widget,
- position=(center[0] - rad,
- center[1] - rad),
- size=(rad * 2, rad * 2),
- draw_controller=button,
- texture=ba.gettexture('circleZigZag'),
- color=(0.5, 0, 1.0))
- sale_title_text = ba.textwidget(parent=self._root_widget,
- position=(center[0],
- center[1] + 0.24 * rad),
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=button,
- maxwidth=1.4 * rad,
- scale=0.6,
- shadow=0.0,
- flatness=1.0,
- color=(0, 1, 0))
- sale_time_text = ba.textwidget(parent=self._root_widget,
- position=(center[0],
- center[1] - 0.29 * rad),
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=button,
- maxwidth=1.4 * rad,
- scale=0.4,
- shadow=0.0,
- flatness=1.0,
- color=(0, 1, 0))
+ sale_img = ba.imagewidget(
+ parent=self._root_widget,
+ position=(center[0] - rad, center[1] - rad),
+ size=(rad * 2, rad * 2),
+ draw_controller=button,
+ texture=ba.gettexture('circleZigZag'),
+ color=(0.5, 0, 1.0),
+ )
+ sale_title_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(center[0], center[1] + 0.24 * rad),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=button,
+ maxwidth=1.4 * rad,
+ scale=0.6,
+ shadow=0.0,
+ flatness=1.0,
+ color=(0, 1, 0),
+ )
+ sale_time_text = ba.textwidget(
+ parent=self._root_widget,
+ position=(center[0], center[1] - 0.29 * rad),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=button,
+ maxwidth=1.4 * rad,
+ scale=0.4,
+ shadow=0.0,
+ flatness=1.0,
+ color=(0, 1, 0),
+ )
self._purchasable_count_widgets[tab_id] = {
'img': img,
'text': txt,
'sale_img': sale_img,
'sale_title_text': sale_title_text,
- 'sale_time_text': sale_time_text
+ 'sale_time_text': sale_time_text,
}
- self._tab_update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update_tabs),
- timetype=ba.TimeType.REAL,
- repeat=True)
+ self._tab_update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update_tabs),
+ timetype=ba.TimeType.REAL,
+ repeat=True,
+ )
self._update_tabs()
if self._get_tickets_button:
last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button
- ba.widget(edit=self._get_tickets_button,
- down_widget=last_tab_button)
- ba.widget(edit=last_tab_button,
- up_widget=self._get_tickets_button,
- right_widget=self._get_tickets_button)
+ ba.widget(
+ edit=self._get_tickets_button, down_widget=last_tab_button
+ )
+ ba.widget(
+ edit=last_tab_button,
+ up_widget=self._get_tickets_button,
+ right_widget=self._get_tickets_button,
+ )
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180
@@ -271,11 +321,20 @@ class StoreBrowserWindow(ba.Window):
def _update_get_tickets_button_pos(self) -> None:
uiscale = ba.app.ui.uiscale
- pos = (self._width - 252 -
- (self._x_inset +
- (47 if uiscale is ba.UIScale.SMALL
- and ba.internal.is_party_icon_visible() else 0)),
- self._height - 70)
+ pos = (
+ self._width
+ - 252
+ - (
+ self._x_inset
+ + (
+ 47
+ if uiscale is ba.UIScale.SMALL
+ and ba.internal.is_party_icon_visible()
+ else 0
+ )
+ ),
+ self._height - 70,
+ )
if self._get_tickets_button:
ba.buttonwidget(edit=self._get_tickets_button, position=pos)
if self._ticket_count_text:
@@ -283,27 +342,36 @@ class StoreBrowserWindow(ba.Window):
def _restore_purchases(self) -> None:
from bastd.ui import account
+
if ba.internal.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
else:
ba.internal.restore_purchases()
def _update_tabs(self) -> None:
- from ba.internal import (get_available_sale_time,
- get_available_purchase_count)
+ from ba.internal import (
+ get_available_sale_time,
+ get_available_purchase_count,
+ )
+
if not self._root_widget:
return
for tab_id, tab_data in list(self._purchasable_count_widgets.items()):
sale_time = get_available_sale_time(tab_id.value)
if sale_time is not None:
- ba.textwidget(edit=tab_data['sale_title_text'],
- text=ba.Lstr(resource='store.saleText'))
- ba.textwidget(edit=tab_data['sale_time_text'],
- text=ba.timestring(
- sale_time,
- centi=False,
- timeformat=ba.TimeFormat.MILLISECONDS))
+ ba.textwidget(
+ edit=tab_data['sale_title_text'],
+ text=ba.Lstr(resource='store.saleText'),
+ )
+ ba.textwidget(
+ edit=tab_data['sale_time_text'],
+ text=ba.timestring(
+ sale_time,
+ centi=False,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ ),
+ )
ba.imagewidget(edit=tab_data['sale_img'], opacity=1.0)
count = 0
else:
@@ -321,12 +389,14 @@ class StoreBrowserWindow(ba.Window):
def _update_tickets_text(self) -> None:
from ba import SpecialChar
+
if not self._root_widget:
return
sval: str | ba.Lstr
if ba.internal.get_v1_account_state() == 'signed_in':
sval = ba.charstr(SpecialChar.TICKET) + str(
- ba.internal.get_v1_account_ticket_count())
+ ba.internal.get_v1_account_ticket_count()
+ )
else:
sval = ba.Lstr(resource='getTicketsWindow.titleText')
if self._get_tickets_button:
@@ -354,12 +424,15 @@ class StoreBrowserWindow(ba.Window):
self._scrollwidget = ba.scrollwidget(
parent=self._root_widget,
highlight=False,
- position=((self._width - self._scroll_width) * 0.5,
- self._height - self._scroll_height - 79 - 48),
+ position=(
+ (self._width - self._scroll_width) * 0.5,
+ self._height - self._scroll_height - 79 - 48,
+ ),
size=(self._scroll_width, self._scroll_height),
claims_left_right=True,
claims_tab=True,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
# NOTE: this stuff is modified by the _Store class.
# Should maybe clean that up.
@@ -377,16 +450,18 @@ class StoreBrowserWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource=self._r + '.loadingText'),
- maxwidth=self._scroll_width * 0.9)
+ maxwidth=self._scroll_width * 0.9,
+ )
class _Request:
-
def __init__(self, window: StoreBrowserWindow):
self._window = weakref.ref(window)
data = {'tab': tab_id.value}
- ba.timer(0.1,
- ba.WeakCall(self._on_response, data),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1,
+ ba.WeakCall(self._on_response, data),
+ timetype=ba.TimeType.REAL,
+ )
def _on_response(self, data: dict[str, Any] | None) -> None:
# FIXME: clean this up.
@@ -401,22 +476,32 @@ class StoreBrowserWindow(ba.Window):
self._request = _Request(self)
# Actually start the purchase locally.
- def _purchase_check_result(self, item: str, is_ticket_purchase: bool,
- result: dict[str, Any] | None) -> None:
+ def _purchase_check_result(
+ self, item: str, is_ticket_purchase: bool, result: dict[str, Any] | None
+ ) -> None:
if result is None:
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
else:
if is_ticket_purchase:
if result['allow']:
price = ba.internal.get_v1_account_misc_read_val(
- 'price.' + item, None)
- if (price is None or not isinstance(price, int)
- or price <= 0):
- print('Error; got invalid local price of', price,
- 'for item', item)
+ 'price.' + item, None
+ )
+ if (
+ price is None
+ or not isinstance(price, int)
+ or price <= 0
+ ):
+ print(
+ 'Error; got invalid local price of',
+ price,
+ 'for item',
+ item,
+ )
ba.playsound(ba.getsound('error'))
else:
ba.playsound(ba.getsound('click01'))
@@ -424,14 +509,20 @@ class StoreBrowserWindow(ba.Window):
else:
if result['reason'] == 'versionTooOld':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.versionTooOldText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='getTicketsWindow.versionTooOldText'
+ ),
+ color=(1, 0, 0),
+ )
else:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.unavailableText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='getTicketsWindow.unavailableText'
+ ),
+ color=(1, 0, 0),
+ )
# Real in-app purchase.
else:
if result['allow']:
@@ -439,18 +530,24 @@ class StoreBrowserWindow(ba.Window):
else:
if result['reason'] == 'versionTooOld':
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.versionTooOldText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='getTicketsWindow.versionTooOldText'
+ ),
+ color=(1, 0, 0),
+ )
else:
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(
- resource='getTicketsWindow.unavailableText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource='getTicketsWindow.unavailableText'
+ ),
+ color=(1, 0, 0),
+ )
- def _do_purchase_check(self,
- item: str,
- is_ticket_purchase: bool = False) -> None:
+ def _do_purchase_check(
+ self, item: str, is_ticket_purchase: bool = False
+ ) -> None:
from ba.internal import master_server_get
# Here we ping the server to ask if it's valid for us to
@@ -465,16 +562,19 @@ class StoreBrowserWindow(ba.Window):
'subplatform': app.subplatform,
'version': app.version,
'buildNumber': app.build_number,
- 'purchaseType': 'ticket' if is_ticket_purchase else 'real'
+ 'purchaseType': 'ticket' if is_ticket_purchase else 'real',
},
- callback=ba.WeakCall(self._purchase_check_result, item,
- is_ticket_purchase),
+ callback=ba.WeakCall(
+ self._purchase_check_result, item, is_ticket_purchase
+ ),
)
def buy(self, item: str) -> None:
"""Attempt to purchase the provided item."""
- from ba.internal import (get_available_sale_time,
- get_store_item_name_translated)
+ from ba.internal import (
+ get_available_sale_time,
+ get_store_item_name_translated,
+ )
from bastd.ui import account
from bastd.ui.confirm import ConfirmWindow
from bastd.ui import getcurrency
@@ -482,8 +582,10 @@ class StoreBrowserWindow(ba.Window):
# Prevent pressing buy within a few seconds of the last press
# (gives the buttons time to disable themselves and whatnot).
curtime = ba.time(ba.TimeType.REAL)
- if self._last_buy_time is not None and (curtime -
- self._last_buy_time) < 2.0:
+ if (
+ self._last_buy_time is not None
+ and (curtime - self._last_buy_time) < 2.0
+ ):
ba.playsound(ba.getsound('error'))
else:
if ba.internal.get_v1_account_state() != 'signed_in':
@@ -497,11 +599,15 @@ class StoreBrowserWindow(ba.Window):
# Purchase either pro or pro_sale depending on whether
# there is a sale going on.
- self._do_purchase_check('pro' if get_available_sale_time(
- 'extras') is None else 'pro_sale')
+ self._do_purchase_check(
+ 'pro'
+ if get_available_sale_time('extras') is None
+ else 'pro_sale'
+ )
else:
price = ba.internal.get_v1_account_misc_read_val(
- 'price.' + item, None)
+ 'price.' + item, None
+ )
our_tickets = ba.internal.get_v1_account_ticket_count()
if price is not None and our_tickets < price:
ba.playsound(ba.getsound('error'))
@@ -509,26 +615,38 @@ class StoreBrowserWindow(ba.Window):
else:
def do_it() -> None:
- self._do_purchase_check(item,
- is_ticket_purchase=True)
+ self._do_purchase_check(
+ item, is_ticket_purchase=True
+ )
ba.playsound(ba.getsound('swish'))
ConfirmWindow(
- ba.Lstr(resource='store.purchaseConfirmText',
- subs=[
- ('${ITEM}',
- get_store_item_name_translated(item))
- ]),
+ ba.Lstr(
+ resource='store.purchaseConfirmText',
+ subs=[
+ (
+ '${ITEM}',
+ get_store_item_name_translated(item),
+ )
+ ],
+ ),
width=400,
height=120,
action=do_it,
- ok_text=ba.Lstr(resource='store.purchaseText',
- fallback_resource='okText'))
+ ok_text=ba.Lstr(
+ resource='store.purchaseText',
+ fallback_resource='okText',
+ ),
+ )
def _print_already_own(self, charname: str) -> None:
- ba.screenmessage(ba.Lstr(resource=self._r + '.alreadyOwnText',
- subs=[('${NAME}', charname)]),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource=self._r + '.alreadyOwnText',
+ subs=[('${NAME}', charname)],
+ ),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
def update_buttons(self) -> None:
@@ -538,20 +656,24 @@ class StoreBrowserWindow(ba.Window):
# pylint: disable=too-many-locals
from ba.internal import get_available_sale_time
from ba import SpecialChar
+
if not self._root_widget:
return
import datetime
+
sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {})
sales = {}
try:
# Look at the current set of sales; filter any with time remaining.
for sale_item, sale_info in list(sales_raw.items()):
- to_end = (datetime.datetime.utcfromtimestamp(sale_info['e']) -
- datetime.datetime.utcnow()).total_seconds()
+ to_end = (
+ datetime.datetime.utcfromtimestamp(sale_info['e'])
+ - datetime.datetime.utcnow()
+ ).total_seconds()
if to_end > 0:
sales[sale_item] = {
'to_end': to_end,
- 'original_price': sale_info['op']
+ 'original_price': sale_info['op'],
}
except Exception:
ba.print_exception('Error parsing sales.')
@@ -589,18 +711,21 @@ class StoreBrowserWindow(ba.Window):
sale_time = get_available_sale_time('extras')
if sale_time is not None:
priceraw = ba.internal.get_price('pro')
- price_text_left = (priceraw
- if priceraw is not None else '?')
+ price_text_left = (
+ priceraw if priceraw is not None else '?'
+ )
priceraw = ba.internal.get_price('pro_sale')
- price_text_right = (priceraw
- if priceraw is not None else '?')
+ price_text_right = (
+ priceraw if priceraw is not None else '?'
+ )
sale_opacity = 1.0
price_text = ''
sale_title_text = ba.Lstr(resource='store.saleText')
sale_time_text = ba.timestring(
sale_time,
centi=False,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
else:
priceraw = ba.internal.get_price('pro')
price_text = priceraw if priceraw is not None else '?'
@@ -608,7 +733,8 @@ class StoreBrowserWindow(ba.Window):
price_text_right = ''
else:
price = ba.internal.get_v1_account_misc_read_val(
- 'price.' + b_type, 0)
+ 'price.' + b_type, 0
+ )
# Color the button differently if we cant afford this.
if ba.internal.get_v1_account_state() == 'signed_in':
@@ -616,7 +742,9 @@ class StoreBrowserWindow(ba.Window):
color = (0.6, 0.61, 0.6)
price_text = ba.charstr(ba.SpecialChar.TICKET) + str(
ba.internal.get_v1_account_misc_read_val(
- 'price.' + b_type, '?'))
+ 'price.' + b_type, '?'
+ )
+ )
price_text_left = ''
price_text_right = ''
@@ -624,14 +752,16 @@ class StoreBrowserWindow(ba.Window):
if b_type in sales:
sale_opacity = 1.0
price_text_left = ba.charstr(SpecialChar.TICKET) + str(
- sales[b_type]['original_price'])
+ sales[b_type]['original_price']
+ )
price_text_right = price_text
price_text = ''
sale_title_text = ba.Lstr(resource='store.saleText')
sale_time_text = ba.timestring(
int(sales[b_type]['to_end'] * 1000),
centi=False,
- timeformat=ba.TimeFormat.MILLISECONDS)
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
description_color = (0.5, 1.0, 0.5)
description_color2 = (0.3, 1.0, 1.0)
@@ -641,39 +771,49 @@ class StoreBrowserWindow(ba.Window):
if 'title_text' in b_info:
ba.textwidget(edit=b_info['title_text'], color=title_color)
if 'purchase_check' in b_info:
- ba.imagewidget(edit=b_info['purchase_check'],
- opacity=1.0 if show_purchase_check else 0.0)
+ ba.imagewidget(
+ edit=b_info['purchase_check'],
+ opacity=1.0 if show_purchase_check else 0.0,
+ )
if 'price_widget' in b_info:
- ba.textwidget(edit=b_info['price_widget'],
- text=price_text,
- color=price_color)
+ ba.textwidget(
+ edit=b_info['price_widget'],
+ text=price_text,
+ color=price_color,
+ )
if 'price_widget_left' in b_info:
- ba.textwidget(edit=b_info['price_widget_left'],
- text=price_text_left)
+ ba.textwidget(
+ edit=b_info['price_widget_left'], text=price_text_left
+ )
if 'price_widget_right' in b_info:
- ba.textwidget(edit=b_info['price_widget_right'],
- text=price_text_right)
+ ba.textwidget(
+ edit=b_info['price_widget_right'], text=price_text_right
+ )
if 'price_slash_widget' in b_info:
- ba.imagewidget(edit=b_info['price_slash_widget'],
- opacity=sale_opacity)
+ ba.imagewidget(
+ edit=b_info['price_slash_widget'], opacity=sale_opacity
+ )
if 'sale_bg_widget' in b_info:
- ba.imagewidget(edit=b_info['sale_bg_widget'],
- opacity=sale_opacity)
+ ba.imagewidget(
+ edit=b_info['sale_bg_widget'], opacity=sale_opacity
+ )
if 'sale_title_widget' in b_info:
- ba.textwidget(edit=b_info['sale_title_widget'],
- text=sale_title_text)
+ ba.textwidget(
+ edit=b_info['sale_title_widget'], text=sale_title_text
+ )
if 'sale_time_widget' in b_info:
- ba.textwidget(edit=b_info['sale_time_widget'],
- text=sale_time_text)
+ ba.textwidget(
+ edit=b_info['sale_time_widget'], text=sale_time_text
+ )
if 'button' in b_info:
- ba.buttonwidget(edit=b_info['button'],
- color=color,
- on_activate_call=call)
+ ba.buttonwidget(
+ edit=b_info['button'], color=color, on_activate_call=call
+ )
if 'extra_backings' in b_info:
for bck in b_info['extra_backings']:
- ba.imagewidget(edit=bck,
- color=color,
- opacity=extra_image_opacity)
+ ba.imagewidget(
+ edit=bck, color=color, opacity=extra_image_opacity
+ )
if 'extra_images' in b_info:
for img in b_info['extra_images']:
ba.imagewidget(edit=img, opacity=extra_image_opacity)
@@ -684,8 +824,9 @@ class StoreBrowserWindow(ba.Window):
for etxt in b_info['extra_texts_2']:
ba.textwidget(edit=etxt, color=description_color2)
if 'descriptionText' in b_info:
- ba.textwidget(edit=b_info['descriptionText'],
- color=description_color)
+ ba.textwidget(
+ edit=b_info['descriptionText'], color=description_color
+ )
def _on_response(self, data: dict[str, Any] | None) -> None:
# pylint: disable=too-many-statements
@@ -706,15 +847,22 @@ class StoreBrowserWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource=self._r + '.loadErrorText'),
- maxwidth=self._scroll_width * 0.9)
+ maxwidth=self._scroll_width * 0.9,
+ )
else:
class _Store:
+ def __init__(
+ self,
+ store_window: StoreBrowserWindow,
+ sdata: dict[str, Any],
+ width: float,
+ ):
+ from ba.internal import (
+ get_store_item_display_size,
+ get_store_layout,
+ )
- def __init__(self, store_window: StoreBrowserWindow,
- sdata: dict[str, Any], width: float):
- from ba.internal import (get_store_item_display_size,
- get_store_layout)
self._store_window = store_window
self._width = width
store_data = get_store_layout()
@@ -737,25 +885,40 @@ class StoreBrowserWindow(ba.Window):
else:
dummy_name = ''
section['button_size'] = get_store_item_display_size(
- dummy_name)
- section['v_spacing'] = (-17 if self._tab
- == 'characters' else 0)
+ dummy_name
+ )
+ section['v_spacing'] = (
+ -17 if self._tab == 'characters' else 0
+ )
if 'title' not in section:
section['title'] = ''
- section['x_offs'] = (130 if self._tab == 'extras' else
- 270 if self._tab == 'maps' else 0)
+ section['x_offs'] = (
+ 130
+ if self._tab == 'extras'
+ else 270
+ if self._tab == 'maps'
+ else 0
+ )
section['y_offs'] = (
- 55 if (self._tab == 'extras'
- and uiscale is ba.UIScale.SMALL) else
- -20 if self._tab == 'icons' else 0)
+ 55
+ if (
+ self._tab == 'extras'
+ and uiscale is ba.UIScale.SMALL
+ )
+ else -20
+ if self._tab == 'icons'
+ else 0
+ )
- def instantiate(self, scrollwidget: ba.Widget,
- tab_button: ba.Widget) -> None:
+ def instantiate(
+ self, scrollwidget: ba.Widget, tab_button: ba.Widget
+ ) -> None:
"""Create the store."""
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-nested-blocks
from bastd.ui.store import item as storeitemui
+
title_spacing = 40
button_border = 20
button_spacing = 4
@@ -769,93 +932,121 @@ class StoreBrowserWindow(ba.Window):
self._height += title_spacing
b_width, b_height = section['button_size']
b_column_count = int(
- math.floor((self._width - boffs_h - 20) /
- (b_width + button_spacing)))
+ math.floor(
+ (self._width - boffs_h - 20)
+ / (b_width + button_spacing)
+ )
+ )
b_row_count = int(
math.ceil(
- float(len(section['items'])) / b_column_count))
+ float(len(section['items'])) / b_column_count
+ )
+ )
b_height_total = (
- 2 * button_border + b_row_count * b_height +
- (b_row_count - 1) * section['v_spacing'])
+ 2 * button_border
+ + b_row_count * b_height
+ + (b_row_count - 1) * section['v_spacing']
+ )
self._height += b_height_total
assert self._height is not None
- cnt2 = ba.containerwidget(parent=scrollwidget,
- scale=1.0,
- size=(self._width, self._height),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ cnt2 = ba.containerwidget(
+ parent=scrollwidget,
+ scale=1.0,
+ size=(self._width, self._height),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
v = self._height - 20
if self._tab == 'characters':
txt = ba.Lstr(
resource='store.howToSwitchCharactersText',
subs=[
- ('${SETTINGS}',
- ba.Lstr(
- resource='accountSettingsWindow.titleText'
- )),
- ('${PLAYER_PROFILES}',
- ba.Lstr(
- resource='playerProfilesWindow.titleText')
- )
- ])
- ba.textwidget(parent=cnt2,
- text=txt,
- size=(0, 0),
- position=(self._width * 0.5,
- self._height - 28),
- h_align='center',
- v_align='center',
- color=(0.7, 1, 0.7, 0.4),
- scale=0.7,
- shadow=0,
- flatness=1.0,
- maxwidth=700,
- transition_delay=0.4)
+ (
+ '${SETTINGS}',
+ ba.Lstr(
+ resource=(
+ 'accountSettingsWindow.titleText'
+ )
+ ),
+ ),
+ (
+ '${PLAYER_PROFILES}',
+ ba.Lstr(
+ resource=(
+ 'playerProfilesWindow.titleText'
+ )
+ ),
+ ),
+ ],
+ )
+ ba.textwidget(
+ parent=cnt2,
+ text=txt,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height - 28),
+ h_align='center',
+ v_align='center',
+ color=(0.7, 1, 0.7, 0.4),
+ scale=0.7,
+ shadow=0,
+ flatness=1.0,
+ maxwidth=700,
+ transition_delay=0.4,
+ )
elif self._tab == 'icons':
txt = ba.Lstr(
resource='store.howToUseIconsText',
subs=[
- ('${SETTINGS}',
- ba.Lstr(resource='mainMenu.settingsText')),
- ('${PLAYER_PROFILES}',
- ba.Lstr(
- resource='playerProfilesWindow.titleText')
- )
- ])
- ba.textwidget(parent=cnt2,
- text=txt,
- size=(0, 0),
- position=(self._width * 0.5,
- self._height - 28),
- h_align='center',
- v_align='center',
- color=(0.7, 1, 0.7, 0.4),
- scale=0.7,
- shadow=0,
- flatness=1.0,
- maxwidth=700,
- transition_delay=0.4)
+ (
+ '${SETTINGS}',
+ ba.Lstr(resource='mainMenu.settingsText'),
+ ),
+ (
+ '${PLAYER_PROFILES}',
+ ba.Lstr(
+ resource=(
+ 'playerProfilesWindow.titleText'
+ )
+ ),
+ ),
+ ],
+ )
+ ba.textwidget(
+ parent=cnt2,
+ text=txt,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height - 28),
+ h_align='center',
+ v_align='center',
+ color=(0.7, 1, 0.7, 0.4),
+ scale=0.7,
+ shadow=0,
+ flatness=1.0,
+ maxwidth=700,
+ transition_delay=0.4,
+ )
elif self._tab == 'maps':
assert self._width is not None
assert self._height is not None
txt = ba.Lstr(resource='store.howToUseMapsText')
- ba.textwidget(parent=cnt2,
- text=txt,
- size=(0, 0),
- position=(self._width * 0.5,
- self._height - 28),
- h_align='center',
- v_align='center',
- color=(0.7, 1, 0.7, 0.4),
- scale=0.7,
- shadow=0,
- flatness=1.0,
- maxwidth=700,
- transition_delay=0.4)
+ ba.textwidget(
+ parent=cnt2,
+ text=txt,
+ size=(0, 0),
+ position=(self._width * 0.5, self._height - 28),
+ h_align='center',
+ v_align='center',
+ color=(0.7, 1, 0.7, 0.4),
+ scale=0.7,
+ shadow=0,
+ flatness=1.0,
+ maxwidth=700,
+ transition_delay=0.4,
+ )
prev_row_buttons: list | None = None
this_row_buttons = []
@@ -873,23 +1064,29 @@ class StoreBrowserWindow(ba.Window):
h_align='left',
v_align='center',
text=ba.Lstr(resource=section['title']),
- maxwidth=self._width * 0.7)
+ maxwidth=self._width * 0.7,
+ )
v -= title_spacing
delay = max(0.100, delay - 0.100)
v -= button_border
b_width, b_height = section['button_size']
b_count = len(section['items'])
b_column_count = int(
- math.floor((self._width - boffs_h - 20) /
- (b_width + button_spacing)))
+ math.floor(
+ (self._width - boffs_h - 20)
+ / (b_width + button_spacing)
+ )
+ )
col = 0
item: dict[str, Any]
assert self._store_window.button_infos is not None
for i, item_name in enumerate(section['items']):
item = self._store_window.button_infos[
- item_name] = {}
- item['call'] = ba.WeakCall(self._store_window.buy,
- item_name)
+ item_name
+ ] = {}
+ item['call'] = ba.WeakCall(
+ self._store_window.buy, item_name
+ )
if 'x_offs' in section:
boffs_h2 = section['x_offs']
else:
@@ -899,9 +1096,12 @@ class StoreBrowserWindow(ba.Window):
boffs_v2 = section['y_offs']
else:
boffs_v2 = 0
- b_pos = (boffs_h + boffs_h2 +
- (b_width + button_spacing) * col,
- v - b_height + boffs_v2)
+ b_pos = (
+ boffs_h
+ + boffs_h2
+ + (b_width + button_spacing) * col,
+ v - b_height + boffs_v2,
+ )
storeitemui.instantiate_store_item_display(
item_name,
item,
@@ -912,7 +1112,8 @@ class StoreBrowserWindow(ba.Window):
b_height=b_height,
boffs_h2=boffs_h2,
boffs_v2=boffs_v2,
- delay=delay)
+ delay=delay,
+ )
btn = item['button']
delay = max(0.1, delay - 0.1)
this_row_buttons.append(btn)
@@ -922,24 +1123,33 @@ class StoreBrowserWindow(ba.Window):
if prev_row_buttons is not None:
# pylint: disable=unsubscriptable-object
if len(prev_row_buttons) > col:
- ba.widget(edit=btn,
- up_widget=prev_row_buttons[col])
- ba.widget(edit=prev_row_buttons[col],
- down_widget=btn)
+ ba.widget(
+ edit=btn,
+ up_widget=prev_row_buttons[col],
+ )
+ ba.widget(
+ edit=prev_row_buttons[col],
+ down_widget=btn,
+ )
# If we're the last button in our row,
# wire any in the previous row past
# our position to go to us if down is
# pressed.
- if (col + 1 == b_column_count
- or i == b_count - 1):
- for b_prev in prev_row_buttons[col +
- 1:]:
- ba.widget(edit=b_prev,
- down_widget=btn)
+ if (
+ col + 1 == b_column_count
+ or i == b_count - 1
+ ):
+ for b_prev in prev_row_buttons[
+ col + 1 :
+ ]:
+ ba.widget(
+ edit=b_prev, down_widget=btn
+ )
else:
- ba.widget(edit=btn,
- up_widget=prev_row_buttons[-1])
+ ba.widget(
+ edit=btn, up_widget=prev_row_buttons[-1]
+ )
else:
ba.widget(edit=btn, up_widget=tab_button)
@@ -960,32 +1170,41 @@ class StoreBrowserWindow(ba.Window):
0.5,
ba.WeakCall(self._store_window.update_buttons),
repeat=True,
- timetype=ba.TimeType.REAL)
+ timetype=ba.TimeType.REAL,
+ )
# Also update them immediately.
self._store_window.update_buttons()
- if self._current_tab in (self.TabID.EXTRAS, self.TabID.MINIGAMES,
- self.TabID.CHARACTERS, self.TabID.MAPS,
- self.TabID.ICONS):
+ if self._current_tab in (
+ self.TabID.EXTRAS,
+ self.TabID.MINIGAMES,
+ self.TabID.CHARACTERS,
+ self.TabID.MAPS,
+ self.TabID.ICONS,
+ ):
store = _Store(self, data, self._scroll_width)
assert self._scrollwidget is not None
store.instantiate(
scrollwidget=self._scrollwidget,
- tab_button=self._tab_row.tabs[self._current_tab].button)
+ tab_button=self._tab_row.tabs[self._current_tab].button,
+ )
else:
- cnt = ba.containerwidget(parent=self._scrollwidget,
- scale=1.0,
- size=(self._scroll_width,
- self._scroll_height * 0.95),
- background=False,
- claims_left_right=True,
- claims_tab=True,
- selection_loops_to_parent=True)
+ cnt = ba.containerwidget(
+ parent=self._scrollwidget,
+ scale=1.0,
+ size=(self._scroll_width, self._scroll_height * 0.95),
+ background=False,
+ claims_left_right=True,
+ claims_tab=True,
+ selection_loops_to_parent=True,
+ )
self._status_textwidget = ba.textwidget(
parent=cnt,
- position=(self._scroll_width * 0.5,
- self._scroll_height * 0.5),
+ position=(
+ self._scroll_width * 0.5,
+ self._scroll_height * 0.5,
+ ),
size=(0, 0),
scale=1.3,
transition_delay=0.1,
@@ -993,13 +1212,15 @@ class StoreBrowserWindow(ba.Window):
h_align='center',
v_align='center',
text=ba.Lstr(resource=self._r + '.comingSoonText'),
- maxwidth=self._scroll_width * 0.9)
+ maxwidth=self._scroll_width * 0.9,
+ )
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
selected_tab_ids = [
- tab_id for tab_id, tab in self._tab_row.tabs.items()
+ tab_id
+ for tab_id, tab in self._tab_row.tabs.items()
if sel == tab.button
]
if sel == self._get_tickets_button:
@@ -1021,15 +1242,18 @@ class StoreBrowserWindow(ba.Window):
def _restore_state(self) -> None:
from efro.util import enum_by_value
+
try:
sel: ba.Widget | None
- sel_name = ba.app.ui.window_states.get(type(self),
- {}).get('sel_name')
+ sel_name = ba.app.ui.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
assert isinstance(sel_name, (str, type(None)))
try:
- current_tab = enum_by_value(self.TabID,
- ba.app.config.get('Store Tab'))
+ current_tab = enum_by_value(
+ self.TabID, ba.app.config.get('Store Tab')
+ )
except ValueError:
current_tab = self.TabID.CHARACTERS
@@ -1043,8 +1267,9 @@ class StoreBrowserWindow(ba.Window):
sel = self._scrollwidget
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(self.TabID,
- sel_name.split(':')[-1])
+ sel_tab_id = enum_by_value(
+ self.TabID, sel_name.split(':')[-1]
+ )
except ValueError:
sel_tab_id = self.TabID.CHARACTERS
sel = self._tab_row.tabs[sel_tab_id].button
@@ -1052,8 +1277,10 @@ class StoreBrowserWindow(ba.Window):
sel = self._tab_row.tabs[current_tab].button
# If we were requested to show a tab, select it too..
- if (self._show_tab is not None
- and self._show_tab in self._tab_row.tabs):
+ if (
+ self._show_tab is not None
+ and self._show_tab in self._tab_row.tabs
+ ):
sel = self._tab_row.tabs[self._show_tab].button
self._set_tab(current_tab)
if sel is not None:
@@ -1065,6 +1292,7 @@ class StoreBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.getcurrency import GetCurrencyWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@@ -1072,7 +1300,8 @@ class StoreBrowserWindow(ba.Window):
ba.containerwidget(edit=self._root_widget, transition='out_left')
window = GetCurrencyWindow(
from_modal_store=self._modal,
- store_back_location=self._back_location).get_root_widget()
+ store_back_location=self._back_location,
+ ).get_root_widget()
if not self._modal:
ba.app.ui.set_main_menu_window(window)
@@ -1080,15 +1309,19 @@ class StoreBrowserWindow(ba.Window):
# pylint: disable=cyclic-import
from bastd.ui.coop.browser import CoopBrowserWindow
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
if not self._modal:
if self._back_location == 'CoopBrowserWindow':
ba.app.ui.set_main_menu_window(
- CoopBrowserWindow(transition='in_left').get_root_widget())
+ CoopBrowserWindow(transition='in_left').get_root_widget()
+ )
else:
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
if self._on_close_call is not None:
self._on_close_call()
diff --git a/assets/src/ba_data/python/bastd/ui/store/button.py b/assets/src/ba_data/python/bastd/ui/store/button.py
index b9d4bf8f..93ca4de3 100644
--- a/assets/src/ba_data/python/bastd/ui/store/button.py
+++ b/assets/src/ba_data/python/bastd/ui/store/button.py
@@ -15,18 +15,20 @@ if TYPE_CHECKING:
class StoreButton:
"""A button leading to the store."""
- def __init__(self,
- parent: ba.Widget,
- position: Sequence[float],
- size: Sequence[float],
- scale: float,
- on_activate_call: Callable[[], Any] | None = None,
- transition_delay: float | None = None,
- color: Sequence[float] | None = None,
- textcolor: Sequence[float] | None = None,
- show_tickets: bool = False,
- button_type: str | None = None,
- sale_scale: float = 1.0):
+ def __init__(
+ self,
+ parent: ba.Widget,
+ position: Sequence[float],
+ size: Sequence[float],
+ scale: float,
+ on_activate_call: Callable[[], Any] | None = None,
+ transition_delay: float | None = None,
+ color: Sequence[float] | None = None,
+ textcolor: Sequence[float] | None = None,
+ show_tickets: bool = False,
+ button_type: str | None = None,
+ sale_scale: float = 1.0,
+ ):
self._position = position
self._size = size
self._scale = scale
@@ -44,7 +46,8 @@ class StoreButton:
on_activate_call=self._on_activate,
transition_delay=transition_delay,
color=color,
- button_type=button_type)
+ button_type=button_type,
+ )
self._title_text: ba.Widget | None
self._ticket_text: ba.Widget | None
@@ -52,8 +55,10 @@ class StoreButton:
if show_tickets:
self._title_text = ba.textwidget(
parent=parent,
- position=(position[0] + size[0] * 0.5 * scale,
- position[1] + size[1] * 0.65 * scale),
+ position=(
+ position[0] + size[0] * 0.5 * scale,
+ position[1] + size[1] * 0.65 * scale,
+ ),
size=(0, 0),
h_align='center',
v_align='center',
@@ -62,7 +67,8 @@ class StoreButton:
draw_controller=self._button,
scale=scale,
transition_delay=transition_delay,
- color=textcolor)
+ color=textcolor,
+ )
self._ticket_text = ba.textwidget(
parent=parent,
size=(0, 0),
@@ -74,7 +80,8 @@ class StoreButton:
flatness=1.0,
shadow=0.0,
scale=scale * 0.6,
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
else:
self._title_text = None
self._ticket_text = None
@@ -89,7 +96,8 @@ class StoreButton:
draw_controller=self._button,
size=(2.2 * self._circle_rad, 2.2 * self._circle_rad),
texture=ba.gettexture('circleShadow'),
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
self._available_purchase_text = ba.textwidget(
parent=parent,
size=(0, 0),
@@ -102,7 +110,8 @@ class StoreButton:
shadow=1.0,
scale=0.6 * scale,
maxwidth=self._circle_rad * 1.4,
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
self._sale_circle_rad = 18 * scale * sale_scale
self._sale_backing = ba.imagewidget(
@@ -111,7 +120,8 @@ class StoreButton:
draw_controller=self._button,
size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad),
texture=ba.gettexture('circleZigZag'),
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
self._sale_title_text = ba.textwidget(
parent=parent,
size=(0, 0),
@@ -123,25 +133,29 @@ class StoreButton:
shadow=0.0,
scale=0.5 * scale * sale_scale,
maxwidth=self._sale_circle_rad * 1.5,
- transition_delay=transition_delay)
- self._sale_time_text = ba.textwidget(parent=parent,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=self._button,
- color=(0, 1, 0),
- flatness=1.0,
- shadow=0.0,
- scale=0.4 * scale * sale_scale,
- maxwidth=self._sale_circle_rad *
- 1.5,
- transition_delay=transition_delay)
+ transition_delay=transition_delay,
+ )
+ self._sale_time_text = ba.textwidget(
+ parent=parent,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=self._button,
+ color=(0, 1, 0),
+ flatness=1.0,
+ shadow=0.0,
+ scale=0.4 * scale * sale_scale,
+ maxwidth=self._sale_circle_rad * 1.5,
+ transition_delay=transition_delay,
+ )
self.set_position(position)
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
self._update()
def _on_activate(self) -> None:
@@ -151,52 +165,73 @@ class StoreButton:
def set_position(self, position: Sequence[float]) -> None:
"""Set the button position."""
self._position = position
- self._circle_center = (position[0] + 0.1 * self._size[0] * self._scale,
- position[1] + self._size[1] * self._scale * 0.8)
- self._sale_circle_center = (position[0] +
- 0.07 * self._size[0] * self._scale,
- position[1] +
- self._size[1] * self._scale * 0.8)
+ self._circle_center = (
+ position[0] + 0.1 * self._size[0] * self._scale,
+ position[1] + self._size[1] * self._scale * 0.8,
+ )
+ self._sale_circle_center = (
+ position[0] + 0.07 * self._size[0] * self._scale,
+ position[1] + self._size[1] * self._scale * 0.8,
+ )
if not self._button:
return
ba.buttonwidget(edit=self._button, position=self._position)
if self._title_text is not None:
- ba.textwidget(edit=self._title_text,
- position=(self._position[0] +
- self._size[0] * 0.5 * self._scale,
- self._position[1] +
- self._size[1] * 0.65 * self._scale))
+ ba.textwidget(
+ edit=self._title_text,
+ position=(
+ self._position[0] + self._size[0] * 0.5 * self._scale,
+ self._position[1] + self._size[1] * 0.65 * self._scale,
+ ),
+ )
if self._ticket_text is not None:
ba.textwidget(
edit=self._ticket_text,
- position=(position[0] + self._size[0] * 0.5 * self._scale,
- position[1] + self._size[1] * 0.28 * self._scale),
- size=(0, 0))
+ position=(
+ position[0] + self._size[0] * 0.5 * self._scale,
+ position[1] + self._size[1] * 0.28 * self._scale,
+ ),
+ size=(0, 0),
+ )
ba.imagewidget(
edit=self._available_purchase_backing,
- position=(self._circle_center[0] - self._circle_rad * 1.02,
- self._circle_center[1] - self._circle_rad * 1.13))
- ba.textwidget(edit=self._available_purchase_text,
- position=self._circle_center)
+ position=(
+ self._circle_center[0] - self._circle_rad * 1.02,
+ self._circle_center[1] - self._circle_rad * 1.13,
+ ),
+ )
+ ba.textwidget(
+ edit=self._available_purchase_text, position=self._circle_center
+ )
ba.imagewidget(
edit=self._sale_backing,
- position=(self._sale_circle_center[0] - self._sale_circle_rad,
- self._sale_circle_center[1] - self._sale_circle_rad))
- ba.textwidget(edit=self._sale_title_text,
- position=(self._sale_circle_center[0],
- self._sale_circle_center[1] +
- self._sale_circle_rad * 0.3))
- ba.textwidget(edit=self._sale_time_text,
- position=(self._sale_circle_center[0],
- self._sale_circle_center[1] -
- self._sale_circle_rad * 0.3))
+ position=(
+ self._sale_circle_center[0] - self._sale_circle_rad,
+ self._sale_circle_center[1] - self._sale_circle_rad,
+ ),
+ )
+ ba.textwidget(
+ edit=self._sale_title_text,
+ position=(
+ self._sale_circle_center[0],
+ self._sale_circle_center[1] + self._sale_circle_rad * 0.3,
+ ),
+ )
+ ba.textwidget(
+ edit=self._sale_time_text,
+ position=(
+ self._sale_circle_center[0],
+ self._sale_circle_center[1] - self._sale_circle_rad * 0.3,
+ ),
+ )
def _default_on_activate_call(self) -> None:
# pylint: disable=cyclic-import
from bastd.ui.account import show_sign_in_prompt
from bastd.ui.store.browser import StoreBrowserWindow
+
if ba.internal.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
@@ -210,15 +245,19 @@ class StoreButton:
# pylint: disable=too-many-branches
# pylint: disable=cyclic-import
from ba import SpecialChar, TimeFormat
- from ba.internal import (get_available_sale_time,
- get_available_purchase_count)
+ from ba.internal import (
+ get_available_sale_time,
+ get_available_purchase_count,
+ )
+
if not self._button:
return # Our instance may outlive our UI objects.
if self._ticket_text is not None:
if ba.internal.get_v1_account_state() == 'signed_in':
sval = ba.charstr(SpecialChar.TICKET) + str(
- ba.internal.get_v1_account_ticket_count())
+ ba.internal.get_v1_account_ticket_count()
+ )
else:
sval = '-'
ba.textwidget(edit=self._ticket_text, text=sval)
@@ -230,6 +269,7 @@ class StoreButton:
# ..also look for new style sales.
if sale_time is None:
import datetime
+
sales_raw = ba.internal.get_v1_account_misc_read_val('sales', {})
sale_times = []
try:
@@ -237,9 +277,10 @@ class StoreButton:
# remaining that we don't own.
for sale_item, sale_info in list(sales_raw.items()):
if not ba.internal.get_purchased(sale_item):
- to_end = (datetime.datetime.utcfromtimestamp(
- sale_info['e']) -
- datetime.datetime.utcnow()).total_seconds()
+ to_end = (
+ datetime.datetime.utcfromtimestamp(sale_info['e'])
+ - datetime.datetime.utcnow()
+ ).total_seconds()
if to_end > 0:
sale_times.append(to_end)
except Exception:
@@ -248,13 +289,16 @@ class StoreButton:
sale_time = int(min(sale_times) * 1000)
if sale_time is not None:
- ba.textwidget(edit=self._sale_title_text,
- text=ba.Lstr(resource='store.saleText'))
- ba.textwidget(edit=self._sale_time_text,
- text=ba.timestring(
- sale_time,
- centi=False,
- timeformat=TimeFormat.MILLISECONDS))
+ ba.textwidget(
+ edit=self._sale_title_text,
+ text=ba.Lstr(resource='store.saleText'),
+ )
+ ba.textwidget(
+ edit=self._sale_time_text,
+ text=ba.timestring(
+ sale_time, centi=False, timeformat=TimeFormat.MILLISECONDS
+ ),
+ )
ba.imagewidget(edit=self._sale_backing, opacity=1.0)
ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0)
ba.textwidget(edit=self._available_purchase_text, text='')
@@ -264,11 +308,15 @@ class StoreButton:
ba.textwidget(edit=self._sale_time_text, text='')
ba.textwidget(edit=self._sale_title_text, text='')
if available_purchases > 0:
- ba.textwidget(edit=self._available_purchase_text,
- text=str(available_purchases))
- ba.imagewidget(edit=self._available_purchase_backing,
- opacity=1.0)
+ ba.textwidget(
+ edit=self._available_purchase_text,
+ text=str(available_purchases),
+ )
+ ba.imagewidget(
+ edit=self._available_purchase_backing, opacity=1.0
+ )
else:
ba.textwidget(edit=self._available_purchase_text, text='')
- ba.imagewidget(edit=self._available_purchase_backing,
- opacity=0.0)
+ ba.imagewidget(
+ edit=self._available_purchase_backing, opacity=0.0
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/store/item.py b/assets/src/ba_data/python/bastd/ui/store/item.py
index fb00ad41..246e84e9 100644
--- a/assets/src/ba_data/python/bastd/ui/store/item.py
+++ b/assets/src/ba_data/python/bastd/ui/store/item.py
@@ -11,23 +11,29 @@ if TYPE_CHECKING:
from typing import Any
-def instantiate_store_item_display(item_name: str,
- item: dict[str, Any],
- parent_widget: ba.Widget,
- b_pos: tuple[float, float],
- b_width: float,
- b_height: float,
- boffs_h: float = 0.0,
- boffs_h2: float = 0.0,
- boffs_v2: float = 0,
- delay: float = 0.0,
- button: bool = True) -> None:
+def instantiate_store_item_display(
+ item_name: str,
+ item: dict[str, Any],
+ parent_widget: ba.Widget,
+ b_pos: tuple[float, float],
+ b_width: float,
+ b_height: float,
+ boffs_h: float = 0.0,
+ boffs_h2: float = 0.0,
+ boffs_v2: float = 0,
+ delay: float = 0.0,
+ button: bool = True,
+) -> None:
"""(internal)"""
# pylint: disable=too-many-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
- from ba.internal import (get_store_item, get_store_item_name_translated,
- get_clean_price)
+ from ba.internal import (
+ get_store_item,
+ get_store_item_name_translated,
+ get_clean_price,
+ )
+
del boffs_h # unused arg
del boffs_h2 # unused arg
del boffs_v2 # unused arg
@@ -40,15 +46,17 @@ def instantiate_store_item_display(item_name: str,
btn: ba.Widget | None
if button:
- item['button'] = btn = ba.buttonwidget(parent=parent_widget,
- position=b_pos,
- transition_delay=delay,
- show_buffer_top=76.0,
- enable_sound=False,
- button_type='square',
- size=(b_width, b_height),
- autoselect=True,
- label='')
+ item['button'] = btn = ba.buttonwidget(
+ parent=parent_widget,
+ position=b_pos,
+ transition_delay=delay,
+ show_buffer_top=76.0,
+ enable_sound=False,
+ button_type='square',
+ size=(b_width, b_height),
+ autoselect=True,
+ label='',
+ )
ba.widget(edit=btn, show_buffer_bottom=76.0)
else:
btn = None
@@ -67,12 +75,19 @@ def instantiate_store_item_display(item_name: str,
if item_name.startswith('characters.'):
character = ba.app.spaz_appearances[item_info['character']]
tint_color = (
- item_info['color'] if 'color' in item_info else
- character.default_color if character.default_color is not None else
- (1, 1, 1))
- tint2_color = (item_info['highlight'] if 'highlight' in item_info else
- character.default_highlight if
- character.default_highlight is not None else (1, 1, 1))
+ item_info['color']
+ if 'color' in item_info
+ else character.default_color
+ if character.default_color is not None
+ else (1, 1, 1)
+ )
+ tint2_color = (
+ item_info['highlight']
+ if 'highlight' in item_info
+ else character.default_highlight
+ if character.default_highlight is not None
+ else (1, 1, 1)
+ )
icon_tex = character.icon_texture
tint_tex = character.icon_mask_texture
title_v = 0.255
@@ -98,12 +113,19 @@ def instantiate_store_item_display(item_name: str,
modes_l.append(ba.Lstr(resource='playModes.freeForAllText'))
if len(modes_l) == 3:
- modes = ba.Lstr(value='${A}, ${B}, ${C}',
- subs=[('${A}', modes_l[0]), ('${B}', modes_l[1]),
- ('${C}', modes_l[2])])
+ modes = ba.Lstr(
+ value='${A}, ${B}, ${C}',
+ subs=[
+ ('${A}', modes_l[0]),
+ ('${B}', modes_l[1]),
+ ('${C}', modes_l[2]),
+ ],
+ )
elif len(modes_l) == 2:
- modes = ba.Lstr(value='${A}, ${B}',
- subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])])
+ modes = ba.Lstr(
+ value='${A}, ${B}',
+ subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])],
+ )
elif len(modes_l) == 1:
modes = modes_l[0]
else:
@@ -122,36 +144,44 @@ def instantiate_store_item_display(item_name: str,
if item_name.startswith('characters.'):
frame_size = b_width * 0.7
im_dim = frame_size * (100.0 / 113.0)
- im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
- b_pos[1] + b_height * 0.57 - im_dim * 0.5)
+ im_pos = (
+ b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
+ b_pos[1] + b_height * 0.57 - im_dim * 0.5,
+ )
mask_texture = ba.gettexture('characterIconMask')
assert icon_tex is not None
assert tint_tex is not None
- ba.imagewidget(parent=parent_widget,
- position=im_pos,
- size=(im_dim, im_dim),
- color=(1, 1, 1),
- transition_delay=delay,
- mask_texture=mask_texture,
- draw_controller=btn,
- texture=ba.gettexture(icon_tex),
- tint_texture=ba.gettexture(tint_tex),
- tint_color=tint_color,
- tint2_color=tint2_color)
+ ba.imagewidget(
+ parent=parent_widget,
+ position=im_pos,
+ size=(im_dim, im_dim),
+ color=(1, 1, 1),
+ transition_delay=delay,
+ mask_texture=mask_texture,
+ draw_controller=btn,
+ texture=ba.gettexture(icon_tex),
+ tint_texture=ba.gettexture(tint_tex),
+ tint_color=tint_color,
+ tint2_color=tint2_color,
+ )
if item_name in ['pro', 'upgrades.pro']:
frame_size = b_width * 0.5
im_dim = frame_size * (100.0 / 113.0)
- im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
- b_pos[1] + b_height * 0.5 - im_dim * 0.5)
- ba.imagewidget(parent=parent_widget,
- position=im_pos,
- size=(im_dim, im_dim),
- transition_delay=delay,
- draw_controller=btn,
- color=(0.3, 0.0, 0.3),
- opacity=0.3,
- texture=ba.gettexture('logo'))
+ im_pos = (
+ b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
+ b_pos[1] + b_height * 0.5 - im_dim * 0.5,
+ )
+ ba.imagewidget(
+ parent=parent_widget,
+ position=im_pos,
+ size=(im_dim, im_dim),
+ transition_delay=delay,
+ draw_controller=btn,
+ color=(0.3, 0.0, 0.3),
+ opacity=0.3,
+ texture=ba.gettexture('logo'),
+ )
txt = ba.Lstr(resource='store.bombSquadProNewDescriptionText')
# t = 'foo\nfoo\nfoo\nfoo\nfoo\nfoo'
@@ -167,7 +197,8 @@ def instantiate_store_item_display(item_name: str,
h_align='center',
v_align='center',
draw_controller=btn,
- color=(0.3, 1, 0.3))
+ color=(0.3, 1, 0.3),
+ )
extra_backings = item['extra_backings'] = []
extra_images = item['extra_images'] = []
@@ -182,174 +213,233 @@ def instantiate_store_item_display(item_name: str,
tile_size = (b_width * 0.16 * 1.2, b_width * 0.2 * 1.2)
tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1])
extra_backings.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - tile_size[0] * 0.5,
- tile_pos[1] - tile_size[1] * 0.5),
- size=tile_size,
- transition_delay=delay,
- draw_controller=btn,
- color=backing_color,
- texture=b_square_texture))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - tile_size[0] * 0.5,
+ tile_pos[1] - tile_size[1] * 0.5,
+ ),
+ size=tile_size,
+ transition_delay=delay,
+ draw_controller=btn,
+ color=backing_color,
+ texture=b_square_texture,
+ )
+ )
im_size = tile_size[0] * 0.8
extra_images.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - im_size * 0.5,
- tile_pos[1] - im_size * 0.4),
- size=(im_size, im_size),
- transition_delay=delay,
- draw_controller=btn,
- color=(1, 1, 1),
- texture=ba.gettexture('ticketsMore')))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - im_size * 0.5,
+ tile_pos[1] - im_size * 0.4,
+ ),
+ size=(im_size, im_size),
+ transition_delay=delay,
+ draw_controller=btn,
+ color=(1, 1, 1),
+ texture=ba.gettexture('ticketsMore'),
+ )
+ )
bonus_tickets = str(
- ba.internal.get_v1_account_misc_read_val('proBonusTickets', 100))
+ ba.internal.get_v1_account_misc_read_val('proBonusTickets', 100)
+ )
extra_texts.append(
- ba.textwidget(parent=parent_widget,
- draw_controller=btn,
- position=(tile_pos[0] - tile_size[0] * 0.03,
- tile_pos[1] - tile_size[1] * 0.25),
- size=(0, 0),
- color=(0.6, 1, 0.6),
- transition_delay=delay,
- h_align='center',
- v_align='center',
- maxwidth=tile_size[0] * 0.7,
- scale=0.55,
- text=ba.Lstr(resource='getTicketsWindow.ticketsText',
- subs=[('${COUNT}', bonus_tickets)]),
- flatness=1.0,
- shadow=0.0))
+ ba.textwidget(
+ parent=parent_widget,
+ draw_controller=btn,
+ position=(
+ tile_pos[0] - tile_size[0] * 0.03,
+ tile_pos[1] - tile_size[1] * 0.25,
+ ),
+ size=(0, 0),
+ color=(0.6, 1, 0.6),
+ transition_delay=delay,
+ h_align='center',
+ v_align='center',
+ maxwidth=tile_size[0] * 0.7,
+ scale=0.55,
+ text=ba.Lstr(
+ resource='getTicketsWindow.ticketsText',
+ subs=[('${COUNT}', bonus_tickets)],
+ ),
+ flatness=1.0,
+ shadow=0.0,
+ )
+ )
- for charname, pos in [('Kronk', (0.32, 0.45)), ('Zoe', (0.425, 0.4)),
- ('Jack Morgan', (0.555, 0.45)),
- ('Mel', (0.645, 0.4))]:
+ for charname, pos in [
+ ('Kronk', (0.32, 0.45)),
+ ('Zoe', (0.425, 0.4)),
+ ('Jack Morgan', (0.555, 0.45)),
+ ('Mel', (0.645, 0.4)),
+ ]:
tile_size = (b_width * 0.16 * 0.9, b_width * 0.2 * 0.9)
- tile_pos = (b_pos[0] + b_width * pos[0],
- b_pos[1] + b_height * pos[1])
+ tile_pos = (
+ b_pos[0] + b_width * pos[0],
+ b_pos[1] + b_height * pos[1],
+ )
character = ba.app.spaz_appearances[charname]
extra_backings.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - tile_size[0] * 0.5,
- tile_pos[1] - tile_size[1] * 0.5),
- size=tile_size,
- transition_delay=delay,
- draw_controller=btn,
- color=backing_color,
- texture=b_square_texture))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - tile_size[0] * 0.5,
+ tile_pos[1] - tile_size[1] * 0.5,
+ ),
+ size=tile_size,
+ transition_delay=delay,
+ draw_controller=btn,
+ color=backing_color,
+ texture=b_square_texture,
+ )
+ )
im_size = tile_size[0] * 0.7
extra_images.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - im_size * 0.53,
- tile_pos[1] - im_size * 0.35),
- size=(im_size, im_size),
- transition_delay=delay,
- draw_controller=btn,
- color=(1, 1, 1),
- texture=ba.gettexture(character.icon_texture),
- tint_texture=ba.gettexture(
- character.icon_mask_texture),
- tint_color=character.default_color,
- tint2_color=character.default_highlight,
- mask_texture=char_mask_texture))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - im_size * 0.53,
+ tile_pos[1] - im_size * 0.35,
+ ),
+ size=(im_size, im_size),
+ transition_delay=delay,
+ draw_controller=btn,
+ color=(1, 1, 1),
+ texture=ba.gettexture(character.icon_texture),
+ tint_texture=ba.gettexture(character.icon_mask_texture),
+ tint_color=character.default_color,
+ tint2_color=character.default_highlight,
+ mask_texture=char_mask_texture,
+ )
+ )
extra_texts.append(
- ba.textwidget(parent=parent_widget,
- draw_controller=btn,
- position=(tile_pos[0] - im_size * 0.03,
- tile_pos[1] - im_size * 0.51),
- size=(0, 0),
- color=(0.6, 1, 0.6),
- transition_delay=delay,
- h_align='center',
- v_align='center',
- maxwidth=tile_size[0] * 0.7,
- scale=0.55,
- text=ba.Lstr(translate=('characterNames',
- charname)),
- flatness=1.0,
- shadow=0.0))
+ ba.textwidget(
+ parent=parent_widget,
+ draw_controller=btn,
+ position=(
+ tile_pos[0] - im_size * 0.03,
+ tile_pos[1] - im_size * 0.51,
+ ),
+ size=(0, 0),
+ color=(0.6, 1, 0.6),
+ transition_delay=delay,
+ h_align='center',
+ v_align='center',
+ maxwidth=tile_size[0] * 0.7,
+ scale=0.55,
+ text=ba.Lstr(translate=('characterNames', charname)),
+ flatness=1.0,
+ shadow=0.0,
+ )
+ )
# If we have a 'total-worth' item-id for this id, show that price so
# the user knows how much this is worth.
total_worth_item = ba.internal.get_v1_account_misc_read_val(
- 'twrths', {}).get(item_name)
+ 'twrths', {}
+ ).get(item_name)
total_worth_price: str | None
if total_worth_item is not None:
price = ba.internal.get_price(total_worth_item)
- total_worth_price = (get_clean_price(price)
- if price is not None else '??')
+ total_worth_price = (
+ get_clean_price(price) if price is not None else '??'
+ )
else:
total_worth_price = None
if total_worth_price is not None:
- total_worth_text = ba.Lstr(resource='store.totalWorthText',
- subs=[('${TOTAL_WORTH}',
- total_worth_price)])
+ total_worth_text = ba.Lstr(
+ resource='store.totalWorthText',
+ subs=[('${TOTAL_WORTH}', total_worth_price)],
+ )
extra_texts_2.append(
- ba.textwidget(parent=parent_widget,
- text=total_worth_text,
- position=(b_pos[0] + b_width * 0.5 + b_offs_x,
- b_pos[1] + b_height * 0.25),
- transition_delay=delay,
- scale=b_width * (1.0 / 230.0) * base_text_scale *
- 0.45,
- maxwidth=b_width * 0.5,
- size=(0, 0),
- h_align='center',
- v_align='center',
- shadow=1.0,
- flatness=1.0,
- draw_controller=btn,
- color=(0.3, 1, 1)))
+ ba.textwidget(
+ parent=parent_widget,
+ text=total_worth_text,
+ position=(
+ b_pos[0] + b_width * 0.5 + b_offs_x,
+ b_pos[1] + b_height * 0.25,
+ ),
+ transition_delay=delay,
+ scale=b_width * (1.0 / 230.0) * base_text_scale * 0.45,
+ maxwidth=b_width * 0.5,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ shadow=1.0,
+ flatness=1.0,
+ draw_controller=btn,
+ color=(0.3, 1, 1),
+ )
+ )
model_opaque = ba.getmodel('level_select_button_opaque')
model_transparent = ba.getmodel('level_select_button_transparent')
mask_tex = ba.gettexture('mapPreviewMask')
for levelname, preview_tex_name, pos in [
('Infinite Onslaught', 'doomShroomPreview', (0.80, 0.48)),
- ('Infinite Runaround', 'towerDPreview', (0.80, 0.32))
+ ('Infinite Runaround', 'towerDPreview', (0.80, 0.32)),
]:
tile_size = (b_width * 0.2, b_width * 0.13)
- tile_pos = (b_pos[0] + b_width * pos[0],
- b_pos[1] + b_height * pos[1])
+ tile_pos = (
+ b_pos[0] + b_width * pos[0],
+ b_pos[1] + b_height * pos[1],
+ )
im_size = tile_size[0] * 0.8
extra_backings.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - tile_size[0] * 0.5,
- tile_pos[1] - tile_size[1] * 0.5),
- size=tile_size,
- transition_delay=delay,
- draw_controller=btn,
- color=backing_color,
- texture=b_square_texture))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - tile_size[0] * 0.5,
+ tile_pos[1] - tile_size[1] * 0.5,
+ ),
+ size=tile_size,
+ transition_delay=delay,
+ draw_controller=btn,
+ color=backing_color,
+ texture=b_square_texture,
+ )
+ )
# Hack - gotta draw two transparent versions to avoid z issues.
for mod in model_opaque, model_transparent:
extra_images.append(
- ba.imagewidget(parent=parent_widget,
- position=(tile_pos[0] - im_size * 0.52,
- tile_pos[1] - im_size * 0.2),
- size=(im_size, im_size * 0.5),
- transition_delay=delay,
- model_transparent=mod,
- mask_texture=mask_tex,
- draw_controller=btn,
- texture=ba.gettexture(preview_tex_name)))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=(
+ tile_pos[0] - im_size * 0.52,
+ tile_pos[1] - im_size * 0.2,
+ ),
+ size=(im_size, im_size * 0.5),
+ transition_delay=delay,
+ model_transparent=mod,
+ mask_texture=mask_tex,
+ draw_controller=btn,
+ texture=ba.gettexture(preview_tex_name),
+ )
+ )
extra_texts.append(
- ba.textwidget(parent=parent_widget,
- draw_controller=btn,
- position=(tile_pos[0] - im_size * 0.03,
- tile_pos[1] - im_size * 0.2),
- size=(0, 0),
- color=(0.6, 1, 0.6),
- transition_delay=delay,
- h_align='center',
- v_align='center',
- maxwidth=tile_size[0] * 0.7,
- scale=0.55,
- text=ba.Lstr(translate=('coopLevelNames',
- levelname)),
- flatness=1.0,
- shadow=0.0))
+ ba.textwidget(
+ parent=parent_widget,
+ draw_controller=btn,
+ position=(
+ tile_pos[0] - im_size * 0.03,
+ tile_pos[1] - im_size * 0.2,
+ ),
+ size=(0, 0),
+ color=(0.6, 1, 0.6),
+ transition_delay=delay,
+ h_align='center',
+ v_align='center',
+ maxwidth=tile_size[0] * 0.7,
+ scale=0.55,
+ text=ba.Lstr(translate=('coopLevelNames', levelname)),
+ flatness=1.0,
+ shadow=0.0,
+ )
+ )
if item_name.startswith('icons.'):
item['icon_text'] = ba.textwidget(
@@ -363,45 +453,54 @@ def instantiate_store_item_display(item_name: str,
size=(0, 0),
h_align='center',
v_align='center',
- draw_controller=btn)
+ draw_controller=btn,
+ )
if item_name.startswith('maps.'):
frame_size = b_width * 0.9
im_dim = frame_size * (100.0 / 113.0)
- im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
- b_pos[1] + b_height * 0.62 - im_dim * 0.25)
+ im_pos = (
+ b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
+ b_pos[1] + b_height * 0.62 - im_dim * 0.25,
+ )
model_opaque = ba.getmodel('level_select_button_opaque')
model_transparent = ba.getmodel('level_select_button_transparent')
mask_tex = ba.gettexture('mapPreviewMask')
assert tex_name is not None
- ba.imagewidget(parent=parent_widget,
- position=im_pos,
- size=(im_dim, im_dim * 0.5),
- transition_delay=delay,
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex,
- draw_controller=btn,
- texture=ba.gettexture(tex_name))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=im_pos,
+ size=(im_dim, im_dim * 0.5),
+ transition_delay=delay,
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ draw_controller=btn,
+ texture=ba.gettexture(tex_name),
+ )
if item_name.startswith('games.'):
frame_size = b_width * 0.8
im_dim = frame_size * (100.0 / 113.0)
- im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
- b_pos[1] + b_height * 0.72 - im_dim * 0.25)
+ im_pos = (
+ b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x,
+ b_pos[1] + b_height * 0.72 - im_dim * 0.25,
+ )
model_opaque = ba.getmodel('level_select_button_opaque')
model_transparent = ba.getmodel('level_select_button_transparent')
mask_tex = ba.gettexture('mapPreviewMask')
assert tex_name is not None
- ba.imagewidget(parent=parent_widget,
- position=im_pos,
- size=(im_dim, im_dim * 0.5),
- transition_delay=delay,
- model_opaque=model_opaque,
- model_transparent=model_transparent,
- mask_texture=mask_tex,
- draw_controller=btn,
- texture=ba.gettexture(tex_name))
+ ba.imagewidget(
+ parent=parent_widget,
+ position=im_pos,
+ size=(im_dim, im_dim * 0.5),
+ transition_delay=delay,
+ model_opaque=model_opaque,
+ model_transparent=model_transparent,
+ mask_texture=mask_tex,
+ draw_controller=btn,
+ texture=ba.gettexture(tex_name),
+ )
item['descriptionText'] = ba.textwidget(
parent=parent_widget,
text=desc,
@@ -416,7 +515,8 @@ def instantiate_store_item_display(item_name: str,
draw_controller=btn,
flatness=1.0,
shadow=0.0,
- color=(0.6, 1, 0.6))
+ color=(0.6, 1, 0.6),
+ )
item['gameModesText'] = ba.textwidget(
parent=parent_widget,
text=modes,
@@ -430,14 +530,17 @@ def instantiate_store_item_display(item_name: str,
draw_controller=btn,
shadow=0,
flatness=1.0,
- color=(0.6, 0.8, 0.6))
+ color=(0.6, 0.8, 0.6),
+ )
if not item_name.startswith('icons.'):
item['title_text'] = ba.textwidget(
parent=parent_widget,
text=title,
- position=(b_pos[0] + b_width * 0.5 + b_offs_x,
- b_pos[1] + b_height * title_v),
+ position=(
+ b_pos[0] + b_width * 0.5 + b_offs_x,
+ b_pos[1] + b_height * title_v,
+ ),
transition_delay=delay,
scale=b_width * (1.0 / 230.0) * base_text_scale,
maxwidth=b_width * 0.8,
@@ -445,7 +548,8 @@ def instantiate_store_item_display(item_name: str,
h_align='center',
v_align='center',
draw_controller=btn,
- color=(0.7, 0.9, 0.7, 1.0))
+ color=(0.7, 0.9, 0.7, 1.0),
+ )
item['purchase_check'] = ba.imagewidget(
parent=parent_widget,
@@ -456,12 +560,15 @@ def instantiate_store_item_display(item_name: str,
size=(60, 60),
color=(0.6, 0.5, 0.8),
draw_controller=btn,
- texture=ba.gettexture('uiAtlas'))
+ texture=ba.gettexture('uiAtlas'),
+ )
item['price_widget'] = ba.textwidget(
parent=parent_widget,
text='',
- position=(b_pos[0] + b_width * 0.5 + b_offs_x,
- b_pos[1] + b_height * price_v),
+ position=(
+ b_pos[0] + b_width * 0.5 + b_offs_x,
+ b_pos[1] + b_height * price_v,
+ ),
transition_delay=delay,
scale=b_width * (1.0 / 300.0) * base_text_scale,
maxwidth=b_width * 0.9,
@@ -469,12 +576,15 @@ def instantiate_store_item_display(item_name: str,
h_align='center',
v_align='center',
draw_controller=btn,
- color=(0.2, 1, 0.2, 1.0))
+ color=(0.2, 1, 0.2, 1.0),
+ )
item['price_widget_left'] = ba.textwidget(
parent=parent_widget,
text='',
- position=(b_pos[0] + b_width * 0.33 + b_offs_x,
- b_pos[1] + b_height * price_v),
+ position=(
+ b_pos[0] + b_width * 0.33 + b_offs_x,
+ b_pos[1] + b_height * price_v,
+ ),
transition_delay=delay,
scale=b_width * (1.0 / 300.0) * base_text_scale,
maxwidth=b_width * 0.3,
@@ -482,12 +592,15 @@ def instantiate_store_item_display(item_name: str,
h_align='center',
v_align='center',
draw_controller=btn,
- color=(0.2, 1, 0.2, 0.5))
+ color=(0.2, 1, 0.2, 0.5),
+ )
item['price_widget_right'] = ba.textwidget(
parent=parent_widget,
text='',
- position=(b_pos[0] + b_width * 0.66 + b_offs_x,
- b_pos[1] + b_height * price_v),
+ position=(
+ b_pos[0] + b_width * 0.66 + b_offs_x,
+ b_pos[1] + b_height * price_v,
+ ),
transition_delay=delay,
scale=1.1 * b_width * (1.0 / 300.0) * base_text_scale,
maxwidth=b_width * 0.3,
@@ -495,20 +608,26 @@ def instantiate_store_item_display(item_name: str,
h_align='center',
v_align='center',
draw_controller=btn,
- color=(0.2, 1, 0.2, 1.0))
+ color=(0.2, 1, 0.2, 1.0),
+ )
item['price_slash_widget'] = ba.imagewidget(
parent=parent_widget,
- position=(b_pos[0] + b_width * 0.33 + b_offs_x - 36,
- b_pos[1] + b_height * price_v - 35),
+ position=(
+ b_pos[0] + b_width * 0.33 + b_offs_x - 36,
+ b_pos[1] + b_height * price_v - 35,
+ ),
transition_delay=delay,
texture=ba.gettexture('slash'),
opacity=0.0,
size=(70, 70),
draw_controller=btn,
- color=(1, 0, 0))
+ color=(1, 0, 0),
+ )
badge_rad = 44
- badge_center = (b_pos[0] + b_width * 0.1 + b_offs_x,
- b_pos[1] + b_height * 0.87)
+ badge_center = (
+ b_pos[0] + b_width * 0.1 + b_offs_x,
+ b_pos[1] + b_height * 0.87,
+ )
item['sale_bg_widget'] = ba.imagewidget(
parent=parent_widget,
position=(badge_center[0] - badge_rad, badge_center[1] - badge_rad),
@@ -517,30 +636,33 @@ def instantiate_store_item_display(item_name: str,
texture=ba.gettexture('circleZigZag'),
draw_controller=btn,
size=(badge_rad * 2, badge_rad * 2),
- color=(0.5, 0, 1))
- item['sale_title_widget'] = ba.textwidget(parent=parent_widget,
- position=(badge_center[0],
- badge_center[1] + 12),
- transition_delay=delay,
- scale=1.0,
- maxwidth=badge_rad * 1.6,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- shadow=0.0,
- flatness=1.0,
- color=(0, 1, 0))
- item['sale_time_widget'] = ba.textwidget(parent=parent_widget,
- position=(badge_center[0],
- badge_center[1] - 12),
- transition_delay=delay,
- scale=0.7,
- maxwidth=badge_rad * 1.6,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- shadow=0.0,
- flatness=1.0,
- color=(0.0, 1, 0.0, 1))
+ color=(0.5, 0, 1),
+ )
+ item['sale_title_widget'] = ba.textwidget(
+ parent=parent_widget,
+ position=(badge_center[0], badge_center[1] + 12),
+ transition_delay=delay,
+ scale=1.0,
+ maxwidth=badge_rad * 1.6,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ shadow=0.0,
+ flatness=1.0,
+ color=(0, 1, 0),
+ )
+ item['sale_time_widget'] = ba.textwidget(
+ parent=parent_widget,
+ position=(badge_center[0], badge_center[1] - 12),
+ transition_delay=delay,
+ scale=0.7,
+ maxwidth=badge_rad * 1.6,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ shadow=0.0,
+ flatness=1.0,
+ color=(0.0, 1, 0.0, 1),
+ )
diff --git a/assets/src/ba_data/python/bastd/ui/tabs.py b/assets/src/ba_data/python/bastd/ui/tabs.py
index fce571ff..6ded23ee 100644
--- a/assets/src/ba_data/python/bastd/ui/tabs.py
+++ b/assets/src/ba_data/python/bastd/ui/tabs.py
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
@dataclass
class Tab:
"""Info for an individual tab in a TabRow"""
+
button: ba.Widget
position: tuple[float, float]
size: tuple[float, float]
@@ -30,12 +31,14 @@ class TabRow(Generic[T]):
Tabs are indexed by id which is an arbitrary user-provided type.
"""
- def __init__(self,
- parent: ba.Widget,
- tabdefs: list[tuple[T, ba.Lstr]],
- pos: tuple[float, float],
- size: tuple[float, float],
- on_select_call: Callable[[T], None] | None = None) -> None:
+ def __init__(
+ self,
+ parent: ba.Widget,
+ tabdefs: list[tuple[T, ba.Lstr]],
+ pos: tuple[float, float],
+ size: tuple[float, float],
+ on_select_call: Callable[[T], None] | None = None,
+ ) -> None:
if not tabdefs:
raise ValueError('At least one tab def is required')
self.tabs: dict[T, Tab] = {}
@@ -46,16 +49,18 @@ class TabRow(Generic[T]):
for tab_id, tab_label in tabdefs:
pos = (h + tab_spacing * 0.5, tab_pos_v)
size = (tab_button_width - tab_spacing, 50.0)
- btn = ba.buttonwidget(parent=parent,
- position=pos,
- autoselect=True,
- button_type='tab',
- size=size,
- label=tab_label,
- enable_sound=False,
- on_activate_call=ba.Call(
- self._tick_and_call, on_select_call,
- tab_id))
+ btn = ba.buttonwidget(
+ parent=parent,
+ position=pos,
+ autoselect=True,
+ button_type='tab',
+ size=size,
+ label=tab_label,
+ enable_sound=False,
+ on_activate_call=ba.Call(
+ self._tick_and_call, on_select_call, tab_id
+ ),
+ )
h += tab_button_width
self.tabs[tab_id] = Tab(button=btn, position=pos, size=size)
@@ -63,16 +68,21 @@ class TabRow(Generic[T]):
"""Update appearances to make the provided tab appear selected."""
for tab_id, tab in self.tabs.items():
if tab_id == selected_tab_id:
- ba.buttonwidget(edit=tab.button,
- color=(0.5, 0.4, 0.93),
- textcolor=(0.85, 0.75, 0.95)) # lit
+ ba.buttonwidget(
+ edit=tab.button,
+ color=(0.5, 0.4, 0.93),
+ textcolor=(0.85, 0.75, 0.95),
+ ) # lit
else:
- ba.buttonwidget(edit=tab.button,
- color=(0.52, 0.48, 0.63),
- textcolor=(0.65, 0.6, 0.7)) # unlit
+ ba.buttonwidget(
+ edit=tab.button,
+ color=(0.52, 0.48, 0.63),
+ textcolor=(0.65, 0.6, 0.7),
+ ) # unlit
- def _tick_and_call(self, call: Callable[[Any], None] | None,
- arg: Any) -> None:
+ def _tick_and_call(
+ self, call: Callable[[Any], None] | None, arg: Any
+ ) -> None:
ba.playsound(ba.getsound('click01'))
if call is not None:
call(arg)
diff --git a/assets/src/ba_data/python/bastd/ui/teamnamescolors.py b/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
index fc789717..959f444d 100644
--- a/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
+++ b/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
@@ -19,6 +19,7 @@ class TeamNamesColorsWindow(popup.PopupWindow):
def __init__(self, scale_origin: tuple[float, float]):
from ba.internal import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES
+
self._width = 500
self._height = 330
self._transitioning_out = False
@@ -26,15 +27,21 @@ class TeamNamesColorsWindow(popup.PopupWindow):
# Creates our _root_widget.
uiscale = ba.app.ui.uiscale
- scale = (1.69 if uiscale is ba.UIScale.SMALL else
- 1.1 if uiscale is ba.UIScale.MEDIUM else 0.85)
- super().__init__(position=scale_origin,
- size=(self._width, self._height),
- scale=scale)
+ scale = (
+ 1.69
+ if uiscale is ba.UIScale.SMALL
+ else 1.1
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.85
+ )
+ super().__init__(
+ position=scale_origin, size=(self._width, self._height), scale=scale
+ )
appconfig = ba.app.config
self._names = list(
- appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES))
+ appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES)
+ )
# We need to flatten the translation since it will be an
# editable string.
@@ -42,7 +49,8 @@ class TeamNamesColorsWindow(popup.PopupWindow):
ba.Lstr(translate=('teamNames', n)).evaluate() for n in self._names
]
self._colors = list(
- appconfig.get('Custom Team Colors', DEFAULT_TEAM_COLORS))
+ appconfig.get('Custom Team Colors', DEFAULT_TEAM_COLORS)
+ )
self._color_buttons: list[ba.Widget] = []
self._color_text_fields: list[ba.Widget] = []
@@ -54,77 +62,99 @@ class TeamNamesColorsWindow(popup.PopupWindow):
scale=0.7,
on_activate_call=self._reset,
size=(120, 50),
- position=(self._width * 0.5 - 60 * 0.7, self._height - 60))
+ position=(self._width * 0.5 - 60 * 0.7, self._height - 60),
+ )
for i in range(2):
self._color_buttons.append(
- ba.buttonwidget(parent=self.root_widget,
- autoselect=True,
- position=(50, 0 + 195 - 90 * i),
- on_activate_call=ba.Call(self._color_click, i),
- size=(70, 70),
- color=self._colors[i],
- label='',
- button_type='square'))
+ ba.buttonwidget(
+ parent=self.root_widget,
+ autoselect=True,
+ position=(50, 0 + 195 - 90 * i),
+ on_activate_call=ba.Call(self._color_click, i),
+ size=(70, 70),
+ color=self._colors[i],
+ label='',
+ button_type='square',
+ )
+ )
self._color_text_fields.append(
- ba.textwidget(parent=self.root_widget,
- position=(135, 0 + 201 - 90 * i),
- size=(280, 46),
- text=self._names[i],
- h_align='left',
- v_align='center',
- max_chars=self._max_name_length,
- color=self._colors[i],
- description=ba.Lstr(resource='nameText'),
- editable=True,
- padding=4))
- ba.widget(edit=self._color_text_fields[0],
- down_widget=self._color_text_fields[1])
- ba.widget(edit=self._color_text_fields[1],
- up_widget=self._color_text_fields[0])
+ ba.textwidget(
+ parent=self.root_widget,
+ position=(135, 0 + 201 - 90 * i),
+ size=(280, 46),
+ text=self._names[i],
+ h_align='left',
+ v_align='center',
+ max_chars=self._max_name_length,
+ color=self._colors[i],
+ description=ba.Lstr(resource='nameText'),
+ editable=True,
+ padding=4,
+ )
+ )
+ ba.widget(
+ edit=self._color_text_fields[0],
+ down_widget=self._color_text_fields[1],
+ )
+ ba.widget(
+ edit=self._color_text_fields[1],
+ up_widget=self._color_text_fields[0],
+ )
ba.widget(edit=self._color_text_fields[0], up_widget=resetbtn)
- cancelbtn = ba.buttonwidget(parent=self.root_widget,
- label=ba.Lstr(resource='cancelText'),
- autoselect=True,
- on_activate_call=self._on_cancel_press,
- size=(150, 50),
- position=(self._width * 0.5 - 200, 20))
- okbtn = ba.buttonwidget(parent=self.root_widget,
- label=ba.Lstr(resource='okText'),
- autoselect=True,
- on_activate_call=self._ok,
- size=(150, 50),
- position=(self._width * 0.5 + 50, 20))
- ba.containerwidget(edit=self.root_widget,
- selected_child=self._color_buttons[0])
+ cancelbtn = ba.buttonwidget(
+ parent=self.root_widget,
+ label=ba.Lstr(resource='cancelText'),
+ autoselect=True,
+ on_activate_call=self._on_cancel_press,
+ size=(150, 50),
+ position=(self._width * 0.5 - 200, 20),
+ )
+ okbtn = ba.buttonwidget(
+ parent=self.root_widget,
+ label=ba.Lstr(resource='okText'),
+ autoselect=True,
+ on_activate_call=self._ok,
+ size=(150, 50),
+ position=(self._width * 0.5 + 50, 20),
+ )
+ ba.containerwidget(
+ edit=self.root_widget, selected_child=self._color_buttons[0]
+ )
ba.widget(edit=okbtn, left_widget=cancelbtn)
self._update()
def _color_click(self, i: int) -> None:
from bastd.ui.colorpicker import ColorPicker
- ColorPicker(parent=self.root_widget,
- position=self._color_buttons[i].get_screen_space_center(),
- offset=(270.0, 0),
- initial_color=self._colors[i],
- delegate=self,
- tag=i)
+
+ ColorPicker(
+ parent=self.root_widget,
+ position=self._color_buttons[i].get_screen_space_center(),
+ offset=(270.0, 0),
+ initial_color=self._colors[i],
+ delegate=self,
+ tag=i,
+ )
def color_picker_closing(self, picker: ColorPicker) -> None:
"""Called when the color picker is closing."""
- def color_picker_selected_color(self, picker: ColorPicker,
- color: Sequence[float]) -> None:
+ def color_picker_selected_color(
+ self, picker: ColorPicker, color: Sequence[float]
+ ) -> None:
"""Called when a color is selected in the color picker."""
self._colors[picker.get_tag()] = color
self._update()
def _reset(self) -> None:
from ba.internal import DEFAULT_TEAM_NAMES, DEFAULT_TEAM_COLORS
+
for i in range(2):
self._colors[i] = DEFAULT_TEAM_COLORS[i]
- name = ba.Lstr(translate=('teamNames',
- DEFAULT_TEAM_NAMES[i])).evaluate()
+ name = ba.Lstr(
+ translate=('teamNames', DEFAULT_TEAM_NAMES[i])
+ ).evaluate()
if len(name) > self._max_name_length:
print('GOT DEFAULT TEAM NAME LONGER THAN MAX LENGTH')
ba.textwidget(edit=self._color_text_fields[i], text=name)
@@ -133,11 +163,13 @@ class TeamNamesColorsWindow(popup.PopupWindow):
def _update(self) -> None:
for i in range(2):
ba.buttonwidget(edit=self._color_buttons[i], color=self._colors[i])
- ba.textwidget(edit=self._color_text_fields[i],
- color=self._colors[i])
+ ba.textwidget(
+ edit=self._color_text_fields[i], color=self._colors[i]
+ )
def _ok(self) -> None:
from ba.internal import DEFAULT_TEAM_COLORS, DEFAULT_TEAM_NAMES
+
cfg = ba.app.config
# First, determine whether the values here are defaults, in which case
@@ -150,8 +182,9 @@ class TeamNamesColorsWindow(popup.PopupWindow):
for i in range(2):
name = cast(str, ba.textwidget(query=self._color_text_fields[i]))
if not name:
- ba.screenmessage(ba.Lstr(resource='nameNotEmptyText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='nameNotEmptyText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
new_names.append(name)
@@ -161,9 +194,12 @@ class TeamNamesColorsWindow(popup.PopupWindow):
is_default = False
default_team_name = DEFAULT_TEAM_NAMES[i]
default_team_name_translated = ba.Lstr(
- translate=('teamNames', default_team_name)).evaluate()
- if ((new_names[i] != default_team_name
- and new_names[i] != default_team_name_translated)):
+ translate=('teamNames', default_team_name)
+ ).evaluate()
+ if (
+ new_names[i] != default_team_name
+ and new_names[i] != default_team_name_translated
+ ):
is_default = False
if is_default:
diff --git a/assets/src/ba_data/python/bastd/ui/telnet.py b/assets/src/ba_data/python/bastd/ui/telnet.py
index 0c7080ca..80c01274 100644
--- a/assets/src/ba_data/python/bastd/ui/telnet.py
+++ b/assets/src/ba_data/python/bastd/ui/telnet.py
@@ -17,31 +17,45 @@ class TelnetAccessRequestWindow(ba.Window):
text = ba.Lstr(resource='telnetAccessText')
uiscale = ba.app.ui.uiscale
- super().__init__(root_widget=ba.containerwidget(
- size=(width, height + 40),
- transition='in_right',
- scale=(1.7 if uiscale is ba.UIScale.SMALL else
- 1.3 if uiscale is ba.UIScale.MEDIUM else 1.0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(width, height + 40),
+ transition='in_right',
+ scale=(
+ 1.7
+ if uiscale is ba.UIScale.SMALL
+ else 1.3
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
+ )
+ )
padding = 20
- ba.textwidget(parent=self._root_widget,
- position=(padding, padding + 33),
- size=(width - 2 * padding, height - 2 * padding),
- h_align='center',
- v_align='top',
- text=text)
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(20, 20),
- size=(140, 50),
- label=ba.Lstr(resource='denyText'),
- on_activate_call=self._cancel)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(padding, padding + 33),
+ size=(width - 2 * padding, height - 2 * padding),
+ h_align='center',
+ v_align='top',
+ text=text,
+ )
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(20, 20),
+ size=(140, 50),
+ label=ba.Lstr(resource='denyText'),
+ on_activate_call=self._cancel,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
ba.containerwidget(edit=self._root_widget, selected_child=btn)
- ba.buttonwidget(parent=self._root_widget,
- position=(width - 155, 20),
- size=(140, 50),
- label=ba.Lstr(resource='allowText'),
- on_activate_call=self._ok)
+ ba.buttonwidget(
+ parent=self._root_widget,
+ position=(width - 155, 20),
+ size=(140, 50),
+ label=ba.Lstr(resource='allowText'),
+ on_activate_call=self._ok,
+ )
def _cancel(self) -> None:
ba.containerwidget(edit=self._root_widget, transition='out_right')
diff --git a/assets/src/ba_data/python/bastd/ui/tournamententry.py b/assets/src/ba_data/python/bastd/ui/tournamententry.py
index 389901ae..cf461912 100644
--- a/assets/src/ba_data/python/bastd/ui/tournamententry.py
+++ b/assets/src/ba_data/python/bastd/ui/tournamententry.py
@@ -17,14 +17,16 @@ if TYPE_CHECKING:
class TournamentEntryWindow(popup.PopupWindow):
"""Popup window for entering tournaments."""
- def __init__(self,
- tournament_id: str,
- tournament_activity: ba.Activity | None = None,
- position: tuple[float, float] = (0.0, 0.0),
- delegate: Any = None,
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- on_close_call: Callable[[], Any] | None = None):
+ def __init__(
+ self,
+ tournament_id: str,
+ tournament_activity: ba.Activity | None = None,
+ position: tuple[float, float] = (0.0, 0.0),
+ delegate: Any = None,
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ on_close_call: Callable[[], Any] | None = None,
+ ):
# Needs some tidying.
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -32,8 +34,9 @@ class TournamentEntryWindow(popup.PopupWindow):
ba.set_analytics_screen('Tournament Entry Window')
self._tournament_id = tournament_id
- self._tournament_info = (
- ba.app.accounts_v1.tournament_info[self._tournament_id])
+ self._tournament_info = ba.app.accounts_v1.tournament_info[
+ self._tournament_id
+ ]
# Set a few vars depending on the tourney fee.
self._fee = self._tournament_info['fee']
@@ -61,8 +64,13 @@ class TournamentEntryWindow(popup.PopupWindow):
self._on_close_call = on_close_call
if scale is None:
uiscale = ba.app.ui.uiscale
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._delegate = delegate
self._transitioning_out = False
@@ -74,13 +82,15 @@ class TournamentEntryWindow(popup.PopupWindow):
bg_color = (0.5, 0.4, 0.6)
# Creates our root_widget.
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color,
- offset=offset,
- toolbar_visibility='menu_currency')
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ offset=offset,
+ toolbar_visibility='menu_currency',
+ )
self._last_ad_press_time = -9999.0
self._last_ticket_press_time = -9999.0
@@ -88,20 +98,22 @@ class TournamentEntryWindow(popup.PopupWindow):
self._launched = False
# Show the ad button only if we support ads *and* it has a level 1 fee.
- self._do_ad_btn = (ba.internal.has_video_ads() and self._allow_ads)
+ self._do_ad_btn = ba.internal.has_video_ads() and self._allow_ads
x_offs = 0 if self._do_ad_btn else 85
- self._cancel_button = ba.buttonwidget(parent=self.root_widget,
- position=(20, self._height - 34),
- size=(60, 60),
- scale=0.5,
- label='',
- color=bg_color,
- on_activate_call=self._on_cancel,
- autoselect=True,
- icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ self._cancel_button = ba.buttonwidget(
+ parent=self.root_widget,
+ position=(20, self._height - 34),
+ size=(60, 60),
+ scale=0.5,
+ label='',
+ color=bg_color,
+ on_activate_call=self._on_cancel,
+ autoselect=True,
+ icon=ba.gettexture('crossOut'),
+ iconscale=1.2,
+ )
self._title_text = ba.textwidget(
parent=self.root_widget,
@@ -112,7 +124,8 @@ class TournamentEntryWindow(popup.PopupWindow):
scale=0.6,
text=ba.Lstr(resource='tournamentEntryText'),
maxwidth=180,
- color=(1, 1, 1, 0.4))
+ color=(1, 1, 1, 0.4),
+ )
btn = self._pay_with_tickets_button = ba.buttonwidget(
parent=self.root_widget,
@@ -121,14 +134,17 @@ class TournamentEntryWindow(popup.PopupWindow):
button_type='square',
size=(120, 120),
label='',
- on_activate_call=self._on_pay_with_tickets_press)
+ on_activate_call=self._on_pay_with_tickets_press,
+ )
self._ticket_img_pos = (50 + x_offs, 94)
self._ticket_img_pos_free = (50 + x_offs, 80)
- self._ticket_img = ba.imagewidget(parent=self.root_widget,
- draw_controller=btn,
- size=(80, 80),
- position=self._ticket_img_pos,
- texture=ba.gettexture('tickets'))
+ self._ticket_img = ba.imagewidget(
+ parent=self.root_widget,
+ draw_controller=btn,
+ size=(80, 80),
+ position=self._ticket_img_pos,
+ texture=ba.gettexture('tickets'),
+ )
self._ticket_cost_text_position = (87 + x_offs, 88)
self._ticket_cost_text_position_free = (87 + x_offs, 120)
self._ticket_cost_text = ba.textwidget(
@@ -141,7 +157,8 @@ class TournamentEntryWindow(popup.PopupWindow):
scale=0.6,
text='',
maxwidth=95,
- color=(0, 1, 0))
+ color=(0, 1, 0),
+ )
self._free_plays_remaining_text = ba.textwidget(
parent=self.root_widget,
draw_controller=btn,
@@ -152,7 +169,8 @@ class TournamentEntryWindow(popup.PopupWindow):
scale=0.33,
text='',
maxwidth=95,
- color=(0, 0.8, 0))
+ color=(0, 0.8, 0),
+ )
self._pay_with_ad_btn: ba.Widget | None
if self._do_ad_btn:
btn = self._pay_with_ad_btn = ba.buttonwidget(
@@ -162,22 +180,27 @@ class TournamentEntryWindow(popup.PopupWindow):
button_type='square',
size=(120, 120),
label='',
- on_activate_call=self._on_pay_with_ad_press)
- self._pay_with_ad_img = ba.imagewidget(parent=self.root_widget,
- draw_controller=btn,
- size=(80, 80),
- position=(210, 94),
- texture=ba.gettexture('tv'))
+ on_activate_call=self._on_pay_with_ad_press,
+ )
+ self._pay_with_ad_img = ba.imagewidget(
+ parent=self.root_widget,
+ draw_controller=btn,
+ size=(80, 80),
+ position=(210, 94),
+ texture=ba.gettexture('tv'),
+ )
self._ad_text_position = (251, 88)
self._ad_text_position_remaining = (251, 92)
have_ad_tries_remaining = (
- self._tournament_info['adTriesRemaining'] is not None)
+ self._tournament_info['adTriesRemaining'] is not None
+ )
self._ad_text = ba.textwidget(
parent=self.root_widget,
draw_controller=btn,
position=self._ad_text_position_remaining
- if have_ad_tries_remaining else self._ad_text_position,
+ if have_ad_tries_remaining
+ else self._ad_text_position,
size=(0, 0),
h_align='center',
v_align='center',
@@ -186,10 +209,13 @@ class TournamentEntryWindow(popup.PopupWindow):
# specifically says 'Ad' in it.
text=ba.Lstr(resource='watchAnAdText'),
maxwidth=95,
- color=(0, 1, 0))
+ color=(0, 1, 0),
+ )
ad_plays_remaining_text = (
- '' if not have_ad_tries_remaining else '' +
- str(self._tournament_info['adTriesRemaining']))
+ ''
+ if not have_ad_tries_remaining
+ else '' + str(self._tournament_info['adTriesRemaining'])
+ )
self._ad_plays_remaining_text = ba.textwidget(
parent=self.root_widget,
draw_controller=btn,
@@ -200,18 +226,22 @@ class TournamentEntryWindow(popup.PopupWindow):
scale=0.33,
text=ad_plays_remaining_text,
maxwidth=95,
- color=(0, 0.8, 0))
+ color=(0, 0.8, 0),
+ )
- ba.textwidget(parent=self.root_widget,
- position=(self._width * 0.5, 120),
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=0.6,
- text=ba.Lstr(resource='orText',
- subs=[('${A}', ''), ('${B}', '')]),
- maxwidth=35,
- color=(1, 1, 1, 0.5))
+ ba.textwidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5, 120),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=0.6,
+ text=ba.Lstr(
+ resource='orText', subs=[('${A}', ''), ('${B}', '')]
+ ),
+ maxwidth=35,
+ color=(1, 1, 1, 0.5),
+ )
else:
self._pay_with_ad_btn = None
@@ -228,7 +258,8 @@ class TournamentEntryWindow(popup.PopupWindow):
textcolor=(0.2, 1, 0.2),
label=ba.charstr(ba.SpecialChar.TICKET),
color=(0.65, 0.5, 0.8),
- on_activate_call=self._on_get_tickets_press)
+ on_activate_call=self._on_get_tickets_press,
+ )
else:
self._ticket_count_text = ba.textwidget(
parent=self.root_widget,
@@ -236,12 +267,14 @@ class TournamentEntryWindow(popup.PopupWindow):
position=(self._width - 190 + 125, self._height - 34),
color=(0.2, 1, 0.2),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
self._seconds_remaining = None
- ba.containerwidget(edit=self.root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
# Let's also ask the server for info about this tournament
# (time remaining, etc) so we can show the user time remaining,
@@ -269,24 +302,39 @@ class TournamentEntryWindow(popup.PopupWindow):
scale=0.45,
flatness=1.0,
maxwidth=100,
- color=(0.7, 0.7, 0.7))
+ color=(0.7, 0.7, 0.7),
+ )
self._last_query_time: float | None = None
# If there seems to be a relatively-recent valid cached info for this
# tournament, use it. Otherwise we'll kick off a query ourselves.
- if (self._tournament_id in ba.app.accounts_v1.tournament_info
- and ba.app.accounts_v1.tournament_info[
- self._tournament_id]['valid']
- and (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) -
- ba.app.accounts_v1.tournament_info[self._tournament_id]
- ['timeReceived'] < 1000 * 60 * 5)):
+ if (
+ self._tournament_id in ba.app.accounts_v1.tournament_info
+ and ba.app.accounts_v1.tournament_info[self._tournament_id]['valid']
+ and (
+ ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
+ - ba.app.accounts_v1.tournament_info[self._tournament_id][
+ 'timeReceived'
+ ]
+ < 1000 * 60 * 5
+ )
+ ):
try:
info = ba.app.accounts_v1.tournament_info[self._tournament_id]
self._seconds_remaining = max(
- 0, info['timeRemaining'] - int(
- (ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
- - info['timeReceived']) / 1000))
+ 0,
+ info['timeRemaining']
+ - int(
+ (
+ ba.time(
+ ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS
+ )
+ - info['timeReceived']
+ )
+ / 1000
+ ),
+ )
self._have_valid_data = True
self._last_query_time = ba.time(ba.TimeType.REAL)
except Exception:
@@ -297,22 +345,26 @@ class TournamentEntryWindow(popup.PopupWindow):
self._fg_state = ba.app.fg_state
self._running_query = False
- self._update_timer = ba.Timer(1.0,
- ba.WeakCall(self._update),
- repeat=True,
- timetype=ba.TimeType.REAL)
+ self._update_timer = ba.Timer(
+ 1.0,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL,
+ )
self._update()
self._restore_state()
- def _on_tournament_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_tournament_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
accounts = ba.app.accounts_v1
self._running_query = False
if data is not None:
data = data['t'] # This used to be the whole payload.
accounts.cache_tournament_info(data)
self._seconds_remaining = accounts.tournament_info[
- self._tournament_id]['timeRemaining']
+ self._tournament_id
+ ]['timeRemaining']
self._have_valid_data = True
def _save_state(self) -> None:
@@ -347,21 +399,25 @@ class TournamentEntryWindow(popup.PopupWindow):
# If we need to run another tournament query, do so.
if not self._running_query and (
- (self._last_query_time is None) or (not self._have_valid_data) or
- (ba.time(ba.TimeType.REAL) - self._last_query_time > 30.0)):
+ (self._last_query_time is None)
+ or (not self._have_valid_data)
+ or (ba.time(ba.TimeType.REAL) - self._last_query_time > 30.0)
+ ):
ba.internal.tournament_query(
args={
- 'source':
- 'entry window' if self._tournament_activity is None
- else 'retry entry window'
+ 'source': 'entry window'
+ if self._tournament_activity is None
+ else 'retry entry window'
},
- callback=ba.WeakCall(self._on_tournament_query_response))
+ callback=ba.WeakCall(self._on_tournament_query_response),
+ )
self._last_query_time = ba.time(ba.TimeType.REAL)
self._running_query = True
# Grab the latest info on our tourney.
self._tournament_info = ba.app.accounts_v1.tournament_info[
- self._tournament_id]
+ self._tournament_id
+ ]
# If we don't have valid data always show a '-' for time.
if not self._have_valid_data:
@@ -369,70 +425,106 @@ class TournamentEntryWindow(popup.PopupWindow):
else:
if self._seconds_remaining is not None:
self._seconds_remaining = max(0, self._seconds_remaining - 1)
- ba.textwidget(edit=self._time_remaining_text,
- text=ba.timestring(
- self._seconds_remaining * 1000,
- centi=False,
- timeformat=ba.TimeFormat.MILLISECONDS))
+ ba.textwidget(
+ edit=self._time_remaining_text,
+ text=ba.timestring(
+ self._seconds_remaining * 1000,
+ centi=False,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ ),
+ )
# Keep price up-to-date and update the button with it.
self._purchase_price = ba.internal.get_v1_account_misc_read_val(
- self._purchase_price_name, None)
+ self._purchase_price_name, None
+ )
ba.textwidget(
edit=self._ticket_cost_text,
- text=(ba.Lstr(resource='getTicketsWindow.freeText')
- if self._purchase_price == 0 else ba.Lstr(
- resource='getTicketsWindow.ticketsText',
- subs=[('${COUNT}', str(self._purchase_price)
- if self._purchase_price is not None else '?')])),
+ text=(
+ ba.Lstr(resource='getTicketsWindow.freeText')
+ if self._purchase_price == 0
+ else ba.Lstr(
+ resource='getTicketsWindow.ticketsText',
+ subs=[
+ (
+ '${COUNT}',
+ str(self._purchase_price)
+ if self._purchase_price is not None
+ else '?',
+ )
+ ],
+ )
+ ),
position=self._ticket_cost_text_position_free
- if self._purchase_price == 0 else self._ticket_cost_text_position,
- scale=1.0 if self._purchase_price == 0 else 0.6)
+ if self._purchase_price == 0
+ else self._ticket_cost_text_position,
+ scale=1.0 if self._purchase_price == 0 else 0.6,
+ )
ba.textwidget(
edit=self._free_plays_remaining_text,
- text='' if
- (self._tournament_info['freeTriesRemaining'] in [None, 0]
- or self._purchase_price != 0) else '' +
- str(self._tournament_info['freeTriesRemaining']))
+ text=''
+ if (
+ self._tournament_info['freeTriesRemaining'] in [None, 0]
+ or self._purchase_price != 0
+ )
+ else '' + str(self._tournament_info['freeTriesRemaining']),
+ )
- ba.imagewidget(edit=self._ticket_img,
- opacity=0.2 if self._purchase_price == 0 else 1.0,
- position=self._ticket_img_pos_free
- if self._purchase_price == 0 else self._ticket_img_pos)
+ ba.imagewidget(
+ edit=self._ticket_img,
+ opacity=0.2 if self._purchase_price == 0 else 1.0,
+ position=self._ticket_img_pos_free
+ if self._purchase_price == 0
+ else self._ticket_img_pos,
+ )
if self._do_ad_btn:
enabled = ba.internal.have_incentivized_ad()
have_ad_tries_remaining = (
self._tournament_info['adTriesRemaining'] is not None
- and self._tournament_info['adTriesRemaining'] > 0)
- ba.textwidget(edit=self._ad_text,
- position=self._ad_text_position_remaining if
- have_ad_tries_remaining else self._ad_text_position,
- color=(0, 1, 0) if enabled else (0.5, 0.5, 0.5))
- ba.imagewidget(edit=self._pay_with_ad_img,
- opacity=1.0 if enabled else 0.2)
- ba.buttonwidget(edit=self._pay_with_ad_btn,
- color=(0.5, 0.7, 0.2) if enabled else
- (0.5, 0.5, 0.5))
+ and self._tournament_info['adTriesRemaining'] > 0
+ )
+ ba.textwidget(
+ edit=self._ad_text,
+ position=self._ad_text_position_remaining
+ if have_ad_tries_remaining
+ else self._ad_text_position,
+ color=(0, 1, 0) if enabled else (0.5, 0.5, 0.5),
+ )
+ ba.imagewidget(
+ edit=self._pay_with_ad_img, opacity=1.0 if enabled else 0.2
+ )
+ ba.buttonwidget(
+ edit=self._pay_with_ad_btn,
+ color=(0.5, 0.7, 0.2) if enabled else (0.5, 0.5, 0.5),
+ )
ad_plays_remaining_text = (
- '' if not have_ad_tries_remaining else '' +
- str(self._tournament_info['adTriesRemaining']))
- ba.textwidget(edit=self._ad_plays_remaining_text,
- text=ad_plays_remaining_text,
- color=(0, 0.8, 0) if enabled else (0.4, 0.4, 0.4))
+ ''
+ if not have_ad_tries_remaining
+ else '' + str(self._tournament_info['adTriesRemaining'])
+ )
+ ba.textwidget(
+ edit=self._ad_plays_remaining_text,
+ text=ad_plays_remaining_text,
+ color=(0, 0.8, 0) if enabled else (0.4, 0.4, 0.4),
+ )
try:
t_str = str(ba.internal.get_v1_account_ticket_count())
except Exception:
t_str = '?'
if self._get_tickets_button:
- ba.buttonwidget(edit=self._get_tickets_button,
- label=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ ba.buttonwidget(
+ edit=self._get_tickets_button,
+ label=ba.charstr(ba.SpecialChar.TICKET) + t_str,
+ )
if self._ticket_count_text:
- ba.textwidget(edit=self._ticket_count_text,
- text=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ ba.textwidget(
+ edit=self._ticket_count_text,
+ text=ba.charstr(ba.SpecialChar.TICKET) + t_str,
+ )
def _launch(self) -> None:
if self._launched:
@@ -443,17 +535,23 @@ class TournamentEntryWindow(popup.PopupWindow):
# If they gave us an existing activity, just restart it.
if self._tournament_activity is not None:
try:
- ba.timer(0.1,
- lambda: ba.playsound(ba.getsound('cashRegister')),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1,
+ lambda: ba.playsound(ba.getsound('cashRegister')),
+ timetype=ba.TimeType.REAL,
+ )
with ba.Context(self._tournament_activity):
- self._tournament_activity.end({'outcome': 'restart'},
- force=True)
+ self._tournament_activity.end(
+ {'outcome': 'restart'}, force=True
+ )
ba.timer(0.3, self._transition_out, timetype=ba.TimeType.REAL)
launched = True
- ba.screenmessage(ba.Lstr(translate=('serverResponses',
- 'Entering tournament...')),
- color=(0, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ translate=('serverResponses', 'Entering tournament...')
+ ),
+ color=(0, 1, 0),
+ )
# We can hit exceptions here if _tournament_activity ends before
# our restart attempt happens.
@@ -466,9 +564,11 @@ class TournamentEntryWindow(popup.PopupWindow):
# If we had no existing activity (or were unable to restart it)
# launch a new session.
if not launched:
- ba.timer(0.1,
- lambda: ba.playsound(ba.getsound('cashRegister')),
- timetype=ba.TimeType.REAL)
+ ba.timer(
+ 0.1,
+ lambda: ba.playsound(ba.getsound('cashRegister')),
+ timetype=ba.TimeType.REAL,
+ )
ba.timer(
1.0,
lambda: ba.app.launch_coop_game(
@@ -476,13 +576,18 @@ class TournamentEntryWindow(popup.PopupWindow):
args={
'min_players': self._tournament_info['minPlayers'],
'max_players': self._tournament_info['maxPlayers'],
- 'tournament_id': self._tournament_id
- }),
- timetype=ba.TimeType.REAL)
+ 'tournament_id': self._tournament_id,
+ },
+ ),
+ timetype=ba.TimeType.REAL,
+ )
ba.timer(0.7, self._transition_out, timetype=ba.TimeType.REAL)
- ba.screenmessage(ba.Lstr(translate=('serverResponses',
- 'Entering tournament...')),
- color=(0, 1, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ translate=('serverResponses', 'Entering tournament...')
+ ),
+ color=(0, 1, 0),
+ )
def _on_pay_with_tickets_press(self) -> None:
from bastd.ui import getcurrency
@@ -492,22 +597,25 @@ class TournamentEntryWindow(popup.PopupWindow):
return
if not self._have_valid_data:
- ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
# If we don't have a price.
if self._purchase_price is None:
- ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
# Deny if it looks like the tourney has ended.
if self._seconds_remaining == 0:
- ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
@@ -531,11 +639,13 @@ class TournamentEntryWindow(popup.PopupWindow):
ba.internal.in_game_purchase(self._purchase_name, ticket_cost)
self._entering = True
- ba.internal.add_transaction({
- 'type': 'ENTER_TOURNAMENT',
- 'fee': self._fee,
- 'tournamentID': self._tournament_id
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'ENTER_TOURNAMENT',
+ 'fee': self._fee,
+ 'tournamentID': self._tournament_id,
+ }
+ )
ba.internal.run_transactions()
self._launch()
@@ -546,24 +656,27 @@ class TournamentEntryWindow(popup.PopupWindow):
return
if not self._have_valid_data:
- ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
# Deny if it looks like the tourney has ended.
if self._seconds_remaining == 0:
- ba.screenmessage(ba.Lstr(resource='tournamentEndedText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)
+ )
ba.playsound(ba.getsound('error'))
return
cur_time = ba.time(ba.TimeType.REAL)
if cur_time - self._last_ad_press_time > 5.0:
self._last_ad_press_time = cur_time
- ba.app.ads.show_ad_2('tournament_entry',
- on_completion_call=ba.WeakCall(
- self._on_ad_complete))
+ ba.app.ads.show_ad_2(
+ 'tournament_entry',
+ on_completion_call=ba.WeakCall(self._on_ad_complete),
+ )
def _on_ad_complete(self, actually_showed: bool) -> None:
@@ -588,11 +701,13 @@ class TournamentEntryWindow(popup.PopupWindow):
return
self._entering = True
- ba.internal.add_transaction({
- 'type': 'ENTER_TOURNAMENT',
- 'fee': 'ad',
- 'tournamentID': self._tournament_id
- })
+ ba.internal.add_transaction(
+ {
+ 'type': 'ENTER_TOURNAMENT',
+ 'fee': 'ad',
+ 'tournamentID': self._tournament_id,
+ }
+ )
ba.internal.run_transactions()
self._launch()
@@ -605,8 +720,9 @@ class TournamentEntryWindow(popup.PopupWindow):
# Bring up get-tickets window and then kill ourself (we're on the
# overlay layer so we'd show up above it).
- getcurrency.GetCurrencyWindow(modal=True,
- origin_widget=self._get_tickets_button)
+ getcurrency.GetCurrencyWindow(
+ modal=True, origin_widget=self._get_tickets_button
+ )
self._transition_out()
def _on_cancel(self) -> None:
@@ -614,11 +730,15 @@ class TournamentEntryWindow(popup.PopupWindow):
# Don't allow canceling for several seconds after poking an enter
# button if it looks like we're waiting on a purchase or entering
# the tournament.
- if ((ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) -
- self._last_ticket_press_time < 6000)
- and (ba.internal.have_outstanding_transactions()
- or ba.internal.get_purchased(self._purchase_name)
- or self._entering)):
+ if (
+ ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS)
+ - self._last_ticket_press_time
+ < 6000
+ ) and (
+ ba.internal.have_outstanding_transactions()
+ or ba.internal.get_purchased(self._purchase_name)
+ or self._entering
+ ):
ba.playsound(ba.getsound('error'))
return
self._transition_out()
diff --git a/assets/src/ba_data/python/bastd/ui/tournamentscores.py b/assets/src/ba_data/python/bastd/ui/tournamentscores.py
index 36625125..418f5788 100644
--- a/assets/src/ba_data/python/bastd/ui/tournamentscores.py
+++ b/assets/src/ba_data/python/bastd/ui/tournamentscores.py
@@ -17,16 +17,18 @@ if TYPE_CHECKING:
class TournamentScoresWindow(popup_ui.PopupWindow):
"""Window for viewing tournament scores."""
- def __init__(self,
- tournament_id: str,
- tournament_activity: ba.GameActivity | None = None,
- position: tuple[float, float] = (0.0, 0.0),
- scale: float | None = None,
- offset: tuple[float, float] = (0.0, 0.0),
- tint_color: Sequence[float] = (1.0, 1.0, 1.0),
- tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
- selected_character: str | None = None,
- on_close_call: Callable[[], Any] | None = None):
+ def __init__(
+ self,
+ tournament_id: str,
+ tournament_activity: ba.GameActivity | None = None,
+ position: tuple[float, float] = (0.0, 0.0),
+ scale: float | None = None,
+ offset: tuple[float, float] = (0.0, 0.0),
+ tint_color: Sequence[float] = (1.0, 1.0, 1.0),
+ tint2_color: Sequence[float] = (1.0, 1.0, 1.0),
+ selected_character: str | None = None,
+ on_close_call: Callable[[], Any] | None = None,
+ ):
del tournament_activity # unused arg
del tint_color # unused arg
@@ -37,22 +39,34 @@ class TournamentScoresWindow(popup_ui.PopupWindow):
self._on_close_call = on_close_call
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 400
- self._height = (300 if uiscale is ba.UIScale.SMALL else
- 370 if uiscale is ba.UIScale.MEDIUM else 450)
+ self._height = (
+ 300
+ if uiscale is ba.UIScale.SMALL
+ else 370
+ if uiscale is ba.UIScale.MEDIUM
+ else 450
+ )
bg_color = (0.5, 0.4, 0.6)
# creates our _root_widget
- super().__init__(position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color,
- offset=offset)
+ super().__init__(
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ offset=offset,
+ )
# app = ba.app
@@ -66,7 +80,8 @@ class TournamentScoresWindow(popup_ui.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
self._title_text = ba.textwidget(
parent=self.root_widget,
@@ -77,38 +92,46 @@ class TournamentScoresWindow(popup_ui.PopupWindow):
scale=0.6,
text=ba.Lstr(resource='tournamentStandingsText'),
maxwidth=200,
- color=(1, 1, 1, 0.4))
+ color=(1, 1, 1, 0.4),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._width - 60,
- self._height - 70),
- position=(30, 30),
- highlight=False,
- simple_culling_v=10)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._width - 60, self._height - 70),
+ position=(30, 30),
+ highlight=False,
+ simple_culling_v=10,
+ )
ba.widget(edit=self._scrollwidget, autoselect=True)
self._loading_text = ba.textwidget(
parent=self._scrollwidget,
scale=0.5,
- text=ba.Lstr(value='${A}...',
- subs=[('${A}', ba.Lstr(resource='loadingText'))]),
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='loadingText'))],
+ ),
size=(self._width - 60, 100),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
- ba.containerwidget(edit=self.root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
- ba.internal.tournament_query(args={
- 'tournamentIDs': [tournament_id],
- 'numScores': 50,
- 'source': 'scores window'
- },
- callback=ba.WeakCall(
- self._on_tournament_query_response))
+ ba.internal.tournament_query(
+ args={
+ 'tournamentIDs': [tournament_id],
+ 'numScores': 50,
+ 'source': 'scores window',
+ },
+ callback=ba.WeakCall(self._on_tournament_query_response),
+ )
- def _on_tournament_query_response(self,
- data: dict[str, Any] | None) -> None:
+ def _on_tournament_query_response(
+ self, data: dict[str, Any] | None
+ ) -> None:
if data is not None:
# this used to be the whole payload
data_t: list[dict[str, Any]] = data['t']
@@ -117,52 +140,62 @@ class TournamentScoresWindow(popup_ui.PopupWindow):
if data_t[0]['scores']:
self._loading_text.delete()
else:
- ba.textwidget(edit=self._loading_text,
- text=ba.Lstr(resource='noScoresYetText'))
+ ba.textwidget(
+ edit=self._loading_text,
+ text=ba.Lstr(resource='noScoresYetText'),
+ )
incr = 30
sub_width = self._width - 90
sub_height = 30 + len(data_t[0]['scores']) * incr
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(sub_width,
- sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(sub_width, sub_height),
+ background=False,
+ )
for i, entry in enumerate(data_t[0]['scores']):
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.1 - 5,
- sub_height - 20 - incr * i),
- maxwidth=20,
- scale=0.5,
- color=(0.6, 0.6, 0.7),
- flatness=1.0,
- shadow=0.0,
- text=str(i + 1),
- size=(0, 0),
- h_align='right',
- v_align='center')
-
ba.textwidget(
parent=self._subcontainer,
- position=(sub_width * 0.25 - 2,
- sub_height - 20 - incr * i),
+ position=(sub_width * 0.1 - 5, sub_height - 20 - incr * i),
+ maxwidth=20,
+ scale=0.5,
+ color=(0.6, 0.6, 0.7),
+ flatness=1.0,
+ shadow=0.0,
+ text=str(i + 1),
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ )
+
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.25 - 2, sub_height - 20 - incr * i),
maxwidth=sub_width * 0.24,
color=(0.9, 1.0, 0.9),
flatness=1.0,
shadow=0.0,
scale=0.6,
- text=(ba.timestring(entry[0] * 10,
- centi=True,
- timeformat=ba.TimeFormat.MILLISECONDS)
- if data_t[0]['scoreType'] == 'time' else str(
- entry[0])),
+ text=(
+ ba.timestring(
+ entry[0] * 10,
+ centi=True,
+ timeformat=ba.TimeFormat.MILLISECONDS,
+ )
+ if data_t[0]['scoreType'] == 'time'
+ else str(entry[0])
+ ),
size=(0, 0),
h_align='center',
- v_align='center')
+ v_align='center',
+ )
txt = ba.textwidget(
parent=self._subcontainer,
- position=(sub_width * 0.25,
- sub_height - 20 - incr * i - (0.5 / 0.7) * incr),
+ position=(
+ sub_width * 0.25,
+ sub_height - 20 - incr * i - (0.5 / 0.7) * incr,
+ ),
maxwidth=sub_width * 0.6,
scale=0.7,
flatness=1.0,
@@ -174,24 +207,31 @@ class TournamentScoresWindow(popup_ui.PopupWindow):
extra_touch_border_scale=0.0,
size=((sub_width * 0.6) / 0.7, incr / 0.7),
h_align='left',
- v_align='center')
+ v_align='center',
+ )
- ba.textwidget(edit=txt,
- on_activate_call=ba.Call(self._show_player_info,
- entry, txt))
+ ba.textwidget(
+ edit=txt,
+ on_activate_call=ba.Call(
+ self._show_player_info, entry, txt
+ ),
+ )
if i == 0:
ba.widget(edit=txt, up_widget=self._cancel_button)
def _show_player_info(self, entry: Any, textwidget: ba.Widget) -> None:
from bastd.ui.account.viewer import AccountViewerWindow
+
# for the moment we only work if a single player-info is present..
if len(entry[2]) != 1:
ba.playsound(ba.getsound('error'))
return
ba.playsound(ba.getsound('swish'))
- AccountViewerWindow(account_id=entry[2][0].get('a', None),
- profile_id=entry[2][0].get('p', None),
- position=textwidget.get_screen_space_center())
+ AccountViewerWindow(
+ account_id=entry[2][0].get('a', None),
+ profile_id=entry[2][0].get('p', None),
+ position=textwidget.get_screen_space_center(),
+ )
self._transition_out()
def _on_cancel_press(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/trophies.py b/assets/src/ba_data/python/bastd/ui/trophies.py
index ec17007d..f263cc23 100644
--- a/assets/src/ba_data/python/bastd/ui/trophies.py
+++ b/assets/src/ba_data/python/bastd/ui/trophies.py
@@ -16,25 +16,34 @@ if TYPE_CHECKING:
class TrophiesWindow(popup.PopupWindow):
"""Popup window for viewing trophies."""
- def __init__(self,
- position: tuple[float, float],
- data: dict[str, Any],
- scale: float | None = None):
+ def __init__(
+ self,
+ position: tuple[float, float],
+ data: dict[str, Any],
+ scale: float | None = None,
+ ):
self._data = data
uiscale = ba.app.ui.uiscale
if scale is None:
- scale = (2.3 if uiscale is ba.UIScale.SMALL else
- 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23)
+ scale = (
+ 2.3
+ if uiscale is ba.UIScale.SMALL
+ else 1.65
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.23
+ )
self._transitioning_out = False
self._width = 300
self._height = 300
bg_color = (0.5, 0.4, 0.6)
- popup.PopupWindow.__init__(self,
- position=position,
- size=(self._width, self._height),
- scale=scale,
- bg_color=bg_color)
+ popup.PopupWindow.__init__(
+ self,
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ )
self._cancel_button = ba.buttonwidget(
parent=self.root_widget,
@@ -46,28 +55,32 @@ class TrophiesWindow(popup.PopupWindow):
on_activate_call=self._on_cancel_press,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
- self._title_text = ba.textwidget(parent=self.root_widget,
- position=(self._width * 0.5,
- self._height - 20),
- size=(0, 0),
- h_align='center',
- v_align='center',
- scale=0.6,
- text=ba.Lstr(resource='trophiesText'),
- maxwidth=200,
- color=(1, 1, 1, 0.4))
+ self._title_text = ba.textwidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5, self._height - 20),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=0.6,
+ text=ba.Lstr(resource='trophiesText'),
+ maxwidth=200,
+ color=(1, 1, 1, 0.4),
+ )
- self._scrollwidget = ba.scrollwidget(parent=self.root_widget,
- size=(self._width - 60,
- self._height - 70),
- position=(30, 30),
- capture_arrows=True)
+ self._scrollwidget = ba.scrollwidget(
+ parent=self.root_widget,
+ size=(self._width - 60, self._height - 70),
+ position=(30, 30),
+ capture_arrows=True,
+ )
ba.widget(edit=self._scrollwidget, autoselect=True)
- ba.containerwidget(edit=self.root_widget,
- cancel_button=self._cancel_button)
+ ba.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
incr = 31
sub_width = self._width - 90
@@ -76,101 +89,119 @@ class TrophiesWindow(popup.PopupWindow):
sub_height = 40 + len(trophy_types) * incr
eq_text = ba.Lstr(
- resource='coopSelectWindow.powerRankingPointsEqualsText').evaluate(
- )
+ resource='coopSelectWindow.powerRankingPointsEqualsText'
+ ).evaluate()
- self._subcontainer = ba.containerwidget(parent=self._scrollwidget,
- size=(sub_width, sub_height),
- background=False)
+ self._subcontainer = ba.containerwidget(
+ parent=self._scrollwidget,
+ size=(sub_width, sub_height),
+ background=False,
+ )
total_pts = 0
multi_txt = ba.Lstr(
- resource='coopSelectWindow.powerRankingPointsMultText').evaluate()
+ resource='coopSelectWindow.powerRankingPointsMultText'
+ ).evaluate()
- total_pts += self._create_trophy_type_widgets(eq_text, incr, multi_txt,
- sub_height, sub_width,
- trophy_types)
+ total_pts += self._create_trophy_type_widgets(
+ eq_text, incr, multi_txt, sub_height, sub_width, trophy_types
+ )
ba.textwidget(
parent=self._subcontainer,
- position=(sub_width * 1.0,
- sub_height - 20 - incr * len(trophy_types)),
+ position=(
+ sub_width * 1.0,
+ sub_height - 20 - incr * len(trophy_types),
+ ),
maxwidth=sub_width * 0.5,
scale=0.7,
color=(0.7, 0.8, 1.0),
flatness=1.0,
shadow=0.0,
- text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate() +
- ' ' + eq_text.replace('${NUMBER}', str(total_pts)),
+ text=ba.Lstr(resource='coopSelectWindow.totalText').evaluate()
+ + ' '
+ + eq_text.replace('${NUMBER}', str(total_pts)),
size=(0, 0),
h_align='right',
- v_align='center')
+ v_align='center',
+ )
- def _create_trophy_type_widgets(self, eq_text: str, incr: int,
- multi_txt: str, sub_height: int,
- sub_width: int,
- trophy_types: list[list[str]]) -> int:
+ def _create_trophy_type_widgets(
+ self,
+ eq_text: str,
+ incr: int,
+ multi_txt: str,
+ sub_height: int,
+ sub_width: int,
+ trophy_types: list[list[str]],
+ ) -> int:
from ba.internal import get_trophy_string
+
total_pts = 0
for i, trophy_type in enumerate(trophy_types):
t_count = self._data['t' + trophy_type[0]]
t_mult = self._data['t' + trophy_type[0] + 'm']
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.15,
- sub_height - 20 - incr * i),
- scale=0.7,
- flatness=1.0,
- shadow=0.7,
- color=(1, 1, 1),
- text=get_trophy_string(trophy_type[0]),
- size=(0, 0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.15, sub_height - 20 - incr * i),
+ scale=0.7,
+ flatness=1.0,
+ shadow=0.7,
+ color=(1, 1, 1),
+ text=get_trophy_string(trophy_type[0]),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.31,
- sub_height - 20 - incr * i),
- maxwidth=sub_width * 0.2,
- scale=0.8,
- flatness=1.0,
- shadow=0.0,
- color=(0, 1, 0) if (t_count > 0) else
- (0.6, 0.6, 0.6, 0.5),
- text=str(t_count),
- size=(0, 0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.31, sub_height - 20 - incr * i),
+ maxwidth=sub_width * 0.2,
+ scale=0.8,
+ flatness=1.0,
+ shadow=0.0,
+ color=(0, 1, 0) if (t_count > 0) else (0.6, 0.6, 0.6, 0.5),
+ text=str(t_count),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
txt = multi_txt.replace('${NUMBER}', str(t_mult))
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.57,
- sub_height - 20 - incr * i),
- maxwidth=sub_width * 0.3,
- scale=0.4,
- flatness=1.0,
- shadow=0.0,
- color=(0.63, 0.6, 0.75) if (t_count > 0) else
- (0.6, 0.6, 0.6, 0.4),
- text=txt,
- size=(0, 0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.57, sub_height - 20 - incr * i),
+ maxwidth=sub_width * 0.3,
+ scale=0.4,
+ flatness=1.0,
+ shadow=0.0,
+ color=(0.63, 0.6, 0.75)
+ if (t_count > 0)
+ else (0.6, 0.6, 0.6, 0.4),
+ text=txt,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
this_pts = t_count * t_mult
- ba.textwidget(parent=self._subcontainer,
- position=(sub_width * 0.88,
- sub_height - 20 - incr * i),
- maxwidth=sub_width * 0.3,
- color=(0.7, 0.8, 1.0) if (t_count > 0) else
- (0.9, 0.9, 1.0, 0.3),
- flatness=1.0,
- shadow=0.0,
- scale=0.5,
- text=eq_text.replace('${NUMBER}', str(this_pts)),
- size=(0, 0),
- h_align='center',
- v_align='center')
+ ba.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.88, sub_height - 20 - incr * i),
+ maxwidth=sub_width * 0.3,
+ color=(0.7, 0.8, 1.0)
+ if (t_count > 0)
+ else (0.9, 0.9, 1.0, 0.3),
+ flatness=1.0,
+ shadow=0.0,
+ scale=0.5,
+ text=eq_text.replace('${NUMBER}', str(this_pts)),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ )
total_pts += this_pts
return total_pts
diff --git a/assets/src/ba_data/python/bastd/ui/url.py b/assets/src/ba_data/python/bastd/ui/url.py
index fe90fb3a..7b87b2d7 100644
--- a/assets/src/ba_data/python/bastd/ui/url.py
+++ b/assets/src/ba_data/python/bastd/ui/url.py
@@ -20,11 +20,19 @@ class ShowURLWindow(ba.Window):
if app.platform == 'android' and app.subplatform == 'alibaba':
self._width = 500
self._height = 500
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height),
- transition='in_right',
- scale=(1.25 if uiscale is ba.UIScale.SMALL else
- 1.25 if uiscale is ba.UIScale.MEDIUM else 1.25)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height),
+ transition='in_right',
+ scale=(
+ 1.25
+ if uiscale is ba.UIScale.SMALL
+ else 1.25
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.25
+ ),
+ )
+ )
self._cancel_button = ba.buttonwidget(
parent=self._root_widget,
position=(50, self._height - 30),
@@ -35,15 +43,21 @@ class ShowURLWindow(ba.Window):
on_activate_call=self._done,
autoselect=True,
icon=ba.gettexture('crossOut'),
- iconscale=1.2)
+ iconscale=1.2,
+ )
qr_size = 400
- ba.imagewidget(parent=self._root_widget,
- position=(self._width * 0.5 - qr_size * 0.5,
- self._height * 0.5 - qr_size * 0.5),
- size=(qr_size, qr_size),
- texture=ba.internal.get_qrcode_texture(address))
- ba.containerwidget(edit=self._root_widget,
- cancel_button=self._cancel_button)
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(
+ self._width * 0.5 - qr_size * 0.5,
+ self._height * 0.5 - qr_size * 0.5,
+ ),
+ size=(qr_size, qr_size),
+ texture=ba.internal.get_qrcode_texture(address),
+ )
+ ba.containerwidget(
+ edit=self._root_widget, cancel_button=self._cancel_button
+ )
else:
# show it as a simple string...
self._width = 800
@@ -51,39 +65,51 @@ class ShowURLWindow(ba.Window):
self._root_widget = ba.containerwidget(
size=(self._width, self._height + 40),
transition='in_right',
- scale=(1.25 if uiscale is ba.UIScale.SMALL else
- 1.25 if uiscale is ba.UIScale.MEDIUM else 1.25))
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 10),
- size=(0, 0),
- color=ba.app.ui.title_color,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='directBrowserToURLText'),
- maxwidth=self._width * 0.95)
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5,
- self._height * 0.5 + 29),
- size=(0, 0),
- scale=1.3,
- color=ba.app.ui.infotextcolor,
- h_align='center',
- v_align='center',
- text=address,
- maxwidth=self._width * 0.95)
+ scale=(
+ 1.25
+ if uiscale is ba.UIScale.SMALL
+ else 1.25
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.25
+ ),
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 10),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='directBrowserToURLText'),
+ maxwidth=self._width * 0.95,
+ )
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.5 + 29),
+ size=(0, 0),
+ scale=1.3,
+ color=ba.app.ui.infotextcolor,
+ h_align='center',
+ v_align='center',
+ text=address,
+ maxwidth=self._width * 0.95,
+ )
button_width = 200
- btn = ba.buttonwidget(parent=self._root_widget,
- position=(self._width * 0.5 -
- button_width * 0.5, 20),
- size=(button_width, 65),
- label=ba.Lstr(resource='doneText'),
- on_activate_call=self._done)
+ btn = ba.buttonwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5 - button_width * 0.5, 20),
+ size=(button_width, 65),
+ label=ba.Lstr(resource='doneText'),
+ on_activate_call=self._done,
+ )
# we have no 'cancel' button but still want to be able to
# hit back/escape/etc to leave..
- ba.containerwidget(edit=self._root_widget,
- selected_child=btn,
- start_button=btn,
- on_cancel_call=btn.activate)
+ ba.containerwidget(
+ edit=self._root_widget,
+ selected_child=btn,
+ start_button=btn,
+ on_cancel_call=btn.activate,
+ )
def _done(self) -> None:
ba.containerwidget(edit=self._root_widget, transition='out_left')
diff --git a/assets/src/ba_data/python/bastd/ui/watch.py b/assets/src/ba_data/python/bastd/ui/watch.py
index eca946b9..85d484e0 100644
--- a/assets/src/ba_data/python/bastd/ui/watch.py
+++ b/assets/src/ba_data/python/bastd/ui/watch.py
@@ -20,15 +20,19 @@ class WatchWindow(ba.Window):
class TabID(Enum):
"""Our available tab types."""
+
MY_REPLAYS = 'my_replays'
TEST_TAB = 'test_tab'
- def __init__(self,
- transition: str | None = 'in_right',
- origin_widget: ba.Widget | None = None):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: ba.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
from bastd.ui.tabs import TabRow
+
ba.set_analytics_screen('Watch Window')
scale_origin: tuple[float, float] | None
if origin_widget is not None:
@@ -51,24 +55,41 @@ class WatchWindow(ba.Window):
uiscale = ba.app.ui.uiscale
self._width = 1240 if uiscale is ba.UIScale.SMALL else 1040
x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
- self._height = (578 if uiscale is ba.UIScale.SMALL else
- 670 if uiscale is ba.UIScale.MEDIUM else 800)
+ self._height = (
+ 578
+ if uiscale is ba.UIScale.SMALL
+ else 670
+ if uiscale is ba.UIScale.MEDIUM
+ else 800
+ )
self._current_tab: WatchWindow.TabID | None = None
extra_top = 20 if uiscale is ba.UIScale.SMALL else 0
- super().__init__(root_widget=ba.containerwidget(
- size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(1.3 if uiscale is ba.UIScale.SMALL else
- 0.97 if uiscale is ba.UIScale.MEDIUM else 0.8),
- stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (
- 0, 15) if uiscale is ba.UIScale.MEDIUM else (0, 0)))
+ super().__init__(
+ root_widget=ba.containerwidget(
+ size=(self._width, self._height + extra_top),
+ transition=transition,
+ toolbar_visibility='menu_minimal',
+ scale_origin_stack_offset=scale_origin,
+ scale=(
+ 1.3
+ if uiscale is ba.UIScale.SMALL
+ else 0.97
+ if uiscale is ba.UIScale.MEDIUM
+ else 0.8
+ ),
+ stack_offset=(0, -10)
+ if uiscale is ba.UIScale.SMALL
+ else (0, 15)
+ if uiscale is ba.UIScale.MEDIUM
+ else (0, 0),
+ )
+ )
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
- ba.containerwidget(edit=self._root_widget,
- on_cancel_call=self._back)
+ ba.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
self._back_button = None
else:
self._back_button = btn = ba.buttonwidget(
@@ -79,49 +100,59 @@ class WatchWindow(ba.Window):
scale=1.1,
label=ba.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back)
+ on_activate_call=self._back,
+ )
ba.containerwidget(edit=self._root_widget, cancel_button=btn)
- ba.buttonwidget(edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=ba.charstr(ba.SpecialChar.BACK))
+ ba.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=ba.charstr(ba.SpecialChar.BACK),
+ )
- ba.textwidget(parent=self._root_widget,
- position=(self._width * 0.5, self._height - 38),
- size=(0, 0),
- color=ba.app.ui.title_color,
- scale=1.5,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource=self._r + '.titleText'),
- maxwidth=400)
+ ba.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 38),
+ size=(0, 0),
+ color=ba.app.ui.title_color,
+ scale=1.5,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource=self._r + '.titleText'),
+ maxwidth=400,
+ )
tabdefs = [
- (self.TabID.MY_REPLAYS,
- ba.Lstr(resource=self._r + '.myReplaysText')),
+ (
+ self.TabID.MY_REPLAYS,
+ ba.Lstr(resource=self._r + '.myReplaysText'),
+ ),
# (self.TabID.TEST_TAB, ba.Lstr(value='Testing')),
]
scroll_buffer_h = 130 + 2 * x_inset
tab_buffer_h = 750 + 2 * x_inset
- self._tab_row = TabRow(self._root_widget,
- tabdefs,
- pos=(tab_buffer_h * 0.5, self._height - 130),
- size=(self._width - tab_buffer_h, 50),
- on_select_call=self._set_tab)
+ self._tab_row = TabRow(
+ self._root_widget,
+ tabdefs,
+ pos=(tab_buffer_h * 0.5, self._height - 130),
+ size=(self._width - tab_buffer_h, 50),
+ on_select_call=self._set_tab,
+ )
if ba.app.ui.use_toolbars:
first_tab = self._tab_row.tabs[tabdefs[0][0]]
last_tab = self._tab_row.tabs[tabdefs[-1][0]]
ba.widget(
edit=last_tab.button,
- right_widget=ba.internal.get_special_widget('party_button'))
+ right_widget=ba.internal.get_special_widget('party_button'),
+ )
if uiscale is ba.UIScale.SMALL:
bbtn = ba.internal.get_special_widget('back_button')
- ba.widget(edit=first_tab.button,
- up_widget=bbtn,
- left_widget=bbtn)
+ ba.widget(
+ edit=first_tab.button, up_widget=bbtn, left_widget=bbtn
+ )
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180
@@ -131,13 +162,16 @@ class WatchWindow(ba.Window):
scroll_bottom = self._height - self._scroll_height - 79 - 48
buffer_h = 10
buffer_v = 4
- ba.imagewidget(parent=self._root_widget,
- position=(scroll_left - buffer_h,
- scroll_bottom - buffer_v),
- size=(self._scroll_width + 2 * buffer_h,
- self._scroll_height + 2 * buffer_v),
- texture=ba.gettexture('scrollWidget'),
- model_transparent=ba.getmodel('softEdgeOutside'))
+ ba.imagewidget(
+ parent=self._root_widget,
+ position=(scroll_left - buffer_h, scroll_bottom - buffer_v),
+ size=(
+ self._scroll_width + 2 * buffer_h,
+ self._scroll_height + 2 * buffer_v,
+ ),
+ texture=ba.gettexture('scrollWidget'),
+ model_transparent=ba.getmodel('softEdgeOutside'),
+ )
self._tab_container: ba.Widget | None = None
self._restore_state()
@@ -173,42 +207,67 @@ class WatchWindow(ba.Window):
c_height = self._scroll_height - 20
sub_scroll_height = c_height - 63
self._my_replays_scroll_width = sub_scroll_width = (
- 680 if uiscale is ba.UIScale.SMALL else 640)
+ 680 if uiscale is ba.UIScale.SMALL else 640
+ )
self._tab_container = cnt = ba.containerwidget(
parent=self._root_widget,
- position=(scroll_left, scroll_bottom +
- (self._scroll_height - c_height) * 0.5),
+ position=(
+ scroll_left,
+ scroll_bottom + (self._scroll_height - c_height) * 0.5,
+ ),
size=(c_width, c_height),
background=False,
- selection_loops_to_parent=True)
+ selection_loops_to_parent=True,
+ )
v = c_height - 30
- ba.textwidget(parent=cnt,
- position=(c_width * 0.5, v),
- color=(0.6, 1.0, 0.6),
- scale=0.7,
- size=(0, 0),
- maxwidth=c_width * 0.9,
- h_align='center',
- v_align='center',
- text=ba.Lstr(
- resource='replayRenameWarningText',
- subs=[('${REPLAY}',
- ba.Lstr(resource='replayNameDefaultText'))
- ]))
+ ba.textwidget(
+ parent=cnt,
+ position=(c_width * 0.5, v),
+ color=(0.6, 1.0, 0.6),
+ scale=0.7,
+ size=(0, 0),
+ maxwidth=c_width * 0.9,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(
+ resource='replayRenameWarningText',
+ subs=[
+ ('${REPLAY}', ba.Lstr(resource='replayNameDefaultText'))
+ ],
+ ),
+ )
b_width = 140 if uiscale is ba.UIScale.SMALL else 178
- b_height = (107 if uiscale is ba.UIScale.SMALL else
- 142 if uiscale is ba.UIScale.MEDIUM else 190)
- b_space_extra = (0 if uiscale is ba.UIScale.SMALL else
- -2 if uiscale is ba.UIScale.MEDIUM else -5)
+ b_height = (
+ 107
+ if uiscale is ba.UIScale.SMALL
+ else 142
+ if uiscale is ba.UIScale.MEDIUM
+ else 190
+ )
+ b_space_extra = (
+ 0
+ if uiscale is ba.UIScale.SMALL
+ else -2
+ if uiscale is ba.UIScale.MEDIUM
+ else -5
+ )
b_color = (0.6, 0.53, 0.63)
b_textcolor = (0.75, 0.7, 0.8)
- btnv = (c_height - (48 if uiscale is ba.UIScale.SMALL else
- 45 if uiscale is ba.UIScale.MEDIUM else 40) -
- b_height)
+ btnv = (
+ c_height
+ - (
+ 48
+ if uiscale is ba.UIScale.SMALL
+ else 45
+ if uiscale is ba.UIScale.MEDIUM
+ else 40
+ )
+ - b_height
+ )
btnh = 40 if uiscale is ba.UIScale.SMALL else 40
smlh = 190 if uiscale is ba.UIScale.SMALL else 225
tscl = 1.0 if uiscale is ba.UIScale.SMALL else 1.2
@@ -222,62 +281,68 @@ class WatchWindow(ba.Window):
on_activate_call=self._on_my_replay_play_press,
text_scale=tscl,
label=ba.Lstr(resource=self._r + '.watchReplayButtonText'),
- autoselect=True)
+ autoselect=True,
+ )
ba.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button)
if uiscale is ba.UIScale.SMALL and ba.app.ui.use_toolbars:
ba.widget(
edit=btn1,
- left_widget=ba.internal.get_special_widget('back_button'))
+ left_widget=ba.internal.get_special_widget('back_button'),
+ )
btnv -= b_height + b_space_extra
- ba.buttonwidget(parent=cnt,
- size=(b_width, b_height),
- position=(btnh, btnv),
- button_type='square',
- color=b_color,
- textcolor=b_textcolor,
- on_activate_call=self._on_my_replay_rename_press,
- text_scale=tscl,
- label=ba.Lstr(resource=self._r +
- '.renameReplayButtonText'),
- autoselect=True)
+ ba.buttonwidget(
+ parent=cnt,
+ size=(b_width, b_height),
+ position=(btnh, btnv),
+ button_type='square',
+ color=b_color,
+ textcolor=b_textcolor,
+ on_activate_call=self._on_my_replay_rename_press,
+ text_scale=tscl,
+ label=ba.Lstr(resource=self._r + '.renameReplayButtonText'),
+ autoselect=True,
+ )
btnv -= b_height + b_space_extra
- ba.buttonwidget(parent=cnt,
- size=(b_width, b_height),
- position=(btnh, btnv),
- button_type='square',
- color=b_color,
- textcolor=b_textcolor,
- on_activate_call=self._on_my_replay_delete_press,
- text_scale=tscl,
- label=ba.Lstr(resource=self._r +
- '.deleteReplayButtonText'),
- autoselect=True)
+ ba.buttonwidget(
+ parent=cnt,
+ size=(b_width, b_height),
+ position=(btnh, btnv),
+ button_type='square',
+ color=b_color,
+ textcolor=b_textcolor,
+ on_activate_call=self._on_my_replay_delete_press,
+ text_scale=tscl,
+ label=ba.Lstr(resource=self._r + '.deleteReplayButtonText'),
+ autoselect=True,
+ )
v -= sub_scroll_height + 23
self._scrollwidget = scrlw = ba.scrollwidget(
parent=cnt,
position=(smlh, v),
- size=(sub_scroll_width, sub_scroll_height))
+ size=(sub_scroll_width, sub_scroll_height),
+ )
ba.containerwidget(edit=cnt, selected_child=scrlw)
- self._columnwidget = ba.columnwidget(parent=scrlw,
- left_border=10,
- border=2,
- margin=0)
+ self._columnwidget = ba.columnwidget(
+ parent=scrlw, left_border=10, border=2, margin=0
+ )
- ba.widget(edit=scrlw,
- autoselect=True,
- left_widget=btn1,
- up_widget=self._tab_row.tabs[tab_id].button)
- ba.widget(edit=self._tab_row.tabs[tab_id].button,
- down_widget=scrlw)
+ ba.widget(
+ edit=scrlw,
+ autoselect=True,
+ left_widget=btn1,
+ up_widget=self._tab_row.tabs[tab_id].button,
+ )
+ ba.widget(edit=self._tab_row.tabs[tab_id].button, down_widget=scrlw)
self._my_replay_selected = None
self._refresh_my_replays()
def _no_replay_selected_error(self) -> None:
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.noReplaySelectedErrorText'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(resource=self._r + '.noReplaySelectedErrorText'),
+ color=(1, 0, 0),
+ )
ba.playsound(ba.getsound('error'))
def _on_my_replay_play_press(self) -> None:
@@ -292,14 +357,18 @@ class WatchWindow(ba.Window):
ba.internal.set_replay_speed_exponent(0)
ba.internal.fade_screen(True)
assert self._my_replay_selected is not None
- ba.internal.new_replay_session(ba.internal.get_replays_dir() +
- '/' + self._my_replay_selected)
+ ba.internal.new_replay_session(
+ ba.internal.get_replays_dir()
+ + '/'
+ + self._my_replay_selected
+ )
except Exception:
ba.print_exception('Error running replay session.')
# Drop back into a fresh main menu session
# in case we half-launched or something.
from bastd import mainmenu
+
ba.internal.new_host_session(mainmenu.MainMenuSession)
ba.internal.fade_screen(False, endcall=ba.Call(ba.pushcall, do_it))
@@ -313,19 +382,29 @@ class WatchWindow(ba.Window):
c_height = 250
uiscale = ba.app.ui.uiscale
self._my_replays_rename_window = cnt = ba.containerwidget(
- scale=(1.8 if uiscale is ba.UIScale.SMALL else
- 1.55 if uiscale is ba.UIScale.MEDIUM else 1.0),
+ scale=(
+ 1.8
+ if uiscale is ba.UIScale.SMALL
+ else 1.55
+ if uiscale is ba.UIScale.MEDIUM
+ else 1.0
+ ),
size=(c_width, c_height),
- transition='in_scale')
+ transition='in_scale',
+ )
dname = self._get_replay_display_name(self._my_replay_selected)
- ba.textwidget(parent=cnt,
- size=(0, 0),
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource=self._r + '.renameReplayText',
- subs=[('${REPLAY}', dname)]),
- maxwidth=c_width * 0.8,
- position=(c_width * 0.5, c_height - 60))
+ ba.textwidget(
+ parent=cnt,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(
+ resource=self._r + '.renameReplayText',
+ subs=[('${REPLAY}', dname)],
+ ),
+ maxwidth=c_width * 0.8,
+ position=(c_width * 0.5, c_height - 60),
+ )
self._my_replay_rename_text = txt = ba.textwidget(
parent=cnt,
size=(c_width * 0.8, 40),
@@ -337,24 +416,29 @@ class WatchWindow(ba.Window):
position=(c_width * 0.1, c_height - 140),
autoselect=True,
maxwidth=c_width * 0.7,
- max_chars=200)
+ max_chars=200,
+ )
cbtn = ba.buttonwidget(
parent=cnt,
label=ba.Lstr(resource='cancelText'),
on_activate_call=ba.Call(
lambda c: ba.containerwidget(edit=c, transition='out_scale'),
- cnt),
+ cnt,
+ ),
size=(180, 60),
position=(30, 30),
- autoselect=True)
- okb = ba.buttonwidget(parent=cnt,
- label=ba.Lstr(resource=self._r + '.renameText'),
- size=(180, 60),
- position=(c_width - 230, 30),
- on_activate_call=ba.Call(
- self._rename_my_replay,
- self._my_replay_selected),
- autoselect=True)
+ autoselect=True,
+ )
+ okb = ba.buttonwidget(
+ parent=cnt,
+ label=ba.Lstr(resource=self._r + '.renameText'),
+ size=(180, 60),
+ position=(c_width - 230, 30),
+ on_activate_call=ba.Call(
+ self._rename_my_replay, self._my_replay_selected
+ ),
+ autoselect=True,
+ )
ba.widget(edit=cbtn, right_widget=okb)
ba.widget(edit=okb, left_widget=cbtn)
ba.textwidget(edit=txt, on_return_press_call=okb.activate)
@@ -366,30 +450,41 @@ class WatchWindow(ba.Window):
if not self._my_replay_rename_text:
return
new_name_raw = cast(
- str, ba.textwidget(query=self._my_replay_rename_text))
+ str, ba.textwidget(query=self._my_replay_rename_text)
+ )
new_name = new_name_raw + '.brp'
# Ignore attempts to change it to what it already is
# (or what it looks like to the user).
- if (replay != new_name
- and self._get_replay_display_name(replay) != new_name_raw):
- old_name_full = (ba.internal.get_replays_dir() + '/' +
- replay).encode('utf-8')
- new_name_full = (ba.internal.get_replays_dir() + '/' +
- new_name).encode('utf-8')
+ if (
+ replay != new_name
+ and self._get_replay_display_name(replay) != new_name_raw
+ ):
+ old_name_full = (
+ ba.internal.get_replays_dir() + '/' + replay
+ ).encode('utf-8')
+ new_name_full = (
+ ba.internal.get_replays_dir() + '/' + new_name
+ ).encode('utf-8')
# False alarm; ba.textwidget can return non-None val.
# pylint: disable=unsupported-membership-test
if os.path.exists(new_name_full):
ba.playsound(ba.getsound('error'))
ba.screenmessage(
- ba.Lstr(resource=self._r +
- '.replayRenameErrorAlreadyExistsText'),
- color=(1, 0, 0))
+ ba.Lstr(
+ resource=self._r
+ + '.replayRenameErrorAlreadyExistsText'
+ ),
+ color=(1, 0, 0),
+ )
elif any(char in new_name_raw for char in ['/', '\\', ':']):
ba.playsound(ba.getsound('error'))
- ba.screenmessage(ba.Lstr(resource=self._r +
- '.replayRenameErrorInvalidName'),
- color=(1, 0, 0))
+ ba.screenmessage(
+ ba.Lstr(
+ resource=self._r + '.replayRenameErrorInvalidName'
+ ),
+ color=(1, 0, 0),
+ )
else:
ba.internal.increment_analytics_count('Replay rename')
os.rename(old_name_full, new_name_full)
@@ -397,27 +492,38 @@ class WatchWindow(ba.Window):
ba.playsound(ba.getsound('gunCocking'))
except Exception:
ba.print_exception(
- f"Error renaming replay '{replay}' to '{new_name}'.")
+ f"Error renaming replay '{replay}' to '{new_name}'."
+ )
ba.playsound(ba.getsound('error'))
ba.screenmessage(
ba.Lstr(resource=self._r + '.replayRenameErrorText'),
color=(1, 0, 0),
)
- ba.containerwidget(edit=self._my_replays_rename_window,
- transition='out_scale')
+ ba.containerwidget(
+ edit=self._my_replays_rename_window, transition='out_scale'
+ )
def _on_my_replay_delete_press(self) -> None:
from bastd.ui import confirm
+
if self._my_replay_selected is None:
self._no_replay_selected_error()
return
confirm.ConfirmWindow(
- ba.Lstr(resource=self._r + '.deleteConfirmText',
- subs=[('${REPLAY}',
- self._get_replay_display_name(
- self._my_replay_selected))]),
- ba.Call(self._delete_replay, self._my_replay_selected), 450, 150)
+ ba.Lstr(
+ resource=self._r + '.deleteConfirmText',
+ subs=[
+ (
+ '${REPLAY}',
+ self._get_replay_display_name(self._my_replay_selected),
+ )
+ ],
+ ),
+ ba.Call(self._delete_replay, self._my_replay_selected),
+ 450,
+ 150,
+ )
def _get_replay_display_name(self, replay: str) -> str:
if replay.endswith('.brp'):
@@ -430,7 +536,8 @@ class WatchWindow(ba.Window):
try:
ba.internal.increment_analytics_count('Replay delete')
os.remove(
- (ba.internal.get_replays_dir() + '/' + replay).encode('utf-8'))
+ (ba.internal.get_replays_dir() + '/' + replay).encode('utf-8')
+ )
self._refresh_my_replays()
ba.playsound(ba.getsound('shieldDown'))
if replay == self._my_replay_selected:
@@ -468,8 +575,9 @@ class WatchWindow(ba.Window):
parent=self._columnwidget,
size=(self._my_replays_scroll_width / t_scale, 30),
selectable=True,
- color=(1.0, 1, 0.4) if name == '__lastReplay.brp' else
- (1, 1, 1),
+ color=(1.0, 1, 0.4)
+ if name == '__lastReplay.brp'
+ else (1, 1, 1),
always_highlight=True,
on_select_call=ba.Call(self._on_my_replay_select, name),
on_activate_call=self._my_replays_watch_replay_button.activate,
@@ -477,17 +585,20 @@ class WatchWindow(ba.Window):
h_align='left',
v_align='center',
corner_scale=t_scale,
- maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93)
+ maxwidth=(self._my_replays_scroll_width / t_scale) * 0.93,
+ )
if i == 0:
ba.widget(
edit=txt,
- up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button)
+ up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button,
+ )
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
selected_tab_ids = [
- tab_id for tab_id, tab in self._tab_row.tabs.items()
+ tab_id
+ for tab_id, tab in self._tab_row.tabs.items()
if sel == tab.button
]
if sel == self._back_button:
@@ -505,14 +616,17 @@ class WatchWindow(ba.Window):
def _restore_state(self) -> None:
from efro.util import enum_by_value
+
try:
sel: ba.Widget | None
- sel_name = ba.app.ui.window_states.get(type(self),
- {}).get('sel_name')
+ sel_name = ba.app.ui.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
assert isinstance(sel_name, (str, type(None)))
try:
- current_tab = enum_by_value(self.TabID,
- ba.app.config.get('Watch Tab'))
+ current_tab = enum_by_value(
+ self.TabID, ba.app.config.get('Watch Tab')
+ )
except ValueError:
current_tab = self.TabID.MY_REPLAYS
self._set_tab(current_tab)
@@ -523,8 +637,9 @@ class WatchWindow(ba.Window):
sel = self._tab_container
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(self.TabID,
- sel_name.split(':')[-1])
+ sel_tab_id = enum_by_value(
+ self.TabID, sel_name.split(':')[-1]
+ )
except ValueError:
sel_tab_id = self.TabID.MY_REPLAYS
sel = self._tab_row.tabs[sel_tab_id].button
@@ -539,8 +654,11 @@ class WatchWindow(ba.Window):
def _back(self) -> None:
from bastd.ui.mainmenu import MainMenuWindow
+
self._save_state()
- ba.containerwidget(edit=self._root_widget,
- transition=self._transition_out)
+ ba.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
ba.app.ui.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget())
+ MainMenuWindow(transition='in_left').get_root_widget()
+ )
diff --git a/assets/src/server/ballisticacore_server.py b/assets/src/server/ballisticacore_server.py
index 821e1285..34289425 100755
--- a/assets/src/server/ballisticacore_server.py
+++ b/assets/src/server/ballisticacore_server.py
@@ -19,7 +19,7 @@ from typing import TYPE_CHECKING
# before we import them.
sys.path += [
str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python')),
- str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python-site-packages'))
+ str(Path(Path(__file__).parent, 'dist', 'ba_data', 'python-site-packages')),
]
from bacommon.servermanager import ServerConfig, StartServerModeCommand
@@ -127,7 +127,8 @@ class ServerManagerApp:
print(
f'{Clr.CYN}{Clr.BLD}BallisticaCore server manager {VERSION_STR}'
f' starting up ({dbgstr} mode)...{Clr.RST}',
- flush=True)
+ flush=True,
+ )
# Python will handle SIGINT for us (as KeyboardInterrupt) but we
# need to register a SIGTERM handler so we have a chance to clean
@@ -150,8 +151,9 @@ class ServerManagerApp:
assert self._subprocess_thread is not None
if self._subprocess_thread.is_alive():
- print(f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.CYN}Waiting for subprocess exit...{Clr.RST}', flush=True
+ )
# Mark ourselves as shutting down and wait for the process to wrap up.
self._done = True
@@ -188,6 +190,7 @@ class ServerManagerApp:
def _run_interactive(self) -> None:
"""Run the app loop to completion interactively."""
import code
+
self._prerun()
# Print basic usage info for interactive mode.
@@ -195,7 +198,8 @@ class ServerManagerApp:
f"{Clr.CYN}Interactive mode enabled; use the 'mgr' object"
f' to interact with the server.\n'
f"Type 'help(mgr)' for more information.{Clr.RST}",
- flush=True)
+ flush=True,
+ )
context = {'__name__': '__console__', '__doc__': None, 'mgr': self}
@@ -216,7 +220,8 @@ class ServerManagerApp:
print(
f'{Clr.SRED}Unexpected interpreter exception:'
f' {exc} ({type(exc)}){Clr.RST}',
- flush=True)
+ flush=True,
+ )
self._postrun()
@@ -248,35 +253,41 @@ class ServerManagerApp:
# we'll hopefully still give it enough time to process/print.
time.sleep(0.1)
- def screenmessage(self,
- message: str,
- color: tuple[float, float, float] | None = None,
- clients: list[int] | None = None) -> None:
+ def screenmessage(
+ self,
+ message: str,
+ color: tuple[float, float, float] | None = None,
+ clients: list[int] | None = None,
+ ) -> None:
"""Display a screen-message.
This will have no name attached and not show up in chat history.
They will show up in replays, however (unless clients is passed).
"""
from bacommon.servermanager import ScreenMessageCommand
- self._enqueue_server_command(
- ScreenMessageCommand(message=message, color=color,
- clients=clients))
- def chatmessage(self,
- message: str,
- clients: list[int] | None = None) -> None:
+ self._enqueue_server_command(
+ ScreenMessageCommand(message=message, color=color, clients=clients)
+ )
+
+ def chatmessage(
+ self, message: str, clients: list[int] | None = None
+ ) -> None:
"""Send a chat message from the server.
This will have the server's name attached and will be logged
in client chat windows, just like other chat messages.
"""
from bacommon.servermanager import ChatMessageCommand
+
self._enqueue_server_command(
- ChatMessageCommand(message=message, clients=clients))
+ ChatMessageCommand(message=message, clients=clients)
+ )
def clientlist(self) -> None:
"""Print a list of connected clients."""
from bacommon.servermanager import ClientListCommand
+
self._enqueue_server_command(ClientListCommand())
self._block_for_command_completion()
@@ -289,8 +300,10 @@ class ServerManagerApp:
ban time.
"""
from bacommon.servermanager import KickCommand
+
self._enqueue_server_command(
- KickCommand(client_id=client_id, ban_time=ban_time))
+ KickCommand(client_id=client_id, ban_time=ban_time)
+ )
def restart(self, immediate: bool = True) -> None:
"""Restart the server subprocess.
@@ -300,15 +313,19 @@ class ServerManagerApp:
the next clean transition point (the end of a series, etc).
"""
from bacommon.servermanager import ShutdownCommand, ShutdownReason
+
self._enqueue_server_command(
- ShutdownCommand(reason=ShutdownReason.RESTARTING,
- immediate=immediate))
+ ShutdownCommand(
+ reason=ShutdownReason.RESTARTING, immediate=immediate
+ )
+ )
# If we're asking for an immediate restart but don't get one within
# the grace period, bring down the hammer.
if immediate:
self._subprocess_force_kill_time = (
- time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT)
+ time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT
+ )
def shutdown(self, immediate: bool = True) -> None:
"""Shut down the server subprocess and exit the wrapper.
@@ -318,8 +335,10 @@ class ServerManagerApp:
the next clean transition point (the end of a series, etc).
"""
from bacommon.servermanager import ShutdownCommand, ShutdownReason
+
self._enqueue_server_command(
- ShutdownCommand(reason=ShutdownReason.NONE, immediate=immediate))
+ ShutdownCommand(reason=ShutdownReason.NONE, immediate=immediate)
+ )
# An explicit shutdown means we know to bail completely once this
# subprocess completes.
@@ -329,7 +348,8 @@ class ServerManagerApp:
# the grace period, bring down the hammer.
if immediate:
self._subprocess_force_kill_time = (
- time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT)
+ time.time() + self.IMMEDIATE_SHUTDOWN_TIME_LIMIT
+ )
def _parse_command_line_args(self) -> None:
"""Parse command line args."""
@@ -348,8 +368,7 @@ class ServerManagerApp:
raise CleanError('Expected a config path as next arg.')
path = sys.argv[i + 1]
if not os.path.exists(path):
- raise CleanError(
- f"Supplied path does not exist: '{path}'.")
+ raise CleanError(f"Supplied path does not exist: '{path}'.")
# We need an abs path because we may be in a different
# cwd currently than we will be during the run.
self._config_path = os.path.abspath(path)
@@ -366,15 +385,19 @@ class ServerManagerApp:
i += 2
elif arg == '--interactive':
if did_set_interactive:
- raise CleanError('interactive/noninteractive can only'
- ' be specified once.')
+ raise CleanError(
+ 'interactive/noninteractive can only'
+ ' be specified once.'
+ )
self._interactive = True
did_set_interactive = True
i += 1
elif arg == '--noninteractive':
if did_set_interactive:
- raise CleanError('interactive/noninteractive can only'
- ' be specified once.')
+ raise CleanError(
+ 'interactive/noninteractive can only'
+ ' be specified once.'
+ )
self._interactive = False
did_set_interactive = True
i += 1
@@ -391,6 +414,7 @@ class ServerManagerApp:
def _par(cls, txt: str) -> str:
"""Spit out a pretty paragraph for our help text."""
import textwrap
+
ind = ' ' * 2
out = textwrap.fill(txt, 80, initial_indent=ind, subsequent_indent=ind)
return f'{out}\n'
@@ -400,30 +424,40 @@ class ServerManagerApp:
"""Print app help."""
filename = os.path.basename(__file__)
out = (
- f'{Clr.BLD}{filename} usage:{Clr.RST}\n' + cls._par(
+ f'{Clr.BLD}{filename} usage:{Clr.RST}\n'
+ + cls._par(
'This script handles configuring, launching, re-launching,'
' and otherwise managing BallisticaCore operating'
' in server mode. It can be run with no arguments, but'
- ' accepts the following optional ones:') + f'\n'
+ ' accepts the following optional ones:'
+ )
+ + f'\n'
f'{Clr.BLD}--help:{Clr.RST}\n'
f' Show this help.\n'
f'\n'
- f'{Clr.BLD}--config [path]{Clr.RST}\n' + cls._par(
+ f'{Clr.BLD}--config [path]{Clr.RST}\n'
+ + cls._par(
'Set the config file read by the server script. The config'
' file contains most options for what kind of game to host.'
' It should be in yaml format. Note that yaml is backwards'
' compatible with json so you can just write json if you'
' want to. If not specified, the script will look for a'
' file named \'config.yaml\' in the same directory as the'
- ' script.') + '\n'
- f'{Clr.BLD}--root [path]{Clr.RST}\n' + cls._par(
+ ' script.'
+ )
+ + '\n'
+ f'{Clr.BLD}--root [path]{Clr.RST}\n'
+ + cls._par(
'Set the ballistica root directory. This is where the server'
' binary will read and write its caches, state files,'
' downloaded assets to, etc. It needs to be a writable'
' directory. If not specified, the script will use the'
- ' \'dist/ba_root\' directory relative to itself.') + '\n'
+ ' \'dist/ba_root\' directory relative to itself.'
+ )
+ + '\n'
f'{Clr.BLD}--interactive{Clr.RST}\n'
- f'{Clr.BLD}--noninteractive{Clr.RST}\n' + cls._par(
+ f'{Clr.BLD}--noninteractive{Clr.RST}\n'
+ + cls._par(
'Specify whether the script should run interactively.'
' In interactive mode, the script creates a Python interpreter'
' and reads commands from stdin, allowing for live interaction'
@@ -431,19 +465,26 @@ class ServerManagerApp:
'end-of-file is reached in stdin. Noninteractive mode creates'
' no interpreter and is more suited to being run in automated'
' scenarios. By default, interactive mode will be used if'
- ' a terminal is detected and noninteractive mode otherwise.') +
- '\n'
- f'{Clr.BLD}--no-auto-restart{Clr.RST}\n' +
- cls._par('Auto-restart is enabled by default, which means the'
- ' server manager will restart the server binary whenever'
- ' it exits (even when uncleanly). Disabling auto-restart'
- ' will cause the server manager to instead exit after a'
- ' single run and also to return error codes if the'
- ' server binary did so.') + '\n'
- f'{Clr.BLD}--no-config-auto-restart{Clr.RST}\n' + cls._par(
+ ' a terminal is detected and noninteractive mode otherwise.'
+ )
+ + '\n'
+ f'{Clr.BLD}--no-auto-restart{Clr.RST}\n'
+ + cls._par(
+ 'Auto-restart is enabled by default, which means the'
+ ' server manager will restart the server binary whenever'
+ ' it exits (even when uncleanly). Disabling auto-restart'
+ ' will cause the server manager to instead exit after a'
+ ' single run and also to return error codes if the'
+ ' server binary did so.'
+ )
+ + '\n'
+ f'{Clr.BLD}--no-config-auto-restart{Clr.RST}\n'
+ + cls._par(
'By default, when auto-restart is enabled, the server binary'
' will be automatically restarted if changes to the server'
- ' config file are detected. This disables that behavior.'))
+ ' config file are detected. This disables that behavior.'
+ )
+ )
print(out)
def load_config(self, strict: bool, print_confirmation: bool) -> None:
@@ -459,26 +500,32 @@ class ServerManagerApp:
for trynum in range(maxtries):
try:
self._config = self._load_config_from_file(
- print_confirmation=print_confirmation)
+ print_confirmation=print_confirmation
+ )
return
except Exception as exc:
if strict:
raise CleanError(
- f'Error loading config file:\n{exc}') from exc
- print(f'{Clr.RED}Error loading config file:\n{exc}.{Clr.RST}',
- flush=True)
+ f'Error loading config file:\n{exc}'
+ ) from exc
+ print(
+ f'{Clr.RED}Error loading config file:\n{exc}.{Clr.RST}',
+ flush=True,
+ )
if trynum == maxtries - 1:
print(
f'{Clr.RED}Max-tries reached; giving up.'
f' Existing config values will be used.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
break
print(
f'{Clr.CYN}Please correct the error.'
f' Will re-attempt load in {retry_seconds}'
f' seconds. (attempt {trynum+1} of'
f' {maxtries-1}).{Clr.RST}',
- flush=True)
+ flush=True,
+ )
for _j in range(retry_seconds):
# If the app is trying to die, drop what we're doing.
@@ -502,16 +549,17 @@ class ServerManagerApp:
f'{Clr.YLW}Default config file not found'
f' (\'{self._config_path}\'); using default'
f' settings.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
self._config_mtime = None
self._last_config_mtime_check_time = time.time()
return ServerConfig()
# Don't be so lenient if the user pointed us at one though.
- raise RuntimeError(
- f"Config file not found: '{self._config_path}'.")
+ raise RuntimeError(f"Config file not found: '{self._config_path}'.")
import yaml
+
with open(self._config_path, encoding='utf-8') as infile:
user_config_raw = yaml.safe_load(infile.read())
@@ -528,8 +576,10 @@ class ServerManagerApp:
out = ServerConfig()
if print_confirmation:
- print(f'{Clr.CYN}Valid server config file loaded.{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.CYN}Valid server config file loaded.{Clr.RST}',
+ flush=True,
+ )
return out
def _enable_tab_completion(self, locs: dict) -> None:
@@ -537,6 +587,7 @@ class ServerManagerApp:
try:
import readline
import rlcompleter
+
readline.set_completer(rlcompleter.Completer(locs).complete)
readline.parse_and_bind('tab:complete')
except ImportError:
@@ -574,8 +625,11 @@ class ServerManagerApp:
os.environ['BA_SERVER_WRAPPER_MANAGED'] = '1'
print(f'{Clr.CYN}Launching server subprocess...{Clr.RST}', flush=True)
- binary_name = ('BallisticaCoreHeadless.exe'
- if os.name == 'nt' else './ballisticacore_headless')
+ binary_name = (
+ 'BallisticaCoreHeadless.exe'
+ if os.name == 'nt'
+ else './ballisticacore_headless'
+ )
assert self._ba_root_path is not None
self._subprocess = None
@@ -584,19 +638,23 @@ class ServerManagerApp:
self._subprocess = subprocess.Popen(
[binary_name, '-cfgdir', self._ba_root_path],
stdin=subprocess.PIPE,
- cwd='dist')
+ cwd='dist',
+ )
except Exception as exc:
self._subprocess_exited_cleanly = False
print(
f'{Clr.RED}Error launching server subprocess: {exc}{Clr.RST}',
- flush=True)
+ flush=True,
+ )
# Do the thing.
try:
self._run_subprocess_until_exit()
except Exception as exc:
- print(f'{Clr.RED}Error running server subprocess: {exc}{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.RED}Error running server subprocess: {exc}{Clr.RST}',
+ flush=True,
+ )
self._kill_subprocess()
@@ -606,13 +664,18 @@ class ServerManagerApp:
# up the interpreter, its possible that it will not break out of its
# loop via the usual SystemExit that gets sent when we die.
if self._interactive:
- while (self._interpreter_start_time is None
- or time.time() - self._interpreter_start_time < 0.5):
+ while (
+ self._interpreter_start_time is None
+ or time.time() - self._interpreter_start_time < 0.5
+ ):
time.sleep(0.1)
# Avoid super fast death loops.
- if (not self._subprocess_exited_cleanly and self._auto_restart
- and not self._done):
+ if (
+ not self._subprocess_exited_cleanly
+ and self._auto_restart
+ and not self._done
+ ):
time.sleep(5.0)
# If they don't want auto-restart, we'll exit the whole wrapper.
@@ -684,13 +747,15 @@ class ServerManagerApp:
Must be called from the server process thread.
"""
import pickle
+
assert current_thread() is self._subprocess_thread
assert self._subprocess is not None
assert self._subprocess.stdin is not None
val = repr(pickle.dumps(command))
assert '\n' not in val
- execcode = (f'import ba._servermode;'
- f' ba._servermode._cmd({val})\n').encode()
+ execcode = (
+ f'import ba._servermode;' f' ba._servermode._cmd({val})\n'
+ ).encode()
self._subprocess.stdin.write(execcode)
self._subprocess.stdin.flush()
@@ -730,13 +795,16 @@ class ServerManagerApp:
# If they want to force-kill our subprocess, simply exit this
# loop; the cleanup code will kill the process if its still
# alive.
- if (self._subprocess_force_kill_time is not None
- and time.time() > self._subprocess_force_kill_time):
+ if (
+ self._subprocess_force_kill_time is not None
+ and time.time() > self._subprocess_force_kill_time
+ ):
print(
f'{Clr.CYN}Immediate shutdown time limit'
f' ({self.IMMEDIATE_SHUTDOWN_TIME_LIMIT:.1f} seconds)'
f' expired; force-killing subprocess...{Clr.RST}',
- flush=True)
+ flush=True,
+ )
break
# Watch for the server process exiting..
@@ -747,8 +815,9 @@ class ServerManagerApp:
print(
f'{clr}Server subprocess exited'
f' with code {code}.{Clr.RST}',
- flush=True)
- self._subprocess_exited_cleanly = (code == 0)
+ flush=True,
+ )
+ self._subprocess_exited_cleanly = code == 0
break
time.sleep(0.25)
@@ -761,10 +830,15 @@ class ServerManagerApp:
minutes_since_launch = (now - self._subprocess_launch_time) / 60.0
# If we're doing auto-restart with config changes, handle that.
- if (self._auto_restart and self._config_auto_restart
- and not self._subprocess_sent_config_auto_restart):
- if (self._last_config_mtime_check_time is None
- or (now - self._last_config_mtime_check_time) > 3.123):
+ if (
+ self._auto_restart
+ and self._config_auto_restart
+ and not self._subprocess_sent_config_auto_restart
+ ):
+ if (
+ self._last_config_mtime_check_time is None
+ or (now - self._last_config_mtime_check_time) > 3.123
+ ):
self._last_config_mtime_check_time = now
mtime: float | None
if os.path.isfile(self._config_path):
@@ -775,7 +849,8 @@ class ServerManagerApp:
print(
f'{Clr.CYN}Config-file change detected;'
f' requesting immediate restart.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
self.restart(immediate=True)
self._subprocess_sent_config_auto_restart = True
@@ -783,18 +858,22 @@ class ServerManagerApp:
# (and enforce a 6 hour max if not provided)
clean_exit_minutes = 360.0
if self._config.clean_exit_minutes is not None:
- clean_exit_minutes = min(clean_exit_minutes,
- self._config.clean_exit_minutes)
+ clean_exit_minutes = min(
+ clean_exit_minutes, self._config.clean_exit_minutes
+ )
if clean_exit_minutes is not None:
- if (minutes_since_launch > clean_exit_minutes
- and not self._subprocess_sent_clean_exit):
+ if (
+ minutes_since_launch > clean_exit_minutes
+ and not self._subprocess_sent_clean_exit
+ ):
opname = 'restart' if self._auto_restart else 'shutdown'
print(
f'{Clr.CYN}clean_exit_minutes'
f' ({clean_exit_minutes})'
f' elapsed; requesting soft'
f' {opname}.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
if self._auto_restart:
self.restart(immediate=False)
else:
@@ -805,18 +884,22 @@ class ServerManagerApp:
# (and enforce a 7 hour max if not provided)
unclean_exit_minutes = 420.0
if self._config.unclean_exit_minutes is not None:
- unclean_exit_minutes = min(unclean_exit_minutes,
- self._config.unclean_exit_minutes)
+ unclean_exit_minutes = min(
+ unclean_exit_minutes, self._config.unclean_exit_minutes
+ )
if unclean_exit_minutes is not None:
- if (minutes_since_launch > unclean_exit_minutes
- and not self._subprocess_sent_unclean_exit):
+ if (
+ minutes_since_launch > unclean_exit_minutes
+ and not self._subprocess_sent_unclean_exit
+ ):
opname = 'restart' if self._auto_restart else 'shutdown'
print(
f'{Clr.CYN}unclean_exit_minutes'
f' ({unclean_exit_minutes})'
f' elapsed; requesting immediate'
f' {opname}.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
if self._auto_restart:
self.restart(immediate=True)
else:
@@ -845,8 +928,7 @@ class ServerManagerApp:
self._subprocess.terminate()
try:
self._subprocess.wait(timeout=10)
- self._subprocess_exited_cleanly = (
- self._subprocess.returncode == 0)
+ self._subprocess_exited_cleanly = self._subprocess.returncode == 0
except subprocess.TimeoutExpired:
self._subprocess_exited_cleanly = False
self._subprocess.kill()
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 80b39d36..09c7f8a9 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -29,6 +29,7 @@
adjoint
adminset
adreno
+ advb
affx
affy
affz
@@ -1015,6 +1016,7 @@
powerup
ppre
pptabcom
+ prab
precalc
precompiling
preconfig
@@ -1047,6 +1049,7 @@
projprefix
prolly
proxykey
+ prtb
psmx
pspec
psps
@@ -1068,6 +1071,7 @@
pver
pverraw
pvrtc
+ pwin
pybuildapple
pycommand
pyconfig
@@ -1530,6 +1534,7 @@
yoffs
yooooooo
ytweak
+ zabcdefghijklmnopqrstuvwxyz
zipdata
zmax
zmin
diff --git a/config/toolconfigsrc/mypy.ini b/config/toolconfigsrc/mypy.ini
index 5e5d14a4..dc7f3b06 100644
--- a/config/toolconfigsrc/mypy.ini
+++ b/config/toolconfigsrc/mypy.ini
@@ -13,8 +13,6 @@ __EFRO_MYPY_STANDARD_SETTINGS__
no_implicit_reexport = False
[mypy-ba.internal]
no_implicit_reexport = False
-[mypy-ba.deprecated]
-no_implicit_reexport = False
[mypy-psutil]
ignore_missing_imports = True
diff --git a/config/toolconfigsrc/pylintrc b/config/toolconfigsrc/pylintrc
index 7a47144f..d8d408cb 100644
--- a/config/toolconfigsrc/pylintrc
+++ b/config/toolconfigsrc/pylintrc
@@ -21,8 +21,14 @@ score=no
init-import=yes
[FORMAT]
-# PEP-8 specifies 79 chars (that's right, not 80)
-max-line-length=79
+# PEP-8 specifies 79 chars (that's right, not 80).
+# Update: switching to 80 to reclaim that extra char. There
+# seems to be a lot of wiggle room in line-lengths even by
+# good PEP-8 citizens (black code formatting defaults to 88).
+# Currently sticking with 80 across the board though since
+# wanting to support coding on phones and smaller devices as
+# much as possible.
+max-line-length=80
[MESSAGES CONTROL]
# broad-except:
diff --git a/tests/test_ba/test_assetmanager.py b/tests/test_ba/test_assetmanager.py
index 06af5307..b9304901 100644
--- a/tests/test_ba/test_assetmanager.py
+++ b/tests/test_ba/test_assetmanager.py
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
import weakref
import tempfile
from pathlib import Path
+
# noinspection PyProtectedMember
from ba._assetmanager import AssetManager
@@ -29,9 +30,11 @@ def test_assetmanager() -> None:
manager = AssetManager(rootdir=Path(tmpdir))
wref = weakref.ref(manager)
manager.start()
- gather = manager.launch_gather(packages=['a@2'],
- flavor=AssetPackageFlavor.DESKTOP,
- account_token='dummytoken')
+ gather = manager.launch_gather(
+ packages=['a@2'],
+ flavor=AssetPackageFlavor.DESKTOP,
+ account_token='dummytoken',
+ )
wref2 = weakref.ref(gather)
manager.stop()
diff --git a/tests/test_efro/test_dataclassio.py b/tests/test_efro/test_dataclassio.py
index 83de627e..28e53eb8 100644
--- a/tests/test_efro/test_dataclassio.py
+++ b/tests/test_efro/test_dataclassio.py
@@ -13,9 +13,17 @@ from typing import TYPE_CHECKING, Any, Sequence, Annotated
import pytest
from efro.util import utc_now
-from efro.dataclassio import (dataclass_validate, dataclass_from_dict,
- dataclass_to_dict, ioprepped, ioprep, IOAttrs,
- Codec, DataclassFieldLookup, IOExtendedData)
+from efro.dataclassio import (
+ dataclass_validate,
+ dataclass_from_dict,
+ dataclass_to_dict,
+ ioprepped,
+ ioprep,
+ IOAttrs,
+ Codec,
+ DataclassFieldLookup,
+ IOExtendedData,
+)
if TYPE_CHECKING:
pass
@@ -102,57 +110,40 @@ def test_assign() -> None:
# get when creating a dataclass and then converting back
# to a dict.
dict1 = {
- 'ival':
- 1,
- 'sval':
- 'foo',
- 'bval':
- True,
- 'fval':
- 2.0,
+ 'ival': 1,
+ 'sval': 'foo',
+ 'bval': True,
+ 'fval': 2.0,
'nval': {
'ival': 1,
'sval': 'bar',
- 'dval': {
- '1': 'foof'
- },
+ 'dval': {'1': 'foof'},
},
- 'enval':
- 'test1',
- 'oival':
- 1,
- 'oival2':
- 1,
- 'osval':
- 'foo',
- 'obval':
- True,
- 'ofval':
- 1.0,
- 'oenval':
- 'test2',
+ 'enval': 'test1',
+ 'oival': 1,
+ 'oival2': 1,
+ 'osval': 'foo',
+ 'obval': True,
+ 'ofval': 1.0,
+ 'oenval': 'test2',
'lsval': ['foo'],
'lival': [10],
'lbval': [False],
'lfval': [1.0],
'lenval': ['test1', 'test2'],
'ssval': ['foo'],
- 'dval': {
- 'k': 123
- },
- 'anyval': {
- 'foo': [1, 2, {
- 'bar': 'eep',
- 'rah': 1
- }]
- },
- 'dictval': {
- '1': 'foo'
- },
+ 'dval': {'k': 123},
+ 'anyval': {'foo': [1, 2, {'bar': 'eep', 'rah': 1}]},
+ 'dictval': {'1': 'foo'},
'tupleval': [2, 'foof', True],
'datetimeval': [
- now.year, now.month, now.day, now.hour, now.minute, now.second,
- now.microsecond
+ now.year,
+ now.month,
+ now.day,
+ now.hour,
+ now.minute,
+ now.second,
+ now.microsecond,
],
}
dc1 = dataclass_from_dict(_TestClass, dict1)
@@ -161,7 +152,8 @@ def test_assign() -> None:
# A few other assignment checks.
assert isinstance(
dataclass_from_dict(
- _TestClass, {
+ _TestClass,
+ {
'oival': None,
'oival2': None,
'osval': None,
@@ -171,11 +163,15 @@ def test_assign() -> None:
'lival': [],
'lbval': [],
'lfval': [],
- 'ssval': []
- }), _TestClass)
+ 'ssval': [],
+ },
+ ),
+ _TestClass,
+ )
assert isinstance(
dataclass_from_dict(
- _TestClass, {
+ _TestClass,
+ {
'oival': 1,
'oival2': 1,
'osval': 'foo',
@@ -184,8 +180,11 @@ def test_assign() -> None:
'lsval': ['foo', 'bar', 'eep'],
'lival': [10, 11, 12],
'lbval': [False, True],
- 'lfval': [1.0, 2.0, 3.0]
- }), _TestClass)
+ 'lfval': [1.0, 2.0, 3.0],
+ },
+ ),
+ _TestClass,
+ )
# Attr assigns mismatched with their value types should fail.
with pytest.raises(TypeError):
@@ -215,7 +214,7 @@ def test_assign() -> None:
with pytest.raises(TypeError):
dataclass_from_dict(_TestClass, {'lsval': [1]})
with pytest.raises(TypeError):
- dataclass_from_dict(_TestClass, {'lsval': (1, )})
+ dataclass_from_dict(_TestClass, {'lsval': (1,)})
with pytest.raises(TypeError):
dataclass_from_dict(_TestClass, {'lbval': [None]})
with pytest.raises(TypeError):
@@ -453,8 +452,9 @@ def test_extra_data() -> None:
# Passing an attr not in the dataclass should fail if we ask it to.
with pytest.raises(AttributeError):
- dataclass_from_dict(_TestClass, {'nonexistent': 'foo'},
- allow_unknown_attrs=False)
+ dataclass_from_dict(
+ _TestClass, {'nonexistent': 'foo'}, allow_unknown_attrs=False
+ )
# But normally it should be preserved and present in re-export.
obj = dataclass_from_dict(_TestClass, {'nonexistent': 'foo'})
@@ -463,8 +463,9 @@ def test_extra_data() -> None:
assert out.get('nonexistent') == 'foo'
# But not if we ask it to discard unknowns.
- obj = dataclass_from_dict(_TestClass, {'nonexistent': 'foo'},
- discard_unknown_attrs=True)
+ obj = dataclass_from_dict(
+ _TestClass, {'nonexistent': 'foo'}, discard_unknown_attrs=True
+ )
assert isinstance(obj, _TestClass)
out = dataclass_to_dict(obj)
assert 'nonexistent' not in out
@@ -496,7 +497,8 @@ def test_ioattrs() -> None:
@dataclass
class _TestClass3:
dval: Annotated[dict, IOAttrs('d', store_default=False)] = field(
- default_factory=dict)
+ default_factory=dict
+ )
ival: Annotated[int, IOAttrs('i', store_default=False)] = 123
# Both attrs are default; should get stripped out.
@@ -510,12 +512,7 @@ def test_ioattrs() -> None:
# Test going the other way.
obj3 = dataclass_from_dict(
_TestClass3,
- {
- 'd': {
- 'foo': 'barf'
- },
- 'i': 125
- },
+ {'d': {'foo': 'barf'}, 'i': 125},
allow_unknown_attrs=False,
)
assert obj3.dval == {'foo': 'barf'}
@@ -554,8 +551,11 @@ def test_codecs() -> None:
# datetime to/from JSON (turns into a list of values)
obj2 = _TestClass2(dval=now)
out = dataclass_to_dict(obj2, codec=Codec.JSON)
- assert (isinstance(out['dval'], list) and len(out['dval']) == 7
- and all(isinstance(val, int) for val in out['dval']))
+ assert (
+ isinstance(out['dval'], list)
+ and len(out['dval']) == 7
+ and all(isinstance(val, int) for val in out['dval'])
+ )
obj2 = dataclass_from_dict(_TestClass2, out, codec=Codec.JSON)
assert obj2.dval == now
@@ -680,13 +680,7 @@ def test_recursive() -> None:
rtest.child.child = _RecursiveTest(val=3)
expected_output = {
'val': 1,
- 'child': {
- 'val': 2,
- 'child': {
- 'val': 3,
- 'child': None
- }
- }
+ 'child': {'val': 2, 'child': {'val': 3, 'child': None}},
}
assert dataclass_to_dict(rtest) == expected_output
assert dataclass_from_dict(_RecursiveTest, expected_output) == rtest
@@ -731,8 +725,9 @@ class _SPTestClass1:
class _SPTestClass2:
rah: bool = False
subc: _SPTestClass1 = field(default_factory=_SPTestClass1)
- subc2: Annotated[_SPTestClass1,
- IOAttrs('s')] = field(default_factory=_SPTestClass1)
+ subc2: Annotated[_SPTestClass1, IOAttrs('s')] = field(
+ default_factory=_SPTestClass1
+ )
def test_datetime_limits() -> None:
@@ -784,15 +779,15 @@ def test_field_paths() -> None:
@ioprepped
@dataclass
class _TestClass:
-
@dataclass
class _TestSubClass:
val1: int = 0
val2: Annotated[int, IOAttrs('v2')] = 0
sub1: _TestSubClass = field(default_factory=_TestSubClass)
- sub2: Annotated[_TestSubClass,
- IOAttrs('s2')] = field(default_factory=_TestSubClass)
+ sub2: Annotated[_TestSubClass, IOAttrs('s2')] = field(
+ default_factory=_TestSubClass
+ )
# Now let's lookup various storage paths.
lookup = DataclassFieldLookup(_TestClass)
@@ -824,7 +819,6 @@ def test_nested() -> None:
@ioprepped
@dataclass
class _TestClass:
-
class _TestEnum(Enum):
VAL1 = 'val1'
VAL2 = 'val2'
@@ -962,8 +956,8 @@ def test_soft_default() -> None:
@dataclass
class _TestClassC3b:
ival: Annotated[
- int,
- IOAttrs(store_default=False, soft_default_factory=lambda: 0)]
+ int, IOAttrs(store_default=False, soft_default_factory=lambda: 0)
+ ]
assert dataclass_to_dict(_TestClassC3b(0)) == {}
diff --git a/tests/test_efro/test_message.py b/tests/test_efro/test_message.py
index a8dbf5c3..acb0baca 100644
--- a/tests/test_efro/test_message.py
+++ b/tests/test_efro/test_message.py
@@ -14,13 +14,20 @@ from dataclasses import dataclass
from typing_extensions import assert_type
import pytest
-# from efrotools.statictest import static_type_equals
+from efrotools.code import format_python_str
from efro.error import CleanError, RemoteError, CommunicationError
from efro.dataclassio import ioprepped
-from efro.message import (Message, Response, MessageProtocol, MessageSender,
- BoundMessageSender, MessageReceiver,
- BoundMessageReceiver, UnregisteredMessageIDError,
- EmptySysResponse)
+from efro.message import (
+ Message,
+ Response,
+ MessageProtocol,
+ MessageSender,
+ BoundMessageSender,
+ MessageReceiver,
+ BoundMessageReceiver,
+ UnregisteredMessageIDError,
+ EmptySysResponse,
+)
if TYPE_CHECKING:
from typing import Any, Callable, Awaitable
@@ -32,6 +39,7 @@ if TYPE_CHECKING:
@dataclass
class _TMsg1(Message):
"""Just testing."""
+
ival: int
@classmethod
@@ -43,6 +51,7 @@ class _TMsg1(Message):
@dataclass
class _TMsg2(Message):
"""Just testing."""
+
sval: str
@classmethod
@@ -54,6 +63,7 @@ class _TMsg2(Message):
@dataclass
class _TMsg3(Message):
"""Just testing."""
+
sval: str
@@ -61,6 +71,7 @@ class _TMsg3(Message):
@dataclass
class _TMsg4(Message):
"""Just testing."""
+
sval2: str
@@ -68,6 +79,7 @@ class _TMsg4(Message):
@dataclass
class _TResp1(Response):
"""Just testing."""
+
bval: bool
@@ -75,6 +87,7 @@ class _TResp1(Response):
@dataclass
class _TResp2(Response):
"""Just testing."""
+
fval: float
@@ -82,6 +95,7 @@ class _TResp2(Response):
@dataclass
class _TResp3(Message):
"""Just testing."""
+
fval: float
@@ -96,9 +110,9 @@ class _TestMessageSenderSingle(MessageSender):
protocol = TEST_PROTOCOL_SINGLE
super().__init__(protocol)
- def __get__(self,
- obj: Any,
- type_in: Any = None) -> _BoundTestMessageSenderSingle:
+ def __get__(
+ self, obj: Any, type_in: Any = None
+ ) -> _BoundTestMessageSenderSingle:
return _BoundTestMessageSenderSingle(obj, self)
@@ -125,9 +139,9 @@ class _TestMessageSenderSync(MessageSender):
protocol = TEST_PROTOCOL
super().__init__(protocol)
- def __get__(self,
- obj: Any,
- type_in: Any = None) -> _BoundTestMessageSenderSync:
+ def __get__(
+ self, obj: Any, type_in: Any = None
+ ) -> _BoundTestMessageSenderSync:
return _BoundTestMessageSenderSync(obj, self)
@@ -164,9 +178,9 @@ class _TestMessageSenderAsync(MessageSender):
protocol = TEST_PROTOCOL
super().__init__(protocol)
- def __get__(self,
- obj: Any,
- type_in: Any = None) -> _BoundTestMessageSenderAsync:
+ def __get__(
+ self, obj: Any, type_in: Any = None
+ ) -> _BoundTestMessageSenderAsync:
return _BoundTestMessageSenderAsync(obj, self)
@@ -203,9 +217,9 @@ class _TestMessageSenderBBoth(MessageSender):
protocol = TEST_PROTOCOL_EVOLVED
super().__init__(protocol)
- def __get__(self,
- obj: Any,
- type_in: Any = None) -> _BoundTestMessageSenderBBoth:
+ def __get__(
+ self, obj: Any, type_in: Any = None
+ ) -> _BoundTestMessageSenderBBoth:
return _BoundTestMessageSenderBBoth(obj, self)
@@ -281,6 +295,7 @@ class _TestSingleMessageReceiver(MessageReceiver):
) -> Callable[[Any, _TMsg1], _TResp1]:
"""Decorator to register message handlers."""
from typing import cast, Callable, Any
+
self.register_handler(cast(Callable[[Any, Message], Response], call))
return call
@@ -288,12 +303,13 @@ class _TestSingleMessageReceiver(MessageReceiver):
class _BoundTestSingleMessageReceiver(BoundMessageReceiver):
"""Protocol-specific bound receiver."""
- def handle_raw_message(self,
- message: str,
- raise_unregistered: bool = False) -> str:
+ def handle_raw_message(
+ self, message: str, raise_unregistered: bool = False
+ ) -> str:
"""Synchronously handle a raw incoming message."""
- return self._receiver.handle_raw_message(self._obj, message,
- raise_unregistered)
+ return self._receiver.handle_raw_message(
+ self._obj, message, raise_unregistered
+ )
# RCV_SINGLE_CODE_TEST_END
@@ -348,12 +364,13 @@ class _TestSyncMessageReceiver(MessageReceiver):
class _BoundTestSyncMessageReceiver(BoundMessageReceiver):
"""Protocol-specific bound receiver."""
- def handle_raw_message(self,
- message: str,
- raise_unregistered: bool = False) -> str:
+ def handle_raw_message(
+ self, message: str, raise_unregistered: bool = False
+ ) -> str:
"""Synchronously handle a raw incoming message."""
- return self._receiver.handle_raw_message(self._obj, message,
- raise_unregistered)
+ return self._receiver.handle_raw_message(
+ self._obj, message, raise_unregistered
+ )
# RCV_SYNC_CODE_TEST_END
@@ -408,12 +425,13 @@ class _TestAsyncMessageReceiver(MessageReceiver):
class _BoundTestAsyncMessageReceiver(BoundMessageReceiver):
"""Protocol-specific bound receiver."""
- async def handle_raw_message(self,
- message: str,
- raise_unregistered: bool = False) -> str:
+ async def handle_raw_message(
+ self, message: str, raise_unregistered: bool = False
+ ) -> str:
"""Asynchronously handle a raw incoming message."""
return await self._receiver.handle_raw_message_async(
- self._obj, message, raise_unregistered)
+ self._obj, message, raise_unregistered
+ )
# RCV_ASYNC_CODE_TEST_END
@@ -482,17 +500,20 @@ def test_protocol_creation() -> None:
def test_sender_module_single_emb() -> None:
+
"""Test generation of protocol-specific sender modules for typing/etc."""
# NOTE: Ideally we should be testing efro.message.create_sender_module()
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL_SINGLE.do_create_sender_module(
- 'TestMessageSenderSingle',
- protocol_create_code='protocol = TEST_PROTOCOL_SINGLE',
- enable_sync_sends=True,
- enable_async_sends=False,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL_SINGLE.do_create_sender_module(
+ 'TestMessageSenderSingle',
+ protocol_create_code='protocol = TEST_PROTOCOL_SINGLE',
+ enable_sync_sends=True,
+ enable_async_sends=False,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
@@ -505,13 +526,17 @@ def test_sender_module_single_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# SEND_SINGLE_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# SEND_SINGLE_CODE_TEST_END\n')
+ emb = (
+ f'# SEND_SINGLE_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# SEND_SINGLE_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED EMBEDDED CODE:\n{emb}')
- raise RuntimeError('Generated sender module does not match embedded;'
- ' test code needs to be updated.'
- ' See test stdout for new code.')
+ raise RuntimeError(
+ 'Generated sender module does not match embedded;'
+ ' test code needs to be updated.'
+ ' See test stdout for new code.'
+ )
def test_sender_module_sync_emb() -> None:
@@ -520,12 +545,14 @@ def test_sender_module_sync_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL.do_create_sender_module(
- 'TestMessageSenderSync',
- protocol_create_code='protocol = TEST_PROTOCOL',
- enable_sync_sends=True,
- enable_async_sends=False,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL.do_create_sender_module(
+ 'TestMessageSenderSync',
+ protocol_create_code='protocol = TEST_PROTOCOL',
+ enable_sync_sends=True,
+ enable_async_sends=False,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
@@ -538,13 +565,17 @@ def test_sender_module_sync_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# SEND_SYNC_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# SEND_SYNC_CODE_TEST_END\n')
+ emb = (
+ f'# SEND_SYNC_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# SEND_SYNC_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED EMBEDDED CODE:\n{emb}')
- raise RuntimeError('Generated sender module does not match embedded;'
- ' test code needs to be updated.'
- ' See test stdout for new code.')
+ raise RuntimeError(
+ 'Generated sender module does not match embedded;'
+ ' test code needs to be updated.'
+ ' See test stdout for new code.'
+ )
def test_sender_module_async_emb() -> None:
@@ -553,12 +584,14 @@ def test_sender_module_async_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL.do_create_sender_module(
- 'TestMessageSenderAsync',
- protocol_create_code='protocol = TEST_PROTOCOL',
- enable_sync_sends=False,
- enable_async_sends=True,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL.do_create_sender_module(
+ 'TestMessageSenderAsync',
+ protocol_create_code='protocol = TEST_PROTOCOL',
+ enable_sync_sends=False,
+ enable_async_sends=True,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
@@ -571,13 +604,17 @@ def test_sender_module_async_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# SEND_ASYNC_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# SEND_ASYNC_CODE_TEST_END\n')
+ emb = (
+ f'# SEND_ASYNC_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# SEND_ASYNC_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED EMBEDDED CODE:\n{emb}')
- raise RuntimeError('Generated sender module does not match embedded;'
- ' test code needs to be updated.'
- ' See test stdout for new code.')
+ raise RuntimeError(
+ 'Generated sender module does not match embedded;'
+ ' test code needs to be updated.'
+ ' See test stdout for new code.'
+ )
def test_sender_module_both_emb() -> None:
@@ -586,12 +623,14 @@ def test_sender_module_both_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL_EVOLVED.do_create_sender_module(
- 'TestMessageSenderBBoth',
- protocol_create_code='protocol = TEST_PROTOCOL_EVOLVED',
- enable_sync_sends=True,
- enable_async_sends=True,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL_EVOLVED.do_create_sender_module(
+ 'TestMessageSenderBBoth',
+ protocol_create_code='protocol = TEST_PROTOCOL_EVOLVED',
+ enable_sync_sends=True,
+ enable_async_sends=True,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
@@ -604,13 +643,17 @@ def test_sender_module_both_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# SEND_BOTH_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# SEND_BOTH_CODE_TEST_END\n')
+ emb = (
+ f'# SEND_BOTH_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# SEND_BOTH_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED EMBEDDED CODE:\n{emb}')
- raise RuntimeError('Generated sender module does not match embedded;'
- ' test code needs to be updated.'
- ' See test stdout for new code.')
+ raise RuntimeError(
+ 'Generated sender module does not match embedded;'
+ ' test code needs to be updated.'
+ ' See test stdout for new code.'
+ )
def test_receiver_module_single_emb() -> None:
@@ -619,17 +662,20 @@ def test_receiver_module_single_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL_SINGLE.do_create_receiver_module(
- 'TestSingleMessageReceiver',
- 'protocol = TEST_PROTOCOL_SINGLE',
- is_async=False,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL_SINGLE.do_create_receiver_module(
+ 'TestSingleMessageReceiver',
+ 'protocol = TEST_PROTOCOL_SINGLE',
+ is_async=False,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
lines = smod.splitlines()
classline = lines.index(
- 'class _TestSingleMessageReceiver(MessageReceiver):')
+ 'class _TestSingleMessageReceiver(MessageReceiver):'
+ )
clipped = '\n'.join(lines[classline:])
# This snippet should match what we've got embedded above;
@@ -637,14 +683,17 @@ def test_receiver_module_single_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# RCV_SINGLE_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# RCV_SINGLE_CODE_TEST_END\n')
+ emb = (
+ f'# RCV_SINGLE_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# RCV_SINGLE_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED SINGLE RECEIVER EMBEDDED CODE:\n{emb}')
raise RuntimeError(
'Generated single receiver module does not match embedded;'
' test code needs to be updated.'
- ' See test stdout for new code.')
+ ' See test stdout for new code.'
+ )
def test_receiver_module_sync_emb() -> None:
@@ -653,11 +702,13 @@ def test_receiver_module_sync_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL.do_create_receiver_module(
- 'TestSyncMessageReceiver',
- 'protocol = TEST_PROTOCOL',
- is_async=False,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL.do_create_receiver_module(
+ 'TestSyncMessageReceiver',
+ 'protocol = TEST_PROTOCOL',
+ is_async=False,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
@@ -670,14 +721,17 @@ def test_receiver_module_sync_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# RCV_SYNC_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# RCV_SYNC_CODE_TEST_END\n')
+ emb = (
+ f'# RCV_SYNC_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# RCV_SYNC_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED SYNC RECEIVER EMBEDDED CODE:\n{emb}')
raise RuntimeError(
'Generated sync receiver module does not match embedded;'
' test code needs to be updated.'
- ' See test stdout for new code.')
+ ' See test stdout for new code.'
+ )
def test_receiver_module_async_emb() -> None:
@@ -686,17 +740,18 @@ def test_receiver_module_async_emb() -> None:
# here, but it requires us to pass code which imports this test module
# to get at the protocol, and that currently fails in our static mypy
# tests.
- smod = TEST_PROTOCOL.do_create_receiver_module(
- 'TestAsyncMessageReceiver',
- 'protocol = TEST_PROTOCOL',
- is_async=True,
- private=True,
+ smod = format_python_str(
+ TEST_PROTOCOL.do_create_receiver_module(
+ 'TestAsyncMessageReceiver',
+ 'protocol = TEST_PROTOCOL',
+ is_async=True,
+ private=True,
+ )
)
# Clip everything up to our first class declaration.
lines = smod.splitlines()
- classline = lines.index(
- 'class _TestAsyncMessageReceiver(MessageReceiver):')
+ classline = lines.index('class _TestAsyncMessageReceiver(MessageReceiver):')
clipped = '\n'.join(lines[classline:])
# This snippet should match what we've got embedded above;
@@ -704,14 +759,17 @@ def test_receiver_module_async_emb() -> None:
with open(__file__, encoding='utf-8') as infile:
ourcode = infile.read()
- emb = (f'# RCV_ASYNC_CODE_TEST_BEGIN'
- f'\n\n\n{clipped}\n\n\n# RCV_ASYNC_CODE_TEST_END\n')
+ emb = (
+ f'# RCV_ASYNC_CODE_TEST_BEGIN'
+ f'\n\n\n{clipped}\n\n\n# RCV_ASYNC_CODE_TEST_END\n'
+ )
if emb not in ourcode:
print(f'EXPECTED ASYNC RECEIVER EMBEDDED CODE:\n{emb}')
raise RuntimeError(
'Generated async receiver module does not match embedded;'
' test code needs to be updated.'
- ' See test stdout for new code.')
+ ' See test stdout for new code.'
+ )
def test_receiver_creation() -> None:
@@ -772,22 +830,26 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
# Test throwing exceptions in send methods.
if self.test_send_method_exceptions:
- raise (CommunicationError()
- if self.test_send_method_exceptions_comm else
- RuntimeError())
+ raise (
+ CommunicationError()
+ if self.test_send_method_exceptions_comm
+ else RuntimeError()
+ )
# Just talk directly to the receiver for this example.
# (currently only support synchronous receivers)
assert isinstance(self._target, TestClassRSync)
try:
return self._target.receiver.handle_raw_message(
- data, raise_unregistered=self.test_handling_unregistered)
+ data, raise_unregistered=self.test_handling_unregistered
+ )
except UnregisteredMessageIDError:
if self.test_handling_unregistered:
# Emulate forwarding unregistered messages on to some
# other handler...
response_dict = self.msg.protocol.response_to_dict(
- EmptySysResponse())
+ EmptySysResponse()
+ )
return self.msg.protocol.encode_dict(response_dict)
raise
@@ -797,9 +859,11 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
# Test throwing exceptions in async send methods.
if self.test_send_method_exceptions:
- raise (CommunicationError()
- if self.test_send_method_exceptions_comm else
- RuntimeError())
+ raise (
+ CommunicationError()
+ if self.test_send_method_exceptions_comm
+ else RuntimeError()
+ )
# Just talk directly to the receiver for this example.
# (we can do sync or async receivers)
@@ -814,8 +878,12 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
outdict['_sidecar_data'] = getattr(msg, '_sidecar_data')
@msg.decode_filter_method
- def _decode_filter(self, message: Message, indata: dict,
- response: Response | SysResponse) -> None:
+ def _decode_filter(
+ self,
+ message: Message,
+ indata: dict,
+ response: Response | SysResponse,
+ ) -> None:
"""Filter our incoming responses."""
del message # Unused.
if self.test_sidecar:
@@ -841,22 +909,26 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
# Test throwing exceptions in send methods.
if self.test_send_method_exceptions:
- raise (CommunicationError()
- if self.test_send_method_exceptions_comm else
- RuntimeError())
+ raise (
+ CommunicationError()
+ if self.test_send_method_exceptions_comm
+ else RuntimeError()
+ )
# Just talk directly to the receiver for this example.
# (currently only support synchronous receivers)
assert isinstance(self._target, TestClassRAlt)
try:
return self._target.receiver.handle_raw_message(
- data, raise_unregistered=self.test_handling_unregistered)
+ data, raise_unregistered=self.test_handling_unregistered
+ )
except UnregisteredMessageIDError:
if self.test_handling_unregistered:
# Emulate forwarding unregistered messages on to some
# other handler...
response_dict = self.msg.protocol.response_to_dict(
- EmptySysResponse())
+ EmptySysResponse()
+ )
return self.msg.protocol.encode_dict(response_dict)
raise
@@ -900,9 +972,12 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
setattr(message, '_sidecar_data', indata['_sidecar_data'])
@receiver.encode_filter_method
- def _encode_filter(self, message: Message | None,
- response: Response | SysResponse,
- outdict: dict) -> None:
+ def _encode_filter(
+ self,
+ message: Message | None,
+ response: Response | SysResponse,
+ outdict: dict,
+ ) -> None:
"""Filter our outgoing responses."""
del message # Unused.
if self.test_sidecar:
@@ -951,8 +1026,7 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
return _TResp1(bval=True)
@receiver.handler
- async def handle_test_message_2(self,
- msg: _TMsg2) -> _TResp1 | _TResp2:
+ async def handle_test_message_2(self, msg: _TMsg2) -> _TResp1 | _TResp2:
"""Test."""
del msg # Unused
return _TResp2(fval=1.2)
@@ -1010,8 +1084,9 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
caplog.clear()
with pytest.raises(RemoteError):
_response5 = objb.msg.send(_TMsg1(ival=1))
- assert (len(caplog.records) == 1
- and caplog.records[0].levelno == logging.ERROR)
+ assert (
+ len(caplog.records) == 1 and caplog.records[0].levelno == logging.ERROR
+ )
# Same with CommunicationErrors occurring on the peer; they should
# come back to us intact if forward_communication_errors is enabled
@@ -1029,8 +1104,9 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
caplog.clear()
with pytest.raises(RemoteError):
_response5 = objb.msg.send(_TMsg1(ival=3))
- assert (len(caplog.records) == 1
- and caplog.records[0].levelno == logging.ERROR)
+ assert (
+ len(caplog.records) == 1 and caplog.records[0].levelno == logging.ERROR
+ )
# Misc other error types happening on peer should result in
# RemoteError and log message.
@@ -1038,8 +1114,9 @@ def test_full_pipeline(caplog: pytest.LogCaptureFixture) -> None:
with pytest.raises(RemoteError):
_response5 = obj.msg.send(_TMsg1(ival=2))
# This should have logged a single error message.
- assert (len(caplog.records) == 1
- and caplog.records[0].levelno == logging.ERROR)
+ assert (
+ len(caplog.records) == 1 and caplog.records[0].levelno == logging.ERROR
+ )
# Now test sends to async handlers.
response6 = asyncio.run(obj2.msg.send_async(_TMsg1(ival=0)))
diff --git a/tests/test_efro/test_rpc.py b/tests/test_efro/test_rpc.py
index 123a8242..6f23de65 100644
--- a/tests/test_efro/test_rpc.py
+++ b/tests/test_efro/test_rpc.py
@@ -50,9 +50,12 @@ class _Message:
class _ServerClientCommon:
-
- def __init__(self, keepalive_interval: float, keepalive_timeout: float,
- debug_print: bool) -> None:
+ def __init__(
+ self,
+ keepalive_interval: float,
+ keepalive_timeout: float,
+ debug_print: bool,
+ ) -> None:
self._endpoint: RPCEndpoint | None = None
self._keepalive_interval = keepalive_interval
self._keepalive_timeout = keepalive_timeout
@@ -69,16 +72,19 @@ class _ServerClientCommon:
raise RuntimeError('Expected endpoint to exist.')
return self._endpoint
- async def send_message(self,
- message: _Message,
- timeout: float | None = None,
- close_on_error: bool = True) -> _Message:
+ async def send_message(
+ self,
+ message: _Message,
+ timeout: float | None = None,
+ close_on_error: bool = True,
+ ) -> _Message:
"""Send high level messages."""
assert self._endpoint is not None
response = await self._endpoint.send_message(
dataclass_to_json(message).encode(),
timeout=timeout,
- close_on_error=close_on_error)
+ close_on_error=close_on_error,
+ )
return dataclass_from_json(_Message, response.decode())
async def handle_message(self, msg: _Message) -> _Message:
@@ -96,8 +102,10 @@ class _ServerClientCommon:
if msg.messagetype is _MessageType.TEST_BIG:
# 5 Mb Response
- return _Message(_MessageType.RESPONSE_BIG,
- extradata=bytes(bytearray(1024 * 1024 * 5)))
+ return _Message(
+ _MessageType.RESPONSE_BIG,
+ extradata=bytes(bytearray(1024 * 1024 * 5)),
+ )
raise RuntimeError(f'Got unexpected message type: {msg.messagetype}')
@@ -108,19 +116,25 @@ class _ServerClientCommon:
class _Server(_ServerClientCommon):
-
- def __init__(self, keepalive_interval: float, keepalive_timeout: float,
- debug_print: bool) -> None:
- super().__init__(keepalive_interval=keepalive_interval,
- keepalive_timeout=keepalive_timeout,
- debug_print=debug_print)
+ def __init__(
+ self,
+ keepalive_interval: float,
+ keepalive_timeout: float,
+ debug_print: bool,
+ ) -> None:
+ super().__init__(
+ keepalive_interval=keepalive_interval,
+ keepalive_timeout=keepalive_timeout,
+ debug_print=debug_print,
+ )
self.listener: asyncio.base_events.Server | None = None
async def start(self) -> None:
"""Start serving. Call this before run()."""
assert self.listener is None
- self.listener = await asyncio.start_server(self._handle_client, ADDR,
- PORT)
+ self.listener = await asyncio.start_server(
+ self._handle_client, ADDR, PORT
+ )
async def run(self) -> None:
"""Do the thing."""
@@ -132,8 +146,9 @@ class _Server(_ServerClientCommon):
except asyncio.CancelledError:
pass
- async def _handle_client(self, reader: asyncio.StreamReader,
- writer: asyncio.StreamWriter) -> None:
+ async def _handle_client(
+ self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
+ ) -> None:
assert self._endpoint is None
# Note to self: passing ourself as a handler creates a dependency
@@ -146,18 +161,24 @@ class _Server(_ServerClientCommon):
keepalive_interval=self._keepalive_interval,
keepalive_timeout=self._keepalive_timeout,
debug_print=self._debug_print,
- label='test_rpc_server')
+ label='test_rpc_server',
+ )
await self._endpoint.run()
class _Client(_ServerClientCommon):
-
- def __init__(self, keepalive_interval: float, keepalive_timeout: float,
- debug_print: bool) -> None:
- super().__init__(keepalive_interval=keepalive_interval,
- keepalive_timeout=keepalive_timeout,
- debug_print=debug_print)
+ def __init__(
+ self,
+ keepalive_interval: float,
+ keepalive_timeout: float,
+ debug_print: bool,
+ ) -> None:
+ super().__init__(
+ keepalive_interval=keepalive_interval,
+ keepalive_timeout=keepalive_timeout,
+ debug_print=debug_print,
+ )
async def run(self) -> None:
"""Do the thing."""
@@ -172,24 +193,29 @@ class _Client(_ServerClientCommon):
keepalive_interval=self._keepalive_interval,
keepalive_timeout=self._keepalive_timeout,
debug_print=self._debug_print,
- label='test_rpc_client')
+ label='test_rpc_client',
+ )
await self._endpoint.run()
class _Tester:
-
def __init__(
- self,
- keepalive_interval: float = RPCEndpoint.DEFAULT_KEEPALIVE_INTERVAL,
- keepalive_timeout: float = RPCEndpoint.DEFAULT_KEEPALIVE_TIMEOUT,
- server_debug_print: bool = True,
- client_debug_print: bool = True) -> None:
- self.client = _Client(keepalive_interval=keepalive_interval,
- keepalive_timeout=keepalive_timeout,
- debug_print=client_debug_print)
- self.server = _Server(keepalive_interval=keepalive_interval,
- keepalive_timeout=keepalive_timeout,
- debug_print=server_debug_print)
+ self,
+ keepalive_interval: float = RPCEndpoint.DEFAULT_KEEPALIVE_INTERVAL,
+ keepalive_timeout: float = RPCEndpoint.DEFAULT_KEEPALIVE_TIMEOUT,
+ server_debug_print: bool = True,
+ client_debug_print: bool = True,
+ ) -> None:
+ self.client = _Client(
+ keepalive_interval=keepalive_interval,
+ keepalive_timeout=keepalive_timeout,
+ debug_print=client_debug_print,
+ )
+ self.server = _Server(
+ keepalive_interval=keepalive_interval,
+ keepalive_timeout=keepalive_timeout,
+ debug_print=server_debug_print,
+ )
# noinspection PyProtectedMember
def run(self, testcall: Awaitable[None]) -> None:
@@ -213,6 +239,7 @@ class _Tester:
]:
if endpoint is not None:
import gc
+
print('referrers:', gc.get_referrers(endpoint))
raise RuntimeError(f'{name} did not go down cleanly')
@@ -267,8 +294,10 @@ def test_keepalive_fail() -> None:
# keepalive timeout.
await asyncio.sleep(ktimeout)
starttime = time.monotonic()
- while (not tester.server.endpoint.is_closing()
- and time.monotonic() - starttime < 5.0):
+ while (
+ not tester.server.endpoint.is_closing()
+ and time.monotonic() - starttime < 5.0
+ ):
await asyncio.sleep(0.01)
assert tester.server.endpoint.is_closing()
@@ -313,13 +342,19 @@ def test_simple_messages() -> None:
assert resp.messagetype is _MessageType.RESPONSE2
resp = await tester.server.send_message(
- _Message(_MessageType.TEST_BIG,
- extradata=bytes(bytearray(1024 * 1024 * 5))))
+ _Message(
+ _MessageType.TEST_BIG,
+ extradata=bytes(bytearray(1024 * 1024 * 5)),
+ )
+ )
assert resp.messagetype is _MessageType.RESPONSE_BIG
resp = await tester.client.send_message(
- _Message(_MessageType.TEST_BIG,
- extradata=bytes(bytearray(1024 * 1024 * 5))))
+ _Message(
+ _MessageType.TEST_BIG,
+ extradata=bytes(bytearray(1024 * 1024 * 5)),
+ )
+ )
assert resp.messagetype is _MessageType.RESPONSE_BIG
tester.run(_do_it())
@@ -347,8 +382,7 @@ def test_simultaneous_messages() -> None:
assert (time.monotonic() - starttime) < 1.25 * SLOW_WAIT
# Make sure we got all correct responses.
- assert all(r.messagetype is _MessageType.RESPONSE_SLOW
- for r in results)
+ assert all(r.messagetype is _MessageType.RESPONSE_SLOW for r in results)
# They should all be uniquely created message objects.
assert len(set(id(r) for r in results)) == len(results)
@@ -364,7 +398,8 @@ def test_message_timeout() -> None:
# This message should return after a short wait.
resp = await tester.server.send_message(
- _Message(_MessageType.TEST_SLOW))
+ _Message(_MessageType.TEST_SLOW)
+ )
assert resp.messagetype is _MessageType.RESPONSE_SLOW
# This message should time out but not close the connection.
@@ -393,7 +428,6 @@ def test_server_interrupt() -> None:
tester = _Tester()
async def _do_it() -> None:
-
async def _kill_connection() -> None:
await asyncio.sleep(0.2)
tester.server.endpoint.close()
@@ -410,7 +444,6 @@ def test_client_interrupt() -> None:
tester = _Tester()
async def _do_it() -> None:
-
async def _kill_connection() -> None:
await asyncio.sleep(0.2)
tester.client.endpoint.close()
diff --git a/tools/bacloud b/tools/bacloud
index 026c97ad..4eeab191 100755
--- a/tools/bacloud
+++ b/tools/bacloud
@@ -21,8 +21,12 @@ import requests
from efro.terminal import Clr
from efro.error import CleanError
-from efro.dataclassio import (dataclass_from_json, dataclass_to_dict,
- dataclass_to_json, ioprepped)
+from efro.dataclassio import (
+ dataclass_from_json,
+ dataclass_to_dict,
+ dataclass_to_json,
+ ioprepped,
+)
from bacommon.bacloud import RequestData, ResponseData, BACLOUD_VERSION
if TYPE_CHECKING:
@@ -40,14 +44,17 @@ BACLOUD_SERVER_URL = os.getenv('BACLOUD_SERVER_URL', 'https://ballistica.net')
@dataclass
class StateData:
"""Persistent state data stored to disk."""
+
login_token: str | None = None
def get_tz_offset_seconds() -> float:
"""Return the offset between utc and local time in seconds."""
tval = time.time()
- utc_offset = (datetime.datetime.fromtimestamp(tval) -
- datetime.datetime.utcfromtimestamp(tval)).total_seconds()
+ utc_offset = (
+ datetime.datetime.fromtimestamp(tval)
+ - datetime.datetime.utcfromtimestamp(tval)
+ ).total_seconds()
return utc_offset
@@ -65,20 +72,24 @@ class App:
# Make sure we can locate the project bacloud is being run from.
self._project_root = Path(sys.argv[0]).parents[1]
if not all(
- Path(self._project_root, name).is_dir()
- for name in ('tools', 'config', 'tests')):
+ Path(self._project_root, name).is_dir()
+ for name in ('tools', 'config', 'tests')
+ ):
raise CleanError('Unable to locate project directory.')
# Also run project prereqs checks so we can hopefully inform the user
# of missing Python modules/etc. instead of just failing cryptically.
try:
- subprocess.run(['make', '--quiet', 'prereqs'],
- check=True,
- cwd=self._project_root)
+ subprocess.run(
+ ['make', '--quiet', 'prereqs'],
+ check=True,
+ cwd=self._project_root,
+ )
except subprocess.CalledProcessError as exc:
raise CleanError(
'"make prereqs" check failed. '
- 'Install missing requirements and try again.') from exc
+ 'Install missing requirements and try again.'
+ ) from exc
self._load_state()
@@ -105,8 +116,10 @@ class App:
with open(self._state_data_path, 'r', encoding='utf-8') as infile:
self._state = dataclass_from_json(StateData, infile.read())
except Exception:
- print(f'{Clr.RED}Error loading {TOOL_NAME} data;'
- f' resetting to defaults.{Clr.RST}')
+ print(
+ f'{Clr.RED}Error loading {TOOL_NAME} data;'
+ f' resetting to defaults.{Clr.RST}'
+ )
def _save_state(self) -> None:
if not self._state_dir.exists():
@@ -114,10 +127,9 @@ class App:
with open(self._state_data_path, 'w', encoding='utf-8') as outfile:
outfile.write(dataclass_to_json(self._state))
- def _servercmd(self,
- cmd: str,
- payload: dict,
- files: dict[str, IO] | None = None) -> ResponseData:
+ def _servercmd(
+ self, cmd: str, payload: dict, files: dict[str, IO] | None = None
+ ) -> ResponseData:
"""Issue a command to the server and get a response."""
response_content: str | None = None
@@ -126,15 +138,16 @@ class App:
headers = {'User-Agent': f'bacloud/{BACLOUD_VERSION}'}
rdata = {
- 'v':
- BACLOUD_VERSION,
- 'r':
- dataclass_to_json(
- RequestData(command=cmd,
- token=self._state.login_token,
- payload=payload,
- tzoffset=get_tz_offset_seconds(),
- isatty=sys.stdout.isatty())),
+ 'v': BACLOUD_VERSION,
+ 'r': dataclass_to_json(
+ RequestData(
+ command=cmd,
+ token=self._state.login_token,
+ payload=payload,
+ tzoffset=get_tz_offset_seconds(),
+ isatty=sys.stdout.isatty(),
+ )
+ ),
}
# Trying urllib for comparison (note that this doesn't support
@@ -144,21 +157,23 @@ class App:
import urllib.parse
with urllib.request.urlopen(
- urllib.request.Request(
- url,
- urllib.parse.urlencode(rdata).encode(),
- headers)) as raw_response:
+ urllib.request.Request(
+ url, urllib.parse.urlencode(rdata).encode(), headers
+ )
+ ) as raw_response:
if raw_response.getcode() != 200:
raise RuntimeError('Error talking to server')
response_content = raw_response.read().decode()
# Using requests module.
else:
- with requests.post(url,
- headers=headers,
- data=rdata,
- files=files,
- timeout=TIMEOUT_SECONDS) as response_raw:
+ with requests.post(
+ url,
+ headers=headers,
+ data=rdata,
+ files=files,
+ timeout=TIMEOUT_SECONDS,
+ ) as response_raw:
response_raw.raise_for_status()
assert isinstance(response_raw.content, bytes)
response_content = response_raw.content.decode()
@@ -181,13 +196,16 @@ class App:
def _upload_file(self, filename: str, call: str, args: dict) -> None:
import tempfile
+
print(f'Uploading {Clr.BLU}{filename}{Clr.RST}', flush=True)
with tempfile.TemporaryDirectory() as tempdir:
srcpath = Path(filename)
gzpath = Path(tempdir, 'file.gz')
- subprocess.run(f'gzip --stdout "{srcpath}" > "{gzpath}"',
- shell=True,
- check=True)
+ subprocess.run(
+ f'gzip --stdout "{srcpath}" > "{gzpath}"',
+ shell=True,
+ check=True,
+ )
with open(gzpath, 'rb') as infile:
putfiles: dict = {'file': infile}
_response = self._servercmd(
@@ -198,11 +216,14 @@ class App:
def _handle_dir_manifest_response(self, dirmanifest: str) -> None:
from bacommon.transfer import DirectoryManifest
+
self._end_command_args['manifest'] = dataclass_to_dict(
- DirectoryManifest.create_from_disk(Path(dirmanifest)))
+ DirectoryManifest.create_from_disk(Path(dirmanifest))
+ )
def _handle_uploads(self, uploads: tuple[list[str], str, dict]) -> None:
from concurrent.futures import ThreadPoolExecutor
+
assert len(uploads) == 3
filenames, uploadcmd, uploadargs = uploads
assert isinstance(filenames, list)
@@ -233,6 +254,7 @@ class App:
) -> None:
"""Handle inline file data to be saved to the client."""
import base64
+
for fname, fdata in downloads_inline.items():
# If there's a directory where we want our file to go, clear it
@@ -260,8 +282,7 @@ class App:
# 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)
@@ -269,6 +290,7 @@ class App:
def _handle_uploads_inline(self, uploads_inline: list[str]) -> None:
"""Handle uploading files inline."""
import base64
+
files: dict[str, str] = {}
for filepath in uploads_inline:
if not os.path.exists(filepath):
@@ -282,12 +304,14 @@ class App:
def _handle_open_url(self, url: str) -> None:
import webbrowser
+
print(f'{Clr.CYN}(url: {url}){Clr.RST}')
webbrowser.open(url)
def _handle_input_prompt(self, prompt: str, as_password: bool) -> None:
if as_password:
from getpass import getpass
+
self._end_command_args['input'] = getpass(prompt=prompt)
else:
if prompt:
@@ -331,12 +355,16 @@ class App:
if response.open_url is not None:
self._handle_open_url(response.open_url)
if response.input_prompt is not None:
- self._handle_input_prompt(prompt=response.input_prompt[0],
- as_password=response.input_prompt[1])
+ self._handle_input_prompt(
+ prompt=response.input_prompt[0],
+ as_password=response.input_prompt[1],
+ )
if response.end_message is not None:
- print(response.end_message,
- end=response.end_message_end,
- flush=True)
+ print(
+ response.end_message,
+ end=response.end_message_end,
+ flush=True,
+ )
if response.end_command is not None:
nextcall = response.end_command
for key, val in self._end_command_args.items():
diff --git a/tools/bacommon/assets.py b/tools/bacommon/assets.py
index 2c4441ab..159307de 100644
--- a/tools/bacommon/assets.py
+++ b/tools/bacommon/assets.py
@@ -26,6 +26,7 @@ class AssetPackageFlavor(Enum):
class AssetType(Enum):
"""Types for individual assets within a package."""
+
TEXTURE = 'texture'
CUBE_TEXTURE = 'cube_texture'
SOUND = 'sound'
@@ -38,8 +39,10 @@ class AssetType(Enum):
@dataclass
class AssetPackageFlavorManifest:
"""A manifest of asset info for a specific flavor of an asset package."""
- cloudfiles: Annotated[dict[str, str],
- IOAttrs('cloudfiles')] = field(default_factory=dict)
+
+ cloudfiles: Annotated[dict[str, str], IOAttrs('cloudfiles')] = field(
+ default_factory=dict
+ )
@ioprepped
@@ -48,8 +51,9 @@ class AssetPackageBuildState:
"""Contains info about an in-progress asset cloud build."""
# Asset names still being built.
- in_progress_builds: Annotated[list[str],
- IOAttrs('b')] = field(default_factory=list)
+ in_progress_builds: Annotated[list[str], IOAttrs('b')] = field(
+ default_factory=list
+ )
# The initial number of assets needing to be built.
initial_build_count: Annotated[int, IOAttrs('c')] = 0
diff --git a/tools/bacommon/bacloud.py b/tools/bacommon/bacloud.py
index 2453aa8e..2f41f622 100644
--- a/tools/bacommon/bacloud.py
+++ b/tools/bacommon/bacloud.py
@@ -21,6 +21,7 @@ BACLOUD_VERSION = 8
@dataclass
class RequestData:
"""Request sent to bacloud server."""
+
command: Annotated[str, IOAttrs('c')]
token: Annotated[str | None, IOAttrs('t')]
payload: Annotated[dict, IOAttrs('p')]
@@ -74,23 +75,32 @@ class ResponseData:
delay_seconds: Annotated[float, IOAttrs('d', store_default=False)] = 0.0
login: Annotated[str | None, IOAttrs('l', store_default=False)] = None
logout: Annotated[bool, IOAttrs('lo', store_default=False)] = False
- dir_manifest: Annotated[str | None,
- IOAttrs('man', store_default=False)] = None
- uploads: Annotated[tuple[list[str], str, dict] | None,
- IOAttrs('u', store_default=False)] = None
- uploads_inline: Annotated[list[str] | None,
- IOAttrs('uinl', store_default=False)] = None
- deletes: Annotated[list[str] | None,
- IOAttrs('dlt', store_default=False)] = None
- downloads_inline: Annotated[dict[str, str] | None,
- IOAttrs('dinl', store_default=False)] = None
- dir_prune_empty: Annotated[str | None,
- IOAttrs('dpe', store_default=False)] = None
+ dir_manifest: Annotated[
+ str | None, IOAttrs('man', store_default=False)
+ ] = None
+ uploads: Annotated[
+ tuple[list[str], str, dict] | None, IOAttrs('u', store_default=False)
+ ] = None
+ uploads_inline: Annotated[
+ list[str] | None, IOAttrs('uinl', store_default=False)
+ ] = None
+ deletes: Annotated[
+ list[str] | None, IOAttrs('dlt', store_default=False)
+ ] = None
+ downloads_inline: Annotated[
+ dict[str, str] | None, IOAttrs('dinl', store_default=False)
+ ] = None
+ dir_prune_empty: Annotated[
+ str | None, IOAttrs('dpe', store_default=False)
+ ] = None
open_url: Annotated[str | None, IOAttrs('url', store_default=False)] = None
- input_prompt: Annotated[tuple[str, bool] | None,
- IOAttrs('inp', store_default=False)] = None
- end_message: Annotated[str | None,
- IOAttrs('em', store_default=False)] = None
+ input_prompt: Annotated[
+ tuple[str, bool] | None, IOAttrs('inp', store_default=False)
+ ] = None
+ end_message: Annotated[
+ str | None, IOAttrs('em', store_default=False)
+ ] = None
end_message_end: Annotated[str, IOAttrs('eme', store_default=False)] = '\n'
- end_command: Annotated[tuple[str, dict] | None,
- IOAttrs('ec', store_default=False)] = None
+ end_command: Annotated[
+ tuple[str, dict] | None, IOAttrs('ec', store_default=False)
+ ] = None
diff --git a/tools/bacommon/build.py b/tools/bacommon/build.py
index 44ce4897..0ffcd3fb 100644
--- a/tools/bacommon/build.py
+++ b/tools/bacommon/build.py
@@ -22,6 +22,7 @@ class BuildInfoSet:
@dataclass
class Entry:
"""Info about a particular build."""
+
filename: Annotated[str, IOAttrs('fname')]
size: Annotated[int, IOAttrs('size')]
version: Annotated[str, IOAttrs('version')]
@@ -29,5 +30,6 @@ class BuildInfoSet:
checksum: Annotated[str, IOAttrs('checksum')]
createtime: Annotated[datetime.datetime, IOAttrs('createtime')]
- builds: Annotated[list[Entry],
- IOAttrs('builds')] = field(default_factory=list)
+ builds: Annotated[list[Entry], IOAttrs('builds')] = field(
+ default_factory=list
+ )
diff --git a/tools/bacommon/cloud.py b/tools/bacommon/cloud.py
index 91512f92..431dac5a 100644
--- a/tools/bacommon/cloud.py
+++ b/tools/bacommon/cloud.py
@@ -44,6 +44,7 @@ class LoginProxyRequestResponse(Response):
@dataclass
class LoginProxyStateQueryMessage(Message):
"""Soo.. how is that login proxy going?"""
+
proxyid: Annotated[str, IOAttrs('p')]
proxykey: Annotated[str, IOAttrs('k')]
@@ -59,6 +60,7 @@ class LoginProxyStateQueryResponse(Response):
class State(Enum):
"""States a login-proxy can be in."""
+
WAITING = 'waiting'
SUCCESS = 'success'
FAIL = 'fail'
@@ -73,6 +75,7 @@ class LoginProxyStateQueryResponse(Response):
@dataclass
class LoginProxyCompleteMessage(Message):
"""Just so you know, we're done with this proxy."""
+
proxyid: Annotated[str, IOAttrs('p')]
@@ -96,6 +99,7 @@ class PingResponse(Response):
@dataclass
class TestMessage(Message):
"""Can I get some of that workspace action?"""
+
testfoo: Annotated[int, IOAttrs('f')]
@classmethod
@@ -115,6 +119,7 @@ class TestResponse(Response):
@dataclass
class WorkspaceFetchState:
"""Common state data for a workspace fetch."""
+
manifest: Annotated[DirectoryManifest, IOAttrs('m')]
iteration: Annotated[int, IOAttrs('i')] = 0
total_deletes: Annotated[int, IOAttrs('tdels')] = 0
@@ -126,6 +131,7 @@ class WorkspaceFetchState:
@dataclass
class WorkspaceFetchMessage(Message):
"""Can I get some of that workspace action?"""
+
workspaceid: Annotated[str, IOAttrs('w')]
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
@@ -140,11 +146,11 @@ class WorkspaceFetchResponse(Response):
"""Here's that workspace you asked for, boss."""
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
- deletes: Annotated[list[str],
- IOAttrs('dlt', store_default=False)] = field(
- default_factory=list)
- downloads_inline: Annotated[dict[str, bytes],
- IOAttrs('dinl', store_default=False)] = field(
- default_factory=dict)
+ deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field(
+ default_factory=list
+ )
+ downloads_inline: Annotated[
+ dict[str, bytes], IOAttrs('dinl', store_default=False)
+ ] = field(default_factory=dict)
done: Annotated[bool, IOAttrs('d')] = False
diff --git a/tools/bacommon/net.py b/tools/bacommon/net.py
index a22a9c5c..2cc97df1 100644
--- a/tools/bacommon/net.py
+++ b/tools/bacommon/net.py
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
@dataclass
class ServerNodeEntry:
"""Information about a specific server."""
+
zone: Annotated[str, IOAttrs('r')]
address: Annotated[str, IOAttrs('a')]
port: Annotated[int, IOAttrs('p')]
@@ -35,15 +36,16 @@ class ServerNodeQueryResponse:
error: Annotated[str | None, IOAttrs('e', store_default=False)] = None
# The set of servernodes.
- servers: Annotated[list[ServerNodeEntry],
- IOAttrs('s', store_default=False)] = field(
- default_factory=list)
+ servers: Annotated[
+ list[ServerNodeEntry], IOAttrs('s', store_default=False)
+ ] = field(default_factory=list)
@ioprepped
@dataclass
class PrivateHostingState:
"""Combined state of whether we're hosting, whether we can, etc."""
+
unavailable_error: str | None = None
party_code: str | None = None
tickets_to_host_now: int = 0
@@ -55,13 +57,15 @@ class PrivateHostingState:
@dataclass
class PrivateHostingConfig:
"""Config provided when hosting a private party."""
+
session_type: str = 'ffa'
playlist_name: str = 'Unknown'
randomize: bool = False
tutorial: bool = False
custom_team_names: tuple[str, str] | None = None
- custom_team_colors: tuple[tuple[float, float, float],
- tuple[float, float, float]] | None = None
+ custom_team_colors: tuple[
+ tuple[float, float, float], tuple[float, float, float]
+ ] | None = None
playlist: list[dict[str, Any]] | None = None
exit_minutes: float = 120.0
exit_minutes_unclean: float = 180.0
@@ -72,6 +76,7 @@ class PrivateHostingConfig:
@dataclass
class PrivatePartyConnectResult:
"""Info about a server we get back when connecting."""
+
error: str | None = None
addr: str | None = None
port: int | None = None
diff --git a/tools/bacommon/servermanager.py b/tools/bacommon/servermanager.py
index 8dd5b1d0..e21b1d1f 100644
--- a/tools/bacommon/servermanager.py
+++ b/tools/bacommon/servermanager.py
@@ -134,8 +134,9 @@ class ServerConfig:
team_names: tuple[str, str] | None = None
# Team colors (teams mode only).
- team_colors: tuple[tuple[float, float, float], tuple[float, float,
- float]] | None = None
+ team_colors: tuple[
+ tuple[float, float, float], tuple[float, float, float]
+ ] | None = None
# (internal) stress-testing mode.
stress_test_players: int | None = None
@@ -151,11 +152,13 @@ class ServerCommand:
@dataclass
class StartServerModeCommand(ServerCommand):
"""Tells the app to switch into 'server' mode."""
+
config: ServerConfig
class ShutdownReason(Enum):
"""Reason a server is shutting down."""
+
NONE = 'none'
RESTARTING = 'restarting'
@@ -163,6 +166,7 @@ class ShutdownReason(Enum):
@dataclass
class ShutdownCommand(ServerCommand):
"""Tells the server to shut down."""
+
reason: ShutdownReason
immediate: bool
@@ -170,6 +174,7 @@ class ShutdownCommand(ServerCommand):
@dataclass
class ChatMessageCommand(ServerCommand):
"""Chat message from the server."""
+
message: str
clients: list[int] | None
@@ -177,6 +182,7 @@ class ChatMessageCommand(ServerCommand):
@dataclass
class ScreenMessageCommand(ServerCommand):
"""Screen-message from the server."""
+
message: str
color: tuple[float, float, float] | None
clients: list[int] | None
@@ -190,5 +196,6 @@ class ClientListCommand(ServerCommand):
@dataclass
class KickCommand(ServerCommand):
"""Kick a client."""
+
client_id: int
ban_time: int | None
diff --git a/tools/bacommon/transfer.py b/tools/bacommon/transfer.py
index 65042879..c0b44926 100644
--- a/tools/bacommon/transfer.py
+++ b/tools/bacommon/transfer.py
@@ -19,6 +19,7 @@ if TYPE_CHECKING:
@dataclass
class DirectoryManifestFile:
"""Describes metadata and hashes for a file in a manifest."""
+
filehash: Annotated[str, IOAttrs('h')]
filesize: Annotated[int, IOAttrs('s')]
@@ -27,6 +28,7 @@ class DirectoryManifestFile:
@dataclass
class DirectoryManifest:
"""Contains a summary of files in a directory."""
+
files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')]
_empty_hash: str | None = None
@@ -48,7 +50,7 @@ class DirectoryManifest:
assert fullname.startswith(pathstr)
# Make sure we end up with forward slashes no matter
# what the os.* stuff above here was using.
- paths.append(Path(fullname[len(pathstr) + 1:]).as_posix())
+ paths.append(Path(fullname[len(pathstr) + 1 :]).as_posix())
elif path.exists():
# Just return a single file entry if path is not a dir.
paths.append(path.as_posix())
@@ -62,9 +64,12 @@ class DirectoryManifest:
filebytes = infile.read()
filesize = len(filebytes)
sha.update(filebytes)
- return (filepath,
- DirectoryManifestFile(filehash=sha.hexdigest(),
- filesize=filesize))
+ return (
+ filepath,
+ DirectoryManifestFile(
+ filehash=sha.hexdigest(), filesize=filesize
+ ),
+ )
# Now use all procs to hash the files efficiently.
cpus = os.cpu_count()
@@ -76,13 +81,15 @@ class DirectoryManifest:
def validate(self) -> None:
"""Log any odd data in the manifest; for debugging."""
import logging
+
for fpath, _fentry in self.files.items():
# We want to be dealing in only forward slashes; make sure
# that's the case (wondering if we'll ever see backslashes
# for escape purposes).
if '\\' in fpath:
- logging.exception("Found unusual path in manifest: '%s'.",
- fpath)
+ logging.exception(
+ "Found unusual path in manifest: '%s'.", fpath
+ )
break # 1 error is enough for now.
@classmethod
@@ -90,6 +97,7 @@ class DirectoryManifest:
"""Return the hash for an empty file."""
if cls._empty_hash is None:
import hashlib
+
sha = hashlib.sha256()
cls._empty_hash = sha.hexdigest()
return cls._empty_hash
diff --git a/tools/batools/android.py b/tools/batools/android.py
index 902ee5fc..bc2b84e4 100644
--- a/tools/batools/android.py
+++ b/tools/batools/android.py
@@ -21,63 +21,74 @@ def androidaddr(archive_dir: str, arch: str, addr: str) -> None:
print('ERROR: invalid archive dir: "' + archive_dir + '"')
sys.exit(255)
archs = {
- 'x86': {
- 'prefix': 'x86-',
- 'libmain': 'libmain_x86.so'
- },
- 'arm': {
- 'prefix': 'arm-',
- 'libmain': 'libmain_arm.so'
- },
- 'arm64': {
- 'prefix': 'aarch64-',
- 'libmain': 'libmain_arm64.so'
- },
- 'x86-64': {
- 'prefix': 'x86_64-',
- 'libmain': 'libmain_x86-64.so'
- }
+ 'x86': {'prefix': 'x86-', 'libmain': 'libmain_x86.so'},
+ 'arm': {'prefix': 'arm-', 'libmain': 'libmain_arm.so'},
+ 'arm64': {'prefix': 'aarch64-', 'libmain': 'libmain_arm64.so'},
+ 'x86-64': {'prefix': 'x86_64-', 'libmain': 'libmain_x86-64.so'},
}
if arch not in archs:
- print('ERROR: invalid arch "' + arch + '"; (choices are ' +
- ', '.join(archs.keys()) + ')')
+ print(
+ 'ERROR: invalid arch "'
+ + arch
+ + '"; (choices are '
+ + ', '.join(archs.keys())
+ + ')'
+ )
sys.exit(255)
rootdir = '.'
- ndkpath = subprocess.check_output(
- ['tools/pcommand', 'android_sdk_utils',
- 'get-ndk-path']).decode().strip()
+ ndkpath = (
+ subprocess.check_output(
+ ['tools/pcommand', 'android_sdk_utils', 'get-ndk-path']
+ )
+ .decode()
+ .strip()
+ )
if not os.path.isdir(ndkpath):
print("ERROR: ndk-path '" + ndkpath + '" does not exist')
sys.exit(255)
- lines = subprocess.check_output(
- ['find',
- os.path.join(ndkpath, 'toolchains'), '-name',
- '*addr2line']).decode().strip().splitlines()
+ lines = (
+ subprocess.check_output(
+ ['find', os.path.join(ndkpath, 'toolchains'), '-name', '*addr2line']
+ )
+ .decode()
+ .strip()
+ .splitlines()
+ )
# print('RAW LINES', lines)
lines = [
- line for line in lines
+ line
+ for line in lines
if archs[arch]['prefix'] in line and '/llvm/' in line
]
if len(lines) != 1:
print(f"ERROR: can't find addr2line binary ({len(lines)} options).")
sys.exit(255)
addr2line = lines[0]
- subprocess.run('mkdir -p "' + os.path.join(rootdir, 'android_addr_tmp') +
- '"',
- shell=True,
- check=True)
+ subprocess.run(
+ 'mkdir -p "' + os.path.join(rootdir, 'android_addr_tmp') + '"',
+ shell=True,
+ check=True,
+ )
try:
- subprocess.run('cd "' + os.path.join(rootdir, 'android_addr_tmp') +
- '" && tar -xf "' +
- os.path.join(archive_dir, 'unstripped_libs',
- archs[arch]['libmain'] + '.tgz') + '"',
- shell=True,
- check=True)
subprocess.run(
- addr2line + ' -e "' +
- os.path.join(rootdir, 'android_addr_tmp', archs[arch]['libmain']) +
- '" ' + addr,
+ 'cd "'
+ + os.path.join(rootdir, 'android_addr_tmp')
+ + '" && tar -xf "'
+ + os.path.join(
+ archive_dir, 'unstripped_libs', archs[arch]['libmain'] + '.tgz'
+ )
+ + '"',
shell=True,
- check=True)
+ check=True,
+ )
+ subprocess.run(
+ addr2line
+ + ' -e "'
+ + os.path.join(rootdir, 'android_addr_tmp', archs[arch]['libmain'])
+ + '" '
+ + addr,
+ shell=True,
+ check=True,
+ )
finally:
os.system('rm -rf "' + os.path.join(rootdir, 'android_addr_tmp') + '"')
diff --git a/tools/batools/androidsdkutils.py b/tools/batools/androidsdkutils.py
index 5c1aaf5e..6f57b86f 100755
--- a/tools/batools/androidsdkutils.py
+++ b/tools/batools/androidsdkutils.py
@@ -60,19 +60,26 @@ def _gen_lprop_file(local_properties_path: str) -> str:
print(
'ERROR: Android sdk not found; install '
'the android sdk and try again',
- file=sys.stderr)
+ file=sys.stderr,
+ )
sys.exit(255)
- config = ('\n# This file was automatically generated by ' +
- os.path.abspath(sys.argv[0]) + '\n'
- '# Feel free to override these paths if you have your android'
- ' sdk elsewhere\n'
- '\n'
- 'sdk.dir=' + sdk_dir + '\n')
+ config = (
+ '\n# This file was automatically generated by '
+ + os.path.abspath(sys.argv[0])
+ + '\n'
+ '# Feel free to override these paths if you have your android'
+ ' sdk elsewhere\n'
+ '\n'
+ 'sdk.dir=' + sdk_dir + '\n'
+ )
with open(local_properties_path, 'w', encoding='utf-8') as outfile:
outfile.write(config)
- print('Generating local.properties file (found Android SDK at "' +
- sdk_dir + '")',
- file=sys.stderr)
+ print(
+ 'Generating local.properties file (found Android SDK at "'
+ + sdk_dir
+ + '")',
+ file=sys.stderr,
+ )
return sdk_dir
@@ -93,8 +100,9 @@ def run(projroot: str, args: list[str]) -> None:
# In all cases we make sure there's a local.properties in our android
# dir that contains valid sdk path. If not, we attempt to create it.
- local_properties_path = os.path.join(projroot, 'ballisticacore-android',
- 'local.properties')
+ local_properties_path = os.path.join(
+ projroot, 'ballisticacore-android', 'local.properties'
+ )
if os.path.isfile(local_properties_path):
sdk_dir = _parse_lprop_file(local_properties_path)
else:
@@ -103,17 +111,23 @@ def run(projroot: str, args: list[str]) -> None:
# Sanity check; look for a few things in the sdk that we expect to
# be there.
if not os.path.isfile(sdk_dir + '/platform-tools/adb'):
- raise Exception('ERROR: android sdk at "' + sdk_dir +
- '" does not seem valid')
+ raise Exception(
+ 'ERROR: android sdk at "' + sdk_dir + '" does not seem valid'
+ )
# Sanity check: if they've got ANDROID_HOME set, make sure it lines up with
# what we're pointing at.
android_home = os.getenv('ANDROID_HOME')
if android_home is not None:
if android_home != sdk_dir:
- print('ERROR: sdk dir mismatch; ANDROID_HOME is "' + android_home +
- '" but local.properties set to "' + sdk_dir + '"',
- file=sys.stderr)
+ print(
+ 'ERROR: sdk dir mismatch; ANDROID_HOME is "'
+ + android_home
+ + '" but local.properties set to "'
+ + sdk_dir
+ + '"',
+ file=sys.stderr,
+ )
sys.exit(255)
if command == 'get-sdk-path':
@@ -127,13 +141,15 @@ def run(projroot: str, args: list[str]) -> None:
gradlepath = Path(projroot, 'ballisticacore-android/build.gradle')
with gradlepath.open(encoding='utf-8') as infile:
lines = [
- l for l in infile.readlines()
+ l
+ for l in infile.readlines()
if l.strip().startswith('ext.ndk_version = ')
]
if len(lines) != 1:
raise RuntimeError(
f'Expected exactly one ndk_version line in build.gradle;'
- f' found {len(lines)}')
+ f' found {len(lines)}'
+ )
ver = lines[0].strip().replace("'", '').replace('"', '').split()[-1]
path = os.path.join(sdk_dir, 'ndk', ver)
if not os.path.isdir(path):
@@ -142,6 +158,7 @@ def run(projroot: str, args: list[str]) -> None:
if command == 'get-adb-path':
import subprocess
+
adbpath = Path(sdk_dir, 'platform-tools/adb')
if not os.path.exists(adbpath):
raise Exception(f'ADB not found at expected path {adbpath}')
@@ -150,10 +167,9 @@ def run(projroot: str, args: list[str]) -> None:
# Now, for extra credit, let's see if 'which adb' points to the
# same one and simply return 'adb' if so. This makes our make
# output nice and readable (and hopefully won't cause problems)
- result = subprocess.run('which adb',
- shell=True,
- capture_output=True,
- check=False)
+ result = subprocess.run(
+ 'which adb', shell=True, capture_output=True, check=False
+ )
if result.returncode == 0:
wpath = result.stdout.decode().strip()
if wpath == str(adbpath):
diff --git a/tools/batools/assetsmakefile.py b/tools/batools/assetsmakefile.py
index 53d7d50a..0be5a773 100755
--- a/tools/batools/assetsmakefile.py
+++ b/tools/batools/assetsmakefile.py
@@ -18,11 +18,13 @@ if TYPE_CHECKING:
PYC_SUFFIX = '.cpython-310.opt-1.pyc'
-def _get_targets(varname: str,
- inext: str,
- outext: str,
- all_targets: set,
- limit_to_prefix: str | None = None) -> str:
+def _get_targets(
+ varname: str,
+ inext: str,
+ outext: str,
+ all_targets: set,
+ limit_to_prefix: str | None = None,
+) -> str:
"""Generic function to map source extension to dst files."""
src = 'assets/src'
@@ -31,16 +33,17 @@ def _get_targets(varname: str,
# Create outext targets for all inext files we find.
for root, _dname, fnames in os.walk(src):
- if (limit_to_prefix is not None
- and not root.startswith(os.path.join(src, limit_to_prefix))):
+ if limit_to_prefix is not None and not root.startswith(
+ os.path.join(src, limit_to_prefix)
+ ):
continue
# Write the target to make sense from within assets/
assert root.startswith(src)
- dstrootvar = 'build' + root[len(src):]
- dstfin = dst + root[len(src):]
+ dstrootvar = 'build' + root[len(src) :]
+ dstfin = dst + root[len(src) :]
for fname in fnames:
- outname = fname[:-len(inext)] + outext
+ outname = fname[: -len(inext)] + outext
if fname.endswith(inext):
all_targets.add(os.path.join(dstfin, outname))
targets.append(os.path.join(dstrootvar, outname))
@@ -48,9 +51,14 @@ def _get_targets(varname: str,
return '\n' + varname + ' = \\\n ' + ' \\\n '.join(sorted(targets))
-def _get_py_targets(src: str, dst: str, py_targets: list[str],
- pyc_targets: list[str], all_targets: set[str],
- subset: str) -> None:
+def _get_py_targets(
+ src: str,
+ dst: str,
+ py_targets: list[str],
+ pyc_targets: list[str],
+ all_targets: set[str],
+ subset: str,
+) -> None:
# pylint: disable=too-many-branches
py_generated_root = 'assets/src/ba_data/python/ba/_generated'
@@ -60,22 +68,28 @@ def _get_py_targets(src: str, dst: str, py_targets: list[str],
if root == 'assets/src/ba_data/data/maps':
return
assert root.startswith(src)
- dstrootvar = dst[len('assets') + 1:] + root[len(src):]
- dstfin = dst + root[len(src):]
+ dstrootvar = dst[len('assets') + 1 :] + root[len(src) :]
+ dstfin = dst + root[len(src) :]
for fname in fnames:
# Ignore flycheck temp files as well as our _ba dummy module.
- if (not fname.endswith('.py') or fname.startswith('flycheck_')
- or fname.startswith('.#') or fname == '_ba.py'):
+ if (
+ not fname.endswith('.py')
+ or fname.startswith('flycheck_')
+ or fname.startswith('.#')
+ or fname == '_ba.py'
+ ):
continue
if root.startswith('assets/src/ba_data/python-site-packages'):
in_subset = 'private-common'
- elif (root.startswith('assets/src/ba_data')
- or root.startswith('assets/src/server')):
+ elif root.startswith('assets/src/ba_data') or root.startswith(
+ 'assets/src/server'
+ ):
in_subset = 'public'
- elif (root.startswith('tools/efro')
- and not root.startswith('tools/efrotools')):
+ elif root.startswith('tools/efro') and not root.startswith(
+ 'tools/efrotools'
+ ):
# We want to pull just 'efro' out of tools; not efrotools.
in_subset = 'public_tools'
elif root.startswith('tools/bacommon'):
@@ -106,7 +120,8 @@ def _get_py_targets(src: str, dst: str, py_targets: list[str],
fname_pyc = fname[:-3] + PYC_SUFFIX
all_targets.add(os.path.join(dstfin, '__pycache__', fname_pyc))
pyc_targets.append(
- os.path.join(dstrootvar, '__pycache__', fname_pyc))
+ os.path.join(dstrootvar, '__pycache__', fname_pyc)
+ )
# Create py and pyc targets for all physical scripts in src, with
# the exception of our dynamically generated stuff.
@@ -114,8 +129,9 @@ def _get_py_targets(src: str, dst: str, py_targets: list[str],
# Skip any generated files; we'll add those from the meta manifest.
# (dont want our results to require a meta build beforehand)
- if (physical_root == py_generated_root
- or physical_root.startswith(py_generated_root + '/')):
+ if physical_root == py_generated_root or physical_root.startswith(
+ py_generated_root + '/'
+ ):
continue
_do_get_targets(physical_root, physical_fnames)
@@ -124,21 +140,25 @@ def _get_py_targets(src: str, dst: str, py_targets: list[str],
# lives under this dir.
meta_targets: list[str] = []
for mantype in ['public', 'private']:
- with open(f'src/meta/.meta_manifest_{mantype}.json',
- encoding='utf-8') as infile:
+ with open(
+ f'src/meta/.meta_manifest_{mantype}.json', encoding='utf-8'
+ ) as infile:
meta_targets += json.loads(infile.read())
meta_targets = [
- t for t in meta_targets
+ t
+ for t in meta_targets
if t.startswith(src + '/') and t.startswith(py_generated_root + '/')
]
for target in meta_targets:
- _do_get_targets(root=os.path.dirname(target),
- fnames=[os.path.basename(target)])
+ _do_get_targets(
+ root=os.path.dirname(target), fnames=[os.path.basename(target)]
+ )
-def _get_py_targets_subset(all_targets: set[str], subset: str,
- suffix: str) -> str:
+def _get_py_targets_subset(
+ all_targets: set[str], subset: str, suffix: str
+) -> str:
if subset == 'public_tools':
src = 'tools'
dst = 'assets/build/ba_data/python'
@@ -152,44 +172,52 @@ def _get_py_targets_subset(all_targets: set[str], subset: str,
py_targets: list[str] = []
pyc_targets: list[str] = []
- _get_py_targets(src,
- dst,
- py_targets,
- pyc_targets,
- all_targets,
- subset=subset)
+ _get_py_targets(
+ src, dst, py_targets, pyc_targets, all_targets, subset=subset
+ )
# Need to sort these combined to keep pairs together.
- combined_targets = [(py_targets[i], pyc_targets[i])
- for i in range(len(py_targets))]
+ combined_targets = [
+ (py_targets[i], pyc_targets[i]) for i in range(len(py_targets))
+ ]
combined_targets.sort()
py_targets = [t[0] for t in combined_targets]
pyc_targets = [t[1] for t in combined_targets]
- out = (f'\nSCRIPT_TARGETS_PY{suffix} = \\\n ' +
- ' \\\n '.join(py_targets) + '\n')
+ out = (
+ f'\nSCRIPT_TARGETS_PY{suffix} = \\\n '
+ + ' \\\n '.join(py_targets)
+ + '\n'
+ )
- out += (f'\nSCRIPT_TARGETS_PYC{suffix} = \\\n ' +
- ' \\\n '.join(pyc_targets) + '\n')
+ out += (
+ f'\nSCRIPT_TARGETS_PYC{suffix} = \\\n '
+ + ' \\\n '.join(pyc_targets)
+ + '\n'
+ )
# We transform all non-public targets into efrocache-fetches in public.
efc = '' if subset.startswith('public') else '# __EFROCACHE_TARGET__\n'
- out += ('\n# Rule to copy src asset scripts to dst.\n'
- '# (and make non-writable so I\'m less likely to '
- 'accidentally edit them there)\n'
- f'{efc}$(SCRIPT_TARGETS_PY{suffix}) : {copyrule}\n'
- '\t@echo Copying script: $@\n'
- '\t@mkdir -p $(dir $@)\n'
- '\t@rm -f $@\n'
- '\t@cp $^ $@\n'
- '\t@chmod 444 $@\n')
+ out += (
+ '\n# Rule to copy src asset scripts to dst.\n'
+ '# (and make non-writable so I\'m less likely to '
+ 'accidentally edit them there)\n'
+ f'{efc}$(SCRIPT_TARGETS_PY{suffix}) : {copyrule}\n'
+ '\t@echo Copying script: $@\n'
+ '\t@mkdir -p $(dir $@)\n'
+ '\t@rm -f $@\n'
+ '\t@cp $^ $@\n'
+ '\t@chmod 444 $@\n'
+ )
# Fancy new simple loop-based target generation.
- out += (f'\n# These are too complex to define in a pattern rule;\n'
- f'# Instead we generate individual targets in a loop.\n'
- f'$(foreach element,$(SCRIPT_TARGETS_PYC{suffix}),\\\n'
- f'$(eval $(call make-opt-pyc-target,$(element))))')
+ out += (
+ f'\n# These are too complex to define in a pattern rule;\n'
+ f'# Instead we generate individual targets in a loop.\n'
+ f'$(foreach element,$(SCRIPT_TARGETS_PYC{suffix}),\\\n'
+ f'$(eval $(call make-opt-pyc-target,$(element))))'
+ )
# Old code to explicitly emit individual targets.
if bool(False):
@@ -197,18 +225,24 @@ def _get_py_targets_subset(all_targets: set[str], subset: str,
'\n# Looks like path mangling from py to pyc is too complex for'
' pattern rules so\n# just generating explicit targets'
' for each. Could perhaps look into using a\n# fancy for-loop'
- ' instead, but perhaps listing these explicitly isn\'t so bad.\n')
+ ' instead, but perhaps listing these explicitly isn\'t so bad.\n'
+ )
for i, target in enumerate(pyc_targets):
# Note: there's currently a bug which can cause python bytecode
# generation to be non-deterministic. This can break our blessing
# process since we bless in core but then regenerate bytecode in
# spinoffs. See https://bugs.python.org/issue34722
# For now setting PYTHONHASHSEED=1 is a workaround.
- out += ('\n' + target + ': \\\n ' + py_targets[i] +
- '\n\t@echo Compiling script: $^\n'
- '\t@rm -rf $@ && PYTHONHASHSEED=1 $(TOOLS_DIR)/pcommand'
- ' compile_python_files $^'
- ' && chmod 444 $@\n')
+ out += (
+ '\n'
+ + target
+ + ': \\\n '
+ + py_targets[i]
+ + '\n\t@echo Compiling script: $^\n'
+ '\t@rm -rf $@ && PYTHONHASHSEED=1 $(TOOLS_DIR)/pcommand'
+ ' compile_python_files $^'
+ ' && chmod 444 $@\n'
+ )
return out
@@ -237,13 +271,31 @@ def _get_extras_targets_win(all_targets: set[str], platform: str) -> str:
# Various stuff we expect to be there...
if ext in [
- '.exe', '.dll', '.bat', '.txt', '.whl', '.ps1', '.css',
- '.sample', '.ico', '.pyd', '.ctypes', '.rst', '.fish',
- '.csh', '.cat', '.pdb', '.lib', '.html'
+ '.exe',
+ '.dll',
+ '.bat',
+ '.txt',
+ '.whl',
+ '.ps1',
+ '.css',
+ '.sample',
+ '.ico',
+ '.pyd',
+ '.ctypes',
+ '.rst',
+ '.fish',
+ '.csh',
+ '.cat',
+ '.pdb',
+ '.lib',
+ '.html',
] or fname in [
- 'activate', 'README', 'command_template', 'fetch_macholib'
+ 'activate',
+ 'README',
+ 'command_template',
+ 'fetch_macholib',
]:
- targetpath = os.path.join(dstbase + root[len(base):], fname)
+ targetpath = os.path.join(dstbase + root[len(base) :], fname)
targets.append(targetpath)
all_targets.add('assets/' + targetpath)
continue
@@ -254,18 +306,21 @@ def _get_extras_targets_win(all_targets: set[str], platform: str) -> str:
targets.sort()
p_up = platform.upper()
- out = (f'\nEXTRAS_TARGETS_WIN_{p_up} = \\\n ' + ' \\\n '.join(targets) +
- '\n')
+ out = (
+ f'\nEXTRAS_TARGETS_WIN_{p_up} = \\\n ' + ' \\\n '.join(targets) + '\n'
+ )
# We transform all these targets into efrocache-fetches in public.
- out += ('\n# Rule to copy src extras to build.\n'
- f'# __EFROCACHE_TARGET__\n'
- f'$(EXTRAS_TARGETS_WIN_{p_up}) : build/% :'
- ' src/%\n'
- '\t@echo Copying file: $@\n'
- '\t@mkdir -p $(dir $@)\n'
- '\t@rm -f $@\n'
- '\t@cp $^ $@\n')
+ out += (
+ '\n# Rule to copy src extras to build.\n'
+ f'# __EFROCACHE_TARGET__\n'
+ f'$(EXTRAS_TARGETS_WIN_{p_up}) : build/% :'
+ ' src/%\n'
+ '\t@echo Copying file: $@\n'
+ '\t@mkdir -p $(dir $@)\n'
+ '\t@rm -f $@\n'
+ '\t@cp $^ $@\n'
+ )
return out
@@ -297,61 +352,87 @@ def update_assets_makefile(projroot: str, check: bool) -> None:
# We always auto-generate the public section.
our_lines_public = [
- _get_py_targets_subset(all_targets_public,
- subset='public',
- suffix='_PUBLIC'),
- _get_py_targets_subset(all_targets_public,
- subset='public_tools',
- suffix='_PUBLIC_TOOLS')
+ _get_py_targets_subset(
+ all_targets_public, subset='public', suffix='_PUBLIC'
+ ),
+ _get_py_targets_subset(
+ all_targets_public, subset='public_tools', suffix='_PUBLIC_TOOLS'
+ ),
]
# Only auto-generate the private section in the private repo.
if public:
- our_lines_private = lines[auto_start_private + 1:auto_end_private]
+ our_lines_private = lines[auto_start_private + 1 : auto_end_private]
else:
our_lines_private = [
- _get_py_targets_subset(all_targets_private,
- subset='private-apple',
- suffix='_PRIVATE_APPLE'),
- _get_py_targets_subset(all_targets_private,
- subset='private-android',
- suffix='_PRIVATE_ANDROID'),
- _get_py_targets_subset(all_targets_private,
- subset='private-common',
- suffix='_PRIVATE_COMMON'),
- _get_py_targets_subset(all_targets_private,
- subset='private-windows-Win32',
- suffix='_PRIVATE_WIN_WIN32'),
- _get_py_targets_subset(all_targets_private,
- subset='private-windows-x64',
- suffix='_PRIVATE_WIN_X64'),
- _get_targets('COB_TARGETS', '.collidemodel.obj', '.cob',
- all_targets_private),
- _get_targets('BOB_TARGETS', '.model.obj', '.bob',
- all_targets_private),
- _get_targets('FONT_TARGETS', '.fdata', '.fdata',
- all_targets_private),
+ _get_py_targets_subset(
+ all_targets_private,
+ subset='private-apple',
+ suffix='_PRIVATE_APPLE',
+ ),
+ _get_py_targets_subset(
+ all_targets_private,
+ subset='private-android',
+ suffix='_PRIVATE_ANDROID',
+ ),
+ _get_py_targets_subset(
+ all_targets_private,
+ subset='private-common',
+ suffix='_PRIVATE_COMMON',
+ ),
+ _get_py_targets_subset(
+ all_targets_private,
+ subset='private-windows-Win32',
+ suffix='_PRIVATE_WIN_WIN32',
+ ),
+ _get_py_targets_subset(
+ all_targets_private,
+ subset='private-windows-x64',
+ suffix='_PRIVATE_WIN_X64',
+ ),
+ _get_targets(
+ 'COB_TARGETS', '.collidemodel.obj', '.cob', all_targets_private
+ ),
+ _get_targets(
+ 'BOB_TARGETS', '.model.obj', '.bob', all_targets_private
+ ),
+ _get_targets(
+ 'FONT_TARGETS', '.fdata', '.fdata', all_targets_private
+ ),
_get_targets('PEM_TARGETS', '.pem', '.pem', all_targets_private),
- _get_targets('DATA_TARGETS',
- '.json',
- '.json',
- all_targets_private,
- limit_to_prefix='ba_data/data'),
+ _get_targets(
+ 'DATA_TARGETS',
+ '.json',
+ '.json',
+ all_targets_private,
+ limit_to_prefix='ba_data/data',
+ ),
_get_targets('AUDIO_TARGETS', '.wav', '.ogg', all_targets_private),
- _get_targets('TEX2D_DDS_TARGETS', '.tex2d.png', '.dds',
- all_targets_private),
- _get_targets('TEX2D_PVR_TARGETS', '.tex2d.png', '.pvr',
- all_targets_private),
- _get_targets('TEX2D_KTX_TARGETS', '.tex2d.png', '.ktx',
- all_targets_private),
- _get_targets('TEX2D_PREVIEW_PNG_TARGETS', '.tex2d.png',
- '_preview.png', all_targets_private),
+ _get_targets(
+ 'TEX2D_DDS_TARGETS', '.tex2d.png', '.dds', all_targets_private
+ ),
+ _get_targets(
+ 'TEX2D_PVR_TARGETS', '.tex2d.png', '.pvr', all_targets_private
+ ),
+ _get_targets(
+ 'TEX2D_KTX_TARGETS', '.tex2d.png', '.ktx', all_targets_private
+ ),
+ _get_targets(
+ 'TEX2D_PREVIEW_PNG_TARGETS',
+ '.tex2d.png',
+ '_preview.png',
+ all_targets_private,
+ ),
_get_extras_targets_win(all_targets_private, 'Win32'),
_get_extras_targets_win(all_targets_private, 'x64'),
]
- filtered = (lines[:auto_start_public + 1] + our_lines_public +
- lines[auto_end_public:auto_start_private + 1] +
- our_lines_private + lines[auto_end_private:])
+ filtered = (
+ lines[: auto_start_public + 1]
+ + our_lines_public
+ + lines[auto_end_public : auto_start_private + 1]
+ + our_lines_private
+ + lines[auto_end_private:]
+ )
out = '\n'.join(filtered) + '\n'
if out == original:
@@ -362,11 +443,13 @@ def update_assets_makefile(projroot: str, check: bool) -> None:
# Print exact contents if we need to debug:
if bool(False):
- print(f'EXPECTED ===========================================\n'
- f'{out}\n'
- f'FOUND ==============================================\n'
- f'{original}\n'
- f'END COMPARE ========================================')
+ print(
+ f'EXPECTED ===========================================\n'
+ f'{out}\n'
+ f'FOUND ==============================================\n'
+ f'{original}\n'
+ f'END COMPARE ========================================'
+ )
sys.exit(255)
print(f'{Clr.SBLU}Updating: {fname}{Clr.RST}')
with open(fname, 'w', encoding='utf-8') as outfile:
@@ -374,15 +457,18 @@ def update_assets_makefile(projroot: str, check: bool) -> None:
# Lastly, write a simple manifest of the things we expect to have
# in build. We can use this to clear out orphaned files as part of builds.
- _write_manifest('assets/.asset_manifest_public.json', all_targets_public,
- check)
+ _write_manifest(
+ 'assets/.asset_manifest_public.json', all_targets_public, check
+ )
if not public:
- _write_manifest('assets/.asset_manifest_private.json',
- all_targets_private, check)
+ _write_manifest(
+ 'assets/.asset_manifest_private.json', all_targets_private, check
+ )
-def _write_manifest(manifest_path: str, all_targets: set[str],
- check: bool) -> None:
+def _write_manifest(
+ manifest_path: str, all_targets: set[str], check: bool
+) -> None:
# Lastly, write a simple manifest of the things we expect to have
# in build. We can use this to clear out orphaned files as part of builds.
assert all(t.startswith('assets/build/') for t in all_targets)
@@ -396,8 +482,10 @@ def _write_manifest(manifest_path: str, all_targets: set[str],
print(f'{manifest_path} is up to date.')
else:
if check:
- print(f'{Clr.SRED}ERROR: file is out of date:'
- f" '{manifest_path}'.{Clr.RST}")
+ print(
+ f'{Clr.SRED}ERROR: file is out of date:'
+ f" '{manifest_path}'.{Clr.RST}"
+ )
sys.exit(255)
print(f'{Clr.SBLU}Updating: {manifest_path}{Clr.RST}')
with open(manifest_path, 'w', encoding='utf-8') as outfile:
diff --git a/tools/batools/assetstaging.py b/tools/batools/assetstaging.py
index 49f62f63..481fb47a 100755
--- a/tools/batools/assetstaging.py
+++ b/tools/batools/assetstaging.py
@@ -21,7 +21,7 @@ if TYPE_CHECKING:
# Note: this means anyone wanting to modify .py files in a build
# will need to wipe out the existing .pyc files first or the changes
# will be ignored.
-OPT_PYC_SUFFIX = ('cpython-' + PYVER.replace('.', '') + '.opt-1.pyc')
+OPT_PYC_SUFFIX = 'cpython-' + PYVER.replace('.', '') + '.opt-1.pyc'
class Config:
@@ -159,18 +159,25 @@ class Config:
self.debug = False
else:
raise RuntimeError(
- "Expected either '-debug' or '-release' in args.")
+ "Expected either '-debug' or '-release' in args."
+ )
elif '-xcode-mac' in args:
self.src = os.environ['SOURCE_ROOT'] + '/assets/build'
- self.dst = (os.environ['TARGET_BUILD_DIR'] + '/' +
- os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'])
+ self.dst = (
+ os.environ['TARGET_BUILD_DIR']
+ + '/'
+ + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']
+ )
self.include_pylib = True
self.pylib_src_name = 'pylib-apple'
self.tex_suffix = '.dds'
elif '-xcode-ios' in args:
self.src = os.environ['SOURCE_ROOT'] + '/assets/build'
- self.dst = (os.environ['TARGET_BUILD_DIR'] + '/' +
- os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH'])
+ self.dst = (
+ os.environ['TARGET_BUILD_DIR']
+ + '/'
+ + os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']
+ )
self.include_pylib = True
self.pylib_src_name = 'pylib-apple'
self.tex_suffix = '.pvr'
@@ -213,7 +220,8 @@ def _write_payload_file(assets_root: str, full: bool) -> None:
fpathshort = fpath.replace(assets_root, '')
if ' ' in fpathshort:
raise RuntimeError(
- f"Invalid filename (contains spaces): '{fpathshort}'")
+ f"Invalid filename (contains spaces): '{fpathshort}'"
+ )
payload_str += fpathshort + ' ' + md5sum(fpath) + '\n'
file_list.append(fpathshort)
@@ -221,8 +229,13 @@ def _write_payload_file(assets_root: str, full: bool) -> None:
if file_list:
# Write the file count, whether this is a 'full' payload, and finally
# the file list.
- payload_str = (str(len(file_list)) + '\n' + ('1' if full else '0') +
- '\n' + payload_str)
+ payload_str = (
+ str(len(file_list))
+ + '\n'
+ + ('1' if full else '0')
+ + '\n'
+ + payload_str
+ )
with open(payload_path, 'w', encoding='utf-8') as outfile:
outfile.write(payload_str)
else:
@@ -259,14 +272,17 @@ def _sync_windows_extras(cfg: Config) -> None:
# it up when running under WSL. Let's install it as lib for now.
dstdirname = 'lib' if dirname == 'Lib' else dirname
_run(f'mkdir -p "{cfg.dst}/{dstdirname}"')
- cmd = ('rsync --recursive --update --delete --delete-excluded '
- ' --prune-empty-dirs'
- " --include '*.ico' --include '*.cat'"
- f" --include '*.dll' {pyd_rules}"
- " --include '*.py' --include '*." + OPT_PYC_SUFFIX + "'"
- " --include '*/' --exclude '*' \"" +
- os.path.join(cfg.win_extras_src, dirname) + '/" '
- '"' + cfg.dst + '/' + dstdirname + '/"')
+ cmd = (
+ 'rsync --recursive --update --delete --delete-excluded '
+ ' --prune-empty-dirs'
+ " --include '*.ico' --include '*.cat'"
+ f" --include '*.dll' {pyd_rules}"
+ " --include '*.py' --include '*." + OPT_PYC_SUFFIX + "'"
+ " --include '*/' --exclude '*' \""
+ + os.path.join(cfg.win_extras_src, dirname)
+ + '/" '
+ '"' + cfg.dst + '/' + dstdirname + '/"'
+ )
_run(cmd)
# Now sync the top level individual files that we want.
@@ -278,8 +294,11 @@ def _sync_windows_extras(cfg: Config) -> None:
if cfg.win_type == 'win':
toplevelfiles += [
- 'libvorbis.dll', 'libvorbisfile.dll', 'ogg.dll', 'OpenAL32.dll',
- 'SDL2.dll'
+ 'libvorbis.dll',
+ 'libvorbisfile.dll',
+ 'ogg.dll',
+ 'OpenAL32.dll',
+ 'SDL2.dll',
]
elif cfg.win_type == 'winserver':
toplevelfiles += [f'python{dbgsfx}.exe']
@@ -288,12 +307,16 @@ def _sync_windows_extras(cfg: Config) -> None:
if cfg.debug:
if cfg.win_platform == 'x64':
toplevelfiles += [
- 'msvcp140d.dll', 'vcruntime140d.dll', 'vcruntime140_1d.dll',
- 'ucrtbased.dll'
+ 'msvcp140d.dll',
+ 'vcruntime140d.dll',
+ 'vcruntime140_1d.dll',
+ 'ucrtbased.dll',
]
else:
toplevelfiles += [
- 'msvcp140d.dll', 'vcruntime140d.dll', 'ucrtbased.dll'
+ 'msvcp140d.dll',
+ 'vcruntime140d.dll',
+ 'ucrtbased.dll',
]
# Include the runtime redistributables in release builds.
@@ -305,9 +328,11 @@ def _sync_windows_extras(cfg: Config) -> None:
else:
raise RuntimeError(f'Invalid win_platform {cfg.win_platform}')
- cmd2 = (['rsync', '--update'] +
- [os.path.join(cfg.win_extras_src, f)
- for f in toplevelfiles] + [cfg.dst + '/'])
+ cmd2 = (
+ ['rsync', '--update']
+ + [os.path.join(cfg.win_extras_src, f) for f in toplevelfiles]
+ + [cfg.dst + '/']
+ )
subprocess.run(cmd2, check=True)
# If we're running under WSL we won't be able to launch these .exe files
@@ -320,24 +345,30 @@ def _sync_pylib(cfg: Config) -> None:
assert cfg.pylib_src_name is not None
assert cfg.dst is not None
_run(f'mkdir -p "{cfg.dst}/pylib"')
- cmd = (f'rsync --recursive --update --delete --delete-excluded '
- f' --prune-empty-dirs'
- f" --include '*.py' --include '*.{OPT_PYC_SUFFIX}'"
- f" --include '*/' --exclude '*'"
- f' "{cfg.src}/{cfg.pylib_src_name}/" '
- f'"{cfg.dst}/pylib/"')
+ cmd = (
+ f'rsync --recursive --update --delete --delete-excluded '
+ f' --prune-empty-dirs'
+ f" --include '*.py' --include '*.{OPT_PYC_SUFFIX}'"
+ f" --include '*/' --exclude '*'"
+ f' "{cfg.src}/{cfg.pylib_src_name}/" '
+ f'"{cfg.dst}/pylib/"'
+ )
_run(cmd)
def _sync_standard_game_data(cfg: Config) -> None:
assert cfg.dst is not None
_run('mkdir -p "' + cfg.dst + '/ba_data"')
- cmd = ('rsync --recursive --update --delete --delete-excluded'
- ' --prune-empty-dirs')
+ cmd = (
+ 'rsync --recursive --update --delete --delete-excluded'
+ ' --prune-empty-dirs'
+ )
if cfg.include_scripts:
- cmd += (f" --include '*.py' --include '*.pem'"
- f" --include '*.{OPT_PYC_SUFFIX}'")
+ cmd += (
+ f" --include '*.py' --include '*.pem'"
+ f" --include '*.{OPT_PYC_SUFFIX}'"
+ )
if cfg.include_textures:
assert cfg.tex_suffix is not None
@@ -358,8 +389,13 @@ def _sync_standard_game_data(cfg: Config) -> None:
if cfg.include_collide_models:
cmd += " --include '*.cob'"
- cmd += (" --include='*/' --exclude='*' \"" + cfg.src + '/ba_data/" "' +
- cfg.dst + '/ba_data/"')
+ cmd += (
+ " --include='*/' --exclude='*' \""
+ + cfg.src
+ + '/ba_data/" "'
+ + cfg.dst
+ + '/ba_data/"'
+ )
_run(cmd)
@@ -374,35 +410,45 @@ def _sync_server_files(cfg: Config) -> None:
mode=modeval,
infilename=f'{cfg.src}/../src/server/ballisticacore_server.py',
outfilename=os.path.join(
- cfg.serverdst, 'ballisticacore_server.py'
- if cfg.win_type is not None else 'ballisticacore_server'))
- stage_server_file(projroot=cfg.projroot,
- mode=modeval,
- infilename=f'{cfg.src}/../src/server/README.txt',
- outfilename=os.path.join(cfg.serverdst, 'README.txt'))
+ cfg.serverdst,
+ 'ballisticacore_server.py'
+ if cfg.win_type is not None
+ else 'ballisticacore_server',
+ ),
+ )
+ stage_server_file(
+ projroot=cfg.projroot,
+ mode=modeval,
+ infilename=f'{cfg.src}/../src/server/README.txt',
+ outfilename=os.path.join(cfg.serverdst, 'README.txt'),
+ )
stage_server_file(
projroot=cfg.projroot,
mode=modeval,
infilename=f'{cfg.src}/../src/server/config_template.yaml',
- outfilename=os.path.join(cfg.serverdst, 'config_template.yaml'))
+ outfilename=os.path.join(cfg.serverdst, 'config_template.yaml'),
+ )
if cfg.win_type is not None:
stage_server_file(
projroot=cfg.projroot,
mode=modeval,
- infilename=
- f'{cfg.src}/../src/server/launch_ballisticacore_server.bat',
- outfilename=os.path.join(cfg.serverdst,
- 'launch_ballisticacore_server.bat'))
+ infilename=(
+ f'{cfg.src}/../src/server/launch_ballisticacore_server.bat'
+ ),
+ outfilename=os.path.join(
+ cfg.serverdst, 'launch_ballisticacore_server.bat'
+ ),
+ )
-def _write_if_changed(path: str,
- contents: str,
- make_executable: bool = False) -> None:
+def _write_if_changed(
+ path: str, contents: str, make_executable: bool = False
+) -> None:
changed: bool
try:
with open(path, encoding='utf-8') as infile:
existing = infile.read()
- changed = (contents != existing)
+ changed = contents != existing
except FileNotFoundError:
changed = True
if changed:
@@ -412,14 +458,18 @@ def _write_if_changed(path: str,
subprocess.run(['chmod', '+x', path], check=True)
-def stage_server_file(projroot: str, mode: str, infilename: str,
- outfilename: str) -> None:
+def stage_server_file(
+ projroot: str, mode: str, infilename: str, outfilename: str
+) -> None:
"""Stage files for the server environment with some filtering."""
import batools.build
from efrotools import replace_exact
+
if mode not in ('debug', 'release'):
- raise RuntimeError(f"Invalid server-file-staging mode '{mode}';"
- f" expected 'debug' or 'release'.")
+ raise RuntimeError(
+ f"Invalid server-file-staging mode '{mode}';"
+ f" expected 'debug' or 'release'."
+ )
print(f'Building server file: {os.path.basename(outfilename)}')
@@ -430,7 +480,8 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
# Inject all available config values into the config file.
_write_if_changed(
outfilename,
- batools.build.filter_server_config(str(projroot), infilename))
+ batools.build.filter_server_config(str(projroot), infilename),
+ )
elif basename == 'ballisticacore_server.py':
# Run Python in opt mode for release builds.
@@ -438,11 +489,13 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
lines = infile.read().splitlines()
if mode == 'release':
lines[0] = replace_exact(
- lines[0], f'#!/usr/bin/env python{PYVER}',
- f'#!/usr/bin/env -S python{PYVER} -O')
- _write_if_changed(outfilename,
- '\n'.join(lines) + '\n',
- make_executable=True)
+ lines[0],
+ f'#!/usr/bin/env python{PYVER}',
+ f'#!/usr/bin/env -S python{PYVER} -O',
+ )
+ _write_if_changed(
+ outfilename, '\n'.join(lines) + '\n', make_executable=True
+ )
elif basename == 'README.txt':
with open(infilename, encoding='utf-8') as infile:
readme = infile.read()
@@ -453,16 +506,23 @@ def stage_server_file(projroot: str, mode: str, infilename: str,
lines = infile.read().splitlines()
if mode == 'release':
lines[1] = replace_exact(
- lines[1], ':: Python interpreter.', ':: Python interpreter.'
- ' (in opt mode so we use bundled .opt-1.pyc files)')
+ lines[1],
+ ':: Python interpreter.',
+ ':: Python interpreter.'
+ ' (in opt mode so we use bundled .opt-1.pyc files)',
+ )
lines[2] = replace_exact(
- lines[2], 'dist\\\\python.exe ballisticacore_server.py',
- 'dist\\\\python.exe -O ballisticacore_server.py')
+ lines[2],
+ 'dist\\\\python.exe ballisticacore_server.py',
+ 'dist\\\\python.exe -O ballisticacore_server.py',
+ )
else:
# In debug mode we use the bundled debug interpreter.
lines[2] = replace_exact(
- lines[2], 'dist\\\\python.exe ballisticacore_server.py',
- 'dist\\\\python_d.exe ballisticacore_server.py')
+ lines[2],
+ 'dist\\\\python.exe ballisticacore_server.py',
+ 'dist\\\\python_d.exe ballisticacore_server.py',
+ )
_write_if_changed(outfilename, '\n'.join(lines) + '\n')
else:
diff --git a/tools/batools/build.py b/tools/batools/build.py
index 93f2b115..c31586f5 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -23,6 +23,7 @@ if TYPE_CHECKING:
@dataclass
class PyRequirement:
"""A Python package/module required by our project."""
+
modulename: str | None = None
minversion: list[int] | None = None # None implies no min version.
pipname: str | None = None # None implies same as modulename.
@@ -41,7 +42,6 @@ class PyRequirement:
PY_REQUIREMENTS = [
PyRequirement(modulename='pylint', minversion=[2, 14, 5]),
PyRequirement(modulename='mypy', minversion=[0, 971]),
- PyRequirement(modulename='yapf', minversion=[0, 32, 0]),
PyRequirement(modulename='cpplint', minversion=[1, 6, 1]),
PyRequirement(modulename='pytest', minversion=[7, 1, 2]),
PyRequirement(modulename='pytz'),
@@ -85,6 +85,7 @@ DO_SPARSE_TEST_BUILDS = 'ballistica' + 'core' == 'ballisticacore'
class PrefabTarget(Enum):
"""Types of prefab builds able to be run."""
+
GUI_DEBUG = 'gui-debug'
SERVER_DEBUG = 'server-debug'
GUI_RELEASE = 'gui-release'
@@ -93,6 +94,7 @@ class PrefabTarget(Enum):
class LazyBuildCategory(Enum):
"""Types of sources."""
+
RESOURCES = 'resources_src'
META = 'meta_src'
CMAKE = 'cmake_src'
@@ -134,8 +136,11 @@ def lazybuild(target: str, category: LazyBuildCategory, command: str) -> None:
'src',
'ballisticacore-cmake/CMakeLists.txt',
],
- dirfilter=(lambda root, dirname: not (
- root == 'src' and dirname in {'meta', 'tools', 'external'})),
+ dirfilter=(
+ lambda root, dirname: not (
+ root == 'src' and dirname in {'meta', 'tools', 'external'}
+ )
+ ),
command=command,
).run()
@@ -178,16 +183,18 @@ def lazybuild(target: str, category: LazyBuildCategory, command: str) -> None:
assert_never(category)
-def archive_old_builds(ssh_server: str, builds_dir: str,
- ssh_args: list[str]) -> None:
+def archive_old_builds(
+ ssh_server: str, builds_dir: str, ssh_args: list[str]
+) -> None:
"""Stuff our old public builds into the 'old' dir.
(called after we push newer ones)
"""
def ssh_run(cmd: str) -> str:
- val: str = subprocess.check_output(['ssh'] + ssh_args +
- [ssh_server, cmd]).decode()
+ val: str = subprocess.check_output(
+ ['ssh'] + ssh_args + [ssh_server, cmd]
+ ).decode()
return val
files = ssh_run('ls -1t "' + builds_dir + '"').splitlines()
@@ -206,8 +213,9 @@ def archive_old_builds(ssh_server: str, builds_dir: str,
# this works.
for fname in sorted(files_to_archive):
print('Archiving ' + fname, file=sys.stderr)
- ssh_run('mv "' + builds_dir + '/' + fname + '" "' + builds_dir +
- '/old/"')
+ ssh_run(
+ 'mv "' + builds_dir + '/' + fname + '" "' + builds_dir + '/old/"'
+ )
def gen_fulltest_buildfile_android() -> None:
@@ -241,7 +249,8 @@ def gen_fulltest_buildfile_android() -> None:
lines = []
for _i, flavor in enumerate(
- sorted(os.listdir('ballisticacore-android/BallisticaCore/src'))):
+ sorted(os.listdir('ballisticacore-android/BallisticaCore/src'))
+ ):
if flavor == 'main' or flavor.startswith('.'):
continue
@@ -250,8 +259,13 @@ def gen_fulltest_buildfile_android() -> None:
else:
# mode = modes[(dayoffset + i) % len(modes)]
mode = 'prod'
- lines.append('ANDROID_PLATFORM=' + flavor + ' ANDROID_MODE=' + mode +
- ' make android-cloud-build')
+ lines.append(
+ 'ANDROID_PLATFORM='
+ + flavor
+ + ' ANDROID_MODE='
+ + mode
+ + ' make android-cloud-build'
+ )
# Now add sparse tests that land on today.
if DO_SPARSE_TEST_BUILDS:
@@ -266,36 +280,49 @@ def gen_fulltest_buildfile_android() -> None:
for extra in extras:
if extra == 'android.pylibs.arm':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android arm')
+ lines.append(
+ f'{cspre} tools/pcommand' f' python_build_android arm'
+ )
elif extra == 'android.pylibs.arm.debug':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android_debug arm')
+ lines.append(
+ f'{cspre} tools/pcommand'
+ f' python_build_android_debug arm'
+ )
elif extra == 'android.pylibs.arm64':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android arm64')
+ lines.append(
+ f'{cspre} tools/pcommand python_build_android arm64'
+ )
elif extra == 'android.pylibs.arm64.debug':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android_debug arm64')
+ lines.append(
+ f'{cspre} tools/pcommand'
+ f' python_build_android_debug arm64'
+ )
elif extra == 'android.pylibs.x86':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android x86')
+ lines.append(
+ f'{cspre} tools/pcommand' f' python_build_android x86'
+ )
elif extra == 'android.pylibs.x86.debug':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android_debug x86')
+ lines.append(
+ f'{cspre} tools/pcommand'
+ f' python_build_android_debug x86'
+ )
elif extra == 'android.pylibs.x86_64':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android x86_64')
+ lines.append(
+ f'{cspre} tools/pcommand'
+ f' python_build_android x86_64'
+ )
elif extra == 'android.pylibs.x86_64.debug':
if do_py_android:
- lines.append(f'{cspre} tools/pcommand'
- f' python_build_android_debug x86_64')
+ lines.append(
+ f'{cspre} tools/pcommand'
+ f' python_build_android_debug x86_64'
+ )
elif extra == 'android.package':
lines.append('make android-cloud-package')
else:
@@ -326,12 +353,18 @@ def gen_fulltest_buildfile_windows() -> None:
cfg2 = 'Release' if (dayoffset + 1) % 7 == 0 else 'Debug'
cfg3 = 'Release' if (dayoffset + 2) % 7 == 0 else 'Debug'
- lines.append(f'WINDOWS_PROJECT=Generic WINDOWS_PLATFORM={pval1} '
- f'WINDOWS_CONFIGURATION={cfg1} make windows-cloud-build')
- lines.append(f'WINDOWS_PROJECT=Headless WINDOWS_PLATFORM={pval2} '
- f'WINDOWS_CONFIGURATION={cfg2} make windows-cloud-build')
- lines.append(f'WINDOWS_PROJECT=Oculus WINDOWS_PLATFORM={pval3} '
- f'WINDOWS_CONFIGURATION={cfg3} make windows-cloud-build')
+ lines.append(
+ f'WINDOWS_PROJECT=Generic WINDOWS_PLATFORM={pval1} '
+ f'WINDOWS_CONFIGURATION={cfg1} make windows-cloud-build'
+ )
+ lines.append(
+ f'WINDOWS_PROJECT=Headless WINDOWS_PLATFORM={pval2} '
+ f'WINDOWS_CONFIGURATION={cfg2} make windows-cloud-build'
+ )
+ lines.append(
+ f'WINDOWS_PROJECT=Oculus WINDOWS_PLATFORM={pval3} '
+ f'WINDOWS_CONFIGURATION={cfg3} make windows-cloud-build'
+ )
# Now add sparse tests that land on today.
if DO_SPARSE_TEST_BUILDS:
@@ -364,8 +397,10 @@ def gen_fulltest_buildfile_apple() -> None:
lines = []
# pybuildapple = 'tools/pcommand python_build_apple'
- pybuildapple = ('tools/cloudshell --env tools fromini -- '
- 'tools/pcommand python_build_apple')
+ pybuildapple = (
+ 'tools/cloudshell --env tools fromini -- '
+ 'tools/pcommand python_build_apple'
+ )
# iOS stuff
lines.append('make ios-build')
@@ -408,8 +443,9 @@ def gen_fulltest_buildfile_apple() -> None:
if extra == 'mac.package':
# FIXME; Currently skipping notarization because it requires us
# to be logged in via the gui to succeed.
- lines.append('BA_MAC_DISK_IMAGE_SKIP_NOTARIZATION=1'
- ' make mac-package')
+ lines.append(
+ 'BA_MAC_DISK_IMAGE_SKIP_NOTARIZATION=1 make mac-package'
+ )
elif extra == 'mac.package.server.x86_64':
lines.append('make mac-server-package-x86-64')
elif extra == 'mac.package.server.arm64':
@@ -459,6 +495,7 @@ def get_current_prefab_platform(wsl_gives_windows: bool = True) -> str:
Throws a RuntimeError on unsupported platforms.
"""
import platform
+
system = platform.system()
machine = platform.machine()
@@ -467,8 +504,9 @@ def get_current_prefab_platform(wsl_gives_windows: bool = True) -> str:
return 'mac_x86_64'
if machine == 'arm64':
return 'mac_arm64'
- raise RuntimeError(f'make_prefab: unsupported mac machine type:'
- f' {machine}.')
+ raise RuntimeError(
+ f'make_prefab: unsupported mac machine type:' f' {machine}.'
+ )
if system == 'Linux':
# If it looks like we're in Windows Subsystem for Linux,
# we may want to operate on Windows versions.
@@ -479,17 +517,19 @@ def get_current_prefab_platform(wsl_gives_windows: bool = True) -> str:
return 'windows_x86'
# TODO: add support for arm windows
raise RuntimeError(
- f'make_prefab: unsupported win machine type:'
- f' {machine}.')
+ f'make_prefab: unsupported win machine type: {machine}.'
+ )
if machine == 'x86_64':
return 'linux_x86_64'
if machine == 'aarch64':
return 'linux_arm64'
- raise RuntimeError(f'make_prefab: unsupported linux machine type:'
- f' {machine}.')
- raise RuntimeError(f'make_prefab: unrecognized platform:'
- f' {platform.system()}.')
+ raise RuntimeError(
+ f'make_prefab: unsupported linux machine type:' f' {machine}.'
+ )
+ raise RuntimeError(
+ f'make_prefab: unrecognized platform:' f' {platform.system()}.'
+ )
def _vstr(nums: Sequence[int]) -> str:
@@ -503,44 +543,75 @@ def checkenv() -> None:
# pylint: disable=too-many-locals
from efrotools import PYTHON_BIN
+
print(f'{Clr.BLD}Checking environment...{Clr.RST}', flush=True)
# Make sure they've got curl.
- if subprocess.run(['which', 'curl'], check=False,
- capture_output=True).returncode != 0:
- raise CleanError('curl is required; please install it via apt,'
- ' brew, etc.')
+ if (
+ subprocess.run(
+ ['which', 'curl'], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
+ raise CleanError(
+ 'curl is required; please install it via apt, brew, etc.'
+ )
# Make sure they've got rsync.
- if subprocess.run(['which', 'rsync'], check=False,
- capture_output=True).returncode != 0:
- raise CleanError('rsync is required; please install it via apt,'
- ' brew, etc.')
+ if (
+ subprocess.run(
+ ['which', 'rsync'], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
+ raise CleanError(
+ 'rsync is required; please install it via apt, brew, etc.'
+ )
# Make sure they've got our target python version.
- if subprocess.run(['which', PYTHON_BIN], check=False,
- capture_output=True).returncode != 0:
- raise CleanError(f'{PYTHON_BIN} is required; please install it'
- 'via apt, brew, etc.')
+ if (
+ subprocess.run(
+ ['which', PYTHON_BIN], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
+ raise CleanError(
+ f'{PYTHON_BIN} is required; please install it' 'via apt, brew, etc.'
+ )
# Make sure they've got clang-format.
- if subprocess.run(['which', 'clang-format'],
- check=False,
- capture_output=True).returncode != 0:
- raise CleanError('clang-format is required; please install '
- 'it via apt, brew, etc.')
+ if (
+ subprocess.run(
+ ['which', 'clang-format'], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
+ raise CleanError(
+ 'clang-format is required; please install it via apt, brew, etc.'
+ )
# Make sure they've got pip for that python version.
- if subprocess.run([PYTHON_BIN, '-m', 'pip', '--version'],
- check=False,
- capture_output=True).returncode != 0:
+ if (
+ subprocess.run(
+ [PYTHON_BIN, '-m', 'pip', '--version'],
+ check=False,
+ capture_output=True,
+ ).returncode
+ != 0
+ ):
raise CleanError(
- f'pip (for {PYTHON_BIN}) is required; please install it.')
+ f'pip (for {PYTHON_BIN}) is required; please install it.'
+ )
# Parse package names and versions from pip.
- piplist = subprocess.run(
- [PYTHON_BIN, '-m', 'pip', 'list'], check=True,
- capture_output=True).stdout.decode().strip().splitlines()
+ piplist = (
+ subprocess.run(
+ [PYTHON_BIN, '-m', 'pip', 'list'], check=True, capture_output=True
+ )
+ .stdout.decode()
+ .strip()
+ .splitlines()
+ )
assert 'Package' in piplist[0] and 'Version' in piplist[0]
assert '--------' in piplist[1]
piplist = piplist[2:]
@@ -551,8 +622,10 @@ def checkenv() -> None:
pver = [int(x) if x.isdigit() else 0 for x in pverraw.split('.')]
pipvers[pname] = pver
except Exception as exc:
- raise RuntimeError(f'Error parsing version info from line {i} of:'
- f'\nBEGIN\n{piplist}\nEND') from exc
+ raise RuntimeError(
+ f'Error parsing version info from line {i} of:'
+ f'\nBEGIN\n{piplist}\nEND'
+ ) from exc
# Check for some required python modules.
# FIXME: since all of these come from pip now, we should just use
@@ -570,7 +643,8 @@ def checkenv() -> None:
f'To install it, try: "{PYTHON_BIN}'
f' -m pip install {pipname}"\n'
f'Alternately, "tools/pcommand install_pip_reqs"'
- f' will update all pip requirements.')
+ f' will update all pip requirements.'
+ )
if minver is not None:
vnums = pipvers[pipname]
# Seeing a decent number of version lengths fluctuating
@@ -580,8 +654,9 @@ def checkenv() -> None:
vnums.append(0)
while len(minver) < len(vnums):
minver.append(0)
- assert len(vnums) == len(minver), (
- f'unexpected version format for {pipname}: {vnums}')
+ assert len(vnums) == len(
+ minver
+ ), f'unexpected version format for {pipname}: {vnums}'
if vnums < minver:
raise CleanError(
f'{pipname} ver. {_vstr(minver)} or newer'
@@ -589,7 +664,8 @@ def checkenv() -> None:
f'To upgrade it, try: "{PYTHON_BIN}'
f' -m pip install --upgrade {pipname}".\n'
'Alternately, "tools/pcommand install_pip_reqs"'
- ' will update all pip requirements.')
+ ' will update all pip requirements.'
+ )
else:
if pipname is None:
pipname = modname
@@ -598,25 +674,29 @@ def checkenv() -> None:
f'{PYTHON_BIN} -m {modname} --version',
shell=True,
check=False,
- capture_output=True)
+ capture_output=True,
+ )
else:
results = subprocess.run(
f'{PYTHON_BIN} -c "import {modname}"',
shell=True,
check=False,
- capture_output=True)
+ capture_output=True,
+ )
if results.returncode != 0:
raise CleanError(
f'{pipname} (for {PYTHON_BIN}) is required.\n'
f'To install it, try: "{PYTHON_BIN}'
f' -m pip install {pipname}"\n'
f'Alternately, "tools/pcommand install_pip_reqs"'
- f' will update all pip requirements.')
+ f' will update all pip requirements.'
+ )
if minver is not None:
# Note: some modules such as pytest print
# their version to stderr, so grab both.
- verlines = (results.stdout +
- results.stderr).decode().splitlines()
+ verlines = (
+ (results.stdout + results.stderr).decode().splitlines()
+ )
if verlines[0].startswith('Cpplint fork'):
verlines = verlines[1:]
ver_line = verlines[0]
@@ -630,8 +710,10 @@ def checkenv() -> None:
int(x) for x in ver_line.split()[-1].split('.')
]
except Exception:
- print(f'ERROR PARSING VER LINE for {req}:'
- f' \'{ver_line}\'')
+ print(
+ f'ERROR PARSING VER LINE for {req}:'
+ f' \'{ver_line}\''
+ )
raise
assert len(vnums) == len(minver)
if vnums < minver:
@@ -641,7 +723,8 @@ def checkenv() -> None:
f'To upgrade it, try: "{PYTHON_BIN}'
f' -m pip install --upgrade {pipname}".\n'
'Alternately, "tools/pcommand install_pip_reqs"'
- ' will update all pip requirements.')
+ ' will update all pip requirements.'
+ )
except Exception:
print(f'ERROR CHECKING PIP REQ \'{req}\'')
raise
@@ -686,8 +769,11 @@ def get_pip_reqs() -> list[str]:
def _get_server_config_raw_contents(projroot: str) -> str:
import textwrap
- with open(os.path.join(projroot, 'tools/bacommon/servermanager.py'),
- encoding='utf-8') as infile:
+
+ with open(
+ os.path.join(projroot, 'tools/bacommon/servermanager.py'),
+ encoding='utf-8',
+ ) as infile:
lines = infile.read().splitlines()
firstline = lines.index('class ServerConfig:') + 1
lastline = firstline + 1
@@ -706,12 +792,13 @@ def _get_server_config_raw_contents(projroot: str) -> str:
while lines[lastline] == '':
lastline -= 1
- return textwrap.dedent('\n'.join(lines[firstline:lastline + 1]))
+ return textwrap.dedent('\n'.join(lines[firstline : lastline + 1]))
def _get_server_config_template_yaml(projroot: str) -> str:
# pylint: disable=too-many-branches
import yaml
+
lines_in = _get_server_config_raw_contents(projroot).splitlines()
lines_out: list[str] = []
ignore_vars = {'stress_test_players'}
@@ -722,6 +809,10 @@ def _get_server_config_template_yaml(projroot: str) -> str:
# Ignore indented lines (our few multi-line special cases).
continue
+ if line.startswith(']'):
+ # Ignore closing lines (our few multi-line special cases).
+ continue
+
if line.startswith('team_names:'):
lines_out += [
'#team_names:',
@@ -766,14 +857,11 @@ def _get_server_config_template_yaml(projroot: str) -> str:
elif vname == 'idle_exit_minutes':
vval = 20
elif vname == 'stats_url':
- vval = ('https://mystatssite.com/'
- 'showstats?player=${ACCOUNT}')
+ vval = 'https://mystatssite.com/showstats?player=${ACCOUNT}'
elif vname == 'admins':
vval = ['pb-yOuRAccOuNtIdHErE', 'pb-aNdMayBeAnotherHeRE']
lines_out += [
- '#' + l for l in yaml.dump({
- vname: vval
- }).strip().splitlines()
+ '#' + l for l in yaml.dump({vname: vval}).strip().splitlines()
]
else:
# Convert comments referring to python bools to yaml bools.
@@ -787,8 +875,10 @@ def filter_server_config(projroot: str, infilepath: str) -> str:
"""Add commented-out config options to a server config."""
with open(infilepath, encoding='utf-8') as infile:
cfg = infile.read()
- return cfg.replace('# __CONFIG_TEMPLATE_VALUES__',
- _get_server_config_template_yaml(projroot))
+ return cfg.replace(
+ '# __CONFIG_TEMPLATE_VALUES__',
+ _get_server_config_template_yaml(projroot),
+ )
def cmake_prep_dir(dirname: str, verbose: bool = False) -> None:
@@ -805,45 +895,60 @@ def cmake_prep_dir(dirname: str, verbose: bool = False) -> None:
@dataclass
class Entry:
"""Item examined for presence/change."""
+
name: str
current_value: str
entries: list[Entry] = []
# Start fresh if cmake version changes.
- cmake_ver_output = subprocess.run(['cmake', '--version'],
- check=True,
- capture_output=True).stdout.decode()
+ cmake_ver_output = subprocess.run(
+ ['cmake', '--version'], check=True, capture_output=True
+ ).stdout.decode()
cmake_ver = cmake_ver_output.splitlines()[0].split('cmake version ')[1]
entries.append(Entry('cmake version', cmake_ver))
# ...or if the actual location of cmake on disk changes.
cmake_path = os.path.realpath(
- subprocess.run(['which', 'cmake'], check=True,
- capture_output=True).stdout.decode().strip())
+ subprocess.run(['which', 'cmake'], check=True, capture_output=True)
+ .stdout.decode()
+ .strip()
+ )
entries.append(Entry('cmake path', cmake_path))
# ...or if python's version changes.
- python_ver_output = subprocess.run(
- [f'python{PYVER}', '--version'], check=True,
- capture_output=True).stdout.decode().strip()
+ python_ver_output = (
+ subprocess.run(
+ [f'python{PYVER}', '--version'], check=True, capture_output=True
+ )
+ .stdout.decode()
+ .strip()
+ )
python_ver = python_ver_output.splitlines()[0].split('Python ')[1]
entries.append(Entry('python_version', python_ver))
# ...or if the actual location of python on disk changes.
python_path = os.path.realpath(
- subprocess.run(['which', f'python{PYVER}'],
- check=True,
- capture_output=True).stdout.decode())
+ subprocess.run(
+ ['which', f'python{PYVER}'], check=True, capture_output=True
+ ).stdout.decode()
+ )
entries.append(Entry('python_path', python_path))
# ...or if mac xcode sdk paths change
- mac_xcode_sdks = (','.join(
- sorted(
- os.listdir('/Applications/Xcode.app/Contents/'
- 'Developer/Platforms/MacOSX.platform/'
- 'Developer/SDKs/')))
- if platform.system() == 'Darwin' else '')
+ mac_xcode_sdks = (
+ ','.join(
+ sorted(
+ os.listdir(
+ '/Applications/Xcode.app/Contents/'
+ 'Developer/Platforms/MacOSX.platform/'
+ 'Developer/SDKs/'
+ )
+ )
+ )
+ if platform.system() == 'Darwin'
+ else ''
+ )
entries.append(Entry('mac_xcode_sdks', mac_xcode_sdks))
# Ok; do the thing.
@@ -863,23 +968,27 @@ def cmake_prep_dir(dirname: str, verbose: bool = False) -> None:
for entry in entries:
previous_value = versions.get(entry.name)
if entry.current_value != previous_value:
- print(f'{Clr.BLU}{entry.name} changed from {previous_value}'
- f' to {entry.current_value}; clearing existing build at'
- f' "{dirname}".{Clr.RST}')
+ print(
+ f'{Clr.BLU}{entry.name} changed from {previous_value}'
+ f' to {entry.current_value}; clearing existing build at'
+ f' "{dirname}".{Clr.RST}'
+ )
changed = True
break
if changed:
if verbose:
print(
- f'{Clr.BLD}{title}:{Clr.RST} Blowing away existing build dir.')
+ f'{Clr.BLD}{title}:{Clr.RST} Blowing away existing build dir.'
+ )
subprocess.run(['rm', '-rf', dirname], check=True)
os.makedirs(dirname, exist_ok=True)
with open(verfilename, 'w', encoding='utf-8') as outfile:
outfile.write(
json.dumps(
- {entry.name: entry.current_value
- for entry in entries}))
+ {entry.name: entry.current_value for entry in entries}
+ )
+ )
else:
if verbose:
print(f'{Clr.BLD}{title}:{Clr.RST} Keeping existing build dir.')
diff --git a/tools/batools/changelog.py b/tools/batools/changelog.py
index abf6bace..bec80fbb 100755
--- a/tools/batools/changelog.py
+++ b/tools/batools/changelog.py
@@ -27,8 +27,10 @@ def generate(projroot: str) -> None:
with open(out_path_tmp, 'w', encoding='utf-8') as outfile:
outfile.write('\n'.join(lines))
- subprocess.run(f'pandoc -f markdown {out_path_tmp} > {out_path}',
- shell=True,
- check=True)
+ subprocess.run(
+ f'pandoc -f markdown {out_path_tmp} > {out_path}',
+ shell=True,
+ check=True,
+ )
print(f'Generated changelog at \'{out_path}\'.')
os.unlink(out_path_tmp)
diff --git a/tools/batools/docs.py b/tools/batools/docs.py
index e5bfc547..2f52d755 100755
--- a/tools/batools/docs.py
+++ b/tools/batools/docs.py
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
@dataclass
class AttributeInfo:
"""Info about an attribute of a class."""
+
name: str
attr_type: str | None = None
docs: str | None = None
@@ -46,8 +47,9 @@ def parse_docs_attrs(attrs: list[AttributeInfo], docs: str) -> str:
# A line with a single alphanumeric word preceding a colon
# is a new attr.
splits = line.split(' ', maxsplit=1)
- if (splits[0].replace('_', '').isalnum()
- and splits[-1].endswith(':')):
+ if splits[0].replace('_', '').isalnum() and splits[-1].endswith(
+ ':'
+ ):
if cur_attr is not None:
attrs.append(cur_attr)
cur_attr = AttributeInfo(name=splits[0])
@@ -86,10 +88,12 @@ def generate(projroot: str) -> None:
# Make sure we're running from the dir above this script.
os.chdir(projroot)
- templatesdir = (Path(projroot) / 'assets' / 'src' / 'pdoc' /
- 'templates').absolute()
- pythondir = (Path(projroot) / 'assets' / 'src' / 'ba_data' /
- 'python').absolute()
+ templatesdir = (
+ Path(projroot) / 'assets' / 'src' / 'pdoc' / 'templates'
+ ).absolute()
+ pythondir = (
+ Path(projroot) / 'assets' / 'src' / 'ba_data' / 'python'
+ ).absolute()
outdirname = (Path(projroot) / 'build' / 'docs_html').absolute()
sys.path.append(str(pythondir))
@@ -99,12 +103,13 @@ def generate(projroot: str) -> None:
os.environ['BA_DOCS_GENERATION'] = '1'
pdoc.render.env.globals['ba_version'] = version
pdoc.render.env.globals['ba_build'] = build_number
- pdoc.render.configure(search=True,
- show_source=True,
- template_directory=templatesdir)
+ pdoc.render.configure(
+ search=True, show_source=True, template_directory=templatesdir
+ )
pdoc.pdoc('ba', 'bastd', output_directory=outdirname)
except Exception as exc:
import traceback
+
traceback.print_exc()
raise CleanError('Docs generation failed') from exc
diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py
index dc5afbbb..36148fbc 100755
--- a/tools/batools/dummymodule.py
+++ b/tools/batools/dummymodule.py
@@ -28,39 +28,48 @@ if TYPE_CHECKING:
def _get_varying_func_info(sig_in: str) -> tuple[str, str]:
"""Return overloaded signatures and return statements for varying funcs."""
returns = 'return None'
- if sig_in == ('getdelegate(self, type: type,'
- ' doraise: bool = False) -> '):
- sig = ('# Show that ur return type varies based on "doraise" value:\n'
- '@overload\n'
- 'def getdelegate(self, type: type[_T],'
- ' doraise: Literal[False] = False) -> _T | None:\n'
- ' ...\n'
- '\n'
- '@overload\n'
- 'def getdelegate(self, type: type[_T],'
- ' doraise: Literal[True]) -> _T:\n'
- ' ...\n'
- '\n'
- 'def getdelegate(self, type: Any,'
- ' doraise: bool = False) -> Any:\n')
- elif sig_in == ('getinputdevice(name: str, unique_id:'
- ' str, doraise: bool = True) -> '):
- sig = ('# Show that our return type varies based on "doraise" value:\n'
- '@overload\n'
- 'def getinputdevice(name: str, unique_id: str,'
- ' doraise: Literal[True] = True) -> ba.InputDevice:\n'
- ' ...\n'
- '\n'
- '@overload\n'
- 'def getinputdevice(name: str, unique_id: str,'
- ' doraise: Literal[False]) -> ba.InputDevice | None:\n'
- ' ...\n'
- '\n'
- 'def getinputdevice(name: str, unique_id: str,'
- ' doraise: bool=True) -> Any:')
- elif sig_in == ('time(timetype: ba.TimeType = TimeType.SIM,'
- ' timeformat: ba.TimeFormat = TimeFormat.SECONDS)'
- ' -> '):
+ if sig_in == (
+ 'getdelegate(self, type: type,' ' doraise: bool = False) -> '
+ ):
+ sig = (
+ '# Show that ur return type varies based on "doraise" value:\n'
+ '@overload\n'
+ 'def getdelegate(self, type: type[_T],'
+ ' doraise: Literal[False] = False) -> _T | None:\n'
+ ' ...\n'
+ '\n'
+ '@overload\n'
+ 'def getdelegate(self, type: type[_T],'
+ ' doraise: Literal[True]) -> _T:\n'
+ ' ...\n'
+ '\n'
+ 'def getdelegate(self, type: Any,'
+ ' doraise: bool = False) -> Any:\n'
+ )
+ elif sig_in == (
+ 'getinputdevice(name: str, unique_id:'
+ ' str, doraise: bool = True) -> '
+ ):
+ sig = (
+ '# Show that our return type varies based on "doraise" value:\n'
+ '@overload\n'
+ 'def getinputdevice(name: str, unique_id: str,'
+ ' doraise: Literal[True] = True) -> ba.InputDevice:\n'
+ ' ...\n'
+ '\n'
+ '@overload\n'
+ 'def getinputdevice(name: str, unique_id: str,'
+ ' doraise: Literal[False]) -> ba.InputDevice | None:\n'
+ ' ...\n'
+ '\n'
+ 'def getinputdevice(name: str, unique_id: str,'
+ ' doraise: bool=True) -> Any:'
+ )
+ elif sig_in == (
+ 'time(timetype: ba.TimeType = TimeType.SIM,'
+ ' timeformat: ba.TimeFormat = TimeFormat.SECONDS)'
+ ' -> '
+ ):
sig = (
'# Overloads to return a type based on requested format.\n'
'\n'
@@ -86,7 +95,8 @@ def _get_varying_func_info(sig_in: str) -> tuple[str, str]:
'\n'
'def time(timetype: ba.TimeType = TimeType.SIM,\n'
' timeformat: ba.TimeFormat = TimeFormat.SECONDS)'
- ' -> Any:\n')
+ ' -> Any:\n'
+ )
elif sig_in == 'getactivity(doraise: bool = True) -> ':
sig = (
'# Show that our return type varies based on "doraise" value:\n'
@@ -101,30 +111,39 @@ def _get_varying_func_info(sig_in: str) -> tuple[str, str]:
' ...\n'
'\n'
'\n'
- 'def getactivity(doraise: bool = True) -> ba.Activity | None:')
+ 'def getactivity(doraise: bool = True) -> ba.Activity | None:'
+ )
elif sig_in == 'getsession(doraise: bool = True) -> ':
- sig = ('# Show that our return type varies based on "doraise" value:\n'
- '@overload\n'
- 'def getsession(doraise: Literal[True] = True) -> ba.Session:\n'
- ' ...\n'
- '\n'
- '\n'
- '@overload\n'
- 'def getsession(doraise: Literal[False])'
- ' -> ba.Session | None:\n'
- ' ...\n'
- '\n'
- '\n'
- 'def getsession(doraise: bool = True) -> ba.Session | None:')
+ sig = (
+ '# Show that our return type varies based on "doraise" value:\n'
+ '@overload\n'
+ 'def getsession(doraise: Literal[True] = True) -> ba.Session:\n'
+ ' ...\n'
+ '\n'
+ '\n'
+ '@overload\n'
+ 'def getsession(doraise: Literal[False])'
+ ' -> ba.Session | None:\n'
+ ' ...\n'
+ '\n'
+ '\n'
+ 'def getsession(doraise: bool = True) -> ba.Session | None:'
+ )
else:
raise RuntimeError(
- f'Unimplemented varying func: {Clr.RED}{sig_in}{Clr.RST}')
+ f'Unimplemented varying func: {Clr.RED}{sig_in}{Clr.RST}'
+ )
return sig, returns
-def _writefuncs(parent: Any, funcnames: Sequence[str], indent: int,
- spacing: int, as_method: bool) -> str:
+def _writefuncs(
+ parent: Any,
+ funcnames: Sequence[str],
+ indent: int,
+ spacing: int,
+ as_method: bool,
+) -> str:
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
@@ -171,35 +190,46 @@ def _writefuncs(parent: Any, funcnames: Sequence[str], indent: int,
defsline = f'{indstr}def {sig}:\n'
# Types can be strings for forward-declaration cases.
- if ((returns[0] == "'" and returns[-1] == "'")
- or (returns[0] == '"' and returns[-1] == '"')):
+ if (returns[0] == "'" and returns[-1] == "'") or (
+ returns[0] == '"' and returns[-1] == '"'
+ ):
returns = returns[1:-1]
if returns == 'None':
returnstr = 'return None'
elif returns == 'ba.Lstr':
- returnstr = ('import ba # pylint: disable=cyclic-import\n'
- "return ba.Lstr(value='')")
+ returnstr = (
+ 'import ba # pylint: disable=cyclic-import\n'
+ "return ba.Lstr(value='')"
+ )
elif returns in {'ba.Activity', 'ba.Activity | None'}:
returnstr = (
- 'import ba # pylint: disable=cyclic-import\nreturn ' +
- 'ba.Activity(settings={})')
+ 'import ba # pylint: disable=cyclic-import\nreturn '
+ + 'ba.Activity(settings={})'
+ )
elif returns in {'ba.Session', 'ba.Session | None'}:
returnstr = (
- 'import ba # pylint: disable=cyclic-import\nreturn ' +
- 'ba.Session([])')
+ 'import ba # pylint: disable=cyclic-import\nreturn '
+ + 'ba.Session([])'
+ )
elif returns == 'ba.SessionPlayer | None':
- returnstr = ('import ba # pylint: disable=cyclic-import\n'
- 'return ba.SessionPlayer()')
+ returnstr = (
+ 'import ba # pylint: disable=cyclic-import\n'
+ 'return ba.SessionPlayer()'
+ )
elif returns == 'ba.Player | None':
- returnstr = ('import ba # pylint: disable=cyclic-import\n'
- 'return ba.Player()')
+ returnstr = (
+ 'import ba # pylint: disable=cyclic-import\n'
+ 'return ba.Player()'
+ )
elif returns.startswith('ba.') and ' | None' not in returns:
# We cant import ba at module level so let's
# do it within funcs as needed.
returnstr = (
- 'import ba # pylint: disable=cyclic-import\nreturn ' +
- returns + '()')
+ 'import ba # pylint: disable=cyclic-import\nreturn '
+ + returns
+ + '()'
+ )
elif returns in {'object', 'Any'}:
@@ -231,25 +261,50 @@ def _writefuncs(parent: Any, funcnames: Sequence[str], indent: int,
elif returns == 'list[dict[str, Any]]':
returnstr = "return [{'foo': 'bar'}]"
elif returns in {
- 'session.Session', 'team.Team', '_app.App',
- 'appconfig.AppConfig'
+ 'session.Session',
+ 'team.Team',
+ '_app.App',
+ 'appconfig.AppConfig',
}:
- returnstr = ('from ba import ' + returns.split('.')[0] +
- '; return ' + returns + '()')
+ returnstr = (
+ 'from ba import '
+ + returns.split('.')[0]
+ + '; return '
+ + returns
+ + '()'
+ )
elif returns in [
- 'bool', 'str', 'int', 'list', 'dict', 'tuple', 'float',
- 'SessionData', 'ActivityData', 'Player', 'SessionPlayer',
- 'InputDevice', 'Sound', 'Texture', 'Model', 'CollideModel',
- 'team.Team', 'Vec3', 'Widget', 'Node'
+ 'bool',
+ 'str',
+ 'int',
+ 'list',
+ 'dict',
+ 'tuple',
+ 'float',
+ 'SessionData',
+ 'ActivityData',
+ 'Player',
+ 'SessionPlayer',
+ 'InputDevice',
+ 'Sound',
+ 'Texture',
+ 'Model',
+ 'CollideModel',
+ 'team.Team',
+ 'Vec3',
+ 'Widget',
+ 'Node',
]:
returnstr = 'return ' + returns + '()'
else:
raise Exception(
- f'unknown returns value: {returns} for {funcname}')
+ f'unknown returns value: {returns} for {funcname}'
+ )
returnspc = indstr + ' '
returnstr = ('\n' + returnspc).join(returnstr.strip().splitlines())
- docstr_out = _formatdoc(_filterdoc(docstr, funcname=funcname),
- indent + 4)
+ docstr_out = _formatdoc(
+ _filterdoc(docstr, funcname=funcname), indent + 4
+ )
out += spcstr + defsline + docstr_out + f'{returnspc}{returnstr}\n'
return out
@@ -260,75 +315,77 @@ def _special_class_cases(classname: str) -> str:
# Special case: define a fallback attr getter with a random
# return type in cases where our class handles attrs itself.
if classname in ['Vec3']:
- out += ('\n'
- ' # pylint: disable=function-redefined\n'
- '\n'
- ' @overload\n'
- ' def __init__(self) -> None:\n'
- ' pass\n'
- '\n'
- ' @overload\n'
- ' def __init__(self, value: float):\n'
- ' pass\n'
- '\n'
- ' @overload\n'
- ' def __init__(self, values: Sequence[float]):\n'
- ' pass\n'
- '\n'
- ' @overload\n'
- ' def __init__(self, x: float, y: float, z: float):\n'
- ' pass\n'
- '\n'
- ' def __init__(self, *args: Any, **kwds: Any):\n'
- ' pass\n'
- '\n'
- ' def __add__(self, other: Vec3) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' def __sub__(self, other: Vec3) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' @overload\n'
- ' def __mul__(self, other: float) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' @overload\n'
- ' def __mul__(self, other: Sequence[float]) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' def __mul__(self, other: Any) -> Any:\n'
- ' return self\n'
- '\n'
- ' @overload\n'
- ' def __rmul__(self, other: float) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' @overload\n'
- ' def __rmul__(self, other: Sequence[float]) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' def __rmul__(self, other: Any) -> Any:\n'
- ' return self\n'
- '\n'
- ' # (for index access)\n'
- ' def __getitem__(self, typeargs: Any) -> Any:\n'
- ' return 0.0\n'
- '\n'
- ' def __len__(self) -> int:\n'
- ' return 3\n'
- '\n'
- ' # (for iterator access)\n'
- ' def __iter__(self) -> Any:\n'
- ' return self\n'
- '\n'
- ' def __next__(self) -> float:\n'
- ' return 0.0\n'
- '\n'
- ' def __neg__(self) -> Vec3:\n'
- ' return self\n'
- '\n'
- ' def __setitem__(self, index: int, val: float) -> None:\n'
- ' pass\n')
+ out += (
+ '\n'
+ ' # pylint: disable=function-redefined\n'
+ '\n'
+ ' @overload\n'
+ ' def __init__(self) -> None:\n'
+ ' pass\n'
+ '\n'
+ ' @overload\n'
+ ' def __init__(self, value: float):\n'
+ ' pass\n'
+ '\n'
+ ' @overload\n'
+ ' def __init__(self, values: Sequence[float]):\n'
+ ' pass\n'
+ '\n'
+ ' @overload\n'
+ ' def __init__(self, x: float, y: float, z: float):\n'
+ ' pass\n'
+ '\n'
+ ' def __init__(self, *args: Any, **kwds: Any):\n'
+ ' pass\n'
+ '\n'
+ ' def __add__(self, other: Vec3) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' def __sub__(self, other: Vec3) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' @overload\n'
+ ' def __mul__(self, other: float) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' @overload\n'
+ ' def __mul__(self, other: Sequence[float]) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' def __mul__(self, other: Any) -> Any:\n'
+ ' return self\n'
+ '\n'
+ ' @overload\n'
+ ' def __rmul__(self, other: float) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' @overload\n'
+ ' def __rmul__(self, other: Sequence[float]) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' def __rmul__(self, other: Any) -> Any:\n'
+ ' return self\n'
+ '\n'
+ ' # (for index access)\n'
+ ' def __getitem__(self, typeargs: Any) -> Any:\n'
+ ' return 0.0\n'
+ '\n'
+ ' def __len__(self) -> int:\n'
+ ' return 3\n'
+ '\n'
+ ' # (for iterator access)\n'
+ ' def __iter__(self) -> Any:\n'
+ ' return self\n'
+ '\n'
+ ' def __next__(self) -> float:\n'
+ ' return 0.0\n'
+ '\n'
+ ' def __neg__(self) -> Vec3:\n'
+ ' return self\n'
+ '\n'
+ ' def __setitem__(self, index: int, val: float) -> None:\n'
+ ' pass\n'
+ )
if classname in ['Node']:
out += (
'\n'
@@ -451,28 +508,35 @@ def _special_class_cases(classname: str) -> str:
' vr_overlay_center_enabled: bool = False\n'
' vignette_outer: Sequence[float] = (0.0, 0.0)\n'
' vignette_inner: Sequence[float] = (0.0, 0.0)\n'
- ' tint: Sequence[float] = (1.0, 1.0, 1.0)\n')
+ ' tint: Sequence[float] = (1.0, 1.0, 1.0)\n'
+ )
# Special case: need to be able to use the 'with' statement
# on some classes.
if classname in ['Context']:
- out += ('\n'
- ' def __enter__(self) -> None:\n'
- ' """Support for "with" statement."""\n'
- ' pass\n'
- '\n'
- ' def __exit__(self, exc_type: Any, exc_value: Any, '
- 'traceback: Any) -> Any:\n'
- ' """Support for "with" statement."""\n'
- ' pass\n')
+ out += (
+ '\n'
+ ' def __enter__(self) -> None:\n'
+ ' """Support for "with" statement."""\n'
+ ' pass\n'
+ '\n'
+ ' def __exit__(self, exc_type: Any, exc_value: Any, '
+ 'traceback: Any) -> Any:\n'
+ ' """Support for "with" statement."""\n'
+ ' pass\n'
+ )
return out
def _filterdoc(docstr: str, funcname: str | None = None) -> str:
docslines = docstr.splitlines()
- if (funcname and docslines and docslines[0]
- and docslines[0].startswith(funcname)):
+ if (
+ funcname
+ and docslines
+ and docslines[0]
+ and docslines[0].startswith(funcname)
+ ):
# Remove this signature from python docstring
# as not to repeat ourselves.
_, docstr = docstr.split('\n\n', maxsplit=1)
@@ -499,14 +563,18 @@ def _filterdoc(docstr: str, funcname: str | None = None) -> str:
if attrs_definitions_last_line is None:
attrs_definitions_last_line = len(docslines) - 1
- return '\n'.join(docslines[:attributes_line] +
- docslines[attrs_definitions_last_line + 1:])
+ return '\n'.join(
+ docslines[:attributes_line]
+ + docslines[attrs_definitions_last_line + 1 :]
+ )
-def _formatdoc(docstr: str,
- indent: int,
- no_end_newline: bool = False,
- inner_indent: int = 0) -> str:
+def _formatdoc(
+ docstr: str,
+ indent: int,
+ no_end_newline: bool = False,
+ inner_indent: int = 0,
+) -> str:
out = ''
indentstr = indent * ' '
inner_indent_str = inner_indent * ' '
@@ -518,8 +586,14 @@ def _formatdoc(docstr: str,
for i, line in enumerate(docslines):
if i != 0 and line != '':
docslines[i] = indentstr + inner_indent_str + line
- out += ('\n' + indentstr + '"""' + '\n'.join(docslines) +
- ('' if no_end_newline else '\n' + indentstr) + '"""\n')
+ out += (
+ '\n'
+ + indentstr
+ + '"""'
+ + '\n'.join(docslines)
+ + ('' if no_end_newline else '\n' + indentstr)
+ + '"""\n'
+ )
return out
@@ -527,6 +601,7 @@ def _writeclasses(module: ModuleType, classnames: Sequence[str]) -> str:
# pylint: disable=too-many-branches
import types
from batools.docs import parse_docs_attrs
+
out = ''
for classname in classnames:
cls = getattr(module, classname)
@@ -549,21 +624,25 @@ def _writeclasses(module: ModuleType, classnames: Sequence[str]) -> str:
# and not category or a usage statement ending with a period,
# assume it has a public constructor.
has_constructor = False
- if ('category:' not in docstr.splitlines()[0].lower()
- and not docstr.splitlines()[0].endswith('.')
- and docstr != '(internal)'):
+ if (
+ 'category:' not in docstr.splitlines()[0].lower()
+ and not docstr.splitlines()[0].endswith('.')
+ and docstr != '(internal)'
+ ):
# Ok.. looks like the first line is a signature.
# Make sure we've got a signature followed by a blank line.
if '\n\n' not in docstr:
raise Exception(
- f'Constructor docstr missing empty line for {cls}.')
+ f'Constructor docstr missing empty line for {cls}.'
+ )
sig = docstr.split('\n\n')[0].replace('\n', ' ').strip()
# Sanity check - make sure name is in the sig.
if classname + '(' not in sig:
raise Exception(
- f'Class name not found in constructor sig for {cls}.')
+ f'Class name not found in constructor sig for {cls}.'
+ )
sig = sig.replace(classname + '(', '__init__(self, ')
out += ' def ' + sig + ':\n pass\n'
has_constructor = True
@@ -577,14 +656,18 @@ def _writeclasses(module: ModuleType, classnames: Sequence[str]) -> str:
if attr.attr_type is not None:
out += f' {attr.name}: {attr.attr_type}\n'
if attr.docs:
- out += _formatdoc(_filterdoc(attr.docs),
- indent=4,
- inner_indent=3,
- no_end_newline=True)
+ out += _formatdoc(
+ _filterdoc(attr.docs),
+ indent=4,
+ inner_indent=3,
+ no_end_newline=True,
+ )
out += '\n'
else:
- raise Exception(f'Found untyped attr in'
- f' {classname} docs: {attr.name}')
+ raise Exception(
+ f'Found untyped attr in'
+ f' {classname} docs: {attr.name}'
+ )
# Special cases such as attributes we add.
out += _special_class_cases(classname)
@@ -597,11 +680,9 @@ def _writeclasses(module: ModuleType, classnames: Sequence[str]) -> str:
else:
raise Exception(f'Unhandled obj {entry} in {cls}')
funcnames.sort()
- functxt = _writefuncs(cls,
- funcnames,
- indent=4,
- spacing=1,
- as_method=True)
+ functxt = _writefuncs(
+ cls, funcnames, indent=4, spacing=1, as_method=True
+ )
if functxt == '' and not has_constructor:
out += ' pass\n'
else:
@@ -613,9 +694,13 @@ def _writeclasses(module: ModuleType, classnames: Sequence[str]) -> str:
def generate(mname: str, sources_hash: str, outfilename: str) -> None:
"""Run the actual generation from within the game."""
# pylint: disable=too-many-locals
- module = __import__(mname)
- from efrotools import get_public_license, PYVER
import types
+
+ from efrotools import get_public_license
+ from efrotools.code import format_python_str
+
+ module = __import__(mname)
+
funcnames = []
classnames = []
for entry in (e for e in dir(module) if not e.startswith('__')):
@@ -628,85 +713,98 @@ def generate(mname: str, sources_hash: str, outfilename: str) -> None:
continue
else:
raise Exception(
- f'found unknown obj {entry}, {getattr(module, entry)}')
+ f'found unknown obj {entry}, {getattr(module, entry)}'
+ )
funcnames.sort()
classnames.sort()
- typing_imports = ('TYPE_CHECKING, overload, Sequence, TypeVar'
- if mname == '_ba' else 'TYPE_CHECKING, TypeVar')
- typing_imports_tc = ('Any, Callable, Literal'
- if mname == '_ba' else 'Any, Callable')
- tc_import_lines_extra = (' from ba._app import App\n'
- ' import ba\n' if mname == '_ba' else '')
- app_declare_lines = ('app: App\n'
- '\n' if mname == '_ba' else '')
+ typing_imports = (
+ 'TYPE_CHECKING, overload, Sequence, TypeVar'
+ if mname == '_ba'
+ else 'TYPE_CHECKING, TypeVar'
+ )
+ typing_imports_tc = (
+ 'Any, Callable, Literal' if mname == '_ba' else 'Any, Callable'
+ )
+ tc_import_lines_extra = (
+ ' from ba._app import App\n' ' import ba\n'
+ if mname == '_ba'
+ else ''
+ )
+ app_declare_lines = 'app: App\n' '\n' if mname == '_ba' else ''
enum_import_lines = (
- 'from ba._generated.enums import TimeFormat, TimeType\n'
- '\n' if mname == '_ba' else '')
- out = (get_public_license('python')
- + '\n'
- '#\n'
- f'"""A dummy stub module for the real {mname}.\n'
- '\n'
- f'The real {mname} is a compiled extension module'
- ' and only available\n'
- 'in the live engine. This dummy-module allows Pylint/Mypy/etc. to\n'
- 'function reasonably well outside of that environment.\n'
- '\n'
- 'Make sure this file is never included'
- ' in dirs seen by the engine!\n'
- '\n'
- 'In the future perhaps this can be a stub (.pyi) file, but'
- ' we will need\n'
- 'to make sure that it works with all our tools'
- ' (mypy, pylint, pycharm).\n'
- '\n'
- 'NOTE: This file was autogenerated by ' + __name__ + '; '
- 'do not edit by hand.\n'
- '"""\n'
- '\n'
- # '# (hash we can use to see if this file is out of date)\n'
- # '# SOURCES_HASH='+sources_hash+'\n'
- # '\n'
- '# I\'m sorry Pylint. I know this file saddens you. Be strong.\n'
- '# pylint: disable=useless-suppression\n'
- '# pylint: disable=unnecessary-pass\n'
- '# pylint: disable=use-dict-literal\n'
- '# pylint: disable=use-list-literal\n'
- '# pylint: disable=unused-argument\n'
- '# pylint: disable=missing-docstring\n'
- '# pylint: disable=too-many-locals\n'
- '# pylint: disable=redefined-builtin\n'
- '# pylint: disable=too-many-lines\n'
- '# pylint: disable=redefined-outer-name\n'
- '# pylint: disable=invalid-name\n'
- '# pylint: disable=no-value-for-parameter\n'
- '\n'
- 'from __future__ import annotations\n'
- '\n'
- f'from typing import {typing_imports}\n'
- '\n'
- f'{enum_import_lines}'
- 'if TYPE_CHECKING:\n'
- f' from typing import {typing_imports_tc}\n'
- f'{tc_import_lines_extra}'
- '\n'
- '\n'
- "_T = TypeVar('_T')\n"
- '\n'
- f'{app_declare_lines}'
- 'def _uninferrable() -> Any:\n'
- ' """Get an "Any" in mypy and "uninferrable" in Pylint."""\n'
- ' # pylint: disable=undefined-variable\n'
- ' return _not_a_real_variable # type: ignore'
- '\n'
- '\n'
- ) # yapf: disable
+ 'from ba._generated.enums import TimeFormat, TimeType\n' '\n'
+ if mname == '_ba'
+ else ''
+ )
+ out = (
+ get_public_license('python') + '\n'
+ '#\n'
+ f'"""A dummy stub module for the real {mname}.\n'
+ '\n'
+ f'The real {mname} is a compiled extension module'
+ ' and only available\n'
+ 'in the live engine. This dummy-module allows Pylint/Mypy/etc. to\n'
+ 'function reasonably well outside of that environment.\n'
+ '\n'
+ 'Make sure this file is never included'
+ ' in dirs seen by the engine!\n'
+ '\n'
+ 'In the future perhaps this can be a stub (.pyi) file, but'
+ ' we will need\n'
+ 'to make sure that it works with all our tools'
+ ' (mypy, pylint, pycharm).\n'
+ '\n'
+ 'NOTE: This file was autogenerated by ' + __name__ + '; '
+ 'do not edit by hand.\n'
+ '"""\n'
+ '\n'
+ # '# (hash we can use to see if this file is out of date)\n'
+ # '# SOURCES_HASH='+sources_hash+'\n'
+ # '\n'
+ '# I\'m sorry Pylint. I know this file saddens you. Be strong.\n'
+ '# pylint: disable=useless-suppression\n'
+ '# pylint: disable=unnecessary-pass\n'
+ '# pylint: disable=use-dict-literal\n'
+ '# pylint: disable=use-list-literal\n'
+ '# pylint: disable=unused-argument\n'
+ '# pylint: disable=missing-docstring\n'
+ '# pylint: disable=too-many-locals\n'
+ '# pylint: disable=redefined-builtin\n'
+ '# pylint: disable=too-many-lines\n'
+ '# pylint: disable=redefined-outer-name\n'
+ '# pylint: disable=invalid-name\n'
+ '# pylint: disable=no-value-for-parameter\n'
+ '\n'
+ 'from __future__ import annotations\n'
+ '\n'
+ f'from typing import {typing_imports}\n'
+ '\n'
+ f'{enum_import_lines}'
+ 'if TYPE_CHECKING:\n'
+ f' from typing import {typing_imports_tc}\n'
+ f'{tc_import_lines_extra}'
+ '\n'
+ '\n'
+ "_T = TypeVar('_T')\n"
+ '\n'
+ f'{app_declare_lines}'
+ 'def _uninferrable() -> Any:\n'
+ ' """Get an "Any" in mypy and "uninferrable" in Pylint."""\n'
+ ' # pylint: disable=undefined-variable\n'
+ ' return _not_a_real_variable # type: ignore'
+ '\n'
+ '\n'
+ )
out += _writeclasses(module, classnames)
out += _writefuncs(module, funcnames, indent=0, spacing=2, as_method=False)
- outhashpath = os.path.join(os.path.dirname(outfilename),
- f'.{mname}_sources_hash')
+ # Lastly format it.
+ out = format_python_str(out)
+
+ outhashpath = os.path.join(
+ os.path.dirname(outfilename), f'.{mname}_sources_hash'
+ )
with open(outfilename, 'w', encoding='utf-8') as outfile:
outfile.write(out)
@@ -714,10 +812,6 @@ def generate(mname: str, sources_hash: str, outfilename: str) -> None:
with open(outhashpath, 'w', encoding='utf-8') as outfile:
outfile.write(sources_hash)
- # Lastly, format it.
- subprocess.run([f'python{PYVER}', '-m', 'yapf', '--in-place', outfilename],
- check=True)
-
def _dummy_module_dirty(mname: str) -> tuple[bool, str]:
"""Test hashes on the dummy-module to see if it needs updates."""
@@ -774,22 +868,27 @@ def update(projroot: str, check: bool, force: bool) -> None:
continue
outfilename = os.path.abspath(
- os.path.join(projroot, f'assets/src/ba_data/python/{mname}.py'))
+ os.path.join(projroot, f'assets/src/ba_data/python/{mname}.py')
+ )
dirty, sources_hash = _dummy_module_dirty(mname)
if dirty:
if check:
- print(f'{Clr.RED}ERROR: dummy {mname} module'
- f' is out of date.{Clr.RST}')
+ print(
+ f'{Clr.RED}ERROR: dummy {mname} module'
+ f' is out of date.{Clr.RST}'
+ )
sys.exit(255)
elif not force:
# Dummy-module is clean and force is off; we're done here.
print(f'Dummy-module {Clr.BLD}{mname}.py{Clr.RST} is up to date.')
continue
- print(f'{Clr.MAG}Updating {Clr.BLD}{mname}.py{Clr.RST}{Clr.MAG}'
- f' dummy-module...{Clr.RST}')
+ print(
+ f'{Clr.MAG}Updating {Clr.BLD}{mname}.py{Clr.RST}{Clr.MAG}'
+ f' dummy-module...{Clr.RST}'
+ )
# Let's build the cmake version; no sandboxing issues to contend with
# there. Also going with the headless build; will need to revisit if
@@ -797,8 +896,10 @@ def update(projroot: str, check: bool, force: bool) -> None:
subprocess.run(['make', 'cmake-server-build'], check=True)
# Launch ballisticacore and exec ourself from within it.
- print(f'Launching ballisticacore to generate'
- f' {Clr.BLD}{mname}.py{Clr.RST} dummy-module...')
+ print(
+ f'Launching ballisticacore to generate'
+ f' {Clr.BLD}{mname}.py{Clr.RST} dummy-module...'
+ )
try:
subprocess.run(
[
@@ -823,10 +924,12 @@ def update(projroot: str, check: bool, force: bool) -> None:
check=True,
)
print(
- f'{Clr.BLU}{mname} dummy-module generation complete.{Clr.RST}')
+ f'{Clr.BLU}{mname} dummy-module generation complete.{Clr.RST}'
+ )
except Exception as exc2:
# Keep our error simple here; we want focus to be on what went
# wrong withing BallisticaCore.
raise CleanError(
- 'BallisticaCore dummy-module generation failed.') from exc2
+ 'BallisticaCore dummy-module generation failed.'
+ ) from exc2
diff --git a/tools/batools/meta.py b/tools/batools/meta.py
index 50a3b3a2..aaeccdfa 100644
--- a/tools/batools/meta.py
+++ b/tools/batools/meta.py
@@ -13,8 +13,9 @@ if TYPE_CHECKING:
pass
-def gen_flat_data_code(projroot: str, in_path: str, out_path: str,
- var_name: str) -> None:
+def gen_flat_data_code(
+ projroot: str, in_path: str, out_path: str, var_name: str
+) -> None:
"""Generate a C++ include file from a Python file."""
out_dir = os.path.dirname(out_path)
@@ -42,7 +43,7 @@ def gen_flat_data_code(projroot: str, in_path: str, out_path: str,
pretty_path = os.path.abspath(out_path)
if pretty_path.startswith(projroot + '/'):
- pretty_path = pretty_path[len(projroot) + 1:]
+ pretty_path = pretty_path[len(projroot) + 1 :]
print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}')
with open(out_path, 'w', encoding='utf-8') as outfile:
outfile.write(sval_out)
@@ -63,44 +64,54 @@ def gen_binding_code(projroot: str, in_path: str, out_path: str) -> None:
if '"' in pycode:
raise Exception('bindings file can\'t contain double quotes.')
lines = [
- l.strip().split(', # ') for l in pycode.splitlines()
+ l.strip().split(', # ')
+ for l in pycode.splitlines()
if l.startswith(' ')
]
if not all(len(l) == 2 for l in lines):
raise Exception('malformatted data')
# Our C++ code first execs our input as a string.
- ccode = ('{const char* bindcode = ' + repr(pycode).replace("'", '"') + ';')
- ccode += ('\nPyObject* result = PyRun_String(bindcode, Py_file_input,'
- ' bootstrap_context.get(), bootstrap_context.get());\n'
- 'if (result == nullptr) {\n'
- ' PyErr_PrintEx(0);\n'
- ' // Use a standard error to avoid a useless stack trace.\n'
- ' throw std::logic_error("Error fetching required Python'
- ' objects.");\n'
- '}\n')
+ ccode = '{const char* bindcode = ' + repr(pycode).replace("'", '"') + ';'
+ ccode += (
+ '\nPyObject* result = PyRun_String(bindcode, Py_file_input,'
+ ' bootstrap_context.get(), bootstrap_context.get());\n'
+ 'if (result == nullptr) {\n'
+ ' PyErr_PrintEx(0);\n'
+ ' // Use a standard error to avoid a useless stack trace.\n'
+ ' throw std::logic_error("Error fetching required Python'
+ ' objects.");\n'
+ '}\n'
+ )
# Then it grabs the function that was defined and runs it.
- ccode += ('PyObject* bindvals = PythonCommand("get_binding_values()",'
- ' "")'
- '.RunReturnObj(true, bootstrap_context.get());\n'
- 'if (bindvals == nullptr) {\n'
- ' // Use a standard error to avoid a useless stack trace.\n'
- ' throw std::logic_error("Error binding required Python'
- ' objects.");\n'
- '}\n')
+ ccode += (
+ 'PyObject* bindvals = PythonCommand("get_binding_values()",'
+ ' "")'
+ '.RunReturnObj(true, bootstrap_context.get());\n'
+ 'if (bindvals == nullptr) {\n'
+ ' // Use a standard error to avoid a useless stack trace.\n'
+ ' throw std::logic_error("Error binding required Python'
+ ' objects.");\n'
+ '}\n'
+ )
# Then it pulls the individual values out of the returned tuple.
for i, line in enumerate(lines):
- storecmd = ('StoreObjCallable' if line[1].endswith('Class')
- or line[1].endswith('Call') else 'StoreObj')
- ccode += (f'{storecmd}(ObjID::{line[1]},'
- f' PyTuple_GET_ITEM(bindvals, {i}), true);\n')
+ storecmd = (
+ 'StoreObjCallable'
+ if line[1].endswith('Class') or line[1].endswith('Call')
+ else 'StoreObj'
+ )
+ ccode += (
+ f'{storecmd}(ObjID::{line[1]},'
+ f' PyTuple_GET_ITEM(bindvals, {i}), true);\n'
+ )
ccode += 'Py_DECREF(bindvals);\n}\n'
pretty_path = os.path.abspath(out_path)
if pretty_path.startswith(projroot + '/'):
- pretty_path = pretty_path[len(projroot) + 1:]
+ pretty_path = pretty_path[len(projroot) + 1 :]
print(f'Meta-building {Clr.BLD}{pretty_path}{Clr.RST}')
with open(out_path, 'w', encoding='utf-8') as outfile:
outfile.write(ccode)
diff --git a/tools/batools/metamakefile.py b/tools/batools/metamakefile.py
index f2f271cb..287f08e3 100755
--- a/tools/batools/metamakefile.py
+++ b/tools/batools/metamakefile.py
@@ -31,6 +31,7 @@ OUT_DIR_PYTHON = '../../assets/src/ba_data/python/ba/_generated'
@dataclass
class Target:
"""A target to be added to the Makefile."""
+
src: list[str]
dst: str
cmd: str
@@ -39,9 +40,18 @@ class Target:
def emit(self) -> str:
"""Gen a Makefile target."""
out: str = self.dst.replace(' ', '\\ ')
- out += ' : ' + ' '.join(s for s in self.src) + (
- ('\n\t@mkdir -p "' + os.path.dirname(self.dst) +
- '"') if self.mkdir else '') + '\n\t@' + self.cmd + '\n'
+ out += (
+ ' : '
+ + ' '.join(s for s in self.src)
+ + (
+ ('\n\t@mkdir -p "' + os.path.dirname(self.dst) + '"')
+ if self.mkdir
+ else ''
+ )
+ + '\n\t@'
+ + self.cmd
+ + '\n'
+ )
return out
@@ -53,8 +63,11 @@ def _emit_sources_lines(targets: list[Target]) -> list[str]:
all_dsts = set()
for target in targets:
all_dsts.add(target.dst)
- out.append('sources: \\\n ' + ' \\\n '.join(
- dst.replace(' ', '\\ ') for dst in sorted(all_dsts)) + '\n')
+ out.append(
+ 'sources: \\\n '
+ + ' \\\n '.join(dst.replace(' ', '\\ ') for dst in sorted(all_dsts))
+ + '\n'
+ )
return out
@@ -69,12 +82,16 @@ def _emit_efrocache_lines(targets: list[Target]) -> list[str]:
# We may need to make pipeline adjustments if/when we get filenames
# with spaces in them.
if ' ' in target.dst:
- raise CleanError('FIXME: need to account for spaces in filename'
- f' "{target.dst}".')
+ raise CleanError(
+ 'FIXME: need to account for spaces in filename'
+ f' "{target.dst}".'
+ )
all_dsts.add(target.dst)
- out.append('efrocache-list:\n\t@echo ' +
- ' \\\n '.join('"' + dst + '"'
- for dst in sorted(all_dsts)) + '\n')
+ out.append(
+ 'efrocache-list:\n\t@echo '
+ + ' \\\n '.join('"' + dst + '"' for dst in sorted(all_dsts))
+ + '\n'
+ )
out.append('efrocache-build: sources\n')
return out
@@ -85,11 +102,12 @@ def _add_enums_module_target(targets: list[Target]) -> None:
Target(
src=[
'../ballistica/core/types.h',
- os.path.join(TOOLS_DIR, 'batools', 'pythonenumsmodule.py')
+ os.path.join(TOOLS_DIR, 'batools', 'pythonenumsmodule.py'),
],
dst=os.path.join(OUT_DIR_PYTHON, 'enums.py'),
cmd='$(PCOMMAND) gen_python_enums_module $< $@',
- ))
+ )
+ )
def _add_init_module_target(targets: list[Target], moduledir: str) -> None:
@@ -98,15 +116,19 @@ def _add_init_module_target(targets: list[Target], moduledir: str) -> None:
src=[os.path.join(TOOLS_DIR, 'batools', 'pcommand.py')],
dst=os.path.join(moduledir, '__init__.py'),
cmd='$(PCOMMAND) gen_python_init_module $@',
- ))
+ )
+ )
def _add_python_embedded_targets(targets: list[Target]) -> None:
pkg = 'bameta'
# Note: sort to keep things deterministic.
for fname in sorted(os.listdir(f'src/meta/{pkg}/python_embedded')):
- if (not fname.endswith('.py') or fname == '__init__.py'
- or 'flycheck' in fname):
+ if (
+ not fname.endswith('.py')
+ or fname == '__init__.py'
+ or 'flycheck' in fname
+ ):
continue
name = os.path.splitext(fname)[0]
src = [
@@ -115,32 +137,38 @@ def _add_python_embedded_targets(targets: list[Target]) -> None:
dst = os.path.join(OUT_DIR_CPP, 'python_embedded', f'{name}.inc')
if name == 'binding':
targets.append(
- Target(src=src,
- dst=dst,
- cmd='$(PCOMMAND) gen_binding_code $< $@'))
+ Target(
+ src=src, dst=dst, cmd='$(PCOMMAND) gen_binding_code $< $@'
+ )
+ )
else:
targets.append(
Target(
src=src,
dst=dst,
- cmd=f'$(PCOMMAND) gen_flat_data_code $< $@ {name}_code'))
+ cmd=f'$(PCOMMAND) gen_flat_data_code $< $@ {name}_code',
+ )
+ )
def _add_python_embedded_targets_internal(targets: list[Target]) -> None:
pkg = 'bametainternal'
# Note: sort to keep things deterministic.
for fname in sorted(os.listdir(f'src/meta/{pkg}/python_embedded')):
- if (not fname.endswith('.py') or fname == '__init__.py'
- or 'flycheck' in fname):
+ if (
+ not fname.endswith('.py')
+ or fname == '__init__.py'
+ or 'flycheck' in fname
+ ):
continue
name = os.path.splitext(fname)[0]
targets.append(
Target(
src=[f'{pkg}/python_embedded/{name}.py'],
- dst=os.path.join(OUT_DIR_CPP, 'python_embedded',
- f'{name}.inc'),
+ dst=os.path.join(OUT_DIR_CPP, 'python_embedded', f'{name}.inc'),
cmd='$(PCOMMAND) gen_encrypted_python_code $< $@',
- ))
+ )
+ )
def _add_extra_targets_internal(targets: list[Target]) -> None:
@@ -158,7 +186,8 @@ def _add_extra_targets_internal(targets: list[Target]) -> None:
src=[f'bametainternal/python_embedded/{srcname}.py'],
dst=f'bametainternal/generated/{dstname}.py',
cmd=f'$(PCOMMAND) {gencmd} $@',
- ))
+ )
+ )
# Now add explicit targets to generate embedded code for the resulting
# classes. We can't simply place them in a scanned dir like
@@ -167,10 +196,10 @@ def _add_extra_targets_internal(targets: list[Target]) -> None:
targets.append(
Target(
src=[f'bametainternal/generated/{name}.py'],
- dst=os.path.join(OUT_DIR_CPP, 'python_embedded',
- f'{name}.inc'),
+ dst=os.path.join(OUT_DIR_CPP, 'python_embedded', f'{name}.inc'),
cmd='$(PCOMMAND) gen_encrypted_python_code $< $@',
- ))
+ )
+ )
def _empty_line_if(condition: bool) -> list[str]:
@@ -183,8 +212,9 @@ def _project_centric_path(path: str) -> str:
abspath = os.path.abspath(os.path.join('src/meta', path))
if not abspath.startswith(projpath):
raise RuntimeError(
- f'Path "{abspath}" is not under project root "{projpath}"')
- return abspath[len(projpath):]
+ f'Path "{abspath}" is not under project root "{projpath}"'
+ )
+ return abspath[len(projpath) :]
def update(projroot: str, check: bool) -> None:
@@ -228,62 +258,85 @@ def update(projroot: str, check: bool) -> None:
_add_python_embedded_targets(targets)
_add_init_module_target(targets, moduledir=OUT_DIR_PYTHON)
_add_enums_module_target(targets)
- our_lines_public = (_empty_line_if(bool(targets)) +
- _emit_sources_lines(targets) +
- [t.emit() for t in targets])
+ our_lines_public = (
+ _empty_line_if(bool(targets))
+ + _emit_sources_lines(targets)
+ + [t.emit() for t in targets]
+ )
all_dsts_public.update(t.dst for t in targets)
# Only rewrite the private section in the private repo; otherwise
# keep the existing one intact.
if public:
- our_lines_private = lines[auto_start_private + 1:auto_end_private]
+ our_lines_private = lines[auto_start_private + 1 : auto_end_private]
else:
# Private targets (available in public through efrocache)
targets = []
our_lines_private_1 = (
- _empty_line_if(bool(targets)) + _emit_sources_lines(targets) +
- ['# __EFROCACHE_TARGET__\n' + t.emit() for t in targets] + [
+ _empty_line_if(bool(targets))
+ + _emit_sources_lines(targets)
+ + ['# __EFROCACHE_TARGET__\n' + t.emit() for t in targets]
+ + [
'\n# Note: we include our public targets in efrocache even\n'
'# though they are buildable in public. This allows us to\n'
'# fetch them to bootstrap binary builds in cases where\n'
'# we can\'t use our full Makefiles (like Windows CI).\n'
- ] + _emit_efrocache_lines(pubtargets + targets))
+ ]
+ + _emit_efrocache_lines(pubtargets + targets)
+ )
all_dsts_private.update(t.dst for t in targets)
# Private-internal targets (not available at all in public)
targets = []
_add_python_embedded_targets_internal(targets)
_add_extra_targets_internal(targets)
- our_lines_private_2 = (['# __PUBSYNC_STRIP_BEGIN__'] +
- _empty_line_if(bool(targets)) +
- _emit_sources_lines(targets) +
- [t.emit() for t in targets] +
- ['# __PUBSYNC_STRIP_END__'])
+ our_lines_private_2 = (
+ ['# __PUBSYNC_STRIP_BEGIN__']
+ + _empty_line_if(bool(targets))
+ + _emit_sources_lines(targets)
+ + [t.emit() for t in targets]
+ + ['# __PUBSYNC_STRIP_END__']
+ )
our_lines_private = our_lines_private_1 + our_lines_private_2
- filtered = (lines[:auto_start_public + 1] + our_lines_public +
- lines[auto_end_public:auto_start_private + 1] +
- our_lines_private + lines[auto_end_private:])
+ filtered = (
+ lines[: auto_start_public + 1]
+ + our_lines_public
+ + lines[auto_end_public : auto_start_private + 1]
+ + our_lines_private
+ + lines[auto_end_private:]
+ )
out = '\n'.join(filtered) + '\n'
- out_pub_man = json.dumps(sorted(
- _project_centric_path(p) for p in all_dsts_public),
- indent=1)
- out_priv_man = json.dumps(sorted(
- _project_centric_path(p) for p in all_dsts_private),
- indent=1)
+ out_pub_man = json.dumps(
+ sorted(_project_centric_path(p) for p in all_dsts_public), indent=1
+ )
+ out_priv_man = json.dumps(
+ sorted(_project_centric_path(p) for p in all_dsts_private), indent=1
+ )
- if (out == original and out_pub_man == original_pub_man
- and out_priv_man == original_priv_man):
+ if (
+ out == original
+ and out_pub_man == original_pub_man
+ and out_priv_man == original_priv_man
+ ):
print(f'{fname} (and manifests) are up to date.')
else:
if check:
- errname = (fname if out != original else fname_pub_man
- if out_pub_man != original_pub_man else fname_priv_man
- if out_priv_man != original_priv_man else 'unknown')
+ errname = (
+ fname
+ if out != original
+ else fname_pub_man
+ if out_pub_man != original_pub_man
+ else fname_priv_man
+ if out_priv_man != original_priv_man
+ else 'unknown'
+ )
raise CleanError(f"ERROR: file is out of date: '{errname}'.")
- print(f'{Clr.SBLU}Updating {fname} (and cleaning existing output).'
- f'{Clr.RST}')
+ print(
+ f'{Clr.SBLU}Updating {fname} (and cleaning existing output).'
+ f'{Clr.RST}'
+ )
if out != original:
with open(fname, 'w', encoding='utf-8') as outfile:
diff --git a/tools/batools/pcommand.py b/tools/batools/pcommand.py
index 1924798e..3552838b 100644
--- a/tools/batools/pcommand.py
+++ b/tools/batools/pcommand.py
@@ -19,11 +19,13 @@ def stage_server_file() -> None:
"""Stage files for the server environment with some filtering."""
from efro.error import CleanError
import batools.assetstaging
+
if len(sys.argv) != 5:
raise CleanError('Expected 3 args (mode, infile, outfile).')
mode, infilename, outfilename = sys.argv[2], sys.argv[3], sys.argv[4]
- batools.assetstaging.stage_server_file(str(PROJROOT), mode, infilename,
- outfilename)
+ batools.assetstaging.stage_server_file(
+ str(PROJROOT), mode, infilename, outfilename
+ )
def py_examine() -> None:
@@ -31,13 +33,14 @@ def py_examine() -> None:
import os
from pathlib import Path
import efrotools
+
if len(sys.argv) != 7:
print('ERROR: expected 7 args')
sys.exit(255)
filename = Path(sys.argv[2])
line = int(sys.argv[3])
column = int(sys.argv[4])
- selection: str | None = (None if sys.argv[5] == '' else sys.argv[5])
+ selection: str | None = None if sys.argv[5] == '' else sys.argv[5]
operation = sys.argv[6]
# This stuff assumes it is being run from project root.
@@ -45,16 +48,18 @@ def py_examine() -> None:
# Set up pypaths so our main distro stuff works.
scriptsdir = os.path.abspath(
- os.path.join(os.path.dirname(sys.argv[0]),
- '../assets/src/ba_data/python'))
+ os.path.join(
+ os.path.dirname(sys.argv[0]), '../assets/src/ba_data/python'
+ )
+ )
toolsdir = os.path.abspath(
- os.path.join(os.path.dirname(sys.argv[0]), '../tools'))
+ os.path.join(os.path.dirname(sys.argv[0]), '../tools')
+ )
if scriptsdir not in sys.path:
sys.path.append(scriptsdir)
if toolsdir not in sys.path:
sys.path.append(toolsdir)
- efrotools.py_examine(PROJROOT, filename, line, column, selection,
- operation)
+ efrotools.py_examine(PROJROOT, filename, line, column, selection, operation)
def clean_orphaned_assets() -> None:
@@ -67,11 +72,11 @@ def clean_orphaned_assets() -> None:
os.chdir(PROJROOT)
# Our manifest is split into 2 files (public and private)
- with open('assets/.asset_manifest_public.json',
- encoding='utf-8') as infile:
+ with open('assets/.asset_manifest_public.json', encoding='utf-8') as infile:
manifest = set(json.loads(infile.read()))
- with open('assets/.asset_manifest_private.json',
- encoding='utf-8') as infile:
+ with open(
+ 'assets/.asset_manifest_private.json', encoding='utf-8'
+ ) as infile:
manifest.update(set(json.loads(infile.read())))
for root, _dirs, fnames in os.walk('assets/build'):
for fname in fnames:
@@ -82,9 +87,11 @@ def clean_orphaned_assets() -> None:
os.unlink(fpath)
# Lastly, clear empty dirs.
- subprocess.run('find assets/build -depth -empty -type d -delete',
- shell=True,
- check=True)
+ subprocess.run(
+ 'find assets/build -depth -empty -type d -delete',
+ shell=True,
+ check=True,
+ )
def resize_image() -> None:
@@ -94,6 +101,7 @@ def resize_image() -> None:
"""
import os
import subprocess
+
if len(sys.argv) != 6:
raise Exception('expected 5 args')
width = int(sys.argv[2])
@@ -105,9 +113,11 @@ def resize_image() -> None:
if not src.endswith('.png'):
raise RuntimeError(f'src must be a png; got "{src}"')
print('Creating: ' + os.path.basename(dst), file=sys.stderr)
- subprocess.run(f'convert "{src}" -resize {width}x{height} "{dst}"',
- shell=True,
- check=True)
+ subprocess.run(
+ f'convert "{src}" -resize {width}x{height} "{dst}"',
+ shell=True,
+ check=True,
+ )
def check_clean_safety() -> None:
@@ -137,6 +147,7 @@ def archive_old_builds() -> None:
(called after we push newer ones)
"""
import batools.build
+
if len(sys.argv) < 3:
raise Exception('invalid arguments')
ssh_server = sys.argv[2]
@@ -159,6 +170,7 @@ def lazy_increment_build() -> None:
from efro.error import CleanError
from efrotools import get_files_hash
from efrotools.code import get_code_filenames
+
if sys.argv[2:] not in [[], ['--update-hash-only']]:
raise CleanError('Invalid arguments')
update_hash_only = '--update-hash-only' in sys.argv
@@ -176,8 +188,9 @@ def lazy_increment_build() -> None:
if not update_hash_only:
# Just go ahead and bless; this will increment the build as needed.
# subprocess.run(['make', 'bless'], check=True)
- subprocess.run(['tools/pcommand', 'version', 'incrementbuild'],
- check=True)
+ subprocess.run(
+ ['tools/pcommand', 'version', 'incrementbuild'], check=True
+ )
# We probably just changed code, so we need to re-calc the hash.
codehash = get_files_hash(codefiles)
@@ -201,15 +214,18 @@ def get_master_asset_src_dir() -> None:
# path if we're on master in and not otherwise. Should
# probably make this configurable.
output = subprocess.check_output(
- ['git', 'status', '--branch', '--porcelain']).decode()
+ ['git', 'status', '--branch', '--porcelain']
+ ).decode()
# Also compare repo name to split version of itself to
# see if we're outside of core (filtering will cause mismatch if so).
# pylint: disable=useless-suppression
# pylint: disable=simplifiable-condition
# pylint: disable=condition-evals-to-constant
- if ('origin/master' in output.splitlines()[0]
- and 'ballistica' + 'core' == 'ballisticacore'):
+ if (
+ 'origin/master' in output.splitlines()[0]
+ and 'ballistica' + 'core' == 'ballisticacore'
+ ):
# We seem to be in master in core repo; lets do it.
print(master_assets_dir)
@@ -226,10 +242,13 @@ def androidaddr() -> None:
"""
import batools.android
from efro.error import CleanError
+
if len(sys.argv) != 5:
- raise CleanError(f'ERROR: expected 3 args; got {len(sys.argv) - 2}\n'
- f'Usage: "tools/pcommand android_addr'
- f' "')
+ raise CleanError(
+ f'ERROR: expected 3 args; got {len(sys.argv) - 2}\n'
+ f'Usage: "tools/pcommand android_addr'
+ f' "'
+ )
archive_dir = sys.argv[2]
arch = sys.argv[3]
addr = sys.argv[4]
@@ -240,6 +259,7 @@ def push_ipa() -> None:
"""Construct and push ios IPA for testing."""
from pathlib import Path
import efrotools.ios
+
root = Path(sys.argv[0], '../..').resolve()
if len(sys.argv) != 3:
raise Exception('expected 1 arg (debug or release)')
@@ -265,9 +285,11 @@ def printcolors() -> None:
continue
shortname = f'Clr.{clrnames[value.value]}'
longname = f'({value.name})'
- print(f'{shortname:<12} {longname:<20} {value.value}'
- f'The quick brown fox jumps over the lazy dog.'
- f'{TerminalColor.RESET.value}')
+ print(
+ f'{shortname:<12} {longname:<20} {value.value}'
+ f'The quick brown fox jumps over the lazy dog.'
+ f'{TerminalColor.RESET.value}'
+ )
def gen_fulltest_buildfile_android() -> None:
@@ -276,6 +298,7 @@ def gen_fulltest_buildfile_android() -> None:
(so we see nice pretty split-up build trees)
"""
import batools.build
+
batools.build.gen_fulltest_buildfile_android()
@@ -285,6 +308,7 @@ def gen_fulltest_buildfile_windows() -> None:
(so we see nice pretty split-up build trees)
"""
import batools.build
+
batools.build.gen_fulltest_buildfile_windows()
@@ -294,6 +318,7 @@ def gen_fulltest_buildfile_apple() -> None:
(so we see nice pretty split-up build trees)
"""
import batools.build
+
batools.build.gen_fulltest_buildfile_apple()
@@ -303,24 +328,28 @@ def gen_fulltest_buildfile_linux() -> None:
(so we see nice pretty split-up build trees)
"""
import batools.build
+
batools.build.gen_fulltest_buildfile_linux()
def python_version_build_base() -> None:
"""Print built Python base version."""
from efrotools.pybuild import PY_VER
+
print(PY_VER, end='')
def python_version_android() -> None:
"""Print Android embedded Python version."""
from efrotools.pybuild import PY_VER_EXACT_ANDROID
+
print(PY_VER_EXACT_ANDROID, end='')
def python_version_apple() -> None:
"""Print Apple embedded Python version."""
from efrotools.pybuild import PY_VER_EXACT_APPLE
+
print(PY_VER_EXACT_APPLE, end='')
@@ -338,6 +367,7 @@ def _python_build_apple(debug: bool) -> None:
"""Build an embeddable python for macOS/iOS/tvOS."""
import os
from efrotools import pybuild
+
os.chdir(PROJROOT)
archs = ('mac', 'ios', 'tvos')
if len(sys.argv) != 3:
@@ -363,6 +393,7 @@ def python_build_android_debug() -> None:
def _python_build_android(debug: bool) -> None:
import os
from efrotools import pybuild
+
os.chdir(PROJROOT)
archs = ('arm', 'arm64', 'x86', 'x86_64')
if len(sys.argv) != 3:
@@ -379,6 +410,7 @@ def python_android_patch() -> None:
"""Patches Python to prep for building for Android."""
import os
from efrotools import pybuild
+
os.chdir(sys.argv[2])
pybuild.android_patch()
@@ -386,12 +418,14 @@ def python_android_patch() -> None:
def python_android_patch_ssl() -> None:
"""Patches Python ssl to prep for building for Android."""
from efrotools import pybuild
+
pybuild.android_patch_ssl()
def python_apple_patch() -> None:
"""Patches Python to prep for building for Apple platforms."""
from efrotools import pybuild
+
arch = sys.argv[2]
slc = sys.argv[3]
assert slc
@@ -406,6 +440,7 @@ def python_gather() -> None:
"""
import os
from efrotools import pybuild
+
os.chdir(PROJROOT)
pybuild.gather()
@@ -414,6 +449,7 @@ def python_winprune() -> None:
"""Prune unneeded files from windows python."""
import os
from efrotools import pybuild
+
os.chdir(PROJROOT)
pybuild.winprune()
@@ -431,6 +467,7 @@ def upper() -> None:
def efrocache_update() -> None:
"""Build & push files to efrocache for public access."""
from efrotools.efrocache import update_cache
+
makefile_dirs = ['', 'assets', 'resources', 'src/meta']
update_cache(makefile_dirs)
@@ -438,6 +475,7 @@ def efrocache_update() -> None:
def efrocache_get() -> None:
"""Get a file from efrocache."""
from efrotools.efrocache import get_target
+
if len(sys.argv) != 3:
raise RuntimeError('Expected exactly 1 arg')
get_target(sys.argv[2])
@@ -452,13 +490,18 @@ def get_modern_make() -> None:
# so let's return 'gmake' there which will point to homebrew make which
# should be up to date.
if platform.system() == 'Darwin':
- if subprocess.run(['which', 'gmake'], check=False,
- capture_output=True).returncode != 0:
+ if (
+ subprocess.run(
+ ['which', 'gmake'], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
print(
'WARNING: this requires gmake (mac system make is too old).'
" Install it with 'brew install make'",
file=sys.stderr,
- flush=True)
+ flush=True,
+ )
print('gmake')
else:
print('make')
@@ -470,10 +513,12 @@ def warm_start_asset_build() -> None:
import subprocess
from pathlib import Path
from efrotools import getconfig
+
public: bool = getconfig(PROJROOT)['public']
if public:
from efrotools.efrocache import warm_start_cache
+
os.chdir(PROJROOT)
warm_start_cache()
else:
@@ -481,8 +526,9 @@ def warm_start_asset_build() -> None:
# internal build cache. Download an initial cache/etc. if need be.
subprocess.run(
[
- str(Path(PROJROOT, 'tools/pcommand')), 'convert_util',
- '--init-asset-cache'
+ str(Path(PROJROOT, 'tools/pcommand')),
+ 'convert_util',
+ '--init-asset-cache',
],
check=True,
)
@@ -500,6 +546,7 @@ def gendocs() -> None:
def list_pip_reqs() -> None:
"""List Python Pip packages needed for this project."""
from batools.build import get_pip_reqs
+
print(' '.join(get_pip_reqs()))
@@ -511,18 +558,21 @@ def install_pip_reqs() -> None:
from batools.build import get_pip_reqs
# Make sure pip itself is up to date first.
- subprocess.run([PYTHON_BIN, '-m', 'pip', 'install', '--upgrade', 'pip'],
- check=True)
+ subprocess.run(
+ [PYTHON_BIN, '-m', 'pip', 'install', '--upgrade', 'pip'], check=True
+ )
- subprocess.run([PYTHON_BIN, '-m', 'pip', 'install', '--upgrade'] +
- get_pip_reqs(),
- check=True)
+ subprocess.run(
+ [PYTHON_BIN, '-m', 'pip', 'install', '--upgrade'] + get_pip_reqs(),
+ check=True,
+ )
print(f'{Clr.GRN}All pip requirements installed!{Clr.RST}')
def checkenv() -> None:
"""Check for tools necessary to build and run the app."""
import batools.build
+
batools.build.checkenv()
@@ -533,18 +583,29 @@ def wsl_build_check_win_drive() -> None:
import textwrap
from efro.error import CleanError
- if subprocess.run(['which', 'wslpath'], check=False,
- capture_output=True).returncode != 0:
- raise CleanError('wslpath not found; you must run'
- ' this from a WSL environment')
+ if (
+ subprocess.run(
+ ['which', 'wslpath'], check=False, capture_output=True
+ ).returncode
+ != 0
+ ):
+ raise CleanError(
+ 'wslpath not found; you must run this from a WSL environment'
+ )
if os.environ.get('WSL_BUILD_CHECK_WIN_DRIVE_IGNORE') == '1':
return
# Get a windows path to the current dir.
- path = subprocess.run(
- ['wslpath', '-w', '-a', os.getcwd()], capture_output=True,
- check=True).stdout.decode().strip()
+ path = (
+ subprocess.run(
+ ['wslpath', '-w', '-a', os.getcwd()],
+ capture_output=True,
+ check=True,
+ )
+ .stdout.decode()
+ .strip()
+ )
# If we're sitting under the linux filesystem, our path
# will start with \\wsl$; fail in that case and explain why.
@@ -554,20 +615,36 @@ def wsl_build_check_win_drive() -> None:
def _wrap(txt: str) -> str:
return textwrap.fill(txt, 76)
- raise CleanError('\n\n'.join([
- _wrap('ERROR: This project appears to live on the Linux filesystem.'),
- _wrap('Visual Studio compiles will error here for reasons related'
- ' to Linux filesystem case-sensitivity, and thus are'
- ' disallowed.'
- ' Clone the repo to a location that maps to a native'
- ' Windows drive such as \'/mnt/c/ballistica\' and try again.'),
- _wrap('Note that WSL2 filesystem performance is poor when accessing'
- ' native Windows drives, so if Visual Studio builds are not'
- ' needed it may be best to keep things on the Linux filesystem.'
- ' This behavior may differ under WSL1 (untested).'),
- _wrap('Set env-var WSL_BUILD_CHECK_WIN_DRIVE_IGNORE=1 to skip'
- ' this check.')
- ]))
+ raise CleanError(
+ '\n\n'.join(
+ [
+ _wrap(
+ 'ERROR: This project appears to live'
+ ' on the Linux filesystem.'
+ ),
+ _wrap(
+ 'Visual Studio compiles will error here for reasons related'
+ ' to Linux filesystem case-sensitivity, and thus are'
+ ' disallowed.'
+ ' Clone the repo to a location that maps to a native'
+ ' Windows drive such as \'/mnt/c/ballistica\''
+ ' and try again.'
+ ),
+ _wrap(
+ 'Note that WSL2 filesystem performance'
+ ' is poor when accessing'
+ ' native Windows drives, so if Visual Studio builds are not'
+ ' needed it may be best to keep things'
+ ' on the Linux filesystem.'
+ ' This behavior may differ under WSL1 (untested).'
+ ),
+ _wrap(
+ 'Set env-var WSL_BUILD_CHECK_WIN_DRIVE_IGNORE=1 to skip'
+ ' this check.'
+ ),
+ ]
+ )
+ )
def wsl_path_to_win() -> None:
@@ -576,6 +653,7 @@ def wsl_path_to_win() -> None:
import logging
import os
from efro.error import CleanError
+
try:
create = False
escape = False
@@ -600,9 +678,9 @@ def wsl_path_to_win() -> None:
if not os.path.exists(wsl_path):
raise CleanError(f'Path \'{wsl_path}\' does not exist.')
- results = subprocess.run(['wslpath', '-w', '-a', wsl_path],
- capture_output=True,
- check=True)
+ results = subprocess.run(
+ ['wslpath', '-w', '-a', wsl_path], capture_output=True, check=True
+ )
except Exception:
# This gets used in a makefile so our returncode is ignored;
# let's try to make our failure known in other ways.
@@ -638,12 +716,14 @@ def ensure_prefab_platform() -> None:
current = batools.build.get_current_prefab_platform()
if current != needed:
raise CleanError(
- f'Incorrect platform: we are {current}, this requires {needed}.')
+ f'Incorrect platform: we are {current}, this requires {needed}.'
+ )
def prefab_run_var() -> None:
"""Print the current platform prefab run target var."""
import batools.build
+
if len(sys.argv) != 3:
raise RuntimeError('Expected 1 arg.')
base = sys.argv[2].replace('-', '_').upper()
@@ -655,6 +735,7 @@ def make_prefab() -> None:
"""Run prefab builds for the current platform."""
import subprocess
import batools.build
+
if len(sys.argv) != 3:
raise RuntimeError('Expected one argument')
target = batools.build.PrefabTarget(sys.argv[2])
@@ -663,8 +744,9 @@ def make_prefab() -> None:
# We use dashes instead of underscores in target names.
platform = platform.replace('_', '-')
try:
- subprocess.run(['make', f'prefab-{platform}-{target.value}-build'],
- check=True)
+ subprocess.run(
+ ['make', f'prefab-{platform}-{target.value}-build'], check=True
+ )
except (Exception, KeyboardInterrupt) as exc:
if str(exc):
print(f'make_prefab failed with error: {exc}')
@@ -676,6 +758,7 @@ def lazybuild() -> None:
import subprocess
import batools.build
from efro.error import CleanError
+
if len(sys.argv) < 5:
raise CleanError('Expected at least 3 args')
try:
@@ -695,6 +778,7 @@ def logcat() -> None:
import subprocess
from efro.terminal import Clr
from efro.error import CleanError
+
if len(sys.argv) != 4:
raise CleanError('Expected 2 args')
adb = sys.argv[2]
@@ -706,9 +790,11 @@ def logcat() -> None:
format_args = ''
else:
format_args = '-v color '
- cmd = (f'{adb} logcat {format_args}SDL:V BallisticaCore:V VrLib:V'
- ' VrApi:V VrApp:V TimeWarp:V EyeBuf:V GlUtils:V DirectRender:V'
- ' HmdInfo:V IabHelper:V CrashAnrDetector:V DEBUG:V \'*:S\'')
+ cmd = (
+ f'{adb} logcat {format_args}SDL:V BallisticaCore:V VrLib:V'
+ ' VrApi:V VrApp:V TimeWarp:V EyeBuf:V GlUtils:V DirectRender:V'
+ ' HmdInfo:V IabHelper:V CrashAnrDetector:V DEBUG:V \'*:S\''
+ )
print(f'{Clr.BLU}Running logcat command: {Clr.BLD}{cmd}{Clr.RST}')
subprocess.run(cmd, shell=True, check=True)
@@ -719,6 +805,7 @@ def android_archive_unstripped_libs() -> None:
from pathlib import Path
from efro.error import CleanError
from efro.terminal import Clr
+
if len(sys.argv) != 4:
raise CleanError('Expected 2 args; src-dir and dst-dir')
src = Path(sys.argv[2])
@@ -742,9 +829,9 @@ def android_archive_unstripped_libs() -> None:
if srcpath.exists():
print(f'Archiving unstripped library: {Clr.BLD}{dstname}{Clr.RST}')
subprocess.run(['cp', srcpath, dstpath], check=True)
- subprocess.run(['tar', '-zcf', dstname + '.tgz', dstname],
- cwd=dst,
- check=True)
+ subprocess.run(
+ ['tar', '-zcf', dstname + '.tgz', dstname], cwd=dst, check=True
+ )
subprocess.run(['rm', dstpath], check=True)
@@ -763,6 +850,7 @@ def efro_gradle() -> None:
import subprocess
from efro.terminal import Clr
from efrotools.android import filter_gradle_file
+
args = ['./gradlew'] + sys.argv[2:]
print(f'{Clr.BLU}Running gradle with args:{Clr.RST} {args}.', flush=True)
enabled_tags: set[str] = {'true'}
@@ -774,10 +862,12 @@ def efro_gradle() -> None:
buildfilename = 'BallisticaCore/build.gradle'
# Move the original file out of the way and operate on a copy of it.
- subprocess.run(['mv', buildfilename, f'{buildfilename}.{prev_suffix}'],
- check=True)
- subprocess.run(['cp', f'{buildfilename}.{prev_suffix}', buildfilename],
- check=True)
+ subprocess.run(
+ ['mv', buildfilename, f'{buildfilename}.{prev_suffix}'], check=True
+ )
+ subprocess.run(
+ ['cp', f'{buildfilename}.{prev_suffix}', buildfilename], check=True
+ )
filter_gradle_file(buildfilename, enabled_tags)
@@ -788,8 +878,9 @@ def efro_gradle() -> None:
errored = True
# Restore the original.
- subprocess.run(['mv', f'{buildfilename}.{prev_suffix}', buildfilename],
- check=True)
+ subprocess.run(
+ ['mv', f'{buildfilename}.{prev_suffix}', buildfilename], check=True
+ )
if errored:
sys.exit(1)
@@ -799,6 +890,7 @@ def stage_assets() -> None:
"""Stage assets for a build."""
from batools.assetstaging import main
from efro.error import CleanError
+
try:
main(projroot=str(PROJROOT), args=sys.argv[2:])
except CleanError as exc:
@@ -809,7 +901,8 @@ def stage_assets() -> None:
def update_assets_makefile() -> None:
"""Update the assets makefile."""
from batools.assetsmakefile import update_assets_makefile as uam
- check = ('--check' in sys.argv)
+
+ check = '--check' in sys.argv
uam(projroot=str(PROJROOT), check=check)
@@ -830,6 +923,7 @@ def update_project() -> None:
(used in CI builds to make sure things are kosher).
"""
from batools.project import Updater
+
check = '--check' in sys.argv
fix = '--fix' in sys.argv
@@ -842,9 +936,11 @@ def update_cmake_prefab_lib() -> None:
import os
from efro.error import CleanError
import batools.build
+
if len(sys.argv) != 5:
- raise CleanError('Expected 3 args (standard/server,'
- ' debug/release, build-dir)')
+ raise CleanError(
+ 'Expected 3 args (standard/server, debug/release, build-dir)'
+ )
buildtype = sys.argv[2]
mode = sys.argv[3]
builddir = sys.argv[4]
@@ -853,10 +949,13 @@ def update_cmake_prefab_lib() -> None:
if mode not in {'debug', 'release'}:
raise CleanError(f'Invalid mode: {mode}')
platform = batools.build.get_current_prefab_platform(
- wsl_gives_windows=False)
+ wsl_gives_windows=False
+ )
suffix = '_server' if buildtype == 'server' else '_gui'
- target = (f'build/prefab/lib/{platform}{suffix}/{mode}/'
- f'libballisticacore_internal.a')
+ target = (
+ f'build/prefab/lib/{platform}{suffix}/{mode}/'
+ f'libballisticacore_internal.a'
+ )
# Build the target and then copy it to dst if it doesn't exist there yet
# or the existing one is older than our target.
@@ -887,17 +986,20 @@ def cmake_prep_dir() -> None:
import os
from efro.error import CleanError
import batools.build
+
if len(sys.argv) != 3:
raise CleanError('Expected 1 arg (dir name)')
dirname = sys.argv[2]
- batools.build.cmake_prep_dir(dirname,
- verbose=os.environ.get('VERBOSE') == '1')
+ batools.build.cmake_prep_dir(
+ dirname, verbose=os.environ.get('VERBOSE') == '1'
+ )
def gen_binding_code() -> None:
"""Generate binding.inc file."""
from efro.error import CleanError
import batools.meta
+
if len(sys.argv) != 4:
raise CleanError('Expected 2 args (srcfile, dstfile)')
inpath = sys.argv[2]
@@ -909,6 +1011,7 @@ def gen_flat_data_code() -> None:
"""Generate a C++ include file from a Python file."""
from efro.error import CleanError
import batools.meta
+
if len(sys.argv) != 5:
raise CleanError('Expected 3 args (srcfile, dstfile, varname)')
inpath = sys.argv[2]
@@ -931,20 +1034,23 @@ def win_ci_install_prereqs() -> None:
'BallisticaCoreGenericInternal.lib',
'build/prefab/lib/windows/Debug_Win32/'
'BallisticaCoreGenericInternal.pdb',
- 'ballisticacore-windows/Generic/BallisticaCore.ico'
+ 'ballisticacore-windows/Generic/BallisticaCore.ico',
}
# Look through everything that gets generated by our meta builds
# and pick out anything we need for our basic builds/tests.
- with open('src/meta/.meta_manifest_public.json',
- encoding='utf-8') as infile:
+ with open(
+ 'src/meta/.meta_manifest_public.json', encoding='utf-8'
+ ) as infile:
meta_public: list[str] = json.loads(infile.read())
- with open('src/meta/.meta_manifest_private.json',
- encoding='utf-8') as infile:
+ with open(
+ 'src/meta/.meta_manifest_private.json', encoding='utf-8'
+ ) as infile:
meta_private: list[str] = json.loads(infile.read())
for target in meta_public + meta_private:
- if (target.startswith('src/ballistica/generated/') or
- target.startswith('assets/src/ba_data/python/ba/_generated/')):
+ if target.startswith('src/ballistica/generated/') or target.startswith(
+ 'assets/src/ba_data/python/ba/_generated/'
+ ):
needed_targets.add(target)
for target in needed_targets:
@@ -960,8 +1066,7 @@ def win_ci_binary_build() -> None:
[
'C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\'
'Enterprise\\MSBuild\\Current\\Bin\\MSBuild.exe',
- 'ballisticacore-windows\\Generic'
- '\\BallisticaCoreGeneric.vcxproj',
+ 'ballisticacore-windows\\Generic\\BallisticaCoreGeneric.vcxproj',
'-target:Build',
'-property:Configuration=Debug',
'-property:Platform=Win32',
@@ -974,35 +1079,40 @@ def win_ci_binary_build() -> None:
def genchangelog() -> None:
"""Gen a pretty html changelog."""
from batools.changelog import generate
+
generate(projroot=str(PROJROOT))
def android_sdk_utils() -> None:
"""Wrangle android sdk stuff."""
from batools.androidsdkutils import run
+
run(projroot=str(PROJROOT), args=sys.argv[2:])
def update_resources_makefile() -> None:
"""Update the resources Makefile if needed."""
from batools.resourcesmakefile import update
+
update(projroot=str(PROJROOT), check='--check' in sys.argv)
def update_meta_makefile() -> None:
"""Update the meta Makefile if needed."""
from batools.metamakefile import update
+
update(projroot=str(PROJROOT), check='--check' in sys.argv)
def gen_python_enums_module() -> None:
"""Update our procedurally generated python enums."""
from batools.pythonenumsmodule import generate
+
if len(sys.argv) != 4:
raise Exception('Expected infile and outfile args.')
- generate(projroot=str(PROJROOT),
- infilename=sys.argv[2],
- outfilename=sys.argv[3])
+ generate(
+ projroot=str(PROJROOT), infilename=sys.argv[2], outfilename=sys.argv[3]
+ )
def gen_python_init_module() -> None:
@@ -1010,6 +1120,7 @@ def gen_python_init_module() -> None:
import os
from efro.terminal import Clr
from batools.project import project_centric_path
+
if len(sys.argv) != 3:
raise Exception('Expected an outfile arg.')
outfilename = sys.argv[2]
@@ -1017,20 +1128,26 @@ def gen_python_init_module() -> None:
prettypath = project_centric_path(projroot=str(PROJROOT), path=outfilename)
print(f'Meta-building {Clr.BLD}{prettypath}{Clr.RST}')
with open(outfilename, 'w', encoding='utf-8') as outfile:
- outfile.write('# Released under the MIT License.'
- ' See LICENSE for details.\n'
- '#\n')
+ outfile.write(
+ '# Released under the MIT License.'
+ ' See LICENSE for details.\n'
+ '#\n'
+ )
def update_dummy_modules() -> None:
"""Update our _ba.py and _bainternal.py dummy modules."""
from batools.dummymodule import update
- update(projroot=str(PROJROOT),
- check='--check' in sys.argv,
- force='--force' in sys.argv)
+
+ update(
+ projroot=str(PROJROOT),
+ check='--check' in sys.argv,
+ force='--force' in sys.argv,
+ )
def version() -> None:
"""Check app versions."""
from batools.version import run
+
run(projroot=str(PROJROOT), args=sys.argv[2:])
diff --git a/tools/batools/project.py b/tools/batools/project.py
index a810b09f..ee54eb59 100755
--- a/tools/batools/project.py
+++ b/tools/batools/project.py
@@ -25,8 +25,9 @@ def project_centric_path(projroot: str, path: str) -> str:
projprefix = f'{projroot}/'
if not abspath.startswith(projprefix):
raise RuntimeError(
- f'Path "{abspath}" is not under project root "{projprefix}"')
- return abspath[len(projprefix):]
+ f'Path "{abspath}" is not under project root "{projprefix}"'
+ )
+ return abspath[len(projprefix) :]
def get_legal_notice_private() -> str:
@@ -37,6 +38,7 @@ def get_legal_notice_private() -> str:
@dataclass
class LineChange:
"""A change applying to a particular line in a file."""
+
line_number: int
expected: str
can_auto_update: bool
@@ -48,6 +50,7 @@ class Updater:
def __init__(self, check: bool, fix: bool) -> None:
from efrotools import getconfig, getlocalconfig
from pathlib import Path
+
self._check = check
self._fix = fix
self._checkarg = ' --check' if self._check else ''
@@ -64,7 +67,8 @@ class Updater:
self._file_changes: dict[str, str] = {}
self._license_line_checks = bool(
- getlocalconfig(Path('.')).get('license_line_checks', True))
+ getlocalconfig(Path('.')).get('license_line_checks', True)
+ )
self._internal_source_dirs: set[str] | None = None
self._internal_source_files: set[str] | None = None
@@ -126,8 +130,10 @@ class Updater:
else:
sources = getconfig(Path('.')).get('internal_source_files', [])
if not isinstance(sources, list):
- raise CleanError(f'Expected list for internal_source_files;'
- f' got {type(sources)}')
+ raise CleanError(
+ f'Expected list for internal_source_files;'
+ f' got {type(sources)}'
+ )
self._internal_source_files = set(sources)
return self._internal_source_files
@@ -143,8 +149,10 @@ class Updater:
else:
sources = getconfig(Path('.')).get('internal_source_dirs', [])
if not isinstance(sources, list):
- raise CleanError(f'Expected list for internal_source_dirs;'
- f' got {type(sources)}')
+ raise CleanError(
+ f'Expected list for internal_source_dirs;'
+ f' got {type(sources)}'
+ )
self._internal_source_dirs = set(sources)
return self._internal_source_dirs
@@ -163,8 +171,10 @@ class Updater:
unchanged_project_count += 1
else:
if self._check:
- print(f'{Clr.RED}ERROR: found out-of-date'
- f' project file: {fname}{Clr.RST}')
+ print(
+ f'{Clr.RED}ERROR: found out-of-date'
+ f' project file: {fname}{Clr.RST}'
+ )
sys.exit(255)
print(f'{Clr.BLU}Writing project file: {fname}{Clr.RST}')
@@ -172,7 +182,8 @@ class Updater:
outfile.write(fcode)
if unchanged_project_count > 0:
print(
- f'All {unchanged_project_count} project files are up to date.')
+ f'All {unchanged_project_count} project files are up to date.'
+ )
def _apply_line_changes(self) -> None:
@@ -189,12 +200,15 @@ class Updater:
# If there are any manual-only entries, list then and bail.
# (Don't wanna allow auto-apply unless it fixes everything)
if manual_changes:
- print(f'{Clr.RED}Found erroneous lines '
- f'requiring manual correction:{Clr.RST}')
+ print(
+ f'{Clr.RED}Found erroneous lines '
+ f'requiring manual correction:{Clr.RST}'
+ )
for change in manual_changes:
print(
f'{Clr.RED}{change[0]}:{change[1].line_number + 1}:'
- f' Expected line to be:\n {change[1].expected}{Clr.RST}')
+ f' Expected line to be:\n {change[1].expected}{Clr.RST}'
+ )
sys.exit(-1)
@@ -202,18 +216,23 @@ class Updater:
if auto_changes:
if not self._fix:
for i, change in enumerate(auto_changes):
- print(f'{Clr.RED}#{i}:'
- f' {change[0]}:{change[1].line_number+1}:'
- f'{Clr.RST}')
print(
- f'{Clr.RED} Expected "{change[1].expected}"{Clr.RST}')
+ f'{Clr.RED}#{i}:'
+ f' {change[0]}:{change[1].line_number+1}:'
+ f'{Clr.RST}'
+ )
+ print(
+ f'{Clr.RED} Expected "{change[1].expected}"{Clr.RST}'
+ )
with open(change[0], encoding='utf-8') as infile:
lines = infile.read().splitlines()
line = lines[change[1].line_number]
print(f'{Clr.RED} Found "{line}"{Clr.RST}')
- print(f'{Clr.RED}All {len(auto_changes)} errors are'
- f' auto-fixable; run tools/pcommand update_project'
- f' --fix to apply corrections. {Clr.RST}')
+ print(
+ f'{Clr.RED}All {len(auto_changes)} errors are'
+ f' auto-fixable; run tools/pcommand update_project'
+ f' --fix to apply corrections. {Clr.RST}'
+ )
sys.exit(255)
else:
for i, change in enumerate(auto_changes):
@@ -239,9 +258,11 @@ class Updater:
# Could just ignore these but it probably means I intended
# to save something and forgot.
if '/.#' in fsrc:
- print(f'{Clr.RED}'
- f'ERROR: Found an unsaved emacs file: "{fsrc}"'
- f'{Clr.RST}')
+ print(
+ f'{Clr.RED}'
+ f'ERROR: Found an unsaved emacs file: "{fsrc}"'
+ f'{Clr.RST}'
+ )
sys.exit(255)
fname = 'src/ballistica' + fsrc
@@ -262,15 +283,23 @@ class Updater:
if header_file.endswith('.h'):
self._check_header(header_file)
- def _add_line_correction(self, filename: str, line_number: int,
- expected: str, can_auto_update: bool) -> None:
+ def _add_line_correction(
+ self,
+ filename: str,
+ line_number: int,
+ expected: str,
+ can_auto_update: bool,
+ ) -> None:
# No longer allowing negatives here since they don't show up nicely
# in correction list.
assert line_number >= 0
self._line_corrections.setdefault(filename, []).append(
- LineChange(line_number=line_number,
- expected=expected,
- can_auto_update=can_auto_update))
+ LineChange(
+ line_number=line_number,
+ expected=expected,
+ can_auto_update=can_auto_update,
+ )
+ )
def _check_c_license(self, fname: str, lines: list[str]) -> None:
from efrotools import get_public_license
@@ -285,21 +314,25 @@ class Updater:
if lines[lnum] != line_public:
# Allow auto-correcting from private to public line
allow_auto = lines[lnum] == line_private
- self._add_line_correction(fname,
- line_number=lnum,
- expected=line_public,
- can_auto_update=allow_auto)
+ self._add_line_correction(
+ fname,
+ line_number=lnum,
+ expected=line_public,
+ can_auto_update=allow_auto,
+ )
else:
if lines[lnum] not in [line_public, line_private]:
- self._add_line_correction(fname,
- line_number=lnum,
- expected=line_private,
- can_auto_update=False)
+ self._add_line_correction(
+ fname,
+ line_number=lnum,
+ expected=line_private,
+ can_auto_update=False,
+ )
def _check_header(self, fname: str) -> None:
# Make sure its define guard is correct.
- guard = (fname[4:].upper().replace('/', '_').replace('.', '_') + '_')
+ guard = fname[4:].upper().replace('/', '_').replace('.', '_') + '_'
with open(fname, encoding='utf-8') as fhdr:
lines = fhdr.read().splitlines()
@@ -313,20 +346,24 @@ class Updater:
# Allow auto-correcting if it looks close already
# (don't want to blow away an unrelated line)
allow_auto = lines[lnum].startswith('#ifndef BALLISTICA_')
- self._add_line_correction(fname,
- line_number=lnum,
- expected=line,
- can_auto_update=allow_auto)
+ self._add_line_correction(
+ fname,
+ line_number=lnum,
+ expected=line,
+ can_auto_update=allow_auto,
+ )
line = '#define ' + guard
lnum = 3
if lines[lnum] != line:
# Allow auto-correcting if it looks close already
# (don't want to blow away an unrelated line)
allow_auto = lines[lnum].startswith('#define BALLISTICA_')
- self._add_line_correction(fname,
- line_number=lnum,
- expected=line,
- can_auto_update=allow_auto)
+ self._add_line_correction(
+ fname,
+ line_number=lnum,
+ expected=line,
+ can_auto_update=allow_auto,
+ )
# Check for header guard at bottom
line = '#endif // ' + guard
@@ -335,19 +372,27 @@ class Updater:
# Allow auto-correcting if it looks close already
# (don't want to blow away an unrelated line)
allow_auto = lines[lnum].startswith('#endif // BALLISTICA_')
- self._add_line_correction(fname,
- line_number=lnum,
- expected=line,
- can_auto_update=allow_auto)
+ self._add_line_correction(
+ fname,
+ line_number=lnum,
+ expected=line,
+ can_auto_update=allow_auto,
+ )
def _check_makefiles(self) -> None:
from efrotools import get_public_license
# Run a few sanity checks on whatever makefiles we come across.
- fnames = subprocess.run('find . -maxdepth 3 -name Makefile',
- shell=True,
- capture_output=True,
- check=True).stdout.decode().split()
+ fnames = (
+ subprocess.run(
+ 'find . -maxdepth 3 -name Makefile',
+ shell=True,
+ capture_output=True,
+ check=True,
+ )
+ .stdout.decode()
+ .split()
+ )
fnames = [n for n in fnames if '/build/' not in n]
for fname in fnames:
@@ -358,14 +403,16 @@ class Updater:
if public_license not in makefile:
raise CleanError(f'Pub license not found in {fname}.')
else:
- if (get_legal_notice_private() not in makefile
- and get_public_license('makefile') not in makefile):
- raise CleanError(
- f'Priv or pub legal not found in {fname}.')
+ if (
+ get_legal_notice_private() not in makefile
+ and get_public_license('makefile') not in makefile
+ ):
+ raise CleanError(f'Priv or pub legal not found in {fname}.')
def _check_python_file(self, fname: str) -> None:
# pylint: disable=too-many-branches
from efrotools import get_public_license, PYVER
+
with open(fname, encoding='utf-8') as infile:
contents = infile.read()
lines = contents.splitlines()
@@ -377,15 +424,18 @@ class Updater:
copyrightline = 1
if fname not in ['tools/vmshell']:
if not contents.startswith(f'#!/usr/bin/env python{PYVER}'):
- raise CleanError(f'Incorrect shebang (first line) for '
- f'{fname}.')
+ raise CleanError(
+ f'Incorrect shebang (first line) for ' f'{fname}.'
+ )
else:
copyrightline = 0
# Special case: it there's spinoff autogenerate notice there,
# look below it.
- if (lines[copyrightline] == ''
- and 'THIS FILE IS AUTOGENERATED' in lines[copyrightline + 1]):
+ if (
+ lines[copyrightline] == ''
+ and 'THIS FILE IS AUTOGENERATED' in lines[copyrightline + 1]
+ ):
copyrightline += 2
if lines[copyrightline].startswith('# Synced from '):
@@ -398,14 +448,16 @@ class Updater:
if line == 'import ba':
raise CleanError(
f'{fname}:{i+1}: no top level ba imports allowed'
- f' under ba module.')
+ f' under ba module.'
+ )
if '/bastd/' in fname:
# Don't allow importing _ba or _bainternal anywhere here.
# (any internal needs should be in ba.internal)
if 'import _ba' in line:
raise CleanError(
f'{fname}:{i+1}: _ba or _bainternal imports not'
- f' allowed under bastd.')
+ f' allowed under bastd.'
+ )
# In all cases, look for our one-line legal notice.
# In the public case, look for the rest of our public license too.
@@ -416,33 +468,41 @@ class Updater:
if len(lines) < lnum + 1:
raise RuntimeError('Not enough lines in file:', fname)
- disable_note = ('NOTE: You can disable license line'
- ' checks by adding "license_line_checks": false\n'
- 'to the root dict in config/localconfig.json.\n'
- 'see https://ballistica.net/wiki'
- '/Knowledge-Nuggets#'
- 'hello-world-creating-a-new-game-type')
+ disable_note = (
+ 'NOTE: You can disable license line'
+ ' checks by adding "license_line_checks": false\n'
+ 'to the root dict in config/localconfig.json.\n'
+ 'see https://ballistica.net/wiki'
+ '/Knowledge-Nuggets#'
+ 'hello-world-creating-a-new-game-type'
+ )
if self._public:
# Check for public license only.
if lines[lnum] != public_license:
- raise CleanError(f'License text not found'
- f" at '{fname}' line {lnum+1};"
- f' please correct.\n'
- f'Expected text is: {public_license}\n'
- f'{disable_note}')
+ raise CleanError(
+ f'License text not found'
+ f" at '{fname}' line {lnum+1};"
+ f' please correct.\n'
+ f'Expected text is: {public_license}\n'
+ f'{disable_note}'
+ )
else:
# Check for public or private license.
- if (lines[lnum] != public_license
- and lines[lnum] != private_license):
- raise CleanError(f'License text not found'
- f" at '{fname}' line {lnum+1};"
- f' please correct.\n'
- f'Expected text (for public files):'
- f' {public_license}\n'
- f'Expected text (for private files):'
- f' {private_license}\n'
- f'{disable_note}')
+ if (
+ lines[lnum] != public_license
+ and lines[lnum] != private_license
+ ):
+ raise CleanError(
+ f'License text not found'
+ f" at '{fname}' line {lnum+1};"
+ f' please correct.\n'
+ f'Expected text (for public files):'
+ f' {public_license}\n'
+ f'Expected text (for private files):'
+ f' {private_license}\n'
+ f'{disable_note}'
+ )
def _check_python_files(self) -> None:
from pathlib import Path
@@ -460,24 +520,32 @@ class Updater:
dirs_of_packages = ['assets/src/ba_data/python', 'tests']
for dir_of_packages in dirs_of_packages:
for name in os.listdir(dir_of_packages):
- if (not name.startswith('.') and os.path.isdir(
- os.path.join(dir_of_packages, name))):
+ if not name.startswith('.') and os.path.isdir(
+ os.path.join(dir_of_packages, name)
+ ):
packagedirs.append(os.path.join(dir_of_packages, name))
for packagedir in packagedirs:
for root, _dirs, files in os.walk(packagedir):
- if ('__pycache__' not in root
- and os.path.basename(root) != '.vscode'):
+ if (
+ '__pycache__' not in root
+ and os.path.basename(root) != '.vscode'
+ ):
if '__init__.py' not in files:
- print(Clr.RED +
- 'Error: no __init__.py in package dir: ' + root +
- Clr.RST)
+ print(
+ Clr.RED
+ + 'Error: no __init__.py in package dir: '
+ + root
+ + Clr.RST
+ )
sys.exit(255)
def _update_visual_studio_project(self, basename: str) -> None:
- fname = (f'ballisticacore-windows/{basename}/'
- f'BallisticaCore{basename}.vcxproj')
+ fname = (
+ f'ballisticacore-windows/{basename}/'
+ f'BallisticaCore{basename}.vcxproj'
+ )
# Currently just silently skipping if not found (for public repo).
if not os.path.exists(fname):
@@ -490,11 +558,16 @@ class Updater:
public_project = 'Internal' not in basename
- all_files = sorted([
- f for f in (self._source_files + self._header_files)
- if not f.endswith('.m') and not f.endswith('.mm') and not f.
- endswith('.c') and self._is_public_source_file(f) == public_project
- ])
+ all_files = sorted(
+ [
+ f
+ for f in (self._source_files + self._header_files)
+ if not f.endswith('.m')
+ and not f.endswith('.mm')
+ and not f.endswith('.c')
+ and self._is_public_source_file(f) == public_project
+ ]
+ )
# Find the ItemGroup containing stdafx.cpp. This is where we'll dump
# our stuff.
@@ -504,7 +577,7 @@ class Updater:
begin_index -= 1
while lines[end_index] != ' ':
end_index += 1
- group_lines = lines[begin_index + 1:end_index]
+ group_lines = lines[begin_index + 1 : end_index]
# Strip out any existing files from src/ballistica.
group_lines = [
@@ -515,20 +588,24 @@ class Updater:
# Note: we can't use C files in this build at the moment; breaks
# precompiled header stuff. (shouldn't be a problem though).
group_lines = [
- ' <' +
- ('ClInclude' if src.endswith('.h') else 'ClCompile') + ' Include="'
- + src_root + '\\ballistica' + src.replace('/', '\\') + '" />'
+ ' <'
+ + ('ClInclude' if src.endswith('.h') else 'ClCompile')
+ + ' Include="'
+ + src_root
+ + '\\ballistica'
+ + src.replace('/', '\\')
+ + '" />'
for src in all_files
] + group_lines
- filtered = lines[:begin_index + 1] + group_lines + lines[end_index:]
+ filtered = lines[: begin_index + 1] + group_lines + lines[end_index:]
self._file_changes[fname] = '\r\n'.join(filtered) + '\r\n'
self._update_visual_studio_project_filters(filtered, fname, src_root)
- def _update_visual_studio_project_filters(self, lines_in: list[str],
- fname: str,
- src_root: str) -> None:
+ def _update_visual_studio_project_filters(
+ self, lines_in: list[str], fname: str, src_root: str
+ ) -> None:
filterpaths: set[str] = set()
filterlines: list[str] = [
'',
@@ -540,18 +617,18 @@ class Updater:
for line in sourcelines:
entrytype = line.strip().split()[0][1:]
path = line.split('"')[1]
- filterlines.append(' <' + entrytype + ' Include="' + path +
- '">')
+ filterlines.append(' <' + entrytype + ' Include="' + path + '">')
# If we have a dir foo/bar/eep we need to create filters for
# each of foo, foo/bar, and foo/bar/eep
- splits = path[len(src_root):].split('\\')
+ splits = path[len(src_root) :].split('\\')
splits = [s for s in splits if s != '']
splits = splits[:-1]
for i in range(len(splits)):
- filterpaths.add('\\'.join(splits[:(i + 1)]))
- filterlines.append(' ' + '\\'.join(splits) +
- '')
+ filterpaths.add('\\'.join(splits[: (i + 1)]))
+ filterlines.append(
+ ' ' + '\\'.join(splits) + ''
+ )
filterlines.append(' ' + entrytype + '>')
filterlines += [
' ',
@@ -563,8 +640,9 @@ class Updater:
' ',
'',
]
- self._file_changes[fname +
- '.filters'] = '\r\n'.join(filterlines) + '\r\n'
+ self._file_changes[fname + '.filters'] = (
+ '\r\n'.join(filterlines) + '\r\n'
+ )
def _update_visual_studio_projects(self) -> None:
self._update_visual_studio_project('Generic')
@@ -600,15 +678,17 @@ class Updater:
auto_start = lines.index(
f' # AUTOGENERATED_{section}_BEGIN (this section'
- f' is managed by the "update_project" tool)')
+ f' is managed by the "update_project" tool)'
+ )
auto_end = lines.index(f' # AUTOGENERATED_{section}_END')
our_lines = [
' ${BA_SRC_ROOT}/ballistica' + f
for f in sorted(self._source_files + self._header_files)
- if not f.endswith('.mm') and not f.endswith('.m')
+ if not f.endswith('.mm')
+ and not f.endswith('.m')
and self._is_public_source_file(f) == (section == 'PUBLIC')
]
- lines = lines[:auto_start + 1] + our_lines + lines[auto_end:]
+ lines = lines[: auto_start + 1] + our_lines + lines[auto_end:]
self._file_changes[fname] = '\n'.join(lines) + '\n'
@@ -622,8 +702,10 @@ class Updater:
self._update_cmake_file(fname)
# CMake android components:
- fname = ('ballisticacore-android/BallisticaCore'
- '/src/main/cpp/CMakeLists.txt')
+ fname = (
+ 'ballisticacore-android/BallisticaCore'
+ '/src/main/cpp/CMakeLists.txt'
+ )
if not self._public:
self._update_cmake_file(fname)
@@ -640,9 +722,9 @@ class Updater:
for root, _dirs, files in os.walk(scan_dir):
for ftst in files:
if any(ftst.endswith(ext) for ext in exts):
- src_files.add(os.path.join(root, ftst)[len(scan_dir):])
+ src_files.add(os.path.join(root, ftst)[len(scan_dir) :])
if any(ftst.endswith(ext) for ext in header_exts):
- header_files.add(os.path.join(root, ftst)[len(scan_dir):])
+ header_files.add(os.path.join(root, ftst)[len(scan_dir) :])
self._source_files = sorted(src_files)
self._header_files = sorted(header_files)
@@ -650,46 +732,58 @@ class Updater:
# Misc sanity checks.
if not self._public:
# Make sure we're set to prod master server.
- with open('src/ballistica/internal/master_server_config.h',
- encoding='utf-8') as infile:
+ with open(
+ 'src/ballistica/internal/master_server_config.h',
+ encoding='utf-8',
+ ) as infile:
msconfig = infile.read()
- if ('// V2 Master Server:\n'
- '\n'
- '// PROD\n'
- '#if 1\n') not in msconfig:
+ if (
+ '// V2 Master Server:\n' '\n' '// PROD\n' '#if 1\n'
+ ) not in msconfig:
raise CleanError('Not using prod v2 master server.')
def _check_sync_states(self) -> None:
# Make sure none of our sync targets have been mucked with since
# their last sync.
- if (subprocess.run(['tools/pcommand', 'sync', 'check'],
- check=False).returncode != 0):
+ if (
+ subprocess.run(
+ ['tools/pcommand', 'sync', 'check'], check=False
+ ).returncode
+ != 0
+ ):
raise CleanError('Sync check failed; you may need to run "sync".')
def _update_assets_makefile(self) -> None:
- if (subprocess.run(
- ['tools/pcommand', 'update_assets_makefile', self._checkarg],
- check=False).returncode != 0):
- print(
- f'{Clr.RED}Error checking/updating assets Makefile.{Clr.RST}')
+ if (
+ subprocess.run(
+ ['tools/pcommand', 'update_assets_makefile', self._checkarg],
+ check=False,
+ ).returncode
+ != 0
+ ):
+ print(f'{Clr.RED}Error checking/updating assets Makefile.{Clr.RST}')
sys.exit(255)
def _update_meta_makefile(self) -> None:
try:
- subprocess.run(['tools/pcommand', 'update_meta_makefile'] +
- self._checkarglist,
- check=True)
+ subprocess.run(
+ ['tools/pcommand', 'update_meta_makefile'] + self._checkarglist,
+ check=True,
+ )
except Exception as exc:
raise CleanError('Error checking/updating meta Makefile.') from exc
def _update_resources_makefile(self) -> None:
try:
- subprocess.run(['tools/pcommand', 'update_resources_makefile'] +
- self._checkarglist,
- check=True)
+ subprocess.run(
+ ['tools/pcommand', 'update_resources_makefile']
+ + self._checkarglist,
+ check=True,
+ )
except Exception as exc:
raise CleanError(
- 'Error checking/updating resources Makefile.') from exc
+ 'Error checking/updating resources Makefile.'
+ ) from exc
def _update_dummy_modules(self) -> None:
# Update our dummy _ba module.
@@ -697,8 +791,9 @@ class Updater:
# build so its success may depend on the cmake build files having
# already been updated.
try:
- subprocess.run(['tools/pcommand', 'update_dummy_modules'] +
- self._checkarglist,
- check=True)
+ subprocess.run(
+ ['tools/pcommand', 'update_dummy_modules'] + self._checkarglist,
+ check=True,
+ )
except Exception as exc:
raise CleanError('Error checking/updating dummy module.') from exc
diff --git a/tools/batools/pythonenumsmodule.py b/tools/batools/pythonenumsmodule.py
index 9f5eddd0..3ea4de93 100755
--- a/tools/batools/pythonenumsmodule.py
+++ b/tools/batools/pythonenumsmodule.py
@@ -52,14 +52,20 @@ def _gen_enums(infilename: str) -> str:
out = _parse_values(lines, lnum, lnumend, out)
# Clear lines with only spaces.
- return ('\n'.join('' if line == ' ' else line
- for line in out.splitlines()) + '\n')
+ return (
+ '\n'.join('' if line == ' ' else line for line in out.splitlines())
+ + '\n'
+ )
def _parse_name(lines: list[str], lnum: int) -> str:
bits = lines[lnum].split(' ')
- if (len(bits) != 4 or bits[0] != 'enum' or bits[1] != 'class'
- or bits[3] != '{'):
+ if (
+ len(bits) != 4
+ or bits[0] != 'enum'
+ or bits[1] != 'class'
+ or bits[3] != '{'
+ ):
raise Exception(f'Unexpected format for enum on line {lnum + 1}.')
enum_name = bits[2]
return enum_name
@@ -83,8 +89,11 @@ def _parse_values(lines: list[str], lnum: int, lnumend: int, out: str) -> str:
# If they're explicitly assigning a value, parse it.
if '=' in line:
splits = line.split()
- if (len(splits) != 3 or splits[1] != '='
- or not splits[2].isnumeric()):
+ if (
+ len(splits) != 3
+ or splits[1] != '='
+ or not splits[2].isnumeric()
+ ):
raise RuntimeError(f'Unable to parse enum value for: {line}')
name = splits[0]
val = int(splits[2])
@@ -100,7 +109,8 @@ def _parse_values(lines: list[str], lnum: int, lnumend: int, out: str) -> str:
if i == lnumend - 1:
if name != 'kLast':
raise RuntimeError(
- f'Expected last enum value of kLast; found {name}.')
+ f'Expected last enum value of kLast; found {name}.'
+ )
continue
name = camel_case_convert(name[1:])
out += f' {name} = {val}\n'
@@ -127,7 +137,8 @@ def _parse_doc_lines(lines: list[str], lnum: int) -> tuple[list[str], int]:
while True:
if lnum > len(lines) - 1:
raise Exception(
- f'No end found for enum docstr line {lnumorig + 1}.')
+ f'No end found for enum docstr line {lnumorig + 1}.'
+ )
if lines[lnum].startswith('enum class '):
break
if not lines[lnum].startswith('///'):
@@ -141,9 +152,11 @@ def generate(projroot: str, infilename: str, outfilename: str) -> None:
"""Main script entry point."""
from batools.project import project_centric_path
- out = (get_public_license('python') +
- f'\n"""Enum vals generated by {__name__}; do not edit by hand."""'
- f'\n\nfrom enum import Enum\n')
+ out = (
+ get_public_license('python')
+ + f'\n"""Enum vals generated by {__name__}; do not edit by hand."""'
+ f'\n\nfrom enum import Enum\n'
+ )
out += _gen_enums(infilename)
diff --git a/tools/batools/resourcesmakefile.py b/tools/batools/resourcesmakefile.py
index ab59bebd..7df6ae8d 100755
--- a/tools/batools/resourcesmakefile.py
+++ b/tools/batools/resourcesmakefile.py
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
@dataclass
class Target:
"""A target to be added to the Makefile."""
+
src: list[str]
dst: str
cmd: str
@@ -30,9 +31,18 @@ class Target:
def emit(self) -> str:
"""Gen a Makefile target."""
out: str = self.dst.replace(' ', '\\ ')
- out += ' : ' + ' '.join(s for s in self.src) + (
- ('\n\t@mkdir -p "' + os.path.dirname(self.dst) +
- '"') if self.mkdir else '') + '\n\t@' + self.cmd + '\n'
+ out += (
+ ' : '
+ + ' '.join(s for s in self.src)
+ + (
+ ('\n\t@mkdir -p "' + os.path.dirname(self.dst) + '"')
+ if self.mkdir
+ else ''
+ )
+ + '\n\t@'
+ + self.cmd
+ + '\n'
+ )
return out
@@ -45,8 +55,11 @@ def _emit_group_build_lines(targets: list[Target], basename: str) -> list[str]:
all_dsts = set()
for target in targets:
all_dsts.add(target.dst)
- out.append('resources: \\\n ' + ' \\\n '.join(
- dst.replace(' ', '\\ ') for dst in sorted(all_dsts)) + '\n')
+ out.append(
+ 'resources: \\\n '
+ + ' \\\n '.join(dst.replace(' ', '\\ ') for dst in sorted(all_dsts))
+ + '\n'
+ )
return out
@@ -59,9 +72,11 @@ def _emit_group_clean_lines(targets: list[Target], basename: str) -> list[str]:
all_dsts = set()
for target in targets:
all_dsts.add(target.dst)
- out.append(f'clean-{basename}:\n\trm -f ' +
- ' \\\n '.join('"' + dst + '"'
- for dst in sorted(all_dsts)) + '\n')
+ out.append(
+ f'clean-{basename}:\n\trm -f '
+ + ' \\\n '.join('"' + dst + '"' for dst in sorted(all_dsts))
+ + '\n'
+ )
return out
@@ -76,12 +91,16 @@ def _emit_group_efrocache_lines(targets: list[Target]) -> list[str]:
# We may need to make pipeline adjustments if/when we get filenames
# with spaces in them.
if ' ' in target.dst:
- raise CleanError('FIXME: need to account for spaces in filename'
- f' "{target.dst}".')
+ raise CleanError(
+ 'FIXME: need to account for spaces in filename'
+ f' "{target.dst}".'
+ )
all_dsts.add(target.dst)
- out.append('efrocache-list:\n\t@echo ' +
- ' \\\n '.join('"' + dst + '"'
- for dst in sorted(all_dsts)) + '\n')
+ out.append(
+ 'efrocache-list:\n\t@echo '
+ + ' \\\n '.join('"' + dst + '"' for dst in sorted(all_dsts))
+ + '\n'
+ )
out.append('efrocache-build: resources\n')
return out
@@ -94,8 +113,9 @@ RES_DIR = '.'
RESIZE_CMD = os.path.join(TOOLS_DIR, 'pcommand resize_image')
-def _add_windows_icon(targets: list[Target], generic: bool, oculus: bool,
- inputs: bool) -> None:
+def _add_windows_icon(
+ targets: list[Target], generic: bool, oculus: bool, inputs: bool
+) -> None:
sizes = [256, 128, 96, 64, 48, 32, 16]
all_icons = []
@@ -103,49 +123,83 @@ def _add_windows_icon(targets: list[Target], generic: bool, oculus: bool,
dst_base = 'build'
src = os.path.join('src', 'icon', 'icon_clipped.png')
dst = os.path.join(dst_base, 'win_icon_' + str(size) + '_tmp.png')
- cmd = ' '.join([
- RESIZE_CMD,
- str(size),
- str(size), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [RESIZE_CMD, str(size), str(size), '"' + src + '"', '"' + dst + '"']
+ )
all_icons.append(dst)
if inputs:
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
# Assemble all the bits we just made into .ico files.
for path, enable in [
- (ROOT_DIR + '/ballisticacore-windows/Generic/BallisticaCore.ico',
- generic),
- (ROOT_DIR + '/ballisticacore-windows/Oculus/BallisticaCore.ico',
- oculus),
+ (
+ ROOT_DIR + '/ballisticacore-windows/Generic/BallisticaCore.ico',
+ generic,
+ ),
+ (
+ ROOT_DIR + '/ballisticacore-windows/Oculus/BallisticaCore.ico',
+ oculus,
+ ),
]:
- cmd = ('convert ' + ''.join([' "' + f + '"'
- for f in all_icons]) + ' "' + path + '"')
+ cmd = (
+ 'convert '
+ + ''.join([' "' + f + '"' for f in all_icons])
+ + ' "'
+ + path
+ + '"'
+ )
if enable:
targets.append(Target(src=all_icons, dst=path, cmd=cmd))
def _add_ios_app_icon(targets: list[Target]) -> None:
- sizes = [(20, 2), (20, 3), (29, 2), (29, 3), (40, 2), (40, 3), (60, 2),
- (60, 3), (20, 1), (29, 1), (40, 1), (76, 1), (76, 2), (83.5, 2),
- (1024, 1)]
+ sizes = [
+ (20, 2),
+ (20, 3),
+ (29, 2),
+ (29, 3),
+ (40, 2),
+ (40, 3),
+ (60, 2),
+ (60, 3),
+ (20, 1),
+ (29, 1),
+ (40, 1),
+ (76, 1),
+ (76, 2),
+ (83.5, 2),
+ (1024, 1),
+ ]
for size in sizes:
res = int(size[0] * size[1])
src = os.path.join('src', 'icon', 'icon_flat.png')
dst = os.path.join(
- ROOT_DIR, 'ballisticacore-xcode', 'BallisticaCore Shared',
- 'Assets.xcassets', 'AppIcon iOS.appiconset',
- 'icon_' + str(size[0]) + 'x' + str(size[1]) + '.png')
+ ROOT_DIR,
+ 'ballisticacore-xcode',
+ 'BallisticaCore Shared',
+ 'Assets.xcassets',
+ 'AppIcon iOS.appiconset',
+ 'icon_' + str(size[0]) + 'x' + str(size[1]) + '.png',
+ )
cmd = ' '.join(
- [RESIZE_CMD,
- str(res),
- str(res), '"' + src + '"', '"' + dst + '"'])
+ [RESIZE_CMD, str(res), str(res), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd))
def _add_macos_app_icon(targets: list[Target]) -> None:
- sizes = [(16, 1), (16, 2), (32, 1), (32, 2), (128, 1), (128, 2), (256, 1),
- (256, 2), (512, 1), (512, 2)]
+ sizes = [
+ (16, 1),
+ (16, 2),
+ (32, 1),
+ (32, 2),
+ (128, 1),
+ (128, 2),
+ (256, 1),
+ (256, 2),
+ (512, 1),
+ (512, 2),
+ ]
for size in sizes:
res = int(size[0] * size[1])
src = os.path.join(RES_DIR, 'src', 'icon', 'icon_clipped.png')
@@ -158,72 +212,107 @@ def _add_macos_app_icon(targets: list[Target]) -> None:
'icon_' + str(size[0]) + 'x' + str(size[1]) + '.png',
)
cmd = ' '.join(
- [RESIZE_CMD,
- str(res),
- str(res), '"' + src + '"', '"' + dst + '"'])
+ [RESIZE_CMD, str(res), str(res), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd))
-def _add_android_app_icon(targets: list[Target],
- src_name: str = 'icon_clipped.png',
- variant_name: str = 'main') -> None:
- sizes = [('mdpi', 48), ('hdpi', 72), ('xhdpi', 96), ('xxhdpi', 144),
- ('xxxhdpi', 192)]
+def _add_android_app_icon(
+ targets: list[Target],
+ src_name: str = 'icon_clipped.png',
+ variant_name: str = 'main',
+) -> None:
+ sizes = [
+ ('mdpi', 48),
+ ('hdpi', 72),
+ ('xhdpi', 96),
+ ('xxhdpi', 144),
+ ('xxxhdpi', 192),
+ ]
for size in sizes:
res = size[1]
src = os.path.join(RES_DIR, 'src', 'icon', src_name)
- dst = os.path.join(ROOT_DIR, 'ballisticacore-android',
- 'BallisticaCore', 'src', variant_name, 'res',
- 'mipmap-' + size[0], 'ic_launcher.png')
+ dst = os.path.join(
+ ROOT_DIR,
+ 'ballisticacore-android',
+ 'BallisticaCore',
+ 'src',
+ variant_name,
+ 'res',
+ 'mipmap-' + size[0],
+ 'ic_launcher.png',
+ )
cmd = ' '.join(
- [RESIZE_CMD,
- str(res),
- str(res), '"' + src + '"', '"' + dst + '"'])
+ [RESIZE_CMD, str(res), str(res), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
-def _add_android_app_icon_new(targets: list[Target],
- src_fg_name: str = 'icon_android_layered_fg.png',
- src_bg_name: str = 'icon_android_layered_bg.png',
- variant_name: str = 'main') -> None:
- sizes = [('mdpi', 108), ('hdpi', 162), ('xhdpi', 216), ('xxhdpi', 324),
- ('xxxhdpi', 432)]
+def _add_android_app_icon_new(
+ targets: list[Target],
+ src_fg_name: str = 'icon_android_layered_fg.png',
+ src_bg_name: str = 'icon_android_layered_bg.png',
+ variant_name: str = 'main',
+) -> None:
+ sizes = [
+ ('mdpi', 108),
+ ('hdpi', 162),
+ ('xhdpi', 216),
+ ('xxhdpi', 324),
+ ('xxxhdpi', 432),
+ ]
for size in sizes:
res = size[1]
# Foreground component.
src = os.path.join(RES_DIR, 'src', 'icon', src_fg_name)
- dst = os.path.join(ROOT_DIR, 'ballisticacore-android',
- 'BallisticaCore', 'src', variant_name, 'res',
- 'mipmap-' + size[0], 'ic_launcher_foreground.png')
+ dst = os.path.join(
+ ROOT_DIR,
+ 'ballisticacore-android',
+ 'BallisticaCore',
+ 'src',
+ variant_name,
+ 'res',
+ 'mipmap-' + size[0],
+ 'ic_launcher_foreground.png',
+ )
cmd = ' '.join(
- [RESIZE_CMD,
- str(res),
- str(res), '"' + src + '"', '"' + dst + '"'])
+ [RESIZE_CMD, str(res), str(res), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
# Background component.
src = os.path.join(RES_DIR, 'src', 'icon', src_bg_name)
- dst = os.path.join(ROOT_DIR, 'ballisticacore-android',
- 'BallisticaCore', 'src', variant_name, 'res',
- 'mipmap-' + size[0], 'ic_launcher_background.png')
+ dst = os.path.join(
+ ROOT_DIR,
+ 'ballisticacore-android',
+ 'BallisticaCore',
+ 'src',
+ variant_name,
+ 'res',
+ 'mipmap-' + size[0],
+ 'ic_launcher_background.png',
+ )
cmd = ' '.join(
- [RESIZE_CMD,
- str(res),
- str(res), '"' + src + '"', '"' + dst + '"'])
+ [RESIZE_CMD, str(res), str(res), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
def _add_android_cardboard_app_icon(targets: list[Target]) -> None:
- _add_android_app_icon(targets=targets,
- src_name='icon_clipped_vr.png',
- variant_name='cardboard')
+ _add_android_app_icon(
+ targets=targets,
+ src_name='icon_clipped_vr.png',
+ variant_name='cardboard',
+ )
def _add_android_cardboard_app_icon_new(targets: list[Target]) -> None:
- _add_android_app_icon_new(targets=targets,
- src_fg_name='icon_android_layered_fg_vr.png',
- variant_name='cardboard')
+ _add_android_app_icon_new(
+ targets=targets,
+ src_fg_name='icon_android_layered_fg_vr.png',
+ variant_name='cardboard',
+ )
def _add_android_tv_banner(targets: list[Target]) -> None:
@@ -239,22 +328,23 @@ def _add_android_tv_banner(targets: list[Target]) -> None:
'drawable-xhdpi',
'banner.png',
)
- cmd = ' '.join([
- RESIZE_CMD,
- str(res[0]),
- str(res[1]), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [RESIZE_CMD, str(res[0]), str(res[1]), '"' + src + '"', '"' + dst + '"']
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
def _add_apple_tv_top_shelf(targets: list[Target]) -> None:
- instances = [('24x9', '', '', 1920, 720),
- ('29x9', ' Wide', '_wide', 2320, 720)]
+ instances = [
+ ('24x9', '', '', 1920, 720),
+ ('29x9', ' Wide', '_wide', 2320, 720),
+ ]
for instance in instances:
for scale in [1, 2]:
res = (instance[3] * scale, instance[4] * scale)
- src = os.path.join(RES_DIR, 'src', 'banner',
- 'banner_' + instance[0] + '.png')
+ src = os.path.join(
+ RES_DIR, 'src', 'banner', 'banner_' + instance[0] + '.png'
+ )
dst = os.path.join(
ROOT_DIR,
'ballisticacore-xcode',
@@ -264,11 +354,15 @@ def _add_apple_tv_top_shelf(targets: list[Target]) -> None:
'Top Shelf Image' + instance[1] + '.imageset',
'shelf' + instance[2] + '_' + str(scale) + 'x.png',
)
- cmd = ' '.join([
- RESIZE_CMD,
- str(res[0]),
- str(res[1]), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [
+ RESIZE_CMD,
+ str(res[0]),
+ str(res[1]),
+ '"' + src + '"',
+ '"' + dst + '"',
+ ]
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd))
@@ -276,8 +370,9 @@ def _add_apple_tv_3d_icon(targets: list[Target]) -> None:
res = (400, 240)
for layer in ['Layer1', 'Layer2', 'Layer3', 'Layer4', 'Layer5']:
for scale in [1, 2]:
- src = os.path.join(RES_DIR, 'src', 'icon_appletv',
- layer.lower() + '.png')
+ src = os.path.join(
+ RES_DIR, 'src', 'icon_appletv', layer.lower() + '.png'
+ )
dst = os.path.join(
ROOT_DIR,
'ballisticacore-xcode',
@@ -289,11 +384,15 @@ def _add_apple_tv_3d_icon(targets: list[Target]) -> None:
'Content.imageset',
layer.lower() + '_' + str(scale) + 'x.png',
)
- cmd = ' '.join([
- RESIZE_CMD,
- str(res[0] * scale),
- str(res[1] * scale), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [
+ RESIZE_CMD,
+ str(res[0] * scale),
+ str(res[1] * scale),
+ '"' + src + '"',
+ '"' + dst + '"',
+ ]
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd))
@@ -301,8 +400,9 @@ def _add_apple_tv_store_icon(targets: list[Target]) -> None:
res = (1280, 768)
for layer in ['Layer1', 'Layer2', 'Layer3', 'Layer4', 'Layer5']:
for scale in [1]:
- src = os.path.join(RES_DIR, 'src', 'icon_appletv',
- layer.lower() + '.png')
+ src = os.path.join(
+ RES_DIR, 'src', 'icon_appletv', layer.lower() + '.png'
+ )
dst = os.path.join(
ROOT_DIR,
'ballisticacore-xcode',
@@ -314,11 +414,15 @@ def _add_apple_tv_store_icon(targets: list[Target]) -> None:
'Content.imageset',
layer.lower() + '_' + str(scale) + 'x.png',
)
- cmd = ' '.join([
- RESIZE_CMD,
- str(res[0] * scale),
- str(res[1] * scale), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [
+ RESIZE_CMD,
+ str(res[0] * scale),
+ str(res[1] * scale),
+ '"' + src + '"',
+ '"' + dst + '"',
+ ]
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd))
@@ -336,11 +440,15 @@ def _add_google_vr_icon(targets: list[Target]) -> None:
'drawable-nodpi',
layer + '.png',
)
- cmd = ' '.join([
- RESIZE_CMD,
- str(res[0]),
- str(res[1]), '"' + src + '"', '"' + dst + '"'
- ])
+ cmd = ' '.join(
+ [
+ RESIZE_CMD,
+ str(res[0]),
+ str(res[1]),
+ '"' + src + '"',
+ '"' + dst + '"',
+ ]
+ )
targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True))
@@ -374,26 +482,29 @@ def update(projroot: str, check: bool) -> None:
# Public targets (full sources available in public)
targets: list[Target] = []
basename = 'public'
- our_lines_public = (_empty_line_if(bool(targets)) +
- _emit_group_build_lines(targets, basename) +
- _emit_group_clean_lines(targets, basename) +
- [t.emit() for t in targets])
+ our_lines_public = (
+ _empty_line_if(bool(targets))
+ + _emit_group_build_lines(targets, basename)
+ + _emit_group_clean_lines(targets, basename)
+ + [t.emit() for t in targets]
+ )
# Only rewrite the private section in the private repo; otherwise
# keep the existing one intact.
if public:
- our_lines_private = lines[auto_start_private + 1:auto_end_private]
+ our_lines_private = lines[auto_start_private + 1 : auto_end_private]
else:
# Private targets (available in public through efrocache)
targets = []
basename = 'private'
_add_windows_icon(targets, generic=True, oculus=False, inputs=False)
our_lines_private_1 = (
- _empty_line_if(bool(targets)) +
- _emit_group_build_lines(targets, basename) +
- _emit_group_clean_lines(targets, basename) +
- ['# __EFROCACHE_TARGET__\n' + t.emit()
- for t in targets] + _emit_group_efrocache_lines(targets))
+ _empty_line_if(bool(targets))
+ + _emit_group_build_lines(targets, basename)
+ + _emit_group_clean_lines(targets, basename)
+ + ['# __EFROCACHE_TARGET__\n' + t.emit() for t in targets]
+ + _emit_group_efrocache_lines(targets)
+ )
# Private-internal targets (not available at all in public)
targets = []
@@ -410,17 +521,23 @@ def update(projroot: str, check: bool) -> None:
_add_apple_tv_3d_icon(targets)
_add_apple_tv_store_icon(targets)
_add_google_vr_icon(targets)
- our_lines_private_2 = (['# __PUBSYNC_STRIP_BEGIN__'] +
- _empty_line_if(bool(targets)) +
- _emit_group_build_lines(targets, basename) +
- _emit_group_clean_lines(targets, basename) +
- [t.emit() for t in targets] +
- ['# __PUBSYNC_STRIP_END__'])
+ our_lines_private_2 = (
+ ['# __PUBSYNC_STRIP_BEGIN__']
+ + _empty_line_if(bool(targets))
+ + _emit_group_build_lines(targets, basename)
+ + _emit_group_clean_lines(targets, basename)
+ + [t.emit() for t in targets]
+ + ['# __PUBSYNC_STRIP_END__']
+ )
our_lines_private = our_lines_private_1 + our_lines_private_2
- filtered = (lines[:auto_start_public + 1] + our_lines_public +
- lines[auto_end_public:auto_start_private + 1] +
- our_lines_private + lines[auto_end_private:])
+ filtered = (
+ lines[: auto_start_public + 1]
+ + our_lines_public
+ + lines[auto_end_public : auto_start_private + 1]
+ + our_lines_private
+ + lines[auto_end_private:]
+ )
out = '\n'.join(filtered) + '\n'
if out == original:
@@ -428,8 +545,10 @@ def update(projroot: str, check: bool) -> None:
else:
if check:
if bool(False):
- print(f'FOUND------\n{original}\nEND FOUND--------\n'
- f'EXPECTED------\n{out}\nEND EXPECTED-------\n')
+ print(
+ f'FOUND------\n{original}\nEND FOUND--------\n'
+ f'EXPECTED------\n{out}\nEND EXPECTED-------\n'
+ )
raise CleanError(f"ERROR: file is out of date: '{fname}'.")
print(f'{Clr.SBLU}Updating: {fname}{Clr.RST}')
with open(fname, 'w', encoding='utf-8') as outfile:
diff --git a/tools/batools/version.py b/tools/batools/version.py
index 0f4e6c6b..0d608cf4 100755
--- a/tools/batools/version.py
+++ b/tools/batools/version.py
@@ -18,6 +18,7 @@ if TYPE_CHECKING:
class Mode(Enum):
"""Mode we can run this command in."""
+
INFO = 'info'
BUILD = 'build'
VERSION = 'version'
@@ -65,8 +66,9 @@ def get_current_version() -> tuple[str, int]:
def get_current_api_version() -> int:
"""Pull current api version from the project."""
- with open('assets/src/ba_data/python/ba/_meta.py',
- encoding='utf-8') as infile:
+ with open(
+ 'assets/src/ba_data/python/ba/_meta.py', encoding='utf-8'
+ ) as infile:
lines = infile.readlines()
linestart = 'CURRENT_API_VERSION = '
for line in lines:
diff --git a/tools/efro/call.py b/tools/efro/call.py
index 9087a0f7..7013f3dd 100644
--- a/tools/efro/call.py
+++ b/tools/efro/call.py
@@ -122,44 +122,67 @@ if TYPE_CHECKING:
def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]):
...
- def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T,
- _arg4: In4T) -> OutT:
+ def __call__(
+ self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T
+ ) -> OutT:
...
class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]):
"""Five argument variant of call wrapper"""
- def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T, In5T],
- OutT]):
+ def __init__(
+ self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT]
+ ):
...
- def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
- _arg5: In5T) -> OutT:
+ def __call__(
+ self,
+ _arg1: In1T,
+ _arg2: In2T,
+ _arg3: In3T,
+ _arg4: In4T,
+ _arg5: In5T,
+ ) -> OutT:
...
class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]):
"""Six argument variant of call wrapper"""
- def __init__(self,
- _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T],
- OutT]):
+ def __init__(
+ self, _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT]
+ ):
...
- def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
- _arg5: In5T, _arg6: In6T) -> OutT:
+ def __call__(
+ self,
+ _arg1: In1T,
+ _arg2: In2T,
+ _arg3: In3T,
+ _arg4: In4T,
+ _arg5: In5T,
+ _arg6: In6T,
+ ) -> OutT:
...
class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]):
"""Seven argument variant of call wrapper"""
def __init__(
- self,
- _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T],
- OutT]):
+ self,
+ _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
+ ):
...
- def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T,
- _arg5: In5T, _arg6: In6T, _arg7: In7T) -> OutT:
+ def __call__(
+ self,
+ _arg1: In1T,
+ _arg2: In2T,
+ _arg3: In3T,
+ _arg4: In4T,
+ _arg5: In5T,
+ _arg6: In6T,
+ _arg7: In7T,
+ ) -> OutT:
...
# No arg call; no args bundled.
@@ -183,44 +206,52 @@ if TYPE_CHECKING:
# 2 arg call; 2 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T], OutT], arg1: In1T,
- arg2: In2T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T], OutT], arg1: In1T, arg2: In2T
+ ) -> _CallNoArgs[OutT]:
...
# 2 arg call; 1 arg bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T], OutT],
- arg1: In1T) -> _Call1Arg[In2T, OutT]:
+ def Call(
+ call: Callable[[In1T, In2T], OutT], arg1: In1T
+ ) -> _Call1Arg[In2T, OutT]:
...
# 2 arg call; no args bundled.
# noinspection PyPep8Naming
@overload
def Call(
- call: Callable[[In1T, In2T],
- OutT]) -> _Call2Args[In1T, In2T, OutT]:
+ call: Callable[[In1T, In2T], OutT]
+ ) -> _Call2Args[In1T, In2T, OutT]:
...
# 3 arg call; 3 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T,
- arg3: In3T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T], OutT],
+ arg1: In1T,
+ arg2: In2T,
+ arg3: In3T,
+ ) -> _CallNoArgs[OutT]:
...
# 3 arg call; 2 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T,
- arg2: In2T) -> _Call1Arg[In3T, OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T
+ ) -> _Call1Arg[In3T, OutT]:
...
# 3 arg call; 1 arg bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T], OutT],
- arg1: In1T) -> _Call2Args[In2T, In3T, OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T
+ ) -> _Call2Args[In2T, In3T, OutT]:
...
# 3 arg call; no args bundled.
@@ -234,32 +265,55 @@ if TYPE_CHECKING:
# 4 arg call; 4 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T, In4T], OutT], arg1: In1T,
- arg2: In2T, arg3: In3T, arg4: In4T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T, In4T], OutT],
+ arg1: In1T,
+ arg2: In2T,
+ arg3: In3T,
+ arg4: In4T,
+ ) -> _CallNoArgs[OutT]:
...
# 5 arg call; 5 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T],
- OutT], arg1: In1T, arg2: In2T, arg3: In3T,
- arg4: In4T, arg5: In5T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT],
+ arg1: In1T,
+ arg2: In2T,
+ arg3: In3T,
+ arg4: In4T,
+ arg5: In5T,
+ ) -> _CallNoArgs[OutT]:
...
# 6 arg call; 6 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T],
- OutT], arg1: In1T, arg2: In2T, arg3: In3T,
- arg4: In4T, arg5: In5T, arg6: In6T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT],
+ arg1: In1T,
+ arg2: In2T,
+ arg3: In3T,
+ arg4: In4T,
+ arg5: In5T,
+ arg6: In6T,
+ ) -> _CallNoArgs[OutT]:
...
# 7 arg call; 7 args bundled.
# noinspection PyPep8Naming
@overload
- def Call(call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
- arg1: In1T, arg2: In2T, arg3: In3T, arg4: In4T, arg5: In5T,
- arg6: In6T, arg7: In7T) -> _CallNoArgs[OutT]:
+ def Call(
+ call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
+ arg1: In1T,
+ arg2: In2T,
+ arg3: In3T,
+ arg4: In4T,
+ arg5: In5T,
+ arg6: In6T,
+ arg7: In7T,
+ ) -> _CallNoArgs[OutT]:
...
# noinspection PyPep8Naming
diff --git a/tools/efro/dataclassio/__init__.py b/tools/efro/dataclassio/__init__.py
index f88be16f..2447ed8e 100644
--- a/tools/efro/dataclassio/__init__.py
+++ b/tools/efro/dataclassio/__init__.py
@@ -11,19 +11,38 @@ data formats in a nondestructive manner.
from __future__ import annotations
from efro.util import set_canonical_module
-from efro.dataclassio._base import (Codec, IOAttrs, IOExtendedData)
-from efro.dataclassio._prep import (ioprep, ioprepped, will_ioprep,
- is_ioprepped_dataclass)
+from efro.dataclassio._base import Codec, IOAttrs, IOExtendedData
+from efro.dataclassio._prep import (
+ ioprep,
+ ioprepped,
+ will_ioprep,
+ is_ioprepped_dataclass,
+)
from efro.dataclassio._pathcapture import DataclassFieldLookup
-from efro.dataclassio._api import (JsonStyle, dataclass_to_dict,
- dataclass_to_json, dataclass_from_dict,
- dataclass_from_json, dataclass_validate)
+from efro.dataclassio._api import (
+ JsonStyle,
+ dataclass_to_dict,
+ dataclass_to_json,
+ dataclass_from_dict,
+ dataclass_from_json,
+ dataclass_validate,
+)
__all__ = [
- 'JsonStyle', 'Codec', 'IOAttrs', 'IOExtendedData', 'ioprep', 'ioprepped',
- 'will_ioprep', 'is_ioprepped_dataclass', 'DataclassFieldLookup',
- 'dataclass_to_dict', 'dataclass_to_json', 'dataclass_from_dict',
- 'dataclass_from_json', 'dataclass_validate'
+ 'JsonStyle',
+ 'Codec',
+ 'IOAttrs',
+ 'IOExtendedData',
+ 'ioprep',
+ 'ioprepped',
+ 'will_ioprep',
+ 'is_ioprepped_dataclass',
+ 'DataclassFieldLookup',
+ 'dataclass_to_dict',
+ 'dataclass_to_json',
+ 'dataclass_from_dict',
+ 'dataclass_from_json',
+ 'dataclass_validate',
]
# Have these things present themselves cleanly as 'thismodule.SomeClass'
diff --git a/tools/efro/dataclassio/_api.py b/tools/efro/dataclassio/_api.py
index 13eb5f9e..ddadd4d8 100644
--- a/tools/efro/dataclassio/_api.py
+++ b/tools/efro/dataclassio/_api.py
@@ -39,9 +39,9 @@ class JsonStyle(Enum):
PRETTY = 'pretty'
-def dataclass_to_dict(obj: Any,
- codec: Codec = Codec.JSON,
- coerce_to_float: bool = True) -> dict:
+def dataclass_to_dict(
+ obj: Any, codec: Codec = Codec.JSON, coerce_to_float: bool = True
+) -> dict:
"""Given a dataclass object, return a json-friendly dict.
All values will be checked to ensure they match the types specified
@@ -58,18 +58,19 @@ def dataclass_to_dict(obj: Any,
will be triggered.
"""
- out = _Outputter(obj,
- create=True,
- codec=codec,
- coerce_to_float=coerce_to_float).run()
+ out = _Outputter(
+ obj, create=True, codec=codec, coerce_to_float=coerce_to_float
+ ).run()
assert isinstance(out, dict)
return out
-def dataclass_to_json(obj: Any,
- coerce_to_float: bool = True,
- pretty: bool = False,
- sort_keys: bool | None = None) -> str:
+def dataclass_to_json(
+ obj: Any,
+ coerce_to_float: bool = True,
+ pretty: bool = False,
+ sort_keys: bool | None = None,
+) -> str:
"""Utility function; return a json string from a dataclass instance.
Basically json.dumps(dataclass_to_dict(...)).
@@ -77,9 +78,10 @@ def dataclass_to_json(obj: Any,
this can be overridden by supplying a value for the 'sort_keys' arg.
"""
import json
- jdict = dataclass_to_dict(obj=obj,
- coerce_to_float=coerce_to_float,
- codec=Codec.JSON)
+
+ jdict = dataclass_to_dict(
+ obj=obj, coerce_to_float=coerce_to_float, codec=Codec.JSON
+ )
if sort_keys is None:
sort_keys = pretty
if pretty:
@@ -87,12 +89,14 @@ def dataclass_to_json(obj: Any,
return json.dumps(jdict, separators=(',', ':'), sort_keys=sort_keys)
-def dataclass_from_dict(cls: type[T],
- values: dict,
- codec: Codec = Codec.JSON,
- coerce_to_float: bool = True,
- allow_unknown_attrs: bool = True,
- discard_unknown_attrs: bool = False) -> T:
+def dataclass_from_dict(
+ cls: type[T],
+ values: dict,
+ codec: Codec = Codec.JSON,
+ coerce_to_float: bool = True,
+ allow_unknown_attrs: bool = True,
+ discard_unknown_attrs: bool = False,
+) -> T:
"""Given a dict, return a dataclass of a given type.
The dict must be formatted to match the specified codec (generally
@@ -116,36 +120,44 @@ def dataclass_from_dict(cls: type[T],
exported back to a dict, unless discard_unknown_attrs is True, in which
case they will simply be discarded.
"""
- return _Inputter(cls,
- codec=codec,
- coerce_to_float=coerce_to_float,
- allow_unknown_attrs=allow_unknown_attrs,
- discard_unknown_attrs=discard_unknown_attrs).run(values)
+ return _Inputter(
+ cls,
+ codec=codec,
+ coerce_to_float=coerce_to_float,
+ allow_unknown_attrs=allow_unknown_attrs,
+ discard_unknown_attrs=discard_unknown_attrs,
+ ).run(values)
-def dataclass_from_json(cls: type[T],
- json_str: str,
- coerce_to_float: bool = True,
- allow_unknown_attrs: bool = True,
- discard_unknown_attrs: bool = False) -> T:
+def dataclass_from_json(
+ cls: type[T],
+ json_str: str,
+ coerce_to_float: bool = True,
+ allow_unknown_attrs: bool = True,
+ discard_unknown_attrs: bool = False,
+) -> T:
"""Utility function; return a dataclass instance given a json string.
Basically dataclass_from_dict(json.loads(...))
"""
import json
- return dataclass_from_dict(cls=cls,
- values=json.loads(json_str),
- coerce_to_float=coerce_to_float,
- allow_unknown_attrs=allow_unknown_attrs,
- discard_unknown_attrs=discard_unknown_attrs)
+
+ return dataclass_from_dict(
+ cls=cls,
+ values=json.loads(json_str),
+ coerce_to_float=coerce_to_float,
+ allow_unknown_attrs=allow_unknown_attrs,
+ discard_unknown_attrs=discard_unknown_attrs,
+ )
-def dataclass_validate(obj: Any,
- coerce_to_float: bool = True,
- codec: Codec = Codec.JSON) -> None:
+def dataclass_validate(
+ obj: Any, coerce_to_float: bool = True, codec: Codec = Codec.JSON
+) -> None:
"""Ensure that values in a dataclass instance are the correct types."""
# Simply run an output pass but tell it not to generate data;
# only run validation.
- _Outputter(obj, create=False, codec=codec,
- coerce_to_float=coerce_to_float).run()
+ _Outputter(
+ obj, create=False, codec=codec, coerce_to_float=coerce_to_float
+ ).run()
diff --git a/tools/efro/dataclassio/_base.py b/tools/efro/dataclassio/_base.py
index ee7834da..134ea001 100644
--- a/tools/efro/dataclassio/_base.py
+++ b/tools/efro/dataclassio/_base.py
@@ -9,6 +9,7 @@ import typing
import datetime
from enum import Enum
from typing import TYPE_CHECKING, get_args
+
# noinspection PyProtectedMember
from typing import _AnnotatedAlias # type: ignore
@@ -23,8 +24,9 @@ SIMPLE_TYPES = {int, bool, str, float, type(None)}
EXTRA_ATTRS_ATTR = '_DCIOEXATTRS'
-def _raise_type_error(fieldpath: str, valuetype: type,
- expected: tuple[type, ...]) -> None:
+def _raise_type_error(
+ fieldpath: str, valuetype: type, expected: tuple[type, ...]
+) -> None:
"""Raise an error when a field value's type does not match expected."""
assert isinstance(expected, tuple)
assert all(isinstance(e, type) for e in expected)
@@ -32,9 +34,11 @@ def _raise_type_error(fieldpath: str, valuetype: type,
expected_str = expected[0].__name__
else:
expected_str = ' | '.join(t.__name__ for t in expected)
- raise TypeError(f'Invalid value type for "{fieldpath}";'
- f' expected "{expected_str}", got'
- f' "{valuetype.__name__}".')
+ raise TypeError(
+ f'Invalid value type for "{fieldpath}";'
+ f' expected "{expected_str}", got'
+ f' "{valuetype.__name__}".'
+ )
class Codec(Enum):
@@ -83,7 +87,8 @@ def _is_valid_for_codec(obj: Any, codec: Codec) -> bool:
# JSON 'objects' supports only string dict keys, but all value types.
return all(
isinstance(k, str) and _is_valid_for_codec(v, codec)
- for k, v in obj.items())
+ for k, v in obj.items()
+ )
if objtype is list:
return all(_is_valid_for_codec(elem, codec) for elem in obj)
@@ -162,13 +167,15 @@ class IOAttrs:
if isinstance(soft_default, (list, dict, set)):
raise ValueError(
f'mutable {type(soft_default)} is not allowed'
- f' for soft_default; use soft_default_factory.')
+ f' for soft_default; use soft_default_factory.'
+ )
self.soft_default = soft_default
if soft_default_factory is not cls.soft_default_factory:
self.soft_default_factory = soft_default_factory
if self.soft_default is not cls.soft_default:
- raise ValueError('Cannot set both soft_default'
- ' and soft_default_factory')
+ raise ValueError(
+ 'Cannot set both soft_default and soft_default_factory'
+ )
def validate_for_field(self, cls: type, field: dataclasses.Field) -> None:
"""Ensure the IOAttrs instance is ok to use with the provided field."""
@@ -178,29 +185,44 @@ class IOAttrs:
if not self.store_default:
field_default_factory: Any = field.default_factory
- if (field_default_factory is dataclasses.MISSING
- and field.default is dataclasses.MISSING
- and self.soft_default is self.MISSING
- and self.soft_default_factory is self.MISSING):
- raise TypeError(f'Field {field.name} of {cls} has'
- f' neither a default nor a default_factory'
- f' and IOAttrs contains neither a soft_default'
- f' nor a soft_default_factory;'
- f' store_default=False cannot be set for it.')
+ if (
+ field_default_factory is dataclasses.MISSING
+ and field.default is dataclasses.MISSING
+ and self.soft_default is self.MISSING
+ and self.soft_default_factory is self.MISSING
+ ):
+ raise TypeError(
+ f'Field {field.name} of {cls} has'
+ f' neither a default nor a default_factory'
+ f' and IOAttrs contains neither a soft_default'
+ f' nor a soft_default_factory;'
+ f' store_default=False cannot be set for it.'
+ )
- def validate_datetime(self, value: datetime.datetime,
- fieldpath: str) -> None:
+ def validate_datetime(
+ self, value: datetime.datetime, fieldpath: str
+ ) -> None:
"""Ensure a datetime value meets our value requirements."""
if self.whole_days:
- if any(x != 0 for x in (value.hour, value.minute, value.second,
- value.microsecond)):
+ if any(
+ x != 0
+ for x in (
+ value.hour,
+ value.minute,
+ value.second,
+ value.microsecond,
+ )
+ ):
raise ValueError(
- f'Value {value} at {fieldpath} is not a whole day.')
+ f'Value {value} at {fieldpath} is not a whole day.'
+ )
if self.whole_hours:
- if any(x != 0
- for x in (value.minute, value.second, value.microsecond)):
- raise ValueError(f'Value {value} at {fieldpath}'
- f' is not a whole hour.')
+ if any(
+ x != 0 for x in (value.minute, value.second, value.microsecond)
+ ):
+ raise ValueError(
+ f'Value {value} at {fieldpath}' f' is not a whole hour.'
+ )
def _get_origin(anntype: Any) -> Any:
@@ -228,7 +250,8 @@ def _parse_annotated(anntype: Any) -> tuple[Any, IOAttrs | None]:
if ioattrs is not None:
raise RuntimeError(
'Multiple IOAttrs instances found for a'
- ' single annotation; this is not supported.')
+ ' single annotation; this is not supported.'
+ )
ioattrs = annarg
# I occasionally just throw a 'x' down when I mean IOAttrs('x');
@@ -236,6 +259,7 @@ def _parse_annotated(anntype: Any) -> tuple[Any, IOAttrs | None]:
elif isinstance(annarg, (str, int, float, bool)):
raise RuntimeError(
f'Raw {type(annarg)} found in Annotated[] entry:'
- f' {anntype}; this is probably not what you intended.')
+ f' {anntype}; this is probably not what you intended.'
+ )
anntype = annargs[0]
return anntype, ioattrs
diff --git a/tools/efro/dataclassio/_inputter.py b/tools/efro/dataclassio/_inputter.py
index 85f45160..9442a25d 100644
--- a/tools/efro/dataclassio/_inputter.py
+++ b/tools/efro/dataclassio/_inputter.py
@@ -16,10 +16,16 @@ import datetime
from typing import TYPE_CHECKING, Generic, TypeVar
from efro.util import enum_by_value, check_utc
-from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR,
- _is_valid_for_codec, _get_origin,
- SIMPLE_TYPES, _raise_type_error,
- IOExtendedData)
+from efro.dataclassio._base import (
+ Codec,
+ _parse_annotated,
+ EXTRA_ATTRS_ATTR,
+ _is_valid_for_codec,
+ _get_origin,
+ SIMPLE_TYPES,
+ _raise_type_error,
+ IOExtendedData,
+)
from efro.dataclassio._prep import PrepSession
if TYPE_CHECKING:
@@ -32,13 +38,14 @@ T = TypeVar('T')
class _Inputter(Generic[T]):
-
- def __init__(self,
- cls: type[T],
- codec: Codec,
- coerce_to_float: bool,
- allow_unknown_attrs: bool = True,
- discard_unknown_attrs: bool = False):
+ def __init__(
+ self,
+ cls: type[T],
+ codec: Codec,
+ coerce_to_float: bool,
+ allow_unknown_attrs: bool = True,
+ discard_unknown_attrs: bool = False,
+ ):
self._cls = cls
self._codec = codec
self._coerce_to_float = coerce_to_float
@@ -47,8 +54,10 @@ class _Inputter(Generic[T]):
self._soft_default_validator: _Outputter | None = None
if not allow_unknown_attrs and discard_unknown_attrs:
- raise ValueError('discard_unknown_attrs cannot be True'
- ' when allow_unknown_attrs is False.')
+ raise ValueError(
+ 'discard_unknown_attrs cannot be True'
+ ' when allow_unknown_attrs is False.'
+ )
def run(self, values: dict) -> T:
"""Do the thing."""
@@ -62,8 +71,14 @@ class _Inputter(Generic[T]):
assert isinstance(out, self._cls)
return out
- def _value_from_input(self, cls: type, fieldpath: str, anntype: Any,
- value: Any, ioattrs: IOAttrs | None) -> Any:
+ def _value_from_input(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: Any,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
"""Convert an assigned value to what a dataclass field expects."""
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
@@ -72,11 +87,13 @@ class _Inputter(Generic[T]):
if origin is typing.Any:
if not _is_valid_for_codec(value, self._codec):
- raise TypeError(f'Invalid value type for \'{fieldpath}\';'
- f' \'Any\' typed values must contain only'
- f' types directly supported by the specified'
- f' codec ({self._codec.name}); found'
- f' \'{type(value).__name__}\' which is not.')
+ raise TypeError(
+ f'Invalid value type for \'{fieldpath}\';'
+ f' \'Any\' typed values must contain only'
+ f' types directly supported by the specified'
+ f' codec ({self._codec.name}); found'
+ f' \'{type(value).__name__}\' which is not.'
+ )
return value
if origin is typing.Union or origin is types.UnionType:
@@ -89,8 +106,9 @@ class _Inputter(Generic[T]):
c for c in typing.get_args(anntype) if c is not type(None)
] # noqa (pycodestyle complains about *is* with type)
assert len(childanntypes_l) == 1
- return self._value_from_input(cls, fieldpath, childanntypes_l[0],
- value, ioattrs)
+ return self._value_from_input(
+ cls, fieldpath, childanntypes_l[0], value, ioattrs
+ )
# Everything below this point assumes the annotation type resolves
# to a concrete type. (This should have been verified at prep time).
@@ -99,23 +117,29 @@ class _Inputter(Generic[T]):
if origin in SIMPLE_TYPES:
if type(value) is not origin:
# Special case: if they want to coerce ints to floats, do so.
- if (self._coerce_to_float and origin is float
- and type(value) is int):
+ if (
+ self._coerce_to_float
+ and origin is float
+ and type(value) is int
+ ):
return float(value)
- _raise_type_error(fieldpath, type(value), (origin, ))
+ _raise_type_error(fieldpath, type(value), (origin,))
return value
if origin in {list, set}:
- return self._sequence_from_input(cls, fieldpath, anntype, value,
- origin, ioattrs)
+ return self._sequence_from_input(
+ cls, fieldpath, anntype, value, origin, ioattrs
+ )
if origin is tuple:
- return self._tuple_from_input(cls, fieldpath, anntype, value,
- ioattrs)
+ return self._tuple_from_input(
+ cls, fieldpath, anntype, value, ioattrs
+ )
if origin is dict:
- return self._dict_from_input(cls, fieldpath, anntype, value,
- ioattrs)
+ return self._dict_from_input(
+ cls, fieldpath, anntype, value, ioattrs
+ )
if dataclasses.is_dataclass(origin):
return self._dataclass_from_input(origin, fieldpath, value)
@@ -130,10 +154,10 @@ class _Inputter(Generic[T]):
return self._bytes_from_input(origin, fieldpath, value)
raise TypeError(
- f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
+ f"Field '{fieldpath}' of type '{anntype}' is unsupported here."
+ )
- def _bytes_from_input(self, cls: type, fieldpath: str,
- value: Any) -> bytes:
+ def _bytes_from_input(self, cls: type, fieldpath: str, value: Any) -> bytes:
"""Given input data, returns bytes."""
import base64
@@ -141,19 +165,24 @@ class _Inputter(Generic[T]):
# as base64.
if self._codec is Codec.FIRESTORE:
if not isinstance(value, bytes):
- raise TypeError(f'Expected a bytes object for {fieldpath}'
- f' on {cls.__name__}; got a {type(value)}.')
+ raise TypeError(
+ f'Expected a bytes object for {fieldpath}'
+ f' on {cls.__name__}; got a {type(value)}.'
+ )
return value
assert self._codec is Codec.JSON
if not isinstance(value, str):
- raise TypeError(f'Expected a string object for {fieldpath}'
- f' on {cls.__name__}; got a {type(value)}.')
+ raise TypeError(
+ f'Expected a string object for {fieldpath}'
+ f' on {cls.__name__}; got a {type(value)}.'
+ )
return base64.b64decode(value)
- def _dataclass_from_input(self, cls: type, fieldpath: str,
- values: dict) -> Any:
+ def _dataclass_from_input(
+ self, cls: type, fieldpath: str, values: dict
+ ) -> Any:
"""Given a dict, instantiates a dataclass of the given type.
The dict must be in the json-friendly format as emitted from
@@ -166,10 +195,12 @@ class _Inputter(Generic[T]):
if not isinstance(values, dict):
raise TypeError(
f'Expected a dict for {fieldpath} on {cls.__name__};'
- f' got a {type(values)}.')
+ f' got a {type(values)}.'
+ )
- prep = PrepSession(explicit=False).prep_dataclass(cls,
- recursion_level=0)
+ prep = PrepSession(explicit=False).prep_dataclass(
+ cls, recursion_level=0
+ )
assert prep is not None
extra_attrs = {}
@@ -181,8 +212,7 @@ class _Inputter(Generic[T]):
# Preprocess all fields to convert Annotated[] to contained types
# and IOAttrs.
parsed_field_annotations = {
- f.name: _parse_annotated(prep.annotations[f.name])
- for f in fields
+ f.name: _parse_annotated(prep.annotations[f.name]) for f in fields
}
# Go through all data in the input, converting it to either dataclass
@@ -205,18 +235,22 @@ class _Inputter(Generic[T]):
f'Unknown attr \'{key}\''
f' on {fieldpath} contains data type(s)'
f' not supported by the specified codec'
- f' ({self._codec.name}).')
+ f' ({self._codec.name}).'
+ )
extra_attrs[key] = value
else:
raise AttributeError(
- f"'{cls.__name__}' has no '{key}' field.")
+ f"'{cls.__name__}' has no '{key}' field."
+ )
else:
fieldname = field.name
anntype, ioattrs = parsed_field_annotations[fieldname]
- subfieldpath = (f'{fieldpath}.{fieldname}'
- if fieldpath else fieldname)
- args[key] = self._value_from_input(cls, subfieldpath, anntype,
- value, ioattrs)
+ subfieldpath = (
+ f'{fieldpath}.{fieldname}' if fieldpath else fieldname
+ )
+ args[key] = self._value_from_input(
+ cls, subfieldpath, anntype, value, ioattrs
+ )
# Go through all fields looking for any not yet present in our data.
# If we find any such fields with a soft-default value or factory
@@ -225,9 +259,10 @@ class _Inputter(Generic[T]):
if key in args:
continue
ioattrs = aparsed[1]
- if (ioattrs is not None and
- (ioattrs.soft_default is not ioattrs.MISSING
- or ioattrs.soft_default_factory is not ioattrs.MISSING)):
+ if ioattrs is not None and (
+ ioattrs.soft_default is not ioattrs.MISSING
+ or ioattrs.soft_default_factory is not ioattrs.MISSING
+ ):
if ioattrs.soft_default is not ioattrs.MISSING:
soft_default = ioattrs.soft_default
else:
@@ -241,19 +276,23 @@ class _Inputter(Generic[T]):
self._type_check_soft_default(
value=soft_default,
anntype=aparsed[0],
- fieldpath=(f'{fieldpath}.{key}' if fieldpath else key))
+ fieldpath=(f'{fieldpath}.{key}' if fieldpath else key),
+ )
try:
out = cls(**args)
except Exception as exc:
- raise ValueError(f'Error instantiating class {cls.__name__}'
- f' at {fieldpath}: {exc}') from exc
+ raise ValueError(
+ f'Error instantiating class {cls.__name__}'
+ f' at {fieldpath}: {exc}'
+ ) from exc
if extra_attrs:
setattr(out, EXTRA_ATTRS_ATTR, extra_attrs)
return out
- def _type_check_soft_default(self, value: Any, anntype: Any,
- fieldpath: str) -> None:
+ def _type_check_soft_default(
+ self, value: Any, anntype: Any, fieldpath: str
+ ) -> None:
from efro.dataclassio._outputter import _Outputter
# Counter-intuitively, we create an outputter as part of
@@ -264,20 +303,28 @@ class _Inputter(Generic[T]):
obj=None,
create=False,
codec=self._codec,
- coerce_to_float=self._coerce_to_float)
- self._soft_default_validator.soft_default_check(value=value,
- anntype=anntype,
- fieldpath=fieldpath)
+ coerce_to_float=self._coerce_to_float,
+ )
+ self._soft_default_validator.soft_default_check(
+ value=value, anntype=anntype, fieldpath=fieldpath
+ )
- def _dict_from_input(self, cls: type, fieldpath: str, anntype: Any,
- value: Any, ioattrs: IOAttrs | None) -> Any:
+ def _dict_from_input(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: Any,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
if not isinstance(value, dict):
raise TypeError(
f'Expected a dict for \'{fieldpath}\' on {cls.__name__};'
- f' got a {type(value)}.')
+ f' got a {type(value)}.'
+ )
childtypes = typing.get_args(anntype)
assert len(childtypes) in (0, 2)
@@ -287,12 +334,15 @@ class _Inputter(Generic[T]):
# We treat 'Any' dicts simply as json; we don't do any translating.
if not childtypes or childtypes[0] is typing.Any:
if not isinstance(value, dict) or not _is_valid_for_codec(
- value, self._codec):
- raise TypeError(f'Got invalid value for Dict[Any, Any]'
- f' at \'{fieldpath}\' on {cls.__name__};'
- f' all keys and values must be'
- f' compatible with the specified codec'
- f' ({self._codec.name}).')
+ value, self._codec
+ ):
+ raise TypeError(
+ f'Got invalid value for Dict[Any, Any]'
+ f' at \'{fieldpath}\' on {cls.__name__};'
+ f' all keys and values must be'
+ f' compatible with the specified codec'
+ f' ({self._codec.name}).'
+ )
out = value
else:
out = {}
@@ -308,9 +358,11 @@ class _Inputter(Generic[T]):
raise TypeError(
f'Got invalid key type {type(key)} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected a str.')
- out[key] = self._value_from_input(cls, fieldpath,
- valanntype, val, ioattrs)
+ f' expected a str.'
+ )
+ out[key] = self._value_from_input(
+ cls, fieldpath, valanntype, val, ioattrs
+ )
# int keys are stored in json as str versions of themselves.
elif keyanntype is int:
@@ -319,16 +371,19 @@ class _Inputter(Generic[T]):
raise TypeError(
f'Got invalid key type {type(key)} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected a str.')
+ f' expected a str.'
+ )
try:
keyint = int(key)
except ValueError as exc:
raise TypeError(
f'Got invalid key value {key} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected an int in string form.') from exc
+ f' expected an int in string form.'
+ ) from exc
out[keyint] = self._value_from_input(
- cls, fieldpath, valanntype, val, ioattrs)
+ cls, fieldpath, valanntype, val, ioattrs
+ )
elif issubclass(keyanntype, Enum):
# In prep, we verified that all these enums' values have
@@ -346,9 +401,11 @@ class _Inputter(Generic[T]):
f' dict key at \'{fieldpath}\''
f' on {cls.__name__};'
f' expected a value corresponding to'
- f' a {keyanntype}.') from exc
+ f' a {keyanntype}.'
+ ) from exc
out[enumval] = self._value_from_input(
- cls, fieldpath, valanntype, val, ioattrs)
+ cls, fieldpath, valanntype, val, ioattrs
+ )
else:
for key, val in value.items():
try:
@@ -359,23 +416,33 @@ class _Inputter(Generic[T]):
f' dict key at \'{fieldpath}\''
f' on {cls.__name__};'
f' expected {keyanntype} value (though'
- f' in string form).') from exc
+ f' in string form).'
+ ) from exc
out[enumval] = self._value_from_input(
- cls, fieldpath, valanntype, val, ioattrs)
+ cls, fieldpath, valanntype, val, ioattrs
+ )
else:
raise RuntimeError(f'Unhandled dict in-key-type {keyanntype}')
return out
- def _sequence_from_input(self, cls: type, fieldpath: str, anntype: Any,
- value: Any, seqtype: type,
- ioattrs: IOAttrs | None) -> Any:
+ def _sequence_from_input(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: Any,
+ seqtype: type,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
# Because we are json-centric, we expect a list for all sequences.
if type(value) is not list:
- raise TypeError(f'Invalid input value for "{fieldpath}";'
- f' expected a list, got a {type(value).__name__}')
+ raise TypeError(
+ f'Invalid input value for "{fieldpath}";'
+ f' expected a list, got a {type(value).__name__}'
+ )
childanntypes = typing.get_args(anntype)
@@ -384,8 +451,10 @@ class _Inputter(Generic[T]):
if len(childanntypes) == 0 or childanntypes[0] is typing.Any:
for i, child in enumerate(value):
if not _is_valid_for_codec(child, self._codec):
- raise TypeError(f'Item {i} of {fieldpath} contains'
- f' data type(s) not supported by json.')
+ raise TypeError(
+ f'Item {i} of {fieldpath} contains'
+ f' data type(s) not supported by json.'
+ )
return value if type(value) is seqtype else seqtype(value)
# We contain elements of some specified type.
@@ -393,10 +462,12 @@ class _Inputter(Generic[T]):
childanntype = childanntypes[0]
return seqtype(
self._value_from_input(cls, fieldpath, childanntype, i, ioattrs)
- for i in value)
+ for i in value
+ )
- def _datetime_from_input(self, cls: type, fieldpath: str, value: Any,
- ioattrs: IOAttrs | None) -> Any:
+ def _datetime_from_input(
+ self, cls: type, fieldpath: str, value: Any, ioattrs: IOAttrs | None
+ ) -> Any:
# For firestore we expect a datetime object.
if self._codec is Codec.FIRESTORE:
@@ -406,7 +477,8 @@ class _Inputter(Generic[T]):
raise TypeError(
f'Invalid input value for "{fieldpath}" on'
f' "{cls.__name__}";'
- f' expected a datetime, got a {type(value).__name__}')
+ f' expected a datetime, got a {type(value).__name__}'
+ )
check_utc(value)
return value
@@ -416,26 +488,37 @@ class _Inputter(Generic[T]):
if type(value) is not list:
raise TypeError(
f'Invalid input value for "{fieldpath}" on "{cls.__name__}";'
- f' expected a list, got a {type(value).__name__}')
+ f' expected a list, got a {type(value).__name__}'
+ )
if len(value) != 7 or not all(isinstance(x, int) for x in value):
raise ValueError(
f'Invalid input value for "{fieldpath}" on "{cls.__name__}";'
- f' expected a list of 7 ints, got {[type(v) for v in value]}.')
+ f' expected a list of 7 ints, got {[type(v) for v in value]}.'
+ )
out = datetime.datetime( # type: ignore
- *value, tzinfo=datetime.timezone.utc)
+ *value, tzinfo=datetime.timezone.utc
+ )
if ioattrs is not None:
ioattrs.validate_datetime(out, fieldpath)
return out
- def _tuple_from_input(self, cls: type, fieldpath: str, anntype: Any,
- value: Any, ioattrs: IOAttrs | None) -> Any:
+ def _tuple_from_input(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: Any,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
out: list = []
# Because we are json-centric, we expect a list for all sequences.
if type(value) is not list:
- raise TypeError(f'Invalid input value for "{fieldpath}";'
- f' expected a list, got a {type(value).__name__}')
+ raise TypeError(
+ f'Invalid input value for "{fieldpath}";'
+ f' expected a list, got a {type(value).__name__}'
+ )
childanntypes = typing.get_args(anntype)
@@ -443,9 +526,11 @@ class _Inputter(Generic[T]):
assert childanntypes
if len(value) != len(childanntypes):
- raise ValueError(f'Invalid tuple input for "{fieldpath}";'
- f' expected {len(childanntypes)} values,'
- f' found {len(value)}.')
+ raise ValueError(
+ f'Invalid tuple input for "{fieldpath}";'
+ f' expected {len(childanntypes)} values,'
+ f' found {len(value)}.'
+ )
for i, childanntype in enumerate(childanntypes):
childval = value[i]
@@ -454,13 +539,17 @@ class _Inputter(Generic[T]):
# and then just grab them.
if childanntype is typing.Any:
if not _is_valid_for_codec(childval, self._codec):
- raise TypeError(f'Item {i} of {fieldpath} contains'
- f' data type(s) not supported by json.')
+ raise TypeError(
+ f'Item {i} of {fieldpath} contains'
+ f' data type(s) not supported by json.'
+ )
out.append(childval)
else:
out.append(
- self._value_from_input(cls, fieldpath, childanntype,
- childval, ioattrs))
+ self._value_from_input(
+ cls, fieldpath, childanntype, childval, ioattrs
+ )
+ )
assert len(out) == len(childanntypes)
return tuple(out)
diff --git a/tools/efro/dataclassio/_outputter.py b/tools/efro/dataclassio/_outputter.py
index 03965e1c..65c5f395 100644
--- a/tools/efro/dataclassio/_outputter.py
+++ b/tools/efro/dataclassio/_outputter.py
@@ -16,10 +16,16 @@ import datetime
from typing import TYPE_CHECKING
from efro.util import check_utc
-from efro.dataclassio._base import (Codec, _parse_annotated, EXTRA_ATTRS_ATTR,
- _is_valid_for_codec, _get_origin,
- SIMPLE_TYPES, _raise_type_error,
- IOExtendedData)
+from efro.dataclassio._base import (
+ Codec,
+ _parse_annotated,
+ EXTRA_ATTRS_ATTR,
+ _is_valid_for_codec,
+ _get_origin,
+ SIMPLE_TYPES,
+ _raise_type_error,
+ IOExtendedData,
+)
from efro.dataclassio._prep import PrepSession
if TYPE_CHECKING:
@@ -30,8 +36,9 @@ if TYPE_CHECKING:
class _Outputter:
"""Validates or exports data contained in a dataclass instance."""
- def __init__(self, obj: Any, create: bool, codec: Codec,
- coerce_to_float: bool) -> None:
+ def __init__(
+ self, obj: Any, create: bool, codec: Codec, coerce_to_float: bool
+ ) -> None:
self._obj = obj
self._create = create
self._codec = codec
@@ -48,20 +55,24 @@ class _Outputter:
return self._process_dataclass(type(self._obj), self._obj, '')
- def soft_default_check(self, value: Any, anntype: Any,
- fieldpath: str) -> None:
+ def soft_default_check(
+ self, value: Any, anntype: Any, fieldpath: str
+ ) -> None:
"""(internal)"""
- self._process_value(type(value),
- fieldpath=fieldpath,
- anntype=anntype,
- value=value,
- ioattrs=None)
+ self._process_value(
+ type(value),
+ fieldpath=fieldpath,
+ anntype=anntype,
+ value=value,
+ ioattrs=None,
+ )
def _process_dataclass(self, cls: type, obj: Any, fieldpath: str) -> Any:
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
- prep = PrepSession(explicit=False).prep_dataclass(type(obj),
- recursion_level=0)
+ prep = PrepSession(explicit=False).prep_dataclass(
+ type(obj), recursion_level=0
+ )
assert prep is not None
fields = dataclasses.fields(obj)
out: dict[str, Any] | None = {} if self._create else None
@@ -102,15 +113,19 @@ class _Outputter:
f'Field {fieldname} of {cls.__name__} has'
f' no source of default values; store_default=False'
f' cannot be set for it. (AND THIS SHOULD HAVE BEEN'
- f' CAUGHT IN PREP!)')
+ f' CAUGHT IN PREP!)'
+ )
- outvalue = self._process_value(cls, subfieldpath, anntype, value,
- ioattrs)
+ outvalue = self._process_value(
+ cls, subfieldpath, anntype, value, ioattrs
+ )
if self._create:
assert out is not None
- storagename = (fieldname if
- (ioattrs is None or ioattrs.storagename is None)
- else ioattrs.storagename)
+ storagename = (
+ fieldname
+ if (ioattrs is None or ioattrs.storagename is None)
+ else ioattrs.storagename
+ )
out[storagename] = outvalue
# If there's extra-attrs stored on us, check/include them.
@@ -120,14 +135,21 @@ class _Outputter:
raise TypeError(
f'Extra attrs on \'{fieldpath}\' contains data type(s)'
f' not supported by \'{self._codec.value}\' codec:'
- f' {extra_attrs}.')
+ f' {extra_attrs}.'
+ )
if self._create:
assert out is not None
out.update(extra_attrs)
return out
- def _process_value(self, cls: type, fieldpath: str, anntype: Any,
- value: Any, ioattrs: IOAttrs | None) -> Any:
+ def _process_value(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: Any,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -140,7 +162,8 @@ class _Outputter:
f'Invalid value type for \'{fieldpath}\';'
f" 'Any' typed values must contain types directly"
f' supported by the specified codec ({self._codec.name});'
- f' found \'{type(value).__name__}\' which is not.')
+ f' found \'{type(value).__name__}\' which is not.'
+ )
return value if self._create else None
if origin is typing.Union or origin is types.UnionType:
@@ -153,8 +176,9 @@ class _Outputter:
c for c in typing.get_args(anntype) if c is not type(None)
] # noqa (pycodestyle complains about *is* with type)
assert len(childanntypes_l) == 1
- return self._process_value(cls, fieldpath, childanntypes_l[0],
- value, ioattrs)
+ return self._process_value(
+ cls, fieldpath, childanntypes_l[0], value, ioattrs
+ )
# Everything below this point assumes the annotation type resolves
# to a concrete type. (This should have been verified at prep time).
@@ -164,38 +188,50 @@ class _Outputter:
if origin in SIMPLE_TYPES:
if type(value) is not origin:
# Special case: if they want to coerce ints to floats, do so.
- if (self._coerce_to_float and origin is float
- and type(value) is int):
+ if (
+ self._coerce_to_float
+ and origin is float
+ and type(value) is int
+ ):
return float(value) if self._create else None
- _raise_type_error(fieldpath, type(value), (origin, ))
+ _raise_type_error(fieldpath, type(value), (origin,))
return value if self._create else None
if origin is tuple:
if not isinstance(value, tuple):
- raise TypeError(f'Expected a tuple for {fieldpath};'
- f' found a {type(value)}')
+ raise TypeError(
+ f'Expected a tuple for {fieldpath};'
+ f' found a {type(value)}'
+ )
childanntypes = typing.get_args(anntype)
# We should have verified this was non-zero at prep-time
assert childanntypes
if len(value) != len(childanntypes):
- raise TypeError(f'Tuple at {fieldpath} contains'
- f' {len(value)} values; type specifies'
- f' {len(childanntypes)}.')
+ raise TypeError(
+ f'Tuple at {fieldpath} contains'
+ f' {len(value)} values; type specifies'
+ f' {len(childanntypes)}.'
+ )
if self._create:
return [
- self._process_value(cls, fieldpath, childanntypes[i], x,
- ioattrs) for i, x in enumerate(value)
+ self._process_value(
+ cls, fieldpath, childanntypes[i], x, ioattrs
+ )
+ for i, x in enumerate(value)
]
for i, x in enumerate(value):
- self._process_value(cls, fieldpath, childanntypes[i], x,
- ioattrs)
+ self._process_value(
+ cls, fieldpath, childanntypes[i], x, ioattrs
+ )
return None
if origin is list:
if not isinstance(value, list):
- raise TypeError(f'Expected a list for {fieldpath};'
- f' found a {type(value)}')
+ raise TypeError(
+ f'Expected a list for {fieldpath};'
+ f' found a {type(value)}'
+ )
childanntypes = typing.get_args(anntype)
# 'Any' type children; make sure they are valid values for
@@ -206,7 +242,8 @@ class _Outputter:
raise TypeError(
f'Item {i} of {fieldpath} contains'
f' data type(s) not supported by the specified'
- f' codec ({self._codec.name}).')
+ f' codec ({self._codec.name}).'
+ )
# Hmm; should we do a copy here?
return value if self._create else None
@@ -214,18 +251,22 @@ class _Outputter:
assert len(childanntypes) == 1
if self._create:
return [
- self._process_value(cls, fieldpath, childanntypes[0], x,
- ioattrs) for x in value
+ self._process_value(
+ cls, fieldpath, childanntypes[0], x, ioattrs
+ )
+ for x in value
]
for x in value:
- self._process_value(cls, fieldpath, childanntypes[0], x,
- ioattrs)
+ self._process_value(
+ cls, fieldpath, childanntypes[0], x, ioattrs
+ )
return None
if origin is set:
if not isinstance(value, set):
- raise TypeError(f'Expected a set for {fieldpath};'
- f' found a {type(value)}')
+ raise TypeError(
+ f'Expected a set for {fieldpath};' f' found a {type(value)}'
+ )
childanntypes = typing.get_args(anntype)
# 'Any' type children; make sure they are valid Any values.
@@ -235,7 +276,8 @@ class _Outputter:
raise TypeError(
f'Set at {fieldpath} contains'
f' data type(s) not supported by the'
- f' specified codec ({self._codec.name}).')
+ f' specified codec ({self._codec.name}).'
+ )
return list(value) if self._create else None
# We contain elements of some specified type.
@@ -244,12 +286,15 @@ class _Outputter:
# Note: we output json-friendly values so this becomes
# a list.
return [
- self._process_value(cls, fieldpath, childanntypes[0], x,
- ioattrs) for x in value
+ self._process_value(
+ cls, fieldpath, childanntypes[0], x, ioattrs
+ )
+ for x in value
]
for x in value:
- self._process_value(cls, fieldpath, childanntypes[0], x,
- ioattrs)
+ self._process_value(
+ cls, fieldpath, childanntypes[0], x, ioattrs
+ )
return None
if origin is dict:
@@ -257,45 +302,63 @@ class _Outputter:
if dataclasses.is_dataclass(origin):
if not isinstance(value, origin):
- raise TypeError(f'Expected a {origin} for {fieldpath};'
- f' found a {type(value)}.')
+ raise TypeError(
+ f'Expected a {origin} for {fieldpath};'
+ f' found a {type(value)}.'
+ )
return self._process_dataclass(cls, value, fieldpath)
if issubclass(origin, Enum):
if not isinstance(value, origin):
- raise TypeError(f'Expected a {origin} for {fieldpath};'
- f' found a {type(value)}.')
+ raise TypeError(
+ f'Expected a {origin} for {fieldpath};'
+ f' found a {type(value)}.'
+ )
# At prep-time we verified that these enums had valid value
# types, so we can blindly return it here.
return value.value if self._create else None
if issubclass(origin, datetime.datetime):
if not isinstance(value, origin):
- raise TypeError(f'Expected a {origin} for {fieldpath};'
- f' found a {type(value)}.')
+ raise TypeError(
+ f'Expected a {origin} for {fieldpath};'
+ f' found a {type(value)}.'
+ )
check_utc(value)
if ioattrs is not None:
ioattrs.validate_datetime(value, fieldpath)
if self._codec is Codec.FIRESTORE:
return value
assert self._codec is Codec.JSON
- return [
- value.year, value.month, value.day, value.hour, value.minute,
- value.second, value.microsecond
- ] if self._create else None
+ return (
+ [
+ value.year,
+ value.month,
+ value.day,
+ value.hour,
+ value.minute,
+ value.second,
+ value.microsecond,
+ ]
+ if self._create
+ else None
+ )
if origin is bytes:
return self._process_bytes(cls, fieldpath, value)
raise TypeError(
- f"Field '{fieldpath}' of type '{anntype}' is unsupported here.")
+ f"Field '{fieldpath}' of type '{anntype}' is unsupported here."
+ )
def _process_bytes(self, cls: type, fieldpath: str, value: bytes) -> Any:
import base64
+
if not isinstance(value, bytes):
raise TypeError(
f'Expected bytes for {fieldpath} on {cls.__name__};'
- f' found a {type(value)}.')
+ f' found a {type(value)}.'
+ )
if not self._create:
return None
@@ -307,25 +370,34 @@ class _Outputter:
assert self._codec is Codec.FIRESTORE
return value
- def _process_dict(self, cls: type, fieldpath: str, anntype: Any,
- value: dict, ioattrs: IOAttrs | None) -> Any:
+ def _process_dict(
+ self,
+ cls: type,
+ fieldpath: str,
+ anntype: Any,
+ value: dict,
+ ioattrs: IOAttrs | None,
+ ) -> Any:
# pylint: disable=too-many-branches
if not isinstance(value, dict):
- raise TypeError(f'Expected a dict for {fieldpath};'
- f' found a {type(value)}.')
+ raise TypeError(
+ f'Expected a dict for {fieldpath};' f' found a {type(value)}.'
+ )
childtypes = typing.get_args(anntype)
assert len(childtypes) in (0, 2)
# We treat 'Any' dicts simply as json; we don't do any translating.
if not childtypes or childtypes[0] is typing.Any:
if not isinstance(value, dict) or not _is_valid_for_codec(
- value, self._codec):
+ value, self._codec
+ ):
raise TypeError(
f'Invalid value for Dict[Any, Any]'
f' at \'{fieldpath}\' on {cls.__name__};'
f' all keys and values must be directly compatible'
f' with the specified codec ({self._codec.name})'
- f' when dict type is Any.')
+ f' when dict type is Any.'
+ )
return value if self._create else None
# Ok; we've got a definite key type (which we verified as valid
@@ -340,9 +412,11 @@ class _Outputter:
raise TypeError(
f'Got invalid key type {type(key)} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected {keyanntype}.')
- outval = self._process_value(cls, fieldpath, valanntype, val,
- ioattrs)
+ f' expected {keyanntype}.'
+ )
+ outval = self._process_value(
+ cls, fieldpath, valanntype, val, ioattrs
+ )
if self._create:
assert out is not None
out[key] = outval
@@ -354,9 +428,11 @@ class _Outputter:
raise TypeError(
f'Got invalid key type {type(key)} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected an int.')
- outval = self._process_value(cls, fieldpath, valanntype, val,
- ioattrs)
+ f' expected an int.'
+ )
+ outval = self._process_value(
+ cls, fieldpath, valanntype, val, ioattrs
+ )
if self._create:
assert out is not None
out[str(key)] = outval
@@ -367,9 +443,11 @@ class _Outputter:
raise TypeError(
f'Got invalid key type {type(key)} for'
f' dict key at \'{fieldpath}\' on {cls.__name__};'
- f' expected a {keyanntype}.')
- outval = self._process_value(cls, fieldpath, valanntype, val,
- ioattrs)
+ f' expected a {keyanntype}.'
+ )
+ outval = self._process_value(
+ cls, fieldpath, valanntype, val, ioattrs
+ )
if self._create:
assert out is not None
out[str(key.value)] = outval
diff --git a/tools/efro/dataclassio/_pathcapture.py b/tools/efro/dataclassio/_pathcapture.py
index b0b34ada..2fb0c8a3 100644
--- a/tools/efro/dataclassio/_pathcapture.py
+++ b/tools/efro/dataclassio/_pathcapture.py
@@ -32,18 +32,23 @@ class _PathCapture:
if not self._is_dataclass:
raise TypeError(
f"Field path cannot include attribute '{name}' "
- f'under parent {self._cls}; parent types must be dataclasses.')
+ f'under parent {self._cls}; parent types must be dataclasses.'
+ )
- prep = PrepSession(explicit=False).prep_dataclass(self._cls,
- recursion_level=0)
+ prep = PrepSession(explicit=False).prep_dataclass(
+ self._cls, recursion_level=0
+ )
assert prep is not None
try:
anntype = prep.annotations[name]
except KeyError as exc:
raise AttributeError(f'{type(self)} has no {name} field.') from exc
anntype, ioattrs = _parse_annotated(anntype)
- storagename = (name if (ioattrs is None or ioattrs.storagename is None)
- else ioattrs.storagename)
+ storagename = (
+ name
+ if (ioattrs is None or ioattrs.storagename is None)
+ else ioattrs.storagename
+ )
origin = _get_origin(anntype)
return _PathCapture(origin, pathparts=self._pathparts + [storagename])
@@ -81,8 +86,10 @@ class DataclassFieldLookup(Generic[T]):
if not TYPE_CHECKING:
out = callback(_PathCapture(self.cls))
if not isinstance(out, _PathCapture):
- raise TypeError(f'Expected a valid path under'
- f' the provided object; got a {type(out)}.')
+ raise TypeError(
+ f'Expected a valid path under'
+ f' the provided object; got a {type(out)}.'
+ )
return out.path
return ''
@@ -102,6 +109,7 @@ class DataclassFieldLookup(Generic[T]):
if not isinstance(out, _PathCapture):
raise TypeError(
f'Expected a valid path under'
- f' the provided object; got a {type(out)}.')
+ f' the provided object; got a {type(out)}.'
+ )
outvals.append(out.path)
return outvals
diff --git a/tools/efro/dataclassio/_prep.py b/tools/efro/dataclassio/_prep.py
index d49a1323..d01db203 100644
--- a/tools/efro/dataclassio/_prep.py
+++ b/tools/efro/dataclassio/_prep.py
@@ -17,8 +17,7 @@ import datetime
from typing import TYPE_CHECKING, TypeVar, get_type_hints
# noinspection PyProtectedMember
-from efro.dataclassio._base import (_parse_annotated, _get_origin,
- SIMPLE_TYPES)
+from efro.dataclassio._base import _parse_annotated, _get_origin, SIMPLE_TYPES
if TYPE_CHECKING:
from typing import Any
@@ -58,8 +57,9 @@ def ioprep(cls: type, globalns: dict | None = None) -> None:
prepping happens as part of an execed string instead of within a
module.
"""
- PrepSession(explicit=True,
- globalns=globalns).prep_dataclass(cls, recursion_level=0)
+ PrepSession(explicit=True, globalns=globalns).prep_dataclass(
+ cls, recursion_level=0
+ )
def ioprepped(cls: type[T]) -> type[T]:
@@ -119,8 +119,9 @@ class PrepSession:
self.explicit = explicit
self.globalns = globalns
- def prep_dataclass(self, cls: type,
- recursion_level: int) -> PrepData | None:
+ def prep_dataclass(
+ self, cls: type, recursion_level: int
+ ) -> PrepData | None:
"""Run prep on a dataclass if necessary and return its prep data.
The only case where this will return None is for recursive types
@@ -165,22 +166,27 @@ class PrepSession:
' It is highly recommended to explicitly prep dataclasses'
' as soon as possible after definition (via'
' efro.dataclassio.ioprep() or the'
- ' @efro.dataclassio.ioprepped decorator).', cls)
+ ' @efro.dataclassio.ioprepped decorator).',
+ cls,
+ )
try:
# NOTE: Now passing the class' __dict__ (vars()) as locals
# which allows us to pick up nested classes, etc.
- resolved_annotations = get_type_hints(cls,
- localns=vars(cls),
- globalns=self.globalns,
- include_extras=True)
+ resolved_annotations = get_type_hints(
+ cls,
+ localns=vars(cls),
+ globalns=self.globalns,
+ include_extras=True,
+ )
# pylint: enable=unexpected-keyword-arg
except Exception as exc:
raise TypeError(
f'dataclassio prep for {cls} failed with error: {exc}.'
f' Make sure all types used in annotations are defined'
f' at the module or class level or add them as part of an'
- f' explicit prep call.') from exc
+ f' explicit prep call.'
+ ) from exc
# noinspection PyDataclass
fields = dataclasses.fields(cls)
@@ -210,20 +216,25 @@ class PrepSession:
# Make sure we don't have any clashes in our storage names.
if storagename in all_storage_names:
- raise TypeError(f'Multiple attrs on {cls} are using'
- f' storage-name \'{storagename}\'')
+ raise TypeError(
+ f'Multiple attrs on {cls} are using'
+ f' storage-name \'{storagename}\''
+ )
all_storage_names.add(storagename)
- self.prep_type(cls,
- attrname,
- anntype,
- ioattrs=ioattrs,
- recursion_level=recursion_level + 1)
+ self.prep_type(
+ cls,
+ attrname,
+ anntype,
+ ioattrs=ioattrs,
+ recursion_level=recursion_level + 1,
+ )
# Success! Store our resolved stuff with the class and we're done.
prepdata = PrepData(
annotations=resolved_annotations,
- storage_names_to_attr_names=storage_names_to_attr_names)
+ storage_names_to_attr_names=storage_names_to_attr_names,
+ )
setattr(cls, PREP_ATTR, prepdata)
# Clear our prep-session tag.
@@ -231,8 +242,14 @@ class PrepSession:
delattr(cls, PREP_SESSION_ATTR)
return prepdata
- def prep_type(self, cls: type, attrname: str, anntype: Any,
- ioattrs: IOAttrs | None, recursion_level: int) -> None:
+ def prep_type(
+ self,
+ cls: type,
+ attrname: str,
+ anntype: Any,
+ ioattrs: IOAttrs | None,
+ recursion_level: int,
+ ) -> None:
"""Run prep on a dataclass."""
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
@@ -244,10 +261,9 @@ class PrepSession:
origin = _get_origin(anntype)
if origin is typing.Union or origin is types.UnionType:
- self.prep_union(cls,
- attrname,
- anntype,
- recursion_level=recursion_level + 1)
+ self.prep_union(
+ cls, attrname, anntype, recursion_level=recursion_level + 1
+ )
return
if anntype is typing.Any:
@@ -258,7 +274,8 @@ class PrepSession:
if not isinstance(origin, type):
raise TypeError(
f'Unsupported type found for \'{attrname}\' on {cls}:'
- f' {anntype}')
+ f' {anntype}'
+ )
# If a soft_default value/factory was passed, we do some basic
# type checking on the top-level value here. We also run full
@@ -298,12 +315,15 @@ class PrepSession:
if len(childtypes) > 1:
raise TypeError(
f'Unrecognized typing arg count {len(childtypes)}'
- f" for {anntype} attr '{attrname}' on {cls}")
- self.prep_type(cls,
- attrname,
- childtypes[0],
- ioattrs=None,
- recursion_level=recursion_level + 1)
+ f" for {anntype} attr '{attrname}' on {cls}"
+ )
+ self.prep_type(
+ cls,
+ attrname,
+ childtypes[0],
+ ioattrs=None,
+ recursion_level=recursion_level + 1,
+ )
return
if origin is dict:
@@ -324,18 +344,21 @@ class PrepSession:
else:
raise TypeError(
f'Dict key type {childtypes[0]} for \'{attrname}\''
- f' on {cls.__name__} is not supported by dataclassio.')
+ f' on {cls.__name__} is not supported by dataclassio.'
+ )
# For value types we support any of our normal types.
if not childtypes or _get_origin(childtypes[1]) is typing.Any:
# 'Any' needs no further checks (just checked per-instance).
pass
else:
- self.prep_type(cls,
- attrname,
- childtypes[1],
- ioattrs=None,
- recursion_level=recursion_level + 1)
+ self.prep_type(
+ cls,
+ attrname,
+ childtypes[1],
+ ioattrs=None,
+ recursion_level=recursion_level + 1,
+ )
return
# For Tuples, simply check individual member types.
@@ -346,18 +369,23 @@ class PrepSession:
if not childtypes:
raise TypeError(
f'Tuple at \'{attrname}\''
- f' has no type args; dataclassio requires type args.')
+ f' has no type args; dataclassio requires type args.'
+ )
if childtypes[-1] is ...:
- raise TypeError(f'Found ellipsis as part of type for'
- f' \'{attrname}\' on {cls.__name__};'
- f' these are not'
- f' supported by dataclassio.')
+ raise TypeError(
+ f'Found ellipsis as part of type for'
+ f' \'{attrname}\' on {cls.__name__};'
+ f' these are not'
+ f' supported by dataclassio.'
+ )
for childtype in childtypes:
- self.prep_type(cls,
- attrname,
- childtype,
- ioattrs=None,
- recursion_level=recursion_level + 1)
+ self.prep_type(
+ cls,
+ attrname,
+ childtype,
+ ioattrs=None,
+ recursion_level=recursion_level + 1,
+ )
return
if issubclass(origin, Enum):
@@ -376,26 +404,35 @@ class PrepSession:
if origin is bytes:
return
- raise TypeError(f"Attr '{attrname}' on {cls.__name__} contains"
- f" type '{anntype}'"
- f' which is not supported by dataclassio.')
+ raise TypeError(
+ f"Attr '{attrname}' on {cls.__name__} contains"
+ f" type '{anntype}'"
+ f' which is not supported by dataclassio.'
+ )
- def prep_union(self, cls: type, attrname: str, anntype: Any,
- recursion_level: int) -> None:
+ def prep_union(
+ self, cls: type, attrname: str, anntype: Any, recursion_level: int
+ ) -> None:
"""Run prep on a Union type."""
typeargs = typing.get_args(anntype)
- if (len(typeargs) != 2
- or len([c for c in typeargs if c is type(None)]) != 1): # noqa
- raise TypeError(f'Union {anntype} for attr \'{attrname}\' on'
- f' {cls.__name__} is not supported by dataclassio;'
- f' only 2 member Unions with one type being None'
- f' are supported.')
+ if (
+ len(typeargs) != 2
+ or len([c for c in typeargs if c is type(None)]) != 1
+ ): # noqa
+ raise TypeError(
+ f'Union {anntype} for attr \'{attrname}\' on'
+ f' {cls.__name__} is not supported by dataclassio;'
+ f' only 2 member Unions with one type being None'
+ f' are supported.'
+ )
for childtype in typeargs:
- self.prep_type(cls,
- attrname,
- childtype,
- None,
- recursion_level=recursion_level + 1)
+ self.prep_type(
+ cls,
+ attrname,
+ childtype,
+ None,
+ recursion_level=recursion_level + 1,
+ )
def prep_enum(self, enumtype: type[Enum]) -> None:
"""Run prep on an enum type."""
@@ -406,13 +443,17 @@ class PrepSession:
# find any others.
for enumval in enumtype:
if not isinstance(enumval.value, (str, int)):
- raise TypeError(f'Enum value {enumval} has value type'
- f' {type(enumval.value)}; only str and int is'
- f' supported by dataclassio.')
+ raise TypeError(
+ f'Enum value {enumval} has value type'
+ f' {type(enumval.value)}; only str and int is'
+ f' supported by dataclassio.'
+ )
if valtype is None:
valtype = type(enumval.value)
else:
if type(enumval.value) is not valtype:
- raise TypeError(f'Enum type {enumtype} has multiple'
- f' value types; dataclassio requires'
- f' them to be uniform.')
+ raise TypeError(
+ f'Enum type {enumtype} has multiple'
+ f' value types; dataclassio requires'
+ f' them to be uniform.'
+ )
diff --git a/tools/efro/dataclassio/extras.py b/tools/efro/dataclassio/extras.py
index cfa19920..327f829e 100644
--- a/tools/efro/dataclassio/extras.py
+++ b/tools/efro/dataclassio/extras.py
@@ -40,8 +40,10 @@ def _diff(obj1: Any, obj2: Any, indent: int) -> str:
assert dataclasses.is_dataclass(obj1)
assert dataclasses.is_dataclass(obj2)
if type(obj1) is not type(obj2):
- raise TypeError(f'Passed objects are not of the same'
- f' type ({type(obj1)} and {type(obj2)}).')
+ raise TypeError(
+ f'Passed objects are not of the same'
+ f' type ({type(obj1)} and {type(obj2)}).'
+ )
bits: list[str] = []
indentstr = ' ' * indent
fields = dataclasses.fields(obj1)
@@ -51,8 +53,11 @@ def _diff(obj1: Any, obj2: Any, indent: int) -> str:
val2 = getattr(obj2, fieldname)
# For nested dataclasses, dive in and do nice piecewise compares.
- if (dataclasses.is_dataclass(val1) and dataclasses.is_dataclass(val2)
- and type(val1) is type(val2)):
+ if (
+ dataclasses.is_dataclass(val1)
+ and dataclasses.is_dataclass(val2)
+ and type(val1) is type(val2)
+ ):
diff = _diff(val1, val2, indent + 2)
if diff != '':
bits.append(f'{indentstr}{fieldname}:')
diff --git a/tools/efro/debug.py b/tools/efro/debug.py
index 0b9b12a3..e5372863 100644
--- a/tools/efro/debug.py
+++ b/tools/efro/debug.py
@@ -80,13 +80,15 @@ def getrefs(obj: Any) -> list[Any]:
def printfiles(file: TextIO | None = None) -> None:
"""Print info about open files in the current app."""
import io
+
file = sys.stderr if file is None else file
try:
import psutil
except ImportError:
print(
"Error: printfiles requires the 'psutil' module to be installed.",
- file=file)
+ file=file,
+ )
return
proc = psutil.Process()
@@ -110,16 +112,20 @@ def printfiles(file: TextIO | None = None) -> None:
textio_s = id(textio) if textio is not None else ''
fileio = fileio_ids.get(ofile.fd)
fileio_s = id(fileio) if fileio is not None else ''
- print(f'#{i+1}: path={ofile.path!r},'
- f' fd={ofile.fd}, mode={mode!r}, TextIOWrapper={textio_s},'
- f' FileIO={fileio_s}')
+ print(
+ f'#{i+1}: path={ofile.path!r},'
+ f' fd={ofile.fd}, mode={mode!r}, TextIOWrapper={textio_s},'
+ f' FileIO={fileio_s}'
+ )
-def printrefs(obj: Any,
- max_level: int = 2,
- exclude_objs: list[Any] | None = None,
- expand_ids: list[int] | None = None,
- file: TextIO | None = None) -> None:
+def printrefs(
+ obj: Any,
+ max_level: int = 2,
+ exclude_objs: list[Any] | None = None,
+ expand_ids: list[int] | None = None,
+ file: TextIO | None = None,
+) -> None:
"""Print human readable list of objects referring to an object.
'max_level' specifies how many levels of recursion are printed.
@@ -129,12 +135,14 @@ def printrefs(obj: Any,
'expand_ids' can be a list of object ids; if that particular object is
found, it will always be expanded even if max_level has been reached.
"""
- _printrefs(obj,
- level=0,
- max_level=max_level,
- exclude_objs=[] if exclude_objs is None else exclude_objs,
- expand_ids=[] if expand_ids is None else expand_ids,
- file=sys.stderr if file is None else file)
+ _printrefs(
+ obj,
+ level=0,
+ max_level=max_level,
+ exclude_objs=[] if exclude_objs is None else exclude_objs,
+ expand_ids=[] if expand_ids is None else expand_ids,
+ file=sys.stderr if file is None else file,
+ )
def printtypes(limit: int = 50, file: TextIO | None = None) -> None:
@@ -160,8 +168,8 @@ def printtypes(limit: int = 50, file: TextIO | None = None) -> None:
print(f'Types most allocated ({allobjc} total objects):', file=file)
for i, tpitem in enumerate(
- sorted(objtypes.items(), key=lambda x: x[1],
- reverse=True)[:limit]):
+ sorted(objtypes.items(), key=lambda x: x[1], reverse=True)[:limit]
+ ):
tpname, tpval = tpitem
percent = tpval / allobjc * 100.0
print(f'{i+1}: {tpname}: {tpval} ({percent:.2f}%)', file=file)
@@ -183,8 +191,13 @@ def _desc(obj: Any) -> str:
# Print length and the first few types.
tps = [_desctype(i) for i in obj[:3]]
tpsj = ', '.join(tps)
- tpss = (f', contains [{tpsj}, ...]'
- if len(obj) > 3 else f', contains [{tpsj}]' if tps else '')
+ tpss = (
+ f', contains [{tpsj}, ...]'
+ if len(obj) > 3
+ else f', contains [{tpsj}]'
+ if tps
+ else ''
+ )
extra = f' (len {len(obj)}{tpss})'
elif isinstance(obj, dict):
# If it seems to be the vars() for a type or module,
@@ -199,16 +212,27 @@ def _desc(obj: Any) -> str:
f'{repr(n)}: {_desctype(v)}' for n, v in list(obj.items())[:3]
]
pairsj = ', '.join(pairs)
- pairss = (f', contains {{{pairsj}, ...}}' if len(obj) > 3 else
- f', contains {{{pairsj}}}' if pairs else '')
+ pairss = (
+ f', contains {{{pairsj}, ...}}'
+ if len(obj) > 3
+ else f', contains {{{pairsj}}}'
+ if pairs
+ else ''
+ )
extra = f' (len {len(obj)}{pairss})'
if extra is None:
extra = ''
return f'{_desctype(obj)} @ {id(obj)}{extra}'
-def _printrefs(obj: Any, level: int, max_level: int, exclude_objs: list,
- expand_ids: list[int], file: TextIO) -> None:
+def _printrefs(
+ obj: Any,
+ level: int,
+ max_level: int,
+ exclude_objs: list,
+ expand_ids: list[int],
+ file: TextIO,
+) -> None:
ind = ' ' * level
print(ind + _desc(obj), file=file)
v = vars()
@@ -233,9 +257,11 @@ def _printrefs(obj: Any, level: int, max_level: int, exclude_objs: list,
# The 'refs' list we just made will be listed as a referrer
# of this obj, so explicitly exclude it from the obj's listing.
- _printrefs(ref,
- level=level + 1,
- max_level=max_level,
- exclude_objs=exclude_objs + [refs],
- expand_ids=expand_ids,
- file=file)
+ _printrefs(
+ ref,
+ level=level + 1,
+ max_level=max_level,
+ exclude_objs=exclude_objs + [refs],
+ expand_ids=expand_ids,
+ file=file,
+ )
diff --git a/tools/efro/error.py b/tools/efro/error.py
index 6b4853bb..7f13148c 100644
--- a/tools/efro/error.py
+++ b/tools/efro/error.py
@@ -31,6 +31,7 @@ class CleanError(Exception):
If the error has an empty message, prints nothing (not even a newline).
"""
from efro.terminal import Clr
+
errstr = str(self)
if errstr:
print(f'{Clr.SRED}{errstr}{Clr.RST}', flush=flush)
@@ -95,9 +96,18 @@ def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool:
import urllib.error
import http.client
import socket
- if isinstance(exc, (urllib.error.URLError, ConnectionError,
- http.client.IncompleteRead, http.client.BadStatusLine,
- http.client.RemoteDisconnected, socket.timeout)):
+
+ if isinstance(
+ exc,
+ (
+ urllib.error.URLError,
+ ConnectionError,
+ http.client.IncompleteRead,
+ http.client.BadStatusLine,
+ http.client.RemoteDisconnected,
+ socket.timeout,
+ ),
+ ):
# Special case: although an HTTPError is a subclass of URLError,
# we don't consider it a communication error. It generally means we
@@ -120,17 +130,16 @@ def is_urllib_communication_error(exc: BaseException, url: str | None) -> bool:
if exc.errno == 10051: # Windows unreachable network error.
return True
if exc.errno in {
- errno.ETIMEDOUT,
- errno.EHOSTUNREACH,
- errno.ENETUNREACH,
+ errno.ETIMEDOUT,
+ errno.EHOSTUNREACH,
+ errno.ENETUNREACH,
}:
return True
return False
def is_requests_communication_error(exc: BaseException) -> bool:
- """Is the provided exception a communication-related error from requests?
- """
+ """Is the provided exception a communication-related error from requests?"""
import requests
# Looks like this maps pretty well onto requests' ConnectionError
@@ -153,18 +162,18 @@ def is_udp_communication_error(exc: BaseException) -> bool:
if exc.errno == 10051: # Windows unreachable network error.
return True
if exc.errno in {
- errno.EADDRNOTAVAIL,
- errno.ETIMEDOUT,
- errno.EHOSTUNREACH,
- errno.ENETUNREACH,
- errno.EINVAL,
- errno.EPERM,
- errno.EACCES,
- # Windows 'invalid argument' error.
- 10022,
- # Windows 'a socket operation was attempted to'
- # 'an unreachable network' error.
- 10051,
+ errno.EADDRNOTAVAIL,
+ errno.ETIMEDOUT,
+ errno.EHOSTUNREACH,
+ errno.ENETUNREACH,
+ errno.EINVAL,
+ errno.EPERM,
+ errno.EACCES,
+ # Windows 'invalid argument' error.
+ 10022,
+ # Windows 'a socket operation was attempted to'
+ # 'an unreachable network' error.
+ 10051,
}:
return True
return False
@@ -181,11 +190,14 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
"""
import ssl
- if isinstance(exc, (
+ if isinstance(
+ exc,
+ (
ConnectionError,
TimeoutError,
EOFError,
- )):
+ ),
+ ):
return True
# Also some specific errno ones.
@@ -193,9 +205,9 @@ def is_asyncio_streams_communication_error(exc: BaseException) -> bool:
if exc.errno == 10051: # Windows unreachable network error.
return True
if exc.errno in {
- errno.ETIMEDOUT,
- errno.EHOSTUNREACH,
- errno.ENETUNREACH,
+ errno.ETIMEDOUT,
+ errno.EHOSTUNREACH,
+ errno.ENETUNREACH,
}:
return True
diff --git a/tools/efro/log.py b/tools/efro/log.py
index 854fcc70..10f2a5f9 100644
--- a/tools/efro/log.py
+++ b/tools/efro/log.py
@@ -30,6 +30,7 @@ class LogLevel(Enum):
Note that these values are not currently interchangeable with the
logging.ERROR, logging.DEBUG, etc. values.
"""
+
DEBUG = 0
INFO = 1
WARNING = 2
@@ -42,7 +43,7 @@ LEVELNO_LOG_LEVELS = {
logging.INFO: LogLevel.INFO,
logging.WARNING: LogLevel.WARNING,
logging.ERROR: LogLevel.ERROR,
- logging.CRITICAL: LogLevel.CRITICAL
+ logging.CRITICAL: LogLevel.CRITICAL,
}
LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = {
@@ -50,9 +51,12 @@ LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = {
logging.INFO: ('', ''),
logging.WARNING: (TerminalColor.YELLOW.value, TerminalColor.RESET.value),
logging.ERROR: (TerminalColor.RED.value, TerminalColor.RESET.value),
- logging.CRITICAL:
- (TerminalColor.STRONG_MAGENTA.value + TerminalColor.BOLD.value +
- TerminalColor.BG_BLACK.value, TerminalColor.RESET.value),
+ logging.CRITICAL: (
+ TerminalColor.STRONG_MAGENTA.value
+ + TerminalColor.BOLD.value
+ + TerminalColor.BG_BLACK.value,
+ TerminalColor.RESET.value,
+ ),
}
@@ -60,8 +64,8 @@ LEVELNO_COLOR_CODES: dict[int, tuple[str, str]] = {
@dataclass
class LogEntry:
"""Single logged message."""
- name: Annotated[str,
- IOAttrs('n', soft_default='root', store_default=False)]
+
+ name: Annotated[str, IOAttrs('n', soft_default='root', store_default=False)]
message: Annotated[str, IOAttrs('m')]
level: Annotated[LogLevel, IOAttrs('l')]
time: Annotated[datetime.datetime, IOAttrs('t')]
@@ -95,15 +99,16 @@ class LogHandler(logging.Handler):
# Otherwise we can get infinite loops as those prints come back to us
# as new log entries.
- def __init__(self,
- path: str | Path | None,
- echofile: TextIO | None,
- suppress_non_root_debug: bool = False,
- cache_size_limit: int = 0):
+ def __init__(
+ self,
+ path: str | Path | None,
+ echofile: TextIO | None,
+ suppress_non_root_debug: bool = False,
+ cache_size_limit: int = 0,
+ ):
super().__init__()
# pylint: disable=consider-using-with
- self._file = (None
- if path is None else open(path, 'w', encoding='utf-8'))
+ self._file = None if path is None else open(path, 'w', encoding='utf-8')
self._echofile = echofile
self._callbacks_lock = Lock()
self._callbacks: list[Callable[[LogEntry], None]] = []
@@ -111,7 +116,7 @@ class LogHandler(logging.Handler):
self._file_chunks: dict[str, list[str]] = {'stdout': [], 'stderr': []}
self._file_chunk_ship_task: dict[str, asyncio.Task | None] = {
'stdout': None,
- 'stderr': None
+ 'stderr': None,
}
self._cache_size = 0
assert cache_size_limit >= 0
@@ -152,12 +157,13 @@ class LogHandler(logging.Handler):
# Try to make some noise however we can.
print('LogHandler died!!!', file=sys.stderr)
import traceback
+
traceback.print_exc()
raise
- def get_cached(self,
- start_index: int = 0,
- max_entries: int | None = None) -> LogArchive:
+ def get_cached(
+ self, start_index: int = 0, max_entries: int | None = None
+ ) -> LogArchive:
"""Build and return an archive of cached log entries.
This will only include entries that have been processed by the
@@ -174,8 +180,11 @@ class LogHandler(logging.Handler):
# Transform start_index to our present cache space.
start_index -= self._cache_index_offset
# Calc end-index in our present cache space.
- end_index = (len(self._cache)
- if max_entries is None else start_index + max_entries)
+ end_index = (
+ len(self._cache)
+ if max_entries is None
+ else start_index + max_entries
+ )
# Clamp both indexes to both ends of our present space.
start_index = max(0, min(start_index, len(self._cache)))
@@ -184,7 +193,8 @@ class LogHandler(logging.Handler):
return LogArchive(
log_size=self._cache_index_offset + len(self._cache),
start_index=start_index + self._cache_index_offset,
- entries=[e[1] for e in self._cache[start_index:end_index]])
+ entries=[e[1] for e in self._cache[start_index:end_index]],
+ )
def emit(self, record: logging.LogRecord) -> None:
# Called by logging to send us records.
@@ -196,8 +206,11 @@ class LogHandler(logging.Handler):
# Special case - filter out this common extra-chatty category.
# TODO - should use a standard logging.Filter for this.
- if (self._suppress_non_root_debug and record.name != 'root'
- and record.levelname == 'DEBUG'):
+ if (
+ self._suppress_non_root_debug
+ and record.name != 'root'
+ and record.levelname == 'DEBUG'
+ ):
return
# We want to forward as much as we can along without processing it
@@ -219,27 +232,40 @@ class LogHandler(logging.Handler):
self._echofile.write(f'{msg}\n')
self._event_loop.call_soon_threadsafe(
- tpartial(self._emit_in_thread, record.name, record.levelno,
- record.created, msg))
+ tpartial(
+ self._emit_in_thread,
+ record.name,
+ record.levelno,
+ record.created,
+ msg,
+ )
+ )
- def _emit_in_thread(self, name: str, levelno: int, created: float,
- message: str) -> None:
+ def _emit_in_thread(
+ self, name: str, levelno: int, created: float, message: str
+ ) -> None:
try:
self._emit_entry(
- LogEntry(name=name,
- message=message,
- level=LEVELNO_LOG_LEVELS.get(levelno, LogLevel.INFO),
- time=datetime.datetime.fromtimestamp(
- created, datetime.timezone.utc)))
+ LogEntry(
+ name=name,
+ message=message,
+ level=LEVELNO_LOG_LEVELS.get(levelno, LogLevel.INFO),
+ time=datetime.datetime.fromtimestamp(
+ created, datetime.timezone.utc
+ ),
+ )
+ )
except Exception:
import traceback
+
traceback.print_exc(file=self._echofile)
def file_write(self, name: str, output: str) -> None:
"""Send raw stdout/stderr output to the logger to be collated."""
self._event_loop.call_soon_threadsafe(
- tpartial(self._file_write_in_thread, name, output))
+ tpartial(self._file_write_in_thread, name, output)
+ )
def _file_write_in_thread(self, name: str, output: str) -> None:
try:
@@ -263,19 +289,23 @@ class LogHandler(logging.Handler):
# after a short bit if we never get a newline.
ship_task = self._file_chunk_ship_task[name]
if ship_task is None:
- self._file_chunk_ship_task[name] = (
- self._event_loop.create_task(
- self._ship_chunks_task(name)))
+ self._file_chunk_ship_task[
+ name
+ ] = self._event_loop.create_task(
+ self._ship_chunks_task(name)
+ )
except Exception:
import traceback
+
traceback.print_exc(file=self._echofile)
def file_flush(self, name: str) -> None:
"""Send raw stdout/stderr flush to the logger to be collated."""
self._event_loop.call_soon_threadsafe(
- tpartial(self._file_flush_in_thread, name))
+ tpartial(self._file_flush_in_thread, name)
+ )
def _file_flush_in_thread(self, name: str) -> None:
try:
@@ -287,6 +317,7 @@ class LogHandler(logging.Handler):
except Exception:
import traceback
+
traceback.print_exc(file=self._echofile)
async def _ship_chunks_task(self, name: str) -> None:
@@ -300,10 +331,10 @@ class LogHandler(logging.Handler):
text = ''.join(self._file_chunks[name]).removesuffix('\n')
self._emit_entry(
- LogEntry(name=name,
- message=text,
- level=LogLevel.INFO,
- time=utc_now()))
+ LogEntry(
+ name=name, message=text, level=LogLevel.INFO, time=utc_now()
+ )
+ )
self._file_chunks[name] = []
ship_task = self._file_chunk_ship_task[name]
if cancel_ship_task and ship_task is not None:
@@ -319,8 +350,14 @@ class LogHandler(logging.Handler):
# Do a rough calc of how many bytes this entry consumes.
entry_size = sum(
sys.getsizeof(x)
- for x in (entry, entry.name, entry.message, entry.level,
- entry.time))
+ for x in (
+ entry,
+ entry.name,
+ entry.message,
+ entry.level,
+ entry.time,
+ )
+ )
self._cache.append((entry_size, entry))
self._cache_size += entry_size
@@ -339,6 +376,7 @@ class LogHandler(logging.Handler):
# Only print one callback error to avoid insanity.
if not self._printed_callback_error:
import traceback
+
traceback.print_exc(file=self._echofile)
self._printed_callback_error = True
@@ -353,8 +391,9 @@ class LogHandler(logging.Handler):
class FileLogEcho:
"""A file-like object for forwarding stdout/stderr to a LogHandler."""
- def __init__(self, original: TextIO, name: str,
- handler: LogHandler) -> None:
+ def __init__(
+ self, original: TextIO, name: str, handler: LogHandler
+ ) -> None:
assert name in ('stdout', 'stderr')
self._original = original
self._name = name
@@ -379,11 +418,13 @@ class FileLogEcho:
return self._original.isatty()
-def setup_logging(log_path: str | Path | None,
- level: LogLevel,
- suppress_non_root_debug: bool = False,
- log_stdout_stderr: bool = False,
- cache_size_limit: int = 0) -> LogHandler:
+def setup_logging(
+ log_path: str | Path | None,
+ level: LogLevel,
+ suppress_non_root_debug: bool = False,
+ log_stdout_stderr: bool = False,
+ cache_size_limit: int = 0,
+) -> LogHandler:
"""Set up our logging environment.
Returns the custom handler which can be used to fetch information
@@ -414,16 +455,19 @@ def setup_logging(log_path: str | Path | None,
# echofile=sys.stderr if sys.stderr.isatty() else None,
echofile=sys.stderr,
suppress_non_root_debug=suppress_non_root_debug,
- cache_size_limit=cache_size_limit)
+ cache_size_limit=cache_size_limit,
+ )
# Note: going ahead with force=True here so that we replace any
# existing logger. Though we warn if it looks like we are doing
# that so we can try to avoid creating the first one.
had_previous_handlers = bool(logging.root.handlers)
- logging.basicConfig(level=lmap[level],
- format='%(message)s',
- handlers=[loghandler],
- force=True)
+ logging.basicConfig(
+ level=lmap[level],
+ format='%(message)s',
+ handlers=[loghandler],
+ force=True,
+ )
if had_previous_handlers:
logging.warning('setup_logging: force-replacing previous handlers.')
@@ -431,8 +475,10 @@ def setup_logging(log_path: str | Path | None,
# log entries from it.
if log_stdout_stderr:
sys.stdout = FileLogEcho( # type: ignore
- sys.stdout, 'stdout', loghandler)
+ sys.stdout, 'stdout', loghandler
+ )
sys.stderr = FileLogEcho( # type: ignore
- sys.stderr, 'stderr', loghandler)
+ sys.stderr, 'stderr', loghandler
+ )
return loghandler
diff --git a/tools/efro/message/__init__.py b/tools/efro/message/__init__.py
index 326ba025..ff67b2b1 100644
--- a/tools/efro/message/__init__.py
+++ b/tools/efro/message/__init__.py
@@ -8,20 +8,36 @@ from __future__ import annotations
from efro.util import set_canonical_module
from efro.message._protocol import MessageProtocol
-from efro.message._sender import (MessageSender, BoundMessageSender)
-from efro.message._receiver import (MessageReceiver, BoundMessageReceiver)
-from efro.message._module import (create_sender_module, create_receiver_module)
-from efro.message._message import (Message, Response, SysResponse,
- EmptySysResponse, ErrorSysResponse,
- StringResponse, BoolResponse,
- UnregisteredMessageIDError)
+from efro.message._sender import MessageSender, BoundMessageSender
+from efro.message._receiver import MessageReceiver, BoundMessageReceiver
+from efro.message._module import create_sender_module, create_receiver_module
+from efro.message._message import (
+ Message,
+ Response,
+ SysResponse,
+ EmptySysResponse,
+ ErrorSysResponse,
+ StringResponse,
+ BoolResponse,
+ UnregisteredMessageIDError,
+)
__all__ = [
- 'Message', 'Response', 'SysResponse', 'EmptySysResponse',
- 'ErrorSysResponse', 'StringResponse', 'BoolResponse', 'MessageProtocol',
- 'MessageSender', 'BoundMessageSender', 'MessageReceiver',
- 'BoundMessageReceiver', 'create_sender_module', 'create_receiver_module',
- 'UnregisteredMessageIDError'
+ 'Message',
+ 'Response',
+ 'SysResponse',
+ 'EmptySysResponse',
+ 'ErrorSysResponse',
+ 'StringResponse',
+ 'BoolResponse',
+ 'MessageProtocol',
+ 'MessageSender',
+ 'BoundMessageSender',
+ 'MessageReceiver',
+ 'BoundMessageReceiver',
+ 'create_sender_module',
+ 'create_receiver_module',
+ 'UnregisteredMessageIDError',
]
# Have these things present themselves cleanly as 'thismodule.SomeClass'
diff --git a/tools/efro/message/_message.py b/tools/efro/message/_message.py
index b0d03265..9343a396 100644
--- a/tools/efro/message/_message.py
+++ b/tools/efro/message/_message.py
@@ -57,6 +57,7 @@ class ErrorSysResponse(SysResponse):
class ErrorType(Enum):
"""Type of error that occurred while sending a message."""
+
REMOTE = 0
REMOTE_CLEAN = 1
LOCAL = 2
diff --git a/tools/efro/message/_module.py b/tools/efro/message/_module.py
index 16c09828..2fa91fa2 100644
--- a/tools/efro/message/_module.py
+++ b/tools/efro/message/_module.py
@@ -38,20 +38,23 @@ def create_sender_module(
If 'private' is True, class-names will be prefixed with an '_'.
- Note that generated line lengths are not clipped, so output may need
- to be run through a formatter to prevent lint warnings about excessive
- line lengths.
+ Note: output code may have long lines and should generally be run
+ through a formatter. We should perhaps move this functionality to
+ efrotools so we can include that functionality inline.
"""
protocol = _protocol_from_code(
- build_time_protocol_create_code if build_time_protocol_create_code
- is not None else protocol_create_code)
+ build_time_protocol_create_code
+ if build_time_protocol_create_code is not None
+ else protocol_create_code
+ )
return protocol.do_create_sender_module(
basename=basename,
protocol_create_code=protocol_create_code,
enable_sync_sends=enable_sync_sends,
enable_async_sends=enable_async_sends,
private=private,
- protocol_module_level_import_code=protocol_module_level_import_code)
+ protocol_module_level_import_code=protocol_module_level_import_code,
+ )
def create_receiver_module(
@@ -62,7 +65,7 @@ def create_receiver_module(
protocol_module_level_import_code: str | None = None,
build_time_protocol_create_code: str | None = None,
) -> str:
- """"Create a Python module defining a MessageReceiver subclass.
+ """ "Create a Python module defining a MessageReceiver subclass.
This class is primarily for type checking and will contain overrides
for the register method for message/response types defined in
@@ -81,14 +84,17 @@ def create_receiver_module(
line lengths.
"""
protocol = _protocol_from_code(
- build_time_protocol_create_code if build_time_protocol_create_code
- is not None else protocol_create_code)
+ build_time_protocol_create_code
+ if build_time_protocol_create_code is not None
+ else protocol_create_code
+ )
return protocol.do_create_receiver_module(
basename=basename,
protocol_create_code=protocol_create_code,
is_async=is_async,
private=private,
- protocol_module_level_import_code=protocol_module_level_import_code)
+ protocol_module_level_import_code=protocol_module_level_import_code,
+ )
def _protocol_from_code(protocol_create_code: str) -> MessageProtocol:
@@ -98,5 +104,6 @@ def _protocol_from_code(protocol_create_code: str) -> MessageProtocol:
if not isinstance(protocol, MessageProtocol):
raise RuntimeError(
f'protocol_create_code yielded'
- f' a {type(protocol)}; expected a MessageProtocol instance.')
+ f' a {type(protocol)}; expected a MessageProtocol instance.'
+ )
return protocol
diff --git a/tools/efro/message/_protocol.py b/tools/efro/message/_protocol.py
index 1bf5e178..cd9fcd50 100644
--- a/tools/efro/message/_protocol.py
+++ b/tools/efro/message/_protocol.py
@@ -11,11 +11,19 @@ import traceback
import json
from efro.error import CleanError, CommunicationError
-from efro.dataclassio import (is_ioprepped_dataclass, dataclass_to_dict,
- dataclass_from_dict)
-from efro.message._message import (Message, Response, SysResponse,
- ErrorSysResponse, EmptySysResponse,
- UnregisteredMessageIDError)
+from efro.dataclassio import (
+ is_ioprepped_dataclass,
+ dataclass_to_dict,
+ dataclass_from_dict,
+)
+from efro.message._message import (
+ Message,
+ Response,
+ SysResponse,
+ ErrorSysResponse,
+ EmptySysResponse,
+ UnregisteredMessageIDError,
+)
if TYPE_CHECKING:
from typing import Any, Literal
@@ -30,13 +38,15 @@ class MessageProtocol:
etc.
"""
- def __init__(self,
- message_types: dict[int, type[Message]],
- response_types: dict[int, type[Response]],
- forward_communication_errors: bool = False,
- forward_clean_errors: bool = False,
- remote_errors_include_stack_traces: bool = False,
- log_remote_errors: bool = True) -> None:
+ def __init__(
+ self,
+ message_types: dict[int, type[Message]],
+ response_types: dict[int, type[Response]],
+ forward_communication_errors: bool = False,
+ forward_clean_errors: bool = False,
+ remote_errors_include_stack_traces: bool = False,
+ log_remote_errors: bool = True,
+ ) -> None:
"""Create a protocol with a given configuration.
If 'forward_communication_errors' is True,
@@ -79,18 +89,21 @@ class MessageProtocol:
# pylint: disable=too-many-locals
self.message_types_by_id: dict[int, type[Message]] = {}
self.message_ids_by_type: dict[type[Message], int] = {}
- self.response_types_by_id: dict[int, type[Response]
- | type[SysResponse]] = {}
- self.response_ids_by_type: dict[type[Response] | type[SysResponse],
- int] = {}
+ self.response_types_by_id: dict[
+ int, type[Response] | type[SysResponse]
+ ] = {}
+ self.response_ids_by_type: dict[
+ type[Response] | type[SysResponse], int
+ ] = {}
for m_id, m_type in message_types.items():
# Make sure only valid message types were passed and each
# id was assigned only once.
assert isinstance(m_id, int)
assert m_id >= 0
- assert (is_ioprepped_dataclass(m_type)
- and issubclass(m_type, Message))
+ assert is_ioprepped_dataclass(m_type) and issubclass(
+ m_type, Message
+ )
assert self.message_types_by_id.get(m_id) is None
self.message_types_by_id[m_id] = m_type
self.message_ids_by_type[m_type] = m_id
@@ -98,8 +111,9 @@ class MessageProtocol:
for r_id, r_type in response_types.items():
assert isinstance(r_id, int)
assert r_id >= 0
- assert (is_ioprepped_dataclass(r_type)
- and issubclass(r_type, Response))
+ assert is_ioprepped_dataclass(r_type) and issubclass(
+ r_type, Response
+ )
assert self.response_types_by_id.get(r_id) is None
self.response_types_by_id[r_id] = r_type
self.response_ids_by_type[r_type] = r_id
@@ -123,8 +137,9 @@ class MessageProtocol:
m_rtypes = m_type.get_response_types()
assert isinstance(m_rtypes, list)
- assert m_rtypes, (
- f'Message type {m_type} specifies no return types.')
+ assert (
+ m_rtypes
+ ), f'Message type {m_type} specifies no return types.'
assert len(set(m_rtypes)) == len(m_rtypes) # check dups
for m_rtype in m_rtypes:
all_response_types.add(m_rtype)
@@ -136,7 +151,8 @@ class MessageProtocol:
if cls not in self.response_ids_by_type:
raise ValueError(
f'Possible response type {cls} needs to be included'
- f' in response_types for this protocol.')
+ f' in response_types for this protocol.'
+ )
# Make sure all registered types have unique base names.
# We can take advantage of this to generate cleaner looking
@@ -145,12 +161,14 @@ class MessageProtocol:
if len(mtypenames) != len(message_types):
raise ValueError(
'message_types contains duplicate __name__s;'
- ' all types are required to have unique names.')
+ ' all types are required to have unique names.'
+ )
self.forward_clean_errors = forward_clean_errors
self.forward_communication_errors = forward_communication_errors
self.remote_errors_include_stack_traces = (
- remote_errors_include_stack_traces)
+ remote_errors_include_stack_traces
+ )
self.log_remote_errors = log_remote_errors
@staticmethod
@@ -176,30 +194,46 @@ class MessageProtocol:
# If anything goes wrong, return a ErrorSysResponse instead.
# (either CLEAN or generic REMOTE)
if self.forward_clean_errors and isinstance(exc, CleanError):
- return (ErrorSysResponse(
- error_message=str(exc),
- error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN), False)
+ return (
+ ErrorSysResponse(
+ error_message=str(exc),
+ error_type=ErrorSysResponse.ErrorType.REMOTE_CLEAN,
+ ),
+ False,
+ )
if self.forward_communication_errors and isinstance(
- exc, CommunicationError):
- return (ErrorSysResponse(
- error_message=str(exc),
- error_type=ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION),
- False)
- return (ErrorSysResponse(
- error_message=(traceback.format_exc()
- if self.remote_errors_include_stack_traces else
- 'An internal error has occurred.'),
- error_type=ErrorSysResponse.ErrorType.REMOTE),
- self.log_remote_errors)
+ exc, CommunicationError
+ ):
+ return (
+ ErrorSysResponse(
+ error_message=str(exc),
+ error_type=ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION,
+ ),
+ False,
+ )
+ return (
+ ErrorSysResponse(
+ error_message=(
+ traceback.format_exc()
+ if self.remote_errors_include_stack_traces
+ else 'An internal error has occurred.'
+ ),
+ error_type=ErrorSysResponse.ErrorType.REMOTE,
+ ),
+ self.log_remote_errors,
+ )
- def _to_dict(self, message: Any, ids_by_type: dict[type, int],
- opname: str) -> dict:
+ def _to_dict(
+ self, message: Any, ids_by_type: dict[type, int], opname: str
+ ) -> dict:
"""Encode a message to a json string for transport."""
m_id: int | None = ids_by_type.get(type(message))
if m_id is None:
- raise TypeError(f'{opname} type is not registered in protocol:'
- f' {type(message)}')
+ raise TypeError(
+ f'{opname} type is not registered in protocol:'
+ f' {type(message)}'
+ )
out = {'t': m_id, 'm': dataclass_to_dict(message)}
return out
@@ -224,8 +258,9 @@ class MessageProtocol:
# Weeeird; we get mypy errors returning dict[int, type] but
# dict[int, typing.Type] or dict[int, type[Any]] works..
- def _from_dict(self, data: dict, types_by_id: dict[int, type[Any]],
- opname: str) -> Any:
+ def _from_dict(
+ self, data: dict, types_by_id: dict[int, type[Any]], opname: str
+ ) -> Any:
"""Decode a message from a json string."""
msgdict: dict | None
@@ -240,15 +275,19 @@ class MessageProtocol:
msgtype = types_by_id.get(m_id)
if msgtype is None:
raise UnregisteredMessageIDError(
- f'Got unregistered {opname} id of {m_id}.')
+ f'Got unregistered {opname} id of {m_id}.'
+ )
return dataclass_from_dict(msgtype, msgdict)
- def _get_module_header(self,
- part: Literal['sender', 'receiver'],
- extra_import_code: str | None = None) -> str:
+ def _get_module_header(
+ self,
+ part: Literal['sender', 'receiver'],
+ extra_import_code: str | None = None,
+ ) -> str:
"""Return common parts of generated modules."""
# pylint: disable=too-many-locals, too-many-branches
import textwrap
+
tpimports: dict[str, list[str]] = {}
imports: dict[str, list[str]] = {}
@@ -258,8 +297,9 @@ class MessageProtocol:
if part == 'sender':
msgtypes.append(Message)
for msgtype in msgtypes:
- tpimports.setdefault(msgtype.__module__,
- []).append(msgtype.__name__)
+ tpimports.setdefault(msgtype.__module__, []).append(
+ msgtype.__name__
+ )
rsptypes = list(self.response_ids_by_type)
if part == 'sender':
rsptypes.append(Response)
@@ -267,15 +307,20 @@ class MessageProtocol:
# Skip these as they don't actually show up in code.
if rsp_tp is EmptySysResponse or rsp_tp is ErrorSysResponse:
continue
- if (single_message_type and part == 'sender'
- and rsp_tp is not Response):
+ if (
+ single_message_type
+ and part == 'sender'
+ and rsp_tp is not Response
+ ):
# We need to cast to the single supported response type
# in this case so need response types at runtime.
- imports.setdefault(rsp_tp.__module__,
- []).append(rsp_tp.__name__)
+ imports.setdefault(rsp_tp.__module__, []).append(
+ rsp_tp.__name__
+ )
else:
- tpimports.setdefault(rsp_tp.__module__,
- []).append(rsp_tp.__name__)
+ tpimports.setdefault(rsp_tp.__module__, []).append(
+ rsp_tp.__name__
+ )
import_lines = ''
tpimport_lines = ''
@@ -296,16 +341,21 @@ class MessageProtocol:
tpimport_lines += f'{line}\n'
if part == 'sender':
- import_lines += ('from efro.message import MessageSender,'
- ' BoundMessageSender')
+ import_lines += (
+ 'from efro.message import MessageSender,' ' BoundMessageSender'
+ )
tpimport_typing_extras = ''
else:
if single_message_type:
- import_lines += ('from efro.message import (MessageReceiver,'
- ' BoundMessageReceiver, Message, Response)')
+ import_lines += (
+ 'from efro.message import (MessageReceiver,'
+ ' BoundMessageReceiver, Message, Response)'
+ )
else:
- import_lines += ('from efro.message import MessageReceiver,'
- ' BoundMessageReceiver')
+ import_lines += (
+ 'from efro.message import MessageReceiver,'
+ ' BoundMessageReceiver'
+ )
tpimport_typing_extras = ', Awaitable'
if extra_import_code is not None:
@@ -318,32 +368,35 @@ class MessageProtocol:
if part == 'receiver':
baseimps.append('Callable')
baseimps_s = ', '.join(baseimps)
- out = ('# Released under the MIT License. See LICENSE for details.\n'
- f'#\n'
- f'"""Auto-generated {part} module. Do not edit by hand."""\n'
- f'\n'
- f'from __future__ import annotations\n'
- f'\n'
- f'from typing import TYPE_CHECKING{ovld}\n'
- f'\n'
- f'{import_lines}\n'
- f'\n'
- f'if TYPE_CHECKING:\n'
- f' from typing import {baseimps_s}'
- f'{tpimport_typing_extras}\n'
- f'{tpimport_lines}'
- f'\n'
- f'\n')
+ out = (
+ '# Released under the MIT License. See LICENSE for details.\n'
+ f'#\n'
+ f'"""Auto-generated {part} module. Do not edit by hand."""\n'
+ f'\n'
+ f'from __future__ import annotations\n'
+ f'\n'
+ f'from typing import TYPE_CHECKING{ovld}\n'
+ f'\n'
+ f'{import_lines}\n'
+ f'\n'
+ f'if TYPE_CHECKING:\n'
+ f' from typing import {baseimps_s}'
+ f'{tpimport_typing_extras}\n'
+ f'{tpimport_lines}'
+ f'\n'
+ f'\n'
+ )
return out
def do_create_sender_module(
- self,
- basename: str,
- protocol_create_code: str,
- enable_sync_sends: bool,
- enable_async_sends: bool,
- private: bool = False,
- protocol_module_level_import_code: str | None = None) -> str:
+ self,
+ basename: str,
+ protocol_create_code: str,
+ enable_sync_sends: bool,
+ enable_async_sends: bool,
+ private: bool = False,
+ protocol_module_level_import_code: str | None = None,
+ ) -> str:
"""Used by create_sender_module(); do not call directly."""
# pylint: disable=too-many-locals
import textwrap
@@ -352,25 +405,28 @@ class MessageProtocol:
ppre = '_' if private else ''
out = self._get_module_header(
- 'sender', extra_import_code=protocol_module_level_import_code)
+ 'sender', extra_import_code=protocol_module_level_import_code
+ )
ccind = textwrap.indent(protocol_create_code, ' ')
- out += (f'class {ppre}{basename}(MessageSender):\n'
- f' """Protocol-specific sender."""\n'
- f'\n'
- f' def __init__(self) -> None:\n'
- f'{ccind}\n'
- f' super().__init__(protocol)\n'
- f'\n'
- f' def __get__(self,\n'
- f' obj: Any,\n'
- f' type_in: Any = None)'
- f' -> {ppre}Bound{basename}:\n'
- f' return {ppre}Bound{basename}'
- f'(obj, self)\n'
- f'\n'
- f'\n'
- f'class {ppre}Bound{basename}(BoundMessageSender):\n'
- f' """Protocol-specific bound sender."""\n')
+ out += (
+ f'class {ppre}{basename}(MessageSender):\n'
+ f' """Protocol-specific sender."""\n'
+ f'\n'
+ f' def __init__(self) -> None:\n'
+ f'{ccind}\n'
+ f' super().__init__(protocol)\n'
+ f'\n'
+ f' def __get__(self,\n'
+ f' obj: Any,\n'
+ f' type_in: Any = None)'
+ f' -> {ppre}Bound{basename}:\n'
+ f' return {ppre}Bound{basename}'
+ f'(obj, self)\n'
+ f'\n'
+ f'\n'
+ f'class {ppre}Bound{basename}(BoundMessageSender):\n'
+ f' """Protocol-specific bound sender."""\n'
+ )
def _filt_tp_name(rtype: type[Response] | None) -> str:
return 'None' if rtype is None else rtype.__name__
@@ -397,15 +453,17 @@ class MessageProtocol:
rtypevar = ' | '.join(_filt_tp_name(t) for t in rtypes)
else:
rtypevar = _filt_tp_name(rtypes[0])
- out += (f'\n'
- f' {pfx}def send{sfx}(self,'
- f' message: {msgtypevar})'
- f' -> {rtypevar}:\n'
- f' """Send a message {how}."""\n'
- f' out = {awt}self._sender.'
- f'send{sfx}(self._obj, message)\n'
- f' assert isinstance(out, {rtypevar})\n'
- f' return out\n')
+ out += (
+ f'\n'
+ f' {pfx}def send{sfx}(self,'
+ f' message: {msgtypevar})'
+ f' -> {rtypevar}:\n'
+ f' """Send a message {how}."""\n'
+ f' out = {awt}self._sender.'
+ f'send{sfx}(self._obj, message)\n'
+ f' assert isinstance(out, {rtypevar})\n'
+ f' return out\n'
+ )
else:
for msgtype in msgtypes:
@@ -414,31 +472,37 @@ class MessageProtocol:
rtypes = msgtype.get_response_types()
if len(rtypes) > 1:
rtypevar = ' | '.join(
- _filt_tp_name(t) for t in rtypes)
+ _filt_tp_name(t) for t in rtypes
+ )
else:
rtypevar = _filt_tp_name(rtypes[0])
- out += (f'\n'
- f' @overload\n'
- f' {pfx}def send{sfx}(self,'
- f' message: {msgtypevar})'
- f' -> {rtypevar}:\n'
- f' ...\n')
- out += (f'\n'
- f' {pfx}def send{sfx}(self, message: Message)'
- f' -> Response | None:\n'
- f' """Send a message {how}."""\n'
- f' return {awt}self._sender.'
- f'send{sfx}(self._obj, message)\n')
+ out += (
+ f'\n'
+ f' @overload\n'
+ f' {pfx}def send{sfx}(self,'
+ f' message: {msgtypevar})'
+ f' -> {rtypevar}:\n'
+ f' ...\n'
+ )
+ out += (
+ f'\n'
+ f' {pfx}def send{sfx}(self, message: Message)'
+ f' -> Response | None:\n'
+ f' """Send a message {how}."""\n'
+ f' return {awt}self._sender.'
+ f'send{sfx}(self._obj, message)\n'
+ )
return out
def do_create_receiver_module(
- self,
- basename: str,
- protocol_create_code: str,
- is_async: bool,
- private: bool = False,
- protocol_module_level_import_code: str | None = None) -> str:
+ self,
+ basename: str,
+ protocol_create_code: str,
+ is_async: bool,
+ private: bool = False,
+ protocol_module_level_import_code: str | None = None,
+ ) -> str:
"""Used by create_receiver_module(); do not call directly."""
# pylint: disable=too-many-locals
import textwrap
@@ -447,24 +511,27 @@ class MessageProtocol:
ppre = '_' if private else ''
msgtypes = list(self.message_ids_by_type.keys())
out = self._get_module_header(
- 'receiver', extra_import_code=protocol_module_level_import_code)
+ 'receiver', extra_import_code=protocol_module_level_import_code
+ )
ccind = textwrap.indent(protocol_create_code, ' ')
- out += (f'class {ppre}{basename}(MessageReceiver):\n'
- f' """Protocol-specific {desc} receiver."""\n'
- f'\n'
- f' is_async = {is_async}\n'
- f'\n'
- f' def __init__(self) -> None:\n'
- f'{ccind}\n'
- f' super().__init__(protocol)\n'
- f'\n'
- f' def __get__(\n'
- f' self,\n'
- f' obj: Any,\n'
- f' type_in: Any = None,\n'
- f' ) -> {ppre}Bound{basename}:\n'
- f' return {ppre}Bound{basename}('
- f'obj, self)\n')
+ out += (
+ f'class {ppre}{basename}(MessageReceiver):\n'
+ f' """Protocol-specific {desc} receiver."""\n'
+ f'\n'
+ f' is_async = {is_async}\n'
+ f'\n'
+ f' def __init__(self) -> None:\n'
+ f'{ccind}\n'
+ f' super().__init__(protocol)\n'
+ f'\n'
+ f' def __get__(\n'
+ f' self,\n'
+ f' obj: Any,\n'
+ f' type_in: Any = None,\n'
+ f' ) -> {ppre}Bound{basename}:\n'
+ f' return {ppre}Bound{basename}('
+ f'obj, self)\n'
+ )
# Define handler() overloads for all registered message types.
@@ -497,7 +564,8 @@ class MessageProtocol:
f' from typing import cast, Callable, Any\n'
f' self.register_handler(cast(Callable'
f'[[Any, Message], Response], call))\n'
- f' return call\n')
+ f' return call\n'
+ )
else:
for msgtype in msgtypes:
msgtypevar = msgtype.__name__
@@ -507,26 +575,31 @@ class MessageProtocol:
else:
rtypevar = _filt_tp_name(rtypes[0])
rtypevar = f'{cbgn}{rtypevar}{cend}'
- out += (f'\n'
- f' @overload\n'
- f' def handler(\n'
- f' self,\n'
- f' call: Callable[[Any, {msgtypevar}], '
- f'{rtypevar}],\n'
- f' )'
- f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n'
- f' ...\n')
+ out += (
+ f'\n'
+ f' @overload\n'
+ f' def handler(\n'
+ f' self,\n'
+ f' call: Callable[[Any, {msgtypevar}], '
+ f'{rtypevar}],\n'
+ f' )'
+ f' -> Callable[[Any, {msgtypevar}], {rtypevar}]:\n'
+ f' ...\n'
+ )
out += (
'\n'
' def handler(self, call: Callable) -> Callable:\n'
' """Decorator to register message handlers."""\n'
' self.register_handler(call)\n'
- ' return call\n')
+ ' return call\n'
+ )
- out += (f'\n'
- f'\n'
- f'class {ppre}Bound{basename}(BoundMessageReceiver):\n'
- f' """Protocol-specific bound receiver."""\n')
+ out += (
+ f'\n'
+ f'\n'
+ f'class {ppre}Bound{basename}(BoundMessageReceiver):\n'
+ f' """Protocol-specific bound receiver."""\n'
+ )
if is_async:
out += (
'\n'
@@ -537,7 +610,8 @@ class MessageProtocol:
' """Asynchronously handle a raw incoming message."""\n'
' return await self._receiver.handle_raw_message_async('
'\n'
- ' self._obj, message, raise_unregistered)\n')
+ ' self._obj, message, raise_unregistered)\n'
+ )
else:
out += (
@@ -550,6 +624,7 @@ class MessageProtocol:
' return self._receiver.handle_raw_message('
'self._obj, message,\n'
' '
- 'raise_unregistered)\n')
+ 'raise_unregistered)\n'
+ )
return out
diff --git a/tools/efro/message/_receiver.py b/tools/efro/message/_receiver.py
index b9870d39..e4c92447 100644
--- a/tools/efro/message/_receiver.py
+++ b/tools/efro/message/_receiver.py
@@ -11,8 +11,12 @@ import inspect
import logging
from typing import TYPE_CHECKING
-from efro.message._message import (Message, Response, EmptySysResponse,
- UnregisteredMessageIDError)
+from efro.message._message import (
+ Message,
+ Response,
+ EmptySysResponse,
+ UnregisteredMessageIDError,
+)
if TYPE_CHECKING:
from typing import Any, Callable, Awaitable
@@ -51,20 +55,23 @@ class MessageReceiver:
def __init__(self, protocol: MessageProtocol) -> None:
self.protocol = protocol
self._handlers: dict[type[Message], Callable] = {}
- self._decode_filter_call: Callable[[Any, dict, Message],
- None] | None = None
+ self._decode_filter_call: Callable[
+ [Any, dict, Message], None
+ ] | None = None
self._encode_filter_call: Callable[
- [Any, Message | None, Response | SysResponse, dict],
- None] | None = None
+ [Any, Message | None, Response | SysResponse, dict], None
+ ] | None = None
# TODO: don't currently have async encode equivalent
# or either for sender; can add as needed.
- self._decode_filter_async_call: Callable[[Any, dict, Message],
- Awaitable[None]] | None = None
+ self._decode_filter_async_call: Callable[
+ [Any, dict, Message], Awaitable[None]
+ ] | None = None
# noinspection PyProtectedMember
def register_handler(
- self, call: Callable[[Any, Message], Response | None]) -> None:
+ self, call: Callable[[Any, Message], Response | None]
+ ) -> None:
"""Register a handler call.
The message type handled by the call is determined by its
@@ -82,15 +89,20 @@ class MessageReceiver:
# The provided callable should be a method taking one 'msg' arg.
expectedsig = ['self', 'msg']
if sig.args != expectedsig:
- raise ValueError(f'Expected callable signature of {expectedsig};'
- f' got {sig.args}')
+ raise ValueError(
+ f'Expected callable signature of {expectedsig};'
+ f' got {sig.args}'
+ )
# Make sure we are only given async methods if we are an async handler
# and sync ones otherwise.
is_async = inspect.iscoroutinefunction(call)
if self.is_async != is_async:
- msg = ('Expected a sync method; found an async one.' if is_async
- else 'Expected an async method; found a sync one.')
+ msg = (
+ 'Expected a sync method; found an async one.'
+ if is_async
+ else 'Expected an async method; found a sync one.'
+ )
raise ValueError(msg)
# Check annotation types to determine what message types we handle.
@@ -104,7 +116,8 @@ class MessageReceiver:
msgtype = anns.get('msg')
if not isinstance(msgtype, type):
raise TypeError(
- f'expected a type for "msg" annotation; got {type(msgtype)}.')
+ f'expected a type for "msg" annotation; got {type(msgtype)}.'
+ )
assert issubclass(msgtype, Message)
ret = anns.get('return')
@@ -114,21 +127,26 @@ class MessageReceiver:
if isinstance(ret, (_GenericAlias, types.UnionType)):
targs = get_args(ret)
if not all(isinstance(a, (type, type(None))) for a in targs):
- raise TypeError(f'expected only types for "return" annotation;'
- f' got {targs}.')
+ raise TypeError(
+ f'expected only types for "return" annotation;'
+ f' got {targs}.'
+ )
responsetypes = targs
else:
if not isinstance(ret, (type, type(None))):
- raise TypeError(f'expected one or more types for'
- f' "return" annotation; got a {type(ret)}.')
+ raise TypeError(
+ f'expected one or more types for'
+ f' "return" annotation; got a {type(ret)}.'
+ )
# This seems like maybe a mypy bug. Appeared after adding
# types.UnionType above.
- responsetypes = (ret, )
+ responsetypes = (ret,)
# This will contain NoneType for empty return cases, but
# we expect it to be None.
- responsetypes = tuple(None if r is type(None) else r
- for r in responsetypes)
+ responsetypes = tuple(
+ None if r is type(None) else r for r in responsetypes
+ )
# Make sure our protocol has this message type registered and our
# return types exactly match. (Technically we could return a subset
@@ -137,19 +155,23 @@ class MessageReceiver:
registered_types = self.protocol.message_ids_by_type.keys()
if msgtype not in registered_types:
- raise TypeError(f'Message type {msgtype} is not registered'
- f' in this Protocol.')
+ raise TypeError(
+ f'Message type {msgtype} is not registered'
+ f' in this Protocol.'
+ )
if msgtype in self._handlers:
- raise TypeError(f'Message type {msgtype} already has a registered'
- f' handler.')
+ raise TypeError(
+ f'Message type {msgtype} already has a registered' f' handler.'
+ )
# Make sure the responses exactly matches what the message expects.
if set(responsetypes) != set(msgtype.get_response_types()):
raise TypeError(
f'Provided response types {responsetypes} do not'
f' match the set expected by message type {msgtype}: '
- f'({msgtype.get_response_types()})')
+ f'({msgtype.get_response_types()})'
+ )
# Ok; we're good!
self._handlers[msgtype] = call
@@ -182,8 +204,9 @@ class MessageReceiver:
def encode_filter_method(
self,
- call: Callable[[Any, Message | None, Response | SysResponse, dict],
- None]
+ call: Callable[
+ [Any, Message | None, Response | SysResponse, dict], None
+ ],
) -> Callable[[Any, Message | None, Response, dict], None]:
"""Function decorator for defining an encode filter.
@@ -200,15 +223,18 @@ class MessageReceiver:
if issubclass(msgtype, Response):
continue
if msgtype not in self._handlers:
- msg = (f'Protocol message type {msgtype} is not handled'
- f' by receiver type {type(self)}.')
+ msg = (
+ f'Protocol message type {msgtype} is not handled'
+ f' by receiver type {type(self)}.'
+ )
if log_only:
logging.error(msg)
else:
raise TypeError(msg)
- def _decode_incoming_message_base(self, bound_obj: Any,
- msg: str) -> tuple[Any, dict, Message]:
+ def _decode_incoming_message_base(
+ self, bound_obj: Any, msg: str
+ ) -> tuple[Any, dict, Message]:
# Decode the incoming message.
msg_dict = self.protocol.decode_dict(msg)
msg_decoded = self.protocol.message_from_dict(msg_dict)
@@ -218,8 +244,9 @@ class MessageReceiver:
return bound_obj, msg_dict, msg_decoded
def _decode_incoming_message(self, bound_obj: Any, msg: str) -> Message:
- bound_obj, _msg_dict, msg_decoded = (
- self._decode_incoming_message_base(bound_obj=bound_obj, msg=msg))
+ bound_obj, _msg_dict, msg_decoded = self._decode_incoming_message_base(
+ bound_obj=bound_obj, msg=msg
+ )
# If they've set an async filter but are calling sync
# handle_raw_message() its likely a bug.
@@ -227,24 +254,29 @@ class MessageReceiver:
return msg_decoded
- async def _decode_incoming_message_async(self, bound_obj: Any,
- msg: str) -> Message:
- bound_obj, msg_dict, msg_decoded = (self._decode_incoming_message_base(
- bound_obj=bound_obj, msg=msg))
+ async def _decode_incoming_message_async(
+ self, bound_obj: Any, msg: str
+ ) -> Message:
+ bound_obj, msg_dict, msg_decoded = self._decode_incoming_message_base(
+ bound_obj=bound_obj, msg=msg
+ )
if self._decode_filter_async_call is not None:
- await self._decode_filter_async_call(bound_obj, msg_dict,
- msg_decoded)
+ await self._decode_filter_async_call(
+ bound_obj, msg_dict, msg_decoded
+ )
return msg_decoded
- def encode_user_response(self, bound_obj: Any, message: Message,
- response: Response | None) -> str:
+ def encode_user_response(
+ self, bound_obj: Any, message: Message, response: Response | None
+ ) -> str:
"""Encode a response provided by the user for sending."""
assert isinstance(response, Response | None)
# (user should never explicitly return error-responses)
- assert (response is None
- or type(response) in message.get_response_types())
+ assert (
+ response is None or type(response) in message.get_response_types()
+ )
# A return value of None equals EmptySysResponse.
out_response: Response | SysResponse
@@ -255,24 +287,26 @@ class MessageReceiver:
response_dict = self.protocol.response_to_dict(out_response)
if self._encode_filter_call is not None:
- self._encode_filter_call(bound_obj, message, out_response,
- response_dict)
+ self._encode_filter_call(
+ bound_obj, message, out_response, response_dict
+ )
return self.protocol.encode_dict(response_dict)
- def encode_error_response(self, bound_obj: Any, message: Message | None,
- exc: Exception) -> tuple[str, bool]:
+ def encode_error_response(
+ self, bound_obj: Any, message: Message | None, exc: Exception
+ ) -> tuple[str, bool]:
"""Given an error, return sysresponse str and whether to log."""
response, dolog = self.protocol.error_to_response(exc)
response_dict = self.protocol.response_to_dict(response)
if self._encode_filter_call is not None:
- self._encode_filter_call(bound_obj, message, response,
- response_dict)
+ self._encode_filter_call(
+ bound_obj, message, response, response_dict
+ )
return self.protocol.encode_dict(response_dict), dolog
- def handle_raw_message(self,
- bound_obj: Any,
- msg: str,
- raise_unregistered: bool = False) -> str:
+ def handle_raw_message(
+ self, bound_obj: Any, msg: str, raise_unregistered: bool = False
+ ) -> str:
"""Decode, handle, and return an response for a message.
if 'raise_unregistered' is True, will raise an
@@ -293,20 +327,20 @@ class MessageReceiver:
return self.encode_user_response(bound_obj, msg_decoded, response)
except Exception as exc:
- if (raise_unregistered
- and isinstance(exc, UnregisteredMessageIDError)):
+ if raise_unregistered and isinstance(
+ exc, UnregisteredMessageIDError
+ ):
raise
- rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
- exc)
+ rstr, dolog = self.encode_error_response(
+ bound_obj, msg_decoded, exc
+ )
if dolog:
logging.exception('Error in efro.message handling.')
return rstr
async def handle_raw_message_async(
- self,
- bound_obj: Any,
- msg: str,
- raise_unregistered: bool = False) -> str:
+ self, bound_obj: Any, msg: str, raise_unregistered: bool = False
+ ) -> str:
"""Should be called when the receiver gets a message.
The return value is the raw response to the message.
@@ -315,7 +349,8 @@ class MessageReceiver:
msg_decoded: Message | None = None
try:
msg_decoded = await self._decode_incoming_message_async(
- bound_obj, msg)
+ bound_obj, msg
+ )
msgtype = type(msg_decoded)
handler = self._handlers.get(msgtype)
if handler is None:
@@ -325,11 +360,13 @@ class MessageReceiver:
return self.encode_user_response(bound_obj, msg_decoded, response)
except Exception as exc:
- if (raise_unregistered
- and isinstance(exc, UnregisteredMessageIDError)):
+ if raise_unregistered and isinstance(
+ exc, UnregisteredMessageIDError
+ ):
raise
- rstr, dolog = self.encode_error_response(bound_obj, msg_decoded,
- exc)
+ rstr, dolog = self.encode_error_response(
+ bound_obj, msg_decoded, exc
+ )
if dolog:
logging.exception('Error in efro.message handling.')
return rstr
diff --git a/tools/efro/message/_sender.py b/tools/efro/message/_sender.py
index a0bc714a..7a105d80 100644
--- a/tools/efro/message/_sender.py
+++ b/tools/efro/message/_sender.py
@@ -43,16 +43,19 @@ class MessageSender:
self.protocol = protocol
self._send_raw_message_call: Callable[[Any, str], str] | None = None
self._send_async_raw_message_call: Callable[
- [Any, str], Awaitable[str]] | None = None
- self._encode_filter_call: Callable[[Any, Message, dict],
- None] | None = None
+ [Any, str], Awaitable[str]
+ ] | None = None
+ self._encode_filter_call: Callable[
+ [Any, Message, dict], None
+ ] | None = None
self._decode_filter_call: Callable[
- [Any, Message, dict, Response | SysResponse], None] | None = None
+ [Any, Message, dict, Response | SysResponse], None
+ ] | None = None
self._peer_desc_call: Callable[[Any], str] | None = None
def send_method(
- self, call: Callable[[Any, str],
- str]) -> Callable[[Any, str], str]:
+ self, call: Callable[[Any, str], str]
+ ) -> Callable[[Any, str], str]:
"""Function decorator for setting raw send method.
Send methods take strings and should return strings.
@@ -91,8 +94,7 @@ class MessageSender:
return call
def decode_filter_method(
- self, call: Callable[[Any, Message, dict, Response | SysResponse],
- None]
+ self, call: Callable[[Any, Message, dict, Response | SysResponse], None]
) -> Callable[[Any, Message, dict, Response], None]:
"""Function decorator for defining a decode filter.
@@ -103,8 +105,9 @@ class MessageSender:
self._decode_filter_call = call
return call
- def peer_desc_method(self, call: Callable[[Any],
- str]) -> Callable[[Any], str]:
+ def peer_desc_method(
+ self, call: Callable[[Any], str]
+ ) -> Callable[[Any], str]:
"""Function decorator for defining peer descriptions.
These are included in error messages or other diagnostics.
@@ -124,8 +127,9 @@ class MessageSender:
),
)
- async def send_async(self, bound_obj: Any,
- message: Message) -> Response | None:
+ async def send_async(
+ self, bound_obj: Any, message: Message
+ ) -> Response | None:
"""Send a message asynchronously."""
return self.send_split_part_2(
bound_obj=bound_obj,
@@ -136,8 +140,9 @@ class MessageSender:
),
)
- def send_split_part_1(self, bound_obj: Any,
- message: Message) -> Response | SysResponse:
+ def send_split_part_1(
+ self, bound_obj: Any, message: Message
+ ) -> Response | SysResponse:
"""Send a message synchronously.
Generally you can just call send(); these split versions are
@@ -150,20 +155,25 @@ class MessageSender:
msg_encoded = self._encode_message(bound_obj, message)
try:
response_encoded = self._send_raw_message_call(
- bound_obj, msg_encoded)
+ bound_obj, msg_encoded
+ )
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorSysResponse(
- error_message=
- f'Error in MessageSender @send_method ({type(exc)}): {exc}',
- error_type=(ErrorSysResponse.ErrorType.COMMUNICATION
- if isinstance(exc, CommunicationError) else
- ErrorSysResponse.ErrorType.LOCAL))
+ error_message=f'Error in MessageSender @send_method'
+ f' ({type(exc)}): {exc}',
+ error_type=(
+ ErrorSysResponse.ErrorType.COMMUNICATION
+ if isinstance(exc, CommunicationError)
+ else ErrorSysResponse.ErrorType.LOCAL
+ ),
+ )
return self._decode_raw_response(bound_obj, message, response_encoded)
async def send_split_part_1_async(
- self, bound_obj: Any, message: Message) -> Response | SysResponse:
+ self, bound_obj: Any, message: Message
+ ) -> Response | SysResponse:
"""Send a message asynchronously.
Generally you can just call send(); these split versions are
@@ -177,22 +187,28 @@ class MessageSender:
msg_encoded = self._encode_message(bound_obj, message)
try:
response_encoded = await self._send_async_raw_message_call(
- bound_obj, msg_encoded)
+ bound_obj, msg_encoded
+ )
except Exception as exc:
# Any error in the raw send call gets recorded as either
# a local or communication error.
return ErrorSysResponse(
- error_message=
- f'Error in MessageSender @send_async_method ({type(exc)}):'
- f' {exc}',
- error_type=(ErrorSysResponse.ErrorType.COMMUNICATION
- if isinstance(exc, CommunicationError) else
- ErrorSysResponse.ErrorType.LOCAL))
+ error_message=f'Error in MessageSender @send_async_method'
+ f' ({type(exc)}): {exc}',
+ error_type=(
+ ErrorSysResponse.ErrorType.COMMUNICATION
+ if isinstance(exc, CommunicationError)
+ else ErrorSysResponse.ErrorType.LOCAL
+ ),
+ )
return self._decode_raw_response(bound_obj, message, response_encoded)
def send_split_part_2(
- self, bound_obj: Any, message: Message,
- raw_response: Response | SysResponse) -> Response | None:
+ self,
+ bound_obj: Any,
+ message: Message,
+ raw_response: Response | SysResponse,
+ ) -> Response | None:
"""Complete message sending (both sync and async).
Generally you can just call send(); these split versions are
@@ -200,8 +216,10 @@ class MessageSender:
in different contexts/threads.
"""
response = self._unpack_raw_response(bound_obj, raw_response)
- assert (response is None
- or type(response) in type(message).get_response_types())
+ assert (
+ response is None
+ or type(response) in type(message).get_response_types()
+ )
return response
def _encode_message(self, bound_obj: Any, message: Message) -> str:
@@ -211,8 +229,9 @@ class MessageSender:
self._encode_filter_call(bound_obj, message, msg_dict)
return self.protocol.encode_dict(msg_dict)
- def _decode_raw_response(self, bound_obj: Any, message: Message,
- response_encoded: str) -> Response | SysResponse:
+ def _decode_raw_response(
+ self, bound_obj: Any, message: Message, response_encoded: str
+ ) -> Response | SysResponse:
"""Create a Response from returned data.
These Responses may encapsulate things like remote errors and
@@ -225,8 +244,9 @@ class MessageSender:
response_dict = self.protocol.decode_dict(response_encoded)
response = self.protocol.response_from_dict(response_dict)
if self._decode_filter_call is not None:
- self._decode_filter_call(bound_obj, message, response_dict,
- response)
+ self._decode_filter_call(
+ bound_obj, message, response_dict, response
+ )
except Exception:
# If we got to this point, we successfully communicated
# with the other end so errors represent protocol mismatches
@@ -235,14 +255,15 @@ class MessageSender:
# available directly to the user later.
logging.exception('Error decoding raw response')
response = ErrorSysResponse(
- error_message=
- 'Error decoding raw response; see log for details.',
- error_type=ErrorSysResponse.ErrorType.LOCAL)
+ error_message='Error decoding raw response;'
+ ' see log for details.',
+ error_type=ErrorSysResponse.ErrorType.LOCAL,
+ )
return response
def _unpack_raw_response(
- self, bound_obj: Any,
- raw_response: Response | SysResponse) -> Response | None:
+ self, bound_obj: Any, raw_response: Response | SysResponse
+ ) -> Response | None:
"""Given a raw Response, unpacks to special values or Exceptions.
The result of this call is what should be passed to users.
@@ -258,8 +279,10 @@ class MessageSender:
# Some error occurred. Raise a local Exception for it.
if isinstance(raw_response, ErrorSysResponse):
- if (raw_response.error_type is
- ErrorSysResponse.ErrorType.COMMUNICATION):
+ if (
+ raw_response.error_type
+ is ErrorSysResponse.ErrorType.COMMUNICATION
+ ):
raise CommunicationError(raw_response.error_message)
# If something went wrong on *our* end of the connection,
@@ -268,19 +291,29 @@ class MessageSender:
raise RuntimeError(raw_response.error_message)
# If they want to support clean errors, do those.
- if (self.protocol.forward_clean_errors and raw_response.error_type
- is ErrorSysResponse.ErrorType.REMOTE_CLEAN):
+ if (
+ self.protocol.forward_clean_errors
+ and raw_response.error_type
+ is ErrorSysResponse.ErrorType.REMOTE_CLEAN
+ ):
raise CleanError(raw_response.error_message)
- if (self.protocol.forward_communication_errors
- and raw_response.error_type is
- ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION):
+ if (
+ self.protocol.forward_communication_errors
+ and raw_response.error_type
+ is ErrorSysResponse.ErrorType.REMOTE_COMMUNICATION
+ ):
raise CommunicationError(raw_response.error_message)
# Everything else gets lumped in as a remote error.
- raise RemoteError(raw_response.error_message,
- peer_desc=('peer' if self._peer_desc_call is None
- else self._peer_desc_call(bound_obj)))
+ raise RemoteError(
+ raw_response.error_message,
+ peer_desc=(
+ 'peer'
+ if self._peer_desc_call is None
+ else self._peer_desc_call(bound_obj)
+ ),
+ )
assert isinstance(raw_response, Response)
return raw_response
@@ -316,20 +349,23 @@ class BoundMessageSender:
subclasses instead of this; it will provide better type safety.
"""
assert self._obj is not None
- return await self._sender.send_async(bound_obj=self._obj,
- message=message)
+ return await self._sender.send_async(
+ bound_obj=self._obj, message=message
+ )
async def send_split_part_1_async_untyped(
- self, message: Message) -> Response | SysResponse:
+ self, message: Message
+ ) -> Response | SysResponse:
"""Split send (part 1 of 2)."""
assert self._obj is not None
- return await self._sender.send_split_part_1_async(bound_obj=self._obj,
- message=message)
+ return await self._sender.send_split_part_1_async(
+ bound_obj=self._obj, message=message
+ )
def send_split_part_2_untyped(
- self, message: Message,
- raw_response: Response | SysResponse) -> Response | None:
+ self, message: Message, raw_response: Response | SysResponse
+ ) -> Response | None:
"""Split send (part 2 of 2)."""
- return self._sender.send_split_part_2(bound_obj=self._obj,
- message=message,
- raw_response=raw_response)
+ return self._sender.send_split_part_2(
+ bound_obj=self._obj, message=message, raw_response=raw_response
+ )
diff --git a/tools/efro/rpc.py b/tools/efro/rpc.py
index d535a62b..051555b4 100644
--- a/tools/efro/rpc.py
+++ b/tools/efro/rpc.py
@@ -14,10 +14,16 @@ from threading import current_thread
from typing import TYPE_CHECKING, Annotated
from efro.util import assert_never
-from efro.error import (CommunicationError,
- is_asyncio_streams_communication_error)
-from efro.dataclassio import (dataclass_to_json, dataclass_from_json,
- ioprepped, IOAttrs)
+from efro.error import (
+ CommunicationError,
+ is_asyncio_streams_communication_error,
+)
+from efro.dataclassio import (
+ dataclass_to_json,
+ dataclass_from_json,
+ ioprepped,
+ IOAttrs,
+)
if TYPE_CHECKING:
from typing import Literal, Awaitable, Callable
@@ -62,7 +68,8 @@ OUR_PROTOCOL = 2
def ssl_stream_writer_underlying_transport_info(
- writer: asyncio.StreamWriter) -> str:
+ writer: asyncio.StreamWriter,
+) -> str:
"""For debugging SSL Stream connections; returns raw transport info."""
# Note: accessing internals here so just returning info and not
# actual objs to reduce potential for breakage.
@@ -80,6 +87,7 @@ def ssl_stream_writer_force_close_check(writer: asyncio.StreamWriter) -> None:
"""Ensure a writer is closed; hacky workaround for odd hang."""
from efro.call import tpartial
from threading import Thread
+
# Hopefully can remove this in Python 3.11?...
# see issue with is_closing() below for more details.
transport = getattr(writer, '_transport', None)
@@ -162,16 +170,18 @@ class RPCEndpoint:
# disconnect.
DEFAULT_KEEPALIVE_TIMEOUT = 30.0
- def __init__(self,
- handle_raw_message_call: Callable[[bytes], Awaitable[bytes]],
- reader: asyncio.StreamReader,
- writer: asyncio.StreamWriter,
- label: str,
- debug_print: bool = False,
- debug_print_io: bool = False,
- debug_print_call: Callable[[str], None] | None = None,
- keepalive_interval: float = DEFAULT_KEEPALIVE_INTERVAL,
- keepalive_timeout: float = DEFAULT_KEEPALIVE_TIMEOUT) -> None:
+ def __init__(
+ self,
+ handle_raw_message_call: Callable[[bytes], Awaitable[bytes]],
+ reader: asyncio.StreamReader,
+ writer: asyncio.StreamWriter,
+ label: str,
+ debug_print: bool = False,
+ debug_print_io: bool = False,
+ debug_print_call: Callable[[str], None] | None = None,
+ keepalive_interval: float = DEFAULT_KEEPALIVE_INTERVAL,
+ keepalive_timeout: float = DEFAULT_KEEPALIVE_TIMEOUT,
+ ) -> None:
self._handle_raw_message_call = handle_raw_message_call
self._reader = reader
self._writer = writer
@@ -210,20 +220,25 @@ class RPCEndpoint:
if self._debug_print:
peername = self._writer.get_extra_info('peername')
self._debug_print_call(
- f'{self._label}: connected to {peername} at {self._tm()}.')
+ f'{self._label}: connected to {peername} at {self._tm()}.'
+ )
def __del__(self) -> None:
if self._run_called:
if not self._did_close_writer:
logging.warning(
'RPCEndpoint %d dying with run'
- ' called but writer not closed (transport=%s).', id(self),
- ssl_stream_writer_underlying_transport_info(self._writer))
+ ' called but writer not closed (transport=%s).',
+ id(self),
+ ssl_stream_writer_underlying_transport_info(self._writer),
+ )
elif not self._did_wait_closed_writer:
logging.warning(
'RPCEndpoint %d dying with run called'
- ' but writer not wait-closed (transport=%s).', id(self),
- ssl_stream_writer_underlying_transport_info(self._writer))
+ ' but writer not wait-closed (transport=%s).',
+ id(self),
+ ssl_stream_writer_underlying_transport_info(self._writer),
+ )
# Currently seeing rare issue where sockets don't go down;
# let's add a timer to force the issue until we can figure it out.
@@ -239,8 +254,10 @@ class RPCEndpoint:
except asyncio.CancelledError:
# We aren't really designed to be cancelled so let's warn
# if it happens.
- logging.warning('RPCEndpoint.run got CancelledError;'
- ' want to try and avoid this.')
+ logging.warning(
+ 'RPCEndpoint.run got CancelledError;'
+ ' want to try and avoid this.'
+ )
raise
async def _do_run(self) -> None:
@@ -253,11 +270,14 @@ class RPCEndpoint:
core_tasks = [
asyncio.create_task(
- self._run_core_task('keepalive', self._run_keepalive_task())),
+ self._run_core_task('keepalive', self._run_keepalive_task())
+ ),
asyncio.create_task(
- self._run_core_task('read', self._run_read_task())),
+ self._run_core_task('read', self._run_read_task())
+ ),
asyncio.create_task(
- self._run_core_task('write', self._run_write_task()))
+ self._run_core_task('write', self._run_write_task())
+ ),
]
self._tasks += [weakref.ref(t) for t in core_tasks]
@@ -270,13 +290,17 @@ class RPCEndpoint:
# We want to know if any errors happened aside from CancelledError
# (which are BaseExceptions, not Exception).
if isinstance(result, Exception):
- logging.warning('Got unexpected error from %s core task: %s',
- self._label, result)
+ logging.warning(
+ 'Got unexpected error from %s core task: %s',
+ self._label,
+ result,
+ )
if not all(task.done() for task in core_tasks):
logging.warning(
'RPCEndpoint %d: not all core tasks marked done after gather.',
- id(self))
+ id(self),
+ )
# Shut ourself down.
try:
@@ -288,10 +312,12 @@ class RPCEndpoint:
if self._debug_print:
self._debug_print_call(f'{self._label}: finished.')
- async def send_message(self,
- message: bytes,
- timeout: float | None = None,
- close_on_error: bool = True) -> bytes:
+ async def send_message(
+ self,
+ message: bytes,
+ timeout: float | None = None,
+ close_on_error: bool = True,
+ ) -> bytes:
"""Send a message to the peer and return a response.
If timeout is not provided, the default will be used.
@@ -330,16 +356,20 @@ class RPCEndpoint:
# Payload consists of type (1b), message_id (2b),
# len (4b), and data.
self._enqueue_outgoing_packet(
- _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER) +
- message_id.to_bytes(2, _BYTE_ORDER) +
- len(message).to_bytes(4, _BYTE_ORDER) + message)
+ _PacketType.MESSAGE_BIG.value.to_bytes(1, _BYTE_ORDER)
+ + message_id.to_bytes(2, _BYTE_ORDER)
+ + len(message).to_bytes(4, _BYTE_ORDER)
+ + message
+ )
else:
# Payload consists of type (1b), message_id (2b),
# len (2b), and data.
self._enqueue_outgoing_packet(
- _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER) +
- message_id.to_bytes(2, _BYTE_ORDER) +
- len(message).to_bytes(2, _BYTE_ORDER) + message)
+ _PacketType.MESSAGE.value.to_bytes(1, _BYTE_ORDER)
+ + message_id.to_bytes(2, _BYTE_ORDER)
+ + len(message).to_bytes(2, _BYTE_ORDER)
+ + message
+ )
# Make an entry so we know this message is out there.
assert message_id not in self._in_flight_messages
@@ -364,14 +394,16 @@ class RPCEndpoint:
# though?
if self._debug_print:
self._debug_print_call(
- f'{self._label}: message {message_id} was cancelled.')
+ f'{self._label}: message {message_id} was cancelled.'
+ )
if close_on_error:
self.close()
raise CommunicationError() from exc
except asyncio.TimeoutError as exc:
if self._debug_print:
self._debug_print_call(
- f'{self._label}: message {message_id} timed out.')
+ f'{self._label}: message {message_id} timed out.'
+ )
# Stop waiting on the response.
msgobj.wait_task.cancel()
@@ -436,14 +468,17 @@ class RPCEndpoint:
raise RuntimeError('Must be called after close()')
if not self._did_close_writer:
- logging.warning('RPCEndpoint wait_closed() called but never'
- ' explicitly closed writer.')
+ logging.warning(
+ 'RPCEndpoint wait_closed() called but never'
+ ' explicitly closed writer.'
+ )
live_tasks = self._get_live_tasks()
if self._debug_print:
self._debug_print_call(
f'{self._label}: waiting for tasks to finish: '
- f' ({live_tasks=})...')
+ f' ({live_tasks=})...'
+ )
# Wait for all of our in-flight tasks to wrap up.
results = await asyncio.gather(*live_tasks, return_exceptions=True)
@@ -451,17 +486,22 @@ class RPCEndpoint:
# We want to know if any errors happened aside from CancelledError
# (which are BaseExceptions, not Exception).
if isinstance(result, Exception):
- logging.warning('Got unexpected error cleaning up %s task: %s',
- self._label, result)
+ logging.warning(
+ 'Got unexpected error cleaning up %s task: %s',
+ self._label,
+ result,
+ )
if not all(task.done() for task in live_tasks):
logging.warning(
'RPCEndpoint %d: not all live tasks marked done after gather.',
- id(self))
+ id(self),
+ )
if self._debug_print:
self._debug_print_call(
- f'{self._label}: tasks finished; waiting for writer close...')
+ f'{self._label}: tasks finished; waiting for writer close...'
+ )
# Now wait for our writer to finish going down.
# When we close our writer it generally triggers errors
@@ -480,11 +520,13 @@ class RPCEndpoint:
logging.info(
'Timeout on _writer.wait_closed() for %s rpc (transport=%s).',
self._label,
- ssl_stream_writer_underlying_transport_info(self._writer))
+ ssl_stream_writer_underlying_transport_info(self._writer),
+ )
if self._debug_print:
self._debug_print_call(
f'{self._label}: got timeout in _writer.wait_closed();'
- ' This should be fixed in future Python versions.')
+ ' This should be fixed in future Python versions.'
+ )
except Exception as exc:
if not self._is_expected_connection_error(exc):
logging.exception('Error closing _writer for %s.', self._label)
@@ -492,10 +534,13 @@ class RPCEndpoint:
if self._debug_print:
self._debug_print_call(
f'{self._label}: silently ignoring error in'
- f' _writer.wait_closed(): {exc}.')
+ f' _writer.wait_closed(): {exc}.'
+ )
except asyncio.CancelledError:
- logging.warning('RPCEndpoint.wait_closed()'
- ' got asyncio.CancelledError; not expected.')
+ logging.warning(
+ 'RPCEndpoint.wait_closed()'
+ ' got asyncio.CancelledError; not expected.'
+ )
raise
assert not self._did_wait_closed_writer
self._did_wait_closed_writer = True
@@ -513,12 +558,13 @@ class RPCEndpoint:
# The first thing they should send us is their handshake; then
# we'll know if/how we can talk to them.
mlen = await self._read_int_32()
- message = (await self._reader.readexactly(mlen))
+ message = await self._reader.readexactly(mlen)
self._peer_info = dataclass_from_json(_PeerInfo, message.decode())
self._last_keepalive_receive_time = time.monotonic()
if self._debug_print:
self._debug_print_call(
- f'{self._label}: received handshake at {self._tm()}.')
+ f'{self._label}: received handshake at {self._tm()}.'
+ )
# Now just sit and handle stuff as it comes in.
while True:
@@ -531,8 +577,10 @@ class RPCEndpoint:
if mtype is _PacketType.KEEPALIVE:
if self._debug_print_io:
- self._debug_print_call(f'{self._label}: received keepalive'
- f' at {self._tm()}.')
+ self._debug_print_call(
+ f'{self._label}: received keepalive'
+ f' at {self._tm()}.'
+ )
self._last_keepalive_receive_time = time.monotonic()
elif mtype is _PacketType.MESSAGE:
@@ -559,8 +607,10 @@ class RPCEndpoint:
msglen = await self._read_int_16()
msg = await self._reader.readexactly(msglen)
if self._debug_print_io:
- self._debug_print_call(f'{self._label}: received message {msgid}'
- f' of size {msglen} at {self._tm()}.')
+ self._debug_print_call(
+ f'{self._label}: received message {msgid}'
+ f' of size {msglen} at {self._tm()}.'
+ )
# Create a message-task to handle this message and return
# a response (we don't want to block while that happens).
@@ -569,10 +619,14 @@ class RPCEndpoint:
self._tasks.append(
weakref.ref(
asyncio.create_task(
- self._handle_raw_message(message_id=msgid, message=msg))))
+ self._handle_raw_message(message_id=msgid, message=msg)
+ )
+ )
+ )
if self._debug_print:
self._debug_print_call(
- f'{self._label}: done handling message at {self._tm()}.')
+ f'{self._label}: done handling message at {self._tm()}.'
+ )
async def _handle_response_packet(self, big: bool) -> None:
assert self._peer_info is not None
@@ -583,8 +637,10 @@ class RPCEndpoint:
else:
rsplen = await self._read_int_16()
if self._debug_print_io:
- self._debug_print_call(f'{self._label}: received response {msgid}'
- f' of size {rsplen} at {self._tm()}.')
+ self._debug_print_call(
+ f'{self._label}: received response {msgid}'
+ f' of size {rsplen} at {self._tm()}.'
+ )
rsp = await self._reader.readexactly(rsplen)
msgobj = self._in_flight_messages.get(msgid)
if msgobj is None:
@@ -594,7 +650,8 @@ class RPCEndpoint:
if self._debug_print:
self._debug_print_call(
f'{self._label}: got response for nonexistent'
- f' message id {msgid}; perhaps it timed out?')
+ f' message id {msgid}; perhaps it timed out?'
+ )
else:
msgobj.set_response(rsp)
@@ -605,8 +662,11 @@ class RPCEndpoint:
# Introduce ourself so our peer knows how it can talk to us.
data = dataclass_to_json(
- _PeerInfo(protocol=OUR_PROTOCOL,
- keepalive_interval=self._keepalive_interval)).encode()
+ _PeerInfo(
+ protocol=OUR_PROTOCOL,
+ keepalive_interval=self._keepalive_interval,
+ )
+ ).encode()
self._writer.write(len(data).to_bytes(4, _BYTE_ORDER) + data)
# Now just write out-messages as they come in.
@@ -636,7 +696,9 @@ class RPCEndpoint:
if not self._did_out_packets_buildup_warning:
logging.warning(
'_out_packets building up too'
- ' much on RPCEndpoint %s.', id(self))
+ ' much on RPCEndpoint %s.',
+ id(self),
+ )
self._did_out_packets_buildup_warning = True
async def _run_keepalive_task(self) -> None:
@@ -655,21 +717,25 @@ class RPCEndpoint:
await asyncio.sleep(self._keepalive_interval)
if not self.test_suppress_keepalives:
self._enqueue_outgoing_packet(
- _PacketType.KEEPALIVE.value.to_bytes(1, _BYTE_ORDER))
+ _PacketType.KEEPALIVE.value.to_bytes(1, _BYTE_ORDER)
+ )
# Also go ahead and handle dropping the connection if we
# haven't heard from the peer in a while.
# NOTE: perhaps we want to do something more exact than
# this which only checks once per keepalive-interval?..
now = time.monotonic()
- if (self._last_keepalive_receive_time is not None
- and now - self._last_keepalive_receive_time >
- self._keepalive_timeout):
+ if (
+ self._last_keepalive_receive_time is not None
+ and now - self._last_keepalive_receive_time
+ > self._keepalive_timeout
+ ):
if self._debug_print:
since = now - self._last_keepalive_receive_time
self._debug_print_call(
f'{self._label}: reached keepalive time-out'
- f' ({since:.1f}s).')
+ f' ({since:.1f}s).'
+ )
raise _KeepaliveTimeoutError()
async def _run_core_task(self, tasklabel: str, call: Awaitable) -> None:
@@ -679,22 +745,28 @@ class RPCEndpoint:
# We expect connection errors to put us here, but make noise
# if something else does.
if not self._is_expected_connection_error(exc):
- logging.exception('Unexpected error in rpc %s %s task.',
- self._label, tasklabel)
+ logging.exception(
+ 'Unexpected error in rpc %s %s task.',
+ self._label,
+ tasklabel,
+ )
else:
if self._debug_print:
self._debug_print_call(
f'{self._label}: {tasklabel} task will exit cleanly'
- f' due to {exc!r}.')
+ f' due to {exc!r}.'
+ )
finally:
# Any core task exiting triggers shutdown.
if self._debug_print:
self._debug_print_call(
- f'{self._label}: {tasklabel} task exiting...')
+ f'{self._label}: {tasklabel} task exiting...'
+ )
self.close()
- async def _handle_raw_message(self, message_id: int,
- message: bytes) -> None:
+ async def _handle_raw_message(
+ self, message_id: int, message: bytes
+ ) -> None:
try:
response = await self._handle_raw_message_call(message)
except Exception:
@@ -709,21 +781,24 @@ class RPCEndpoint:
if self._peer_info.protocol == 1:
if len(response) > 65535:
- raise RuntimeError(
- 'Response cannot be larger than 65535 bytes')
+ raise RuntimeError('Response cannot be larger than 65535 bytes')
# Now send back our response.
# Payload consists of type (1b), msgid (2b), len (2b), and data.
if len(response) > 65535:
self._enqueue_outgoing_packet(
- _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER) +
- message_id.to_bytes(2, _BYTE_ORDER) +
- len(response).to_bytes(4, _BYTE_ORDER) + response)
+ _PacketType.RESPONSE_BIG.value.to_bytes(1, _BYTE_ORDER)
+ + message_id.to_bytes(2, _BYTE_ORDER)
+ + len(response).to_bytes(4, _BYTE_ORDER)
+ + response
+ )
else:
self._enqueue_outgoing_packet(
- _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER) +
- message_id.to_bytes(2, _BYTE_ORDER) +
- len(response).to_bytes(2, _BYTE_ORDER) + response)
+ _PacketType.RESPONSE.value.to_bytes(1, _BYTE_ORDER)
+ + message_id.to_bytes(2, _BYTE_ORDER)
+ + len(response).to_bytes(2, _BYTE_ORDER)
+ + response
+ )
async def _read_int_8(self) -> int:
return int.from_bytes(await self._reader.readexactly(1), _BYTE_ORDER)
@@ -749,8 +824,10 @@ class RPCEndpoint:
# that this is part of the design), so let's enforce a single
# thread for all use of an instance.
if current_thread() is not self._thread:
- raise RuntimeError('This must be called from the same thread'
- ' that the endpoint was created in.')
+ raise RuntimeError(
+ 'This must be called from the same thread'
+ ' that the endpoint was created in.'
+ )
# This should always be the case if thread is the same.
assert asyncio.get_running_loop() is self._event_loop
@@ -760,8 +837,10 @@ class RPCEndpoint:
self._check_env()
if self._debug_print_io:
- self._debug_print_call(f'{self._label}: enqueueing outgoing packet'
- f' {data[:50]!r} at {self._tm()}.')
+ self._debug_print_call(
+ f'{self._label}: enqueueing outgoing packet'
+ f' {data[:50]!r} at {self._tm()}.'
+ )
# Add the data and let our write task know about it.
self._out_packets.append(data)
diff --git a/tools/efro/terminal.py b/tools/efro/terminal.py
index d0351d54..38ec78ed 100644
--- a/tools/efro/terminal.py
+++ b/tools/efro/terminal.py
@@ -95,6 +95,7 @@ def _windows_enable_color() -> bool:
import msvcrt
import ctypes
from ctypes import wintypes
+
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) # type: ignore
ERROR_INVALID_PARAMETER = 0x0057
@@ -111,7 +112,7 @@ def _windows_enable_color() -> bool:
kernel32.SetConsoleMode.errcheck = _check_bool
kernel32.SetConsoleMode.argtypes = (wintypes.HANDLE, wintypes.DWORD)
- def set_conout_mode(new_mode: int, mask: int = 0xffffffff) -> int:
+ def set_conout_mode(new_mode: int, mask: int = 0xFFFFFFFF) -> int:
# don't assume StandardOutput is a console.
# open CONOUT$ instead
fdout = os.open('CONOUT$', os.O_RDWR)
@@ -146,6 +147,7 @@ def _windows_enable_color() -> bool:
class ClrBase:
"""Base class for color convenience class."""
+
RST: ClassVar[str]
BLD: ClassVar[str]
UND: ClassVar[str]
@@ -198,6 +200,7 @@ class ClrAlways(ClrBase):
This version has colors always enabled. Generally you should use Clr which
points to the correct enabled/disabled class depending on the environment.
"""
+
color_enabled = True
# Styles
@@ -253,6 +256,7 @@ class ClrNever(ClrBase):
This version has colors disabled. Generally you should use Clr which
points to the correct enabled/disabled class depending on the environment.
"""
+
color_enabled = False
# Styles
@@ -303,8 +307,13 @@ class ClrNever(ClrBase):
_envval = os.environ.get('EFRO_TERMCOLORS')
-_color_enabled: bool = (True if _envval == '1' else
- False if _envval == '0' else _default_color_enabled())
+_color_enabled: bool = (
+ True
+ if _envval == '1'
+ else False
+ if _envval == '0'
+ else _default_color_enabled()
+)
Clr: type[ClrBase]
if _color_enabled:
Clr = ClrAlways
diff --git a/tools/efro/util.py b/tools/efro/util.py
index c8b8df01..699fb068 100644
--- a/tools/efro/util.py
+++ b/tools/efro/util.py
@@ -17,6 +17,7 @@ _pytz_utc: Any
# We don't *require* pytz, but we want to support it for tzinfos if available.
try:
import pytz
+
_pytz_utc = pytz.utc
except ModuleNotFoundError:
_pytz_utc = None # pylint: disable=invalid-name
@@ -68,16 +69,20 @@ def enum_by_value(cls: type[EnumT], value: Any) -> EnumT:
return out
except KeyError:
# pylint: disable=consider-using-f-string
- raise ValueError('%r is not a valid %s' %
- (value, cls.__name__)) from None
+ raise ValueError(
+ '%r is not a valid %s' % (value, cls.__name__)
+ ) from None
def check_utc(value: datetime.datetime) -> None:
"""Ensure a datetime value is timezone-aware utc."""
- if (value.tzinfo is not datetime.timezone.utc
- and (_pytz_utc is None or value.tzinfo is not _pytz_utc)):
- raise ValueError('datetime value does not have timezone set as'
- ' datetime.timezone.utc')
+ if value.tzinfo is not datetime.timezone.utc and (
+ _pytz_utc is None or value.tzinfo is not _pytz_utc
+ ):
+ raise ValueError(
+ 'datetime value does not have timezone set as'
+ ' datetime.timezone.utc'
+ )
def utc_now() -> datetime.datetime:
@@ -94,31 +99,34 @@ def utc_now() -> datetime.datetime:
def utc_today() -> datetime.datetime:
"""Get offset-aware midnight in the utc time zone."""
now = datetime.datetime.now(datetime.timezone.utc)
- return datetime.datetime(year=now.year,
- month=now.month,
- day=now.day,
- tzinfo=now.tzinfo)
+ return datetime.datetime(
+ year=now.year, month=now.month, day=now.day, tzinfo=now.tzinfo
+ )
def utc_this_hour() -> datetime.datetime:
"""Get offset-aware beginning of the current hour in the utc time zone."""
now = datetime.datetime.now(datetime.timezone.utc)
- return datetime.datetime(year=now.year,
- month=now.month,
- day=now.day,
- hour=now.hour,
- tzinfo=now.tzinfo)
+ return datetime.datetime(
+ year=now.year,
+ month=now.month,
+ day=now.day,
+ hour=now.hour,
+ tzinfo=now.tzinfo,
+ )
def utc_this_minute() -> datetime.datetime:
"""Get offset-aware beginning of current minute in the utc time zone."""
now = datetime.datetime.now(datetime.timezone.utc)
- return datetime.datetime(year=now.year,
- month=now.month,
- day=now.day,
- hour=now.hour,
- minute=now.minute,
- tzinfo=now.tzinfo)
+ return datetime.datetime(
+ year=now.year,
+ month=now.month,
+ day=now.day,
+ hour=now.hour,
+ minute=now.minute,
+ tzinfo=now.tzinfo,
+ )
def empty_weakref(objtype: type[T]) -> weakref.ref[T]:
@@ -172,26 +180,31 @@ class DirtyBit:
when updates fail)
"""
- def __init__(self,
- dirty: bool = False,
- retry_interval: float = 5.0,
- use_lock: bool = False,
- auto_dirty_seconds: float | None = None,
- min_update_interval: float | None = None):
+ def __init__(
+ self,
+ dirty: bool = False,
+ retry_interval: float = 5.0,
+ use_lock: bool = False,
+ auto_dirty_seconds: float | None = None,
+ min_update_interval: float | None = None,
+ ):
curtime = time.time()
self._retry_interval = retry_interval
self._auto_dirty_seconds = auto_dirty_seconds
self._min_update_interval = min_update_interval
self._dirty = dirty
- self._next_update_time: float | None = (curtime if dirty else None)
+ self._next_update_time: float | None = curtime if dirty else None
self._last_update_time: float | None = None
self._next_auto_dirty_time: float | None = (
- (curtime + self._auto_dirty_seconds) if
- (not dirty and self._auto_dirty_seconds is not None) else None)
+ (curtime + self._auto_dirty_seconds)
+ if (not dirty and self._auto_dirty_seconds is not None)
+ else None
+ )
self._use_lock = use_lock
self.lock: asyncio.Lock
if self._use_lock:
import asyncio
+
self.lock = asyncio.Lock()
@property
@@ -216,11 +229,14 @@ class DirtyBit:
# If they want to enforce a minimum update interval,
# push out the next update time if it hasn't been long enough.
- if (self._min_update_interval is not None
- and self._last_update_time is not None):
+ if (
+ self._min_update_interval is not None
+ and self._last_update_time is not None
+ ):
self._next_update_time = max(
self._next_update_time,
- self._last_update_time + self._min_update_interval)
+ self._last_update_time + self._min_update_interval,
+ )
self._dirty = value
@@ -235,8 +251,10 @@ class DirtyBit:
curtime = time.time()
# Auto-dirty ourself if we're into that.
- if (self._next_auto_dirty_time is not None
- and curtime > self._next_auto_dirty_time):
+ if (
+ self._next_auto_dirty_time is not None
+ and curtime > self._next_auto_dirty_time
+ ):
self.dirty = True
self._next_auto_dirty_time = None
if not self._dirty:
@@ -259,7 +277,8 @@ class DispatchMethodWrapper(Generic[ArgT, RetT]):
@staticmethod
def register(
- func: Callable[[Any, Any], RetT]) -> Callable[[Any, Any], RetT]:
+ func: Callable[[Any, Any], RetT]
+ ) -> Callable[[Any, Any], RetT]:
"""Register a new dispatch handler for this dispatch-method."""
registry: dict[Any, Callable]
@@ -267,8 +286,8 @@ class DispatchMethodWrapper(Generic[ArgT, RetT]):
# noinspection PyProtectedMember,PyTypeHints
def dispatchmethod(
- func: Callable[[Any, ArgT],
- RetT]) -> DispatchMethodWrapper[ArgT, RetT]:
+ func: Callable[[Any, ArgT], RetT]
+) -> DispatchMethodWrapper[ArgT, RetT]:
"""A variation of functools.singledispatch for methods.
Note: as of Python 3.9 there is now functools.singledispatchmethod,
@@ -276,6 +295,7 @@ def dispatchmethod(
which gives us a reason to keep this one around for now.
"""
from functools import singledispatch, update_wrapper
+
origwrapper: Any = singledispatch(func)
# Pull this out so hopefully origwrapper can die,
@@ -291,8 +311,9 @@ def dispatchmethod(
# NOTE: sounds like we can use functools singledispatchmethod in 3.8
def wrapper(*args: Any, **kw: Any) -> Any:
if not args or len(args) < 2:
- raise TypeError(f'{funcname} requires at least '
- '2 positional arguments')
+ raise TypeError(
+ f'{funcname} requires at least ' '2 positional arguments'
+ )
return dispatch(args[1].__class__)(*args, **kw)
@@ -331,24 +352,26 @@ class ValueDispatcher(Generic[ValT, RetT]):
return handler()
return self._base_call(value)
- def _add_handler(self, value: ValT,
- call: Callable[[], RetT]) -> Callable[[], RetT]:
+ def _add_handler(
+ self, value: ValT, call: Callable[[], RetT]
+ ) -> Callable[[], RetT]:
if value in self._handlers:
raise RuntimeError(f'Duplicate handlers added for {value}')
self._handlers[value] = call
return call
def register(
- self,
- value: ValT) -> Callable[[Callable[[], RetT]], Callable[[], RetT]]:
+ self, value: ValT
+ ) -> Callable[[Callable[[], RetT]], Callable[[], RetT]]:
"""Add a handler to the dispatcher."""
from functools import partial
+
return partial(self._add_handler, value)
def valuedispatch1arg(
- call: Callable[[ValT, ArgT],
- RetT]) -> ValueDispatcher1Arg[ValT, ArgT, RetT]:
+ call: Callable[[ValT, ArgT], RetT]
+) -> ValueDispatcher1Arg[ValT, ArgT, RetT]:
"""Like valuedispatch but for functions taking an extra argument."""
return ValueDispatcher1Arg(call)
@@ -366,8 +389,9 @@ class ValueDispatcher1Arg(Generic[ValT, ArgT, RetT]):
return handler(arg)
return self._base_call(value, arg)
- def _add_handler(self, value: ValT,
- call: Callable[[ArgT], RetT]) -> Callable[[ArgT], RetT]:
+ def _add_handler(
+ self, value: ValT, call: Callable[[ArgT], RetT]
+ ) -> Callable[[ArgT], RetT]:
if value in self._handlers:
raise RuntimeError(f'Duplicate handlers added for {value}')
self._handlers[value] = call
@@ -378,6 +402,7 @@ class ValueDispatcher1Arg(Generic[ValT, ArgT, RetT]):
) -> Callable[[Callable[[ArgT], RetT]], Callable[[ArgT], RetT]]:
"""Add a handler to the dispatcher."""
from functools import partial
+
return partial(self._add_handler, value)
@@ -397,8 +422,8 @@ if TYPE_CHECKING:
def valuedispatchmethod(
- call: Callable[[SelfT, ValT],
- RetT]) -> ValueDispatcherMethod[ValT, RetT]:
+ call: Callable[[SelfT, ValT], RetT]
+) -> ValueDispatcherMethod[ValT, RetT]:
"""Like valuedispatch but works with methods instead of functions."""
# NOTE: It seems that to wrap a method with a decorator and have self
@@ -416,6 +441,7 @@ def valuedispatchmethod(
def _register(value: ValT) -> Callable[[Callable[[SelfT], RetT]], None]:
from functools import partial
+
return partial(_add_handler, value)
def _call_wrapper(self: SelfT, value: ValT) -> RetT:
@@ -520,6 +546,7 @@ def warntype(obj: Any, typ: type[T]) -> T:
assert isinstance(typ, type), 'only actual types accepted'
if not isinstance(obj, typ):
import logging
+
logging.warning('warntype: expected a %s, got a %s', typ, type(obj))
return obj # type: ignore
@@ -533,8 +560,10 @@ def warntype_o(obj: Any, typ: type[T]) -> T | None:
assert isinstance(typ, type), 'only actual types accepted'
if not isinstance(obj, (typ, type(None))):
import logging
- logging.warning('warntype: expected a %s or None, got a %s', typ,
- type(obj))
+
+ logging.warning(
+ 'warntype: expected a %s or None, got a %s', typ, type(obj)
+ )
return obj # type: ignore
@@ -633,8 +662,8 @@ def compact_id(num: int) -> str:
Sort order for these ids is the same as the original numbers.
"""
return _compact_id(
- num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
- 'abcdefghijklmnopqrstuvwxyz')
+ num, '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
+ )
# NOTE: Even though this is available as part of typing_extensions, keeping
@@ -660,15 +689,22 @@ def unchanging_hostname() -> str:
# On Mac, this should give the computer name assigned in System Prefs.
if platform.system() == 'Darwin':
- return subprocess.run(
- ['scutil', '--get', 'ComputerName'],
- check=True,
- capture_output=True).stdout.decode().strip().replace(' ', '-')
+ return (
+ subprocess.run(
+ ['scutil', '--get', 'ComputerName'],
+ check=True,
+ capture_output=True,
+ )
+ .stdout.decode()
+ .strip()
+ .replace(' ', '-')
+ )
return os.uname().nodename
-def set_canonical_module(module_globals: dict[str, Any],
- names: list[str]) -> None:
+def set_canonical_module(
+ module_globals: dict[str, Any], names: list[str]
+) -> None:
"""Override any __module__ attrs on passed classes/etc.
This allows classes to present themselves using clean paths such as
@@ -686,7 +722,12 @@ def set_canonical_module(module_globals: dict[str, Any],
obj.__module__ = modulename
except Exception:
import logging
+
logging.warning(
'set_canonical_module: unable to change __module__'
- " from '%s' to '%s' on %s object at '%s'.", existing,
- modulename, type(obj), name)
+ " from '%s' to '%s' on %s object at '%s'.",
+ existing,
+ modulename,
+ type(obj),
+ name,
+ )
diff --git a/tools/efrotools/__init__.py b/tools/efrotools/__init__.py
index 4fd12899..c0452242 100644
--- a/tools/efrotools/__init__.py
+++ b/tools/efrotools/__init__.py
@@ -36,8 +36,9 @@ def getlocalconfig(projroot: Path) -> dict[str, Any]:
"""Return a project's localconfig contents (or default if missing)."""
localconfig: dict[str, Any]
try:
- with open(Path(projroot, 'config/localconfig.json'),
- encoding='utf-8') as infile:
+ with open(
+ Path(projroot, 'config/localconfig.json'), encoding='utf-8'
+ ) as infile:
localconfig = json.loads(infile.read())
except FileNotFoundError:
localconfig = {}
@@ -48,8 +49,9 @@ def getconfig(projroot: Path) -> dict[str, Any]:
"""Return a project's config contents (or default if missing)."""
config: dict[str, Any]
try:
- with open(Path(projroot, 'config/config.json'),
- encoding='utf-8') as infile:
+ with open(
+ Path(projroot, 'config/config.json'), encoding='utf-8'
+ ) as infile:
config = json.loads(infile.read())
except FileNotFoundError:
config = {}
@@ -59,8 +61,9 @@ def getconfig(projroot: Path) -> dict[str, Any]:
def setconfig(projroot: Path, config: dict[str, Any]) -> None:
"""Set the project config contents."""
os.makedirs(Path(projroot, 'config'), exist_ok=True)
- with Path(projroot,
- 'config/config.json').open('w', encoding='utf-8') as outfile:
+ with Path(projroot, 'config/config.json').open(
+ 'w', encoding='utf-8'
+ ) as outfile:
outfile.write(json.dumps(config, indent=2))
@@ -104,17 +107,22 @@ def replace_exact(opstr: str, old: str, new: str, count: int = 1) -> str:
"""
found = opstr.count(old)
if found != count:
- raise Exception(f'expected {count} string occurrence(s);'
- f' found {found}. String = {old}')
+ raise Exception(
+ f'expected {count} string occurrence(s);'
+ f' found {found}. String = {old}'
+ )
return opstr.replace(old, new)
-def get_files_hash(filenames: Sequence[str | Path],
- extrahash: str = '',
- int_only: bool = False,
- hashtype: Literal['md5', 'sha256'] = 'md5') -> str:
- """Return a md5 hash for the given files."""
+def get_files_hash(
+ filenames: Sequence[str | Path],
+ extrahash: str = '',
+ int_only: bool = False,
+ hashtype: Literal['md5', 'sha256'] = 'md5',
+) -> str:
+ """Return a hash for the given files."""
import hashlib
+
if not isinstance(filenames, list):
raise Exception('expected a list')
if TYPE_CHECKING:
@@ -137,6 +145,29 @@ def get_files_hash(filenames: Sequence[str | Path],
return hashobj.hexdigest()
+def get_string_hash(
+ value: str,
+ int_only: bool = False,
+ hashtype: Literal['md5', 'sha256'] = 'md5',
+) -> str:
+ """Return a hash for the given files."""
+ import hashlib
+
+ if not isinstance(value, str):
+ raise TypeError('Expected a str.')
+ if TYPE_CHECKING:
+ # Help Mypy infer the right type for this.
+ hashobj = hashlib.md5()
+ else:
+ hashobj = getattr(hashlib, hashtype)()
+ hashobj.update(value.encode())
+
+ if int_only:
+ return str(int.from_bytes(hashobj.digest(), byteorder='big'))
+
+ return hashobj.hexdigest()
+
+
def _py_symbol_at_column(line: str, col: int) -> str:
start = col
while start > 0 and line[start - 1] != ' ':
@@ -147,8 +178,14 @@ def _py_symbol_at_column(line: str, col: int) -> str:
return line[start:end]
-def py_examine(projroot: Path, filename: Path, line: int, column: int,
- selection: str | None, operation: str) -> None:
+def py_examine(
+ projroot: Path,
+ filename: Path,
+ line: int,
+ column: int,
+ selection: str | None,
+ operation: str,
+) -> None:
"""Given file position info, performs some code inspection."""
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
@@ -167,8 +204,11 @@ def py_examine(projroot: Path, filename: Path, line: int, column: int,
if operation == 'pylint_infer':
# See what asteroid can infer about the target symbol.
- symbol = (selection if selection is not None else _py_symbol_at_column(
- flines[line - 1], column))
+ symbol = (
+ selection
+ if selection is not None
+ else _py_symbol_at_column(flines[line - 1], column)
+ )
# Insert a line after the provided one which is just the symbol so
# that we can ask for its value alone.
@@ -182,8 +222,11 @@ def py_examine(projroot: Path, filename: Path, line: int, column: int,
elif operation in ('mypy_infer', 'mypy_locals'):
# Ask mypy for the type of the target symbol.
- symbol = (selection if selection is not None else _py_symbol_at_column(
- flines[line - 1], column))
+ symbol = (
+ selection
+ if selection is not None
+ else _py_symbol_at_column(flines[line - 1], column)
+ )
# Insert a line after the provided one which is just the symbol so
# that we can ask for its value alone.
diff --git a/tools/efrotools/android.py b/tools/efrotools/android.py
index 98904e6f..ac0da379 100644
--- a/tools/efrotools/android.py
+++ b/tools/efrotools/android.py
@@ -13,6 +13,7 @@ if TYPE_CHECKING:
@dataclass
class GradleFilterSection:
"""Filtered section of gradle file."""
+
tag: str
firstline: int
lastline: int
@@ -32,9 +33,9 @@ def filter_gradle_file(buildfilename: str, enabled_tags: set[str]) -> None:
if line.strip().startswith('// EFRO_IF'):
if current_section is not None:
raise RuntimeError('Malformed gradle file')
- current_section = GradleFilterSection(tag=line.split()[2],
- firstline=i,
- lastline=i)
+ current_section = GradleFilterSection(
+ tag=line.split()[2], firstline=i, lastline=i
+ )
elif line.strip().startswith('// EFRO_ENDIF'):
if current_section is None:
raise RuntimeError('Malformed gradle file')
diff --git a/tools/efrotools/build.py b/tools/efrotools/build.py
index ab9d53a0..6e2fd5c9 100644
--- a/tools/efrotools/build.py
+++ b/tools/efrotools/build.py
@@ -33,25 +33,30 @@ class Lazybuild:
source regardless of whether the build itself was triggered.
"""
- def __init__(self,
- target: str,
- srcpaths: list[str],
- command: str,
- dirfilter: Callable[[str, str], bool] | None = None,
- filefilter: Callable[[str, str], bool] | None = None,
- srcpaths_fullclean: list[str] | None = None,
- command_fullclean: str | None = None) -> None:
+ def __init__(
+ self,
+ target: str,
+ srcpaths: list[str],
+ command: str,
+ dirfilter: Callable[[str, str], bool] | None = None,
+ filefilter: Callable[[str, str], bool] | None = None,
+ srcpaths_fullclean: list[str] | None = None,
+ command_fullclean: str | None = None,
+ ) -> None:
self.target = target
self.srcpaths = srcpaths
self.command = command
self.dirfilter = dirfilter
self.filefilter = filefilter
- self.mtime = None if not os.path.exists(
- self.target) else os.path.getmtime(self.target)
+ self.mtime = (
+ None
+ if not os.path.exists(self.target)
+ else os.path.getmtime(self.target)
+ )
# Show prettier names for lazybuild cache dir targets.
if target.startswith('.cache/lazybuild/'):
- self.target_name_pretty = target[len('.cache/lazybuild/'):]
+ self.target_name_pretty = target[len('.cache/lazybuild/') :]
else:
self.target_name_pretty = target
@@ -67,10 +72,13 @@ class Lazybuild:
# as sources in a Makefile.
self.srcpaths_fullclean = srcpaths_fullclean
self.command_fullclean = command_fullclean
- if ((self.srcpaths_fullclean is None) !=
- (self.command_fullclean is None)):
- raise RuntimeError('Must provide both srcpaths_fullclean and'
- ' command_fullclean together')
+ if (self.srcpaths_fullclean is None) != (
+ self.command_fullclean is None
+ ):
+ raise RuntimeError(
+ 'Must provide both srcpaths_fullclean and'
+ ' command_fullclean together'
+ )
def run(self) -> None:
"""Do the thing."""
@@ -79,8 +87,10 @@ class Lazybuild:
if self.have_fullclean_changes:
assert self.command_fullclean is not None
- print(f'{Clr.MAG}Lazybuild: full-clean input changed;'
- f' running {Clr.BLD}{self.command_fullclean}.{Clr.RST}')
+ print(
+ f'{Clr.MAG}Lazybuild: full-clean input changed;'
+ f' running {Clr.BLD}{self.command_fullclean}.{Clr.RST}'
+ )
subprocess.run(self.command_fullclean, shell=True, check=True)
if self.have_changes:
@@ -92,12 +102,14 @@ class Lazybuild:
# We make a special exception for files under .cache/lazybuild
# since those are not actually meaningful files; only used for
# dep tracking.
- if (not self.target.startswith('.cache/lazybuild')
- and not os.path.isfile(self.target)):
+ if not self.target.startswith(
+ '.cache/lazybuild'
+ ) and not os.path.isfile(self.target):
raise RuntimeError(
f'Expected output file \'{self.target}\' not found'
f' after running lazybuild command:'
- f' \'{self.command}\'.')
+ f' \'{self.command}\'.'
+ )
# We also explicitly update the mod-time of the target;
# the command we (such as a VM build) may not have actually
@@ -108,7 +120,8 @@ class Lazybuild:
else:
print(
f'{Clr.BLU}Lazybuild: skipping "{self.target_name_pretty}"'
- f' ({self.total_unchanged_count} inputs unchanged).{Clr.RST}')
+ f' ({self.total_unchanged_count} inputs unchanged).{Clr.RST}'
+ )
def _check_paths(self) -> None:
@@ -146,15 +159,17 @@ class Lazybuild:
# In top-down mode we can modify dirnames in-place to
# prevent recursing into them at all.
for dirname in list(dirnames): # (make a copy)
- if (not self._default_dir_filter(root, dirname)
- or (self.dirfilter is not None
- and not self.dirfilter(root, dirname))):
+ if not self._default_dir_filter(root, dirname) or (
+ self.dirfilter is not None
+ and not self.dirfilter(root, dirname)
+ ):
dirnames.remove(dirname)
for fname in fnames:
- if (not self._default_file_filter(root, fname)
- or (self.filefilter is not None
- and not self.filefilter(root, fname))):
+ if not self._default_file_filter(root, fname) or (
+ self.filefilter is not None
+ and not self.filefilter(root, fname)
+ ):
continue
fpath = os.path.join(root, fname)
@@ -192,10 +207,12 @@ class Lazybuild:
def _test_path(self, path: str) -> bool:
# Now see this path is newer than our target..
if self.mtime is None or os.path.getmtime(path) >= self.mtime:
- print(f'{Clr.MAG}Lazybuild: '
- f'{Clr.BLD}{self.target_name_pretty}{Clr.RST}{Clr.MAG}'
- f' build'
- f' triggered by change in {Clr.BLD}{path}{Clr.RST}{Clr.MAG}'
- f'.{Clr.RST}')
+ print(
+ f'{Clr.MAG}Lazybuild: '
+ f'{Clr.BLD}{self.target_name_pretty}{Clr.RST}{Clr.MAG}'
+ f' build'
+ f' triggered by change in {Clr.BLD}{path}{Clr.RST}{Clr.MAG}'
+ f'.{Clr.RST}'
+ )
return True
return False
diff --git a/tools/efrotools/code.py b/tools/efrotools/code.py
index dd793d5c..e07849e1 100644
--- a/tools/efrotools/code.py
+++ b/tools/efrotools/code.py
@@ -17,9 +17,9 @@ if TYPE_CHECKING:
from typing import Any
-def format_cpp_str(projroot: Path,
- text: str,
- filename: str = 'untitled.cc') -> str:
+def format_cpp_str(
+ projroot: Path, text: str, filename: str = 'untitled.cc'
+) -> str:
"""Run clang-format inline on c++ code.
Note that some cpp formatting keys off the filename, so a fake one can
@@ -32,8 +32,9 @@ def format_cpp_str(projroot: Path,
with open(filename, 'w', encoding='utf-8') as outfile:
outfile.write(text)
cfg = os.path.join(projroot, 'config/toolconfigsrc/clang-format')
- subprocess.run(['clang-format', f'--style=file:{cfg}', '-i', filename],
- check=True)
+ subprocess.run(
+ ['clang-format', f'--style=file:{cfg}', '-i', filename], check=True
+ )
with open(filename, encoding='utf-8') as infile:
return infile.read()
@@ -44,6 +45,7 @@ def format_project_cpp_files(projroot: Path, full: bool) -> None:
import concurrent.futures
from multiprocessing import cpu_count
from efrotools import get_files_hash
+
os.chdir(projroot)
cachepath = Path(projroot, '.cache/format_project_cpp_files')
if full and cachepath.exists():
@@ -71,7 +73,8 @@ def format_project_cpp_files(projroot: Path, full: bool) -> None:
return {'f': filename, 't': duration}
with concurrent.futures.ThreadPoolExecutor(
- max_workers=cpu_count()) as executor:
+ max_workers=cpu_count()
+ ) as executor:
# Converting this to a list will propagate any errors.
list(executor.map(format_file, dirtyfiles))
@@ -80,8 +83,9 @@ def format_project_cpp_files(projroot: Path, full: bool) -> None:
cache.update(filenames, confighash)
cache.mark_clean(filenames)
cache.write()
- print(f'Formatting is up to date for {len(filenames)} code files.',
- flush=True)
+ print(
+ f'Formatting is up to date for {len(filenames)} code files.', flush=True
+ )
def check_cpplint(projroot: Path, full: bool) -> None:
@@ -100,8 +104,7 @@ def check_cpplint(projroot: Path, full: bool) -> None:
raise Exception(f'Found space in path {fpath}; unexpected.')
# Check the config for a list of ones to ignore.
- code_blacklist: list[str] = getconfig(projroot).get(
- 'cpplint_blacklist', [])
+ code_blacklist: list[str] = getconfig(projroot).get('cpplint_blacklist', [])
# Just pretend blacklisted ones don't exist.
filenames = [f for f in filenames if f not in code_blacklist]
@@ -118,8 +121,10 @@ def check_cpplint(projroot: Path, full: bool) -> None:
dirtyfiles = cache.get_dirty_files()
if dirtyfiles:
- print(f'{Clr.BLU}CppLint checking'
- f' {len(dirtyfiles)} file(s)...{Clr.RST}')
+ print(
+ f'{Clr.BLU}CppLint checking'
+ f' {len(dirtyfiles)} file(s)...{Clr.RST}'
+ )
disabled_filters: list[str] = [
'build/include_what_you_use',
@@ -130,13 +135,20 @@ def check_cpplint(projroot: Path, full: bool) -> None:
filterstr = ','.join(f'-{x}' for x in disabled_filters)
def lint_file(filename: str) -> None:
- result = subprocess.call([
- f'python{PYVER}', '-m', 'cpplint', '--root=src',
- f'--filter={filterstr}', filename
- ])
+ result = subprocess.call(
+ [
+ f'python{PYVER}',
+ '-m',
+ 'cpplint',
+ '--root=src',
+ f'--filter={filterstr}',
+ filename,
+ ]
+ )
if result != 0:
raise CleanError(
- f'{Clr.RED}Cpplint failed for {filename}.{Clr.RST}')
+ f'{Clr.RED}Cpplint failed for {filename}.{Clr.RST}'
+ )
with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
# Converting this to a list will propagate any errors.
@@ -147,12 +159,14 @@ def check_cpplint(projroot: Path, full: bool) -> None:
cache.write()
print(
f'{Clr.GRN}CppLint: all {len(filenames)} files are passing.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
def get_code_filenames(projroot: Path) -> list[str]:
"""Return the list of files to lint-check or auto-formatting."""
from efrotools import getconfig
+
exts = ('.h', '.c', '.cc', '.cpp', '.cxx', '.m', '.mm')
places = getconfig(projroot).get('code_source_dirs', None)
if places is None:
@@ -170,72 +184,69 @@ def get_code_filenames(projroot: Path) -> list[str]:
def black_base_args() -> list[str]:
"""Build base args for running black Python formatting."""
from efrotools import PYVER
- pyver = PYVER.replace('.', '')
- if len(pyver) != 3:
+
+ pyver = 'py' + PYVER.replace('.', '')
+ if len(pyver) != 5:
raise RuntimeError('Py version filtering err.')
return [
- f'python{PYVER}', '-m', 'black', '--target-version', pyver,
- '--line-length', '80', '--skip-string-normalization'
+ f'python{PYVER}',
+ '-m',
+ 'black',
+ '--target-version',
+ pyver,
+ '--line-length',
+ '80',
+ '--skip-string-normalization',
]
def format_project_python_files(projroot: Path, full: bool) -> None:
- """Runs yapf on all of our Python code."""
- import time
- from concurrent.futures import ThreadPoolExecutor
- from multiprocessing import cpu_count
- from efrotools import get_files_hash, PYVER
+ """Runs formatting on all of our Python code."""
+ from efrotools import get_string_hash
+ from efro.error import CleanError
+
os.chdir(projroot)
cachepath = Path(projroot, '.cache/format_project_python_files')
if full and cachepath.exists():
cachepath.unlink()
cache = FileCache(cachepath)
- yapfconfig = Path(projroot, '.style.yapf')
-
filenames = get_script_filenames(projroot)
- confighash = get_files_hash([yapfconfig])
+
+ # Calc a config hash so we redo formatting after it changes.
+ confighash = get_string_hash(' '.join(black_base_args()))
cache.update(filenames, confighash)
dirtyfiles = cache.get_dirty_files()
- def format_file(filename: str) -> None:
- start_time = time.time()
- result = subprocess.call(
- [f'python{PYVER}', '-m', 'yapf', '--in-place', filename])
- if result != 0:
- raise Exception(f'Formatting failed for {filename}')
- duration = time.time() - start_time
- print(f'Formatted {filename} in {duration:.2f} seconds.')
- sys.stdout.flush()
-
- with ThreadPoolExecutor(max_workers=cpu_count()) as executor:
- # Convert the futures to a list to propagate any errors even
- # though there are no return values we use.
- list(executor.map(format_file, dirtyfiles))
+ if dirtyfiles:
+ # Run a single black command to batch everything.
+ cmd = black_base_args() + list(dirtyfiles)
+ if subprocess.run(cmd, check=False).returncode != 0:
+ raise CleanError(
+ f'Black formatting failed for {len(dirtyfiles)} files.'
+ )
if dirtyfiles:
# Since we changed files, need to update hashes again.
cache.update(filenames, confighash)
cache.mark_clean(filenames)
cache.write()
- print(f'Formatting is up to date for {len(filenames)} script files.',
- flush=True)
+ print(
+ f'Formatting is up to date for {len(filenames)} script files.',
+ flush=True,
+ )
-def format_python_str(projroot: Path, code: str) -> str:
- """Run yapf formatting on the provided inline code."""
- from efrotools import PYVER
+def format_python_str(code: str) -> str:
+ """Run our Python formatting on the provided inline code."""
- # We'll get incorrect results if run on an uninited repo dir/etc.
- assert os.path.exists(os.path.join(projroot, '.style.yapf'))
- out = subprocess.run([f'python{PYVER}', '-m', 'yapf'],
- capture_output=True,
- check=True,
- input=code.encode(),
- cwd=projroot)
- return out.stdout.decode()
+ return subprocess.run(
+ black_base_args() + ['--code', code],
+ capture_output=True,
+ check=True,
+ ).stdout.decode()
def _should_include_script(fnamefull: str) -> bool:
@@ -260,6 +271,7 @@ def _should_include_script(fnamefull: str) -> bool:
def get_script_filenames(projroot: Path) -> list[str]:
"""Return the Python filenames to lint-check or auto-format."""
from efrotools import getconfig
+
filenames = set()
places = getconfig(projroot).get('python_source_dirs', None)
if places is None:
@@ -286,17 +298,16 @@ def runpylint(projroot: Path, filenames: list[str]) -> None:
# Technically we could just run pylint standalone via command line here,
# but let's go ahead and run it inline so we're consistent with our cached
# full-project version.
- _run_pylint(projroot,
- pylintrc,
- cache=None,
- dirtyfiles=filenames,
- allfiles=None)
+ _run_pylint(
+ projroot, pylintrc, cache=None, dirtyfiles=filenames, allfiles=None
+ )
def pylint(projroot: Path, full: bool, fast: bool) -> None:
"""Run Pylint on all scripts in our project (with smart dep tracking)."""
from efrotools import get_files_hash
from efro.terminal import Clr
+
pylintrc = Path(projroot, '.pylintrc')
if not os.path.isfile(pylintrc):
raise Exception('pylintrc not found where expected')
@@ -331,21 +342,29 @@ def pylint(projroot: Path, full: bool, fast: bool) -> None:
if dirtyfiles:
print(
f'{Clr.BLU}Pylint checking {len(dirtyfiles)} file(s)...{Clr.RST}',
- flush=True)
+ flush=True,
+ )
try:
_run_pylint(projroot, pylintrc, cache, dirtyfiles, filenames)
finally:
# No matter what happens, we still want to
# update our disk cache (since some lints may have passed).
cache.write()
- print(f'{Clr.GRN}Pylint: all {len(filenames)} files are passing.{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.GRN}Pylint: all {len(filenames)} files are passing.{Clr.RST}',
+ flush=True,
+ )
cache.write()
-def _dirty_dep_check(fname: str, filestates: dict[str, bool], cache: FileCache,
- fast: bool, recursion: int) -> bool:
+def _dirty_dep_check(
+ fname: str,
+ filestates: dict[str, bool],
+ cache: FileCache,
+ fast: bool,
+ recursion: int,
+) -> bool:
"""Recursively check a file's deps and return whether it is dirty."""
# pylint: disable=too-many-branches
@@ -390,8 +409,9 @@ def _dirty_dep_check(fname: str, filestates: dict[str, bool], cache: FileCache,
if not os.path.exists(dep):
dirty = True
break
- if _dirty_dep_check(dep, filestates, cache, fast,
- recursion2):
+ if _dirty_dep_check(
+ dep, filestates, cache, fast, recursion2
+ ):
dirty = True
break
@@ -403,13 +423,18 @@ def _dirty_dep_check(fname: str, filestates: dict[str, bool], cache: FileCache,
return dirty
-def _run_pylint(projroot: Path, pylintrc: Path | str, cache: FileCache | None,
- dirtyfiles: list[str],
- allfiles: list[str] | None) -> dict[str, Any]:
+def _run_pylint(
+ projroot: Path,
+ pylintrc: Path | str,
+ cache: FileCache | None,
+ dirtyfiles: list[str],
+ allfiles: list[str] | None,
+) -> dict[str, Any]:
import time
from pylint import lint
from efro.error import CleanError
from efro.terminal import Clr
+
start_time = time.time()
args = ['--rcfile', str(pylintrc), '--output-format=colorized']
@@ -418,8 +443,9 @@ def _run_pylint(projroot: Path, pylintrc: Path | str, cache: FileCache | None,
run = lint.Run(args, do_exit=False)
if cache is not None:
assert allfiles is not None
- result = _apply_pylint_run_to_cache(projroot, run, dirtyfiles,
- allfiles, cache)
+ result = _apply_pylint_run_to_cache(
+ projroot, run, dirtyfiles, allfiles, cache
+ )
if result != 0:
raise CleanError(f'Pylint failed for {result} file(s).')
@@ -427,21 +453,30 @@ def _run_pylint(projroot: Path, pylintrc: Path | str, cache: FileCache | None,
# If not, it means we're probably missing something and incorrectly
# marking a failed file as clean.
if run.linter.msg_status != 0 and result == 0:
- raise RuntimeError('Pylint linter returned non-zero result'
- ' but we did not; this is probably a bug.')
+ raise RuntimeError(
+ 'Pylint linter returned non-zero result'
+ ' but we did not; this is probably a bug.'
+ )
else:
if run.linter.msg_status != 0:
raise CleanError('Pylint failed.')
duration = time.time() - start_time
- print(f'{Clr.GRN}Pylint passed for {name}'
- f' in {duration:.1f} seconds.{Clr.RST}')
+ print(
+ f'{Clr.GRN}Pylint passed for {name}'
+ f' in {duration:.1f} seconds.{Clr.RST}'
+ )
sys.stdout.flush()
return {'f': dirtyfiles, 't': duration}
-def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: list[str],
- allfiles: list[str], cache: FileCache) -> int:
+def _apply_pylint_run_to_cache(
+ projroot: Path,
+ run: Any,
+ dirtyfiles: list[str],
+ allfiles: list[str],
+ cache: FileCache,
+) -> int:
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
@@ -495,7 +530,8 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: list[str],
untracked_deps.add(mname)
ignored_untracked_deps: set[str] = set(
- getconfig(projroot).get('pylint_ignored_untracked_deps', []))
+ getconfig(projroot).get('pylint_ignored_untracked_deps', [])
+ )
# Add a few that this package itself triggers.
ignored_untracked_deps |= {'pylint.lint', 'astroid.modutils', 'astroid'}
@@ -505,28 +541,81 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: list[str],
# suppressing them here but should come up with a more robust system
# as I feel this will get annoying fast.
ignored_untracked_deps |= {
- 're', 'importlib', 'os', 'xml.dom', 'weakref', 'random',
- 'collections.abc', 'textwrap', 'webbrowser', 'signal', 'pathlib',
- 'zlib', 'json', 'pydoc', 'base64', 'functools', 'asyncio', 'xml',
- '__future__', 'traceback', 'typing', 'urllib.parse', 'ctypes.wintypes',
- 'code', 'urllib.error', 'threading', 'xml.etree.ElementTree', 'pickle',
- 'dataclasses', 'enum', 'py_compile', 'urllib.request', 'math',
- 'multiprocessing', 'socket', 'getpass', 'hashlib', 'ctypes', 'inspect',
- 'rlcompleter', 'http.client', 'readline', 'platform', 'datetime',
- 'copy', 'concurrent.futures', 'ast', 'subprocess', 'numbers',
- 'logging', 'xml.dom.minidom', 'uuid', 'types', 'tempfile', 'shutil',
- 'shlex', 'stat', 'wave', 'html', 'binascii'
+ 're',
+ 'importlib',
+ 'os',
+ 'xml.dom',
+ 'weakref',
+ 'random',
+ 'collections.abc',
+ 'textwrap',
+ 'webbrowser',
+ 'signal',
+ 'pathlib',
+ 'zlib',
+ 'json',
+ 'pydoc',
+ 'base64',
+ 'functools',
+ 'asyncio',
+ 'xml',
+ '__future__',
+ 'traceback',
+ 'typing',
+ 'urllib.parse',
+ 'ctypes.wintypes',
+ 'code',
+ 'urllib.error',
+ 'threading',
+ 'xml.etree.ElementTree',
+ 'pickle',
+ 'dataclasses',
+ 'enum',
+ 'py_compile',
+ 'urllib.request',
+ 'math',
+ 'multiprocessing',
+ 'socket',
+ 'getpass',
+ 'hashlib',
+ 'ctypes',
+ 'inspect',
+ 'rlcompleter',
+ 'http.client',
+ 'readline',
+ 'platform',
+ 'datetime',
+ 'copy',
+ 'concurrent.futures',
+ 'ast',
+ 'subprocess',
+ 'numbers',
+ 'logging',
+ 'xml.dom.minidom',
+ 'uuid',
+ 'types',
+ 'tempfile',
+ 'shutil',
+ 'shlex',
+ 'stat',
+ 'wave',
+ 'html',
+ 'binascii',
}
# Ignore some specific untracked deps; complain about any others.
- untracked_deps = set(dep for dep in untracked_deps
- if dep not in ignored_untracked_deps
- and not dep.startswith('bametainternal'))
+ untracked_deps = set(
+ dep
+ for dep in untracked_deps
+ if dep not in ignored_untracked_deps
+ and not dep.startswith('bametainternal')
+ )
if untracked_deps:
raise CleanError(
f'Pylint found untracked dependencies: {untracked_deps}.'
' If these are external to your project, add them to'
- ' "pylint_ignored_untracked_deps" in the project config.')
+ ' "pylint_ignored_untracked_deps" in the project config.'
+ )
# Finally add the dependency lists to our entries (operate on
# everything in the run; it may not be mentioned in deps).
@@ -547,8 +636,9 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: list[str],
# this is behaving.
if no_deps_modules:
if bool(False):
- print('NOTE: no dependencies found for:',
- ', '.join(no_deps_modules))
+ print(
+ 'NOTE: no dependencies found for:', ', '.join(no_deps_modules)
+ )
# Ok, now go through all dirtyfiles involved in this run.
# Mark them as either errored or clean depending on whether there's
@@ -577,7 +667,7 @@ def _apply_pylint_run_to_cache(projroot: Path, run: Any, dirtyfiles: list[str],
errcount += 1
else:
# print('MARKING FILE CLEAN', mname2, fname)
- cache.entries[fname]['hash'] = (cache.curhashes[fname])
+ cache.entries[fname]['hash'] = cache.curhashes[fname]
return errcount
@@ -591,16 +681,20 @@ def _filter_module_name(mpath: str) -> str:
return mpath[:-9] if mpath.endswith('.__init__') else mpath
-def runmypy(projroot: Path,
- filenames: list[str],
- full: bool = False,
- check: bool = True) -> None:
+def runmypy(
+ projroot: Path, filenames: list[str], full: bool = False, check: bool = True
+) -> None:
"""Run MyPy on provided filenames."""
from efrotools import PYTHON_BIN
+
args = [
- PYTHON_BIN, '-m', 'mypy', '--pretty', '--no-error-summary',
+ PYTHON_BIN,
+ '-m',
+ 'mypy',
+ '--pretty',
+ '--no-error-summary',
'--config-file',
- str(Path(projroot, '.mypy.ini'))
+ str(Path(projroot, '.mypy.ini')),
] + filenames
if full:
args.insert(args.index('mypy') + 1, '--no-incremental')
@@ -612,6 +706,7 @@ def mypy(projroot: Path, full: bool) -> None:
import time
from efro.terminal import Clr
from efro.error import CleanError
+
filenames = get_script_filenames(projroot)
desc = '(full)' if full else '(incremental)'
print(f'{Clr.BLU}Running Mypy {desc}...{Clr.RST}', flush=True)
@@ -621,8 +716,9 @@ def mypy(projroot: Path, full: bool) -> None:
except Exception as exc:
raise CleanError('Mypy failed.') from exc
duration = time.time() - starttime
- print(f'{Clr.GRN}Mypy passed in {duration:.1f} seconds.{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.GRN}Mypy passed in {duration:.1f} seconds.{Clr.RST}', flush=True
+ )
def dmypy(projroot: Path) -> None:
@@ -630,6 +726,7 @@ def dmypy(projroot: Path) -> None:
import time
from efro.terminal import Clr
from efro.error import CleanError
+
filenames = get_script_filenames(projroot)
# Special case; explicitly kill the daemon.
@@ -641,15 +738,23 @@ def dmypy(projroot: Path) -> None:
starttime = time.time()
try:
args = [
- 'dmypy', 'run', '--timeout', '3600', '--', '--config-file',
- '.mypy.ini', '--pretty'
+ 'dmypy',
+ 'run',
+ '--timeout',
+ '3600',
+ '--',
+ '--config-file',
+ '.mypy.ini',
+ '--pretty',
] + filenames
subprocess.run(args, check=True)
except Exception as exc:
raise CleanError('Mypy daemon: fail.') from exc
duration = time.time() - starttime
- print(f'{Clr.GRN}Mypy daemon passed in {duration:.1f} seconds.{Clr.RST}',
- flush=True)
+ print(
+ f'{Clr.GRN}Mypy daemon passed in {duration:.1f} seconds.{Clr.RST}',
+ flush=True,
+ )
def _parse_idea_results(path: Path) -> int:
@@ -658,6 +763,7 @@ def _parse_idea_results(path: Path) -> int:
Returns the number of errors found.
"""
import xml.etree.ElementTree as Et
+
error_count = 0
root = Et.parse(str(path)).getroot()
for child in root:
@@ -686,12 +792,14 @@ def _parse_idea_results(path: Path) -> int:
return error_count
-def _run_idea_inspections(projroot: Path,
- scripts: list[str],
- displayname: str,
- inspect: Path,
- verbose: bool,
- inspectdir: Path | None = None) -> None:
+def _run_idea_inspections(
+ projroot: Path,
+ scripts: list[str],
+ displayname: str,
+ inspect: Path,
+ verbose: bool,
+ inspectdir: Path | None = None,
+) -> None:
"""Actually run idea inspections.
Throw an Exception if anything is found or goes wrong.
@@ -703,11 +811,13 @@ def _run_idea_inspections(projroot: Path,
import datetime
from efro.error import CleanError
from efro.terminal import Clr
+
start_time = time.time()
print(
f'{Clr.BLU}{displayname} checking'
f' {len(scripts)} file(s)...{Clr.RST}',
- flush=True)
+ flush=True,
+ )
tmpdir = tempfile.TemporaryDirectory()
iprof = Path(projroot, '.idea/inspectionProfiles/Default.xml')
if not iprof.exists():
@@ -727,6 +837,7 @@ def _run_idea_inspections(projroot: Path,
if verbose:
import threading
+
print(cmd, flush=True)
threading.Thread(target=heartbeat, daemon=True).start()
@@ -735,12 +846,20 @@ def _run_idea_inspections(projroot: Path,
if result.returncode != 0:
# In verbose mode this stuff got printed already.
if not verbose:
- stdout = (result.stdout.decode() if isinstance(
- result.stdout, bytes) else str(result.stdout))
- stderr = (result.stderr.decode() if isinstance(
- result.stdout, bytes) else str(result.stdout))
- print(f'{displayname} inspection failure stdout:\n{stdout}' +
- f'{displayname} inspection failure stderr:\n{stderr}')
+ stdout = (
+ result.stdout.decode()
+ if isinstance(result.stdout, bytes)
+ else str(result.stdout)
+ )
+ stderr = (
+ result.stderr.decode()
+ if isinstance(result.stdout, bytes)
+ else str(result.stdout)
+ )
+ print(
+ f'{displayname} inspection failure stdout:\n{stdout}'
+ + f'{displayname} inspection failure stderr:\n{stderr}'
+ )
raise RuntimeError(f'{displayname} inspection failed.')
files = [f for f in os.listdir(tmpdir.name) if not f.startswith('.')]
total_errors = 0
@@ -748,27 +867,33 @@ def _run_idea_inspections(projroot: Path,
for fname in files:
total_errors += _parse_idea_results(Path(tmpdir.name, fname))
if total_errors > 0:
- raise CleanError(f'{Clr.SRED}{displayname} inspection'
- f' found {total_errors} error(s).{Clr.RST}')
+ raise CleanError(
+ f'{Clr.SRED}{displayname} inspection'
+ f' found {total_errors} error(s).{Clr.RST}'
+ )
duration = time.time() - start_time
print(
f'{Clr.GRN}{displayname} passed for {len(scripts)} files'
f' in {duration:.1f} seconds.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
-def _run_idea_inspections_cached(cachepath: Path,
- filenames: list[str],
- full: bool,
- projroot: Path,
- displayname: str,
- inspect: Path,
- verbose: bool,
- inspectdir: Path | None = None) -> None:
+def _run_idea_inspections_cached(
+ cachepath: Path,
+ filenames: list[str],
+ full: bool,
+ projroot: Path,
+ displayname: str,
+ inspect: Path,
+ verbose: bool,
+ inspectdir: Path | None = None,
+) -> None:
# pylint: disable=too-many-locals
import hashlib
import json
from efro.terminal import Clr
+
md5 = hashlib.md5()
# Let's calc a single hash from the contents of all script files and only
@@ -783,7 +908,7 @@ def _run_idea_inspections_cached(cachepath: Path,
extra_hash_paths = [
Path(projroot, '.idea/inspectionProfiles/Default.xml'),
Path(projroot, '.idea/inspectionProfiles/Project_Default.xml'),
- Path(projroot, '.idea/dictionaries/ericf.xml')
+ Path(projroot, '.idea/dictionaries/ericf.xml'),
]
for epath in extra_hash_paths:
if os.path.exists(epath):
@@ -798,19 +923,22 @@ def _run_idea_inspections_cached(cachepath: Path,
except Exception:
existing_hash = None
if full or current_hash != existing_hash:
- _run_idea_inspections(projroot,
- filenames,
- displayname,
- inspect=inspect,
- verbose=verbose,
- inspectdir=inspectdir)
+ _run_idea_inspections(
+ projroot,
+ filenames,
+ displayname,
+ inspect=inspect,
+ verbose=verbose,
+ inspectdir=inspectdir,
+ )
cachepath.parent.mkdir(parents=True, exist_ok=True)
with open(cachepath, 'w', encoding='utf-8') as outfile:
outfile.write(json.dumps({'hash': current_hash}))
print(
f'{Clr.GRN}{displayname}: all {len(filenames)}'
f' files are passing.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
def check_pycharm(projroot: Path, full: bool, verbose: bool) -> None:
@@ -838,9 +966,11 @@ def check_pycharm(projroot: Path, full: bool, verbose: bool) -> None:
# the cache for 'full' mode checks without the env var.
if full and os.environ.get('EFROTOOLS_FULL_PYCHARM_RECACHE') == '1':
print('Clearing PyCharm caches...', flush=True)
- subprocess.run('rm -rf ~/Library/Caches/JetBrains/PyCharmCE*',
- shell=True,
- check=True)
+ subprocess.run(
+ 'rm -rf ~/Library/Caches/JetBrains/PyCharmCE*',
+ shell=True,
+ check=True,
+ )
# Hoping this isn't necessary anymore. Need to rework this if it is,
# since it now gets run through ssh and gui stuff doesn't seem to
@@ -859,17 +989,20 @@ def check_pycharm(projroot: Path, full: bool, verbose: bool) -> None:
subprocess.run(
"osascript -e 'tell application \"PyCharm CE\" to quit'",
shell=True,
- check=False)
+ check=False,
+ )
print('Waiting for GUI PyCharm to quit...', flush=True)
process.wait()
- _run_idea_inspections_cached(cachepath=cachepath,
- filenames=filenames,
- full=full,
- projroot=projroot,
- displayname='PyCharm',
- inspect=inspect,
- verbose=verbose)
+ _run_idea_inspections_cached(
+ cachepath=cachepath,
+ filenames=filenames,
+ full=full,
+ projroot=projroot,
+ displayname='PyCharm',
+ inspect=inspect,
+ verbose=verbose,
+ )
def check_clioncode(projroot: Path, full: bool, verbose: bool) -> None:
@@ -892,9 +1025,9 @@ def check_clioncode(projroot: Path, full: bool, verbose: bool) -> None:
caches_root = os.environ['HOME'] + '/Library/Caches/JetBrains'
if not os.path.exists(caches_root):
raise RuntimeError(f'CLion caches root not found: {caches_root}')
- subprocess.run('rm -rf ~/Library/Caches/JetBrains/CLion*',
- shell=True,
- check=True)
+ subprocess.run(
+ 'rm -rf ~/Library/Caches/JetBrains/CLion*', shell=True, check=True
+ )
# UPDATE: seems this is unnecessary now; should double check.
# Note: I'm assuming this project needs to be open when the GUI
@@ -904,9 +1037,9 @@ def check_clioncode(projroot: Path, full: bool, verbose: bool) -> None:
print('Launching GUI CLion to rebuild caches...', flush=True)
# process = subprocess.Popen(str(clionbin))
subprocess.run(
- ['open', '-a', clionroot,
- Path(projroot, 'ballisticacore-cmake')],
- check=True)
+ ['open', '-a', clionroot, Path(projroot, 'ballisticacore-cmake')],
+ check=True,
+ )
# Wait a moment and ask it nicely to die.
waittime = 60
@@ -920,10 +1053,12 @@ def check_clioncode(projroot: Path, full: bool, verbose: bool) -> None:
print('Waiting for GUI CLion to quit...', flush=True)
subprocess.run(
[
- 'osascript', '-e', 'tell application "CLion" to quit\n'
+ 'osascript',
+ '-e',
+ 'tell application "CLion" to quit\n'
'repeat until application "CLion" is not running\n'
' delay 1\n'
- 'end repeat'
+ 'end repeat',
],
check=False,
)
@@ -933,15 +1068,16 @@ def check_clioncode(projroot: Path, full: bool, verbose: bool) -> None:
# process.wait(timeout=60)
print('Launching Offline CLion to run inspections...', flush=True)
- _run_idea_inspections_cached(cachepath=cachepath,
- filenames=filenames,
- full=full,
- projroot=Path(projroot,
- 'ballisticacore-cmake'),
- inspectdir=Path(projroot, 'src/ballistica'),
- displayname='CLion',
- inspect=inspect,
- verbose=verbose)
+ _run_idea_inspections_cached(
+ cachepath=cachepath,
+ filenames=filenames,
+ full=full,
+ projroot=Path(projroot, 'ballisticacore-cmake'),
+ inspectdir=Path(projroot, 'src/ballistica'),
+ displayname='CLion',
+ inspect=inspect,
+ verbose=verbose,
+ )
def check_android_studio(projroot: Path, full: bool, verbose: bool) -> None:
@@ -992,12 +1128,13 @@ def check_android_studio(projroot: Path, full: bool, verbose: bool) -> None:
projroot=Path(projroot, 'ballisticacore-android'),
inspectdir=Path(
projroot,
- 'ballisticacore-android/BallisticaCore/src/main/cpp/src/ballistica'
+ 'ballisticacore-android/BallisticaCore/src/main/cpp/src/ballistica',
),
# inspectdir=None,
displayname='Android Studio',
inspect=inspect,
- verbose=verbose)
+ verbose=verbose,
+ )
def sort_jetbrains_dict(original: str) -> str:
@@ -1008,8 +1145,8 @@ def sort_jetbrains_dict(original: str) -> str:
if lines[-3] != ' ':
raise RuntimeError('Unexpected dictionary format b.')
if not all(
- l.startswith(' ') and l.endswith('')
- for l in lines[3:-3]):
+ l.startswith(' ') and l.endswith('') for l in lines[3:-3]
+ ):
raise RuntimeError('Unexpected dictionary format.')
# Sort lines in the words section.
@@ -1018,6 +1155,8 @@ def sort_jetbrains_dict(original: str) -> str:
# Note: need to pull the off the end of the line when sorting
# or it messes with the order and we get different results than
# Jetbrains stuff.
- return '\n'.join(lines[:3] +
- sorted(lines[3:-3], key=lambda x: x.replace('', '')) +
- lines[-3:])
+ return '\n'.join(
+ lines[:3]
+ + sorted(lines[3:-3], key=lambda x: x.replace('', ''))
+ + lines[-3:]
+ )
diff --git a/tools/efrotools/efrocache.py b/tools/efrotools/efrocache.py
index 7b38c52b..f26ab962 100644
--- a/tools/efrotools/efrocache.py
+++ b/tools/efrotools/efrocache.py
@@ -38,6 +38,7 @@ def get_file_hash(path: str) -> str:
This incorporates the file contents as well as its path.
"""
import hashlib
+
md5 = hashlib.md5()
with open(path, 'rb') as infile:
md5.update(infile.read())
@@ -55,8 +56,9 @@ def _project_centric_path(path: str) -> str:
abspath = os.path.abspath(path).replace('\\', '/')
if not abspath.startswith(projpath):
raise RuntimeError(
- f'Path "{abspath}" is not under project root "{projpath}"')
- return abspath[len(projpath):]
+ f'Path "{abspath}" is not under project root "{projpath}"'
+ )
+ return abspath[len(projpath) :]
def get_target(path: str) -> None:
@@ -98,21 +100,26 @@ def get_target(path: str) -> None:
result = subprocess.run(
f'curl --fail --silent {url} --output {local_cache_path_dl}',
shell=True,
- check=False)
+ check=False,
+ )
# We prune old cache files on the server, so its possible for one to
# be trying to build something the server can no longer provide.
# try to explain the situation.
if result.returncode == 22:
- raise CleanError('Server gave an error.'
- ' Old build files may no longer be available;'
- ' make sure you are using a recent commit.')
+ raise CleanError(
+ 'Server gave an error.'
+ ' Old build files may no longer be available;'
+ ' make sure you are using a recent commit.'
+ )
if result.returncode != 0:
raise CleanError('Download failed; is your internet working?')
- subprocess.run(f'mv {local_cache_path_dl} {local_cache_path}',
- shell=True,
- check=True)
+ subprocess.run(
+ f'mv {local_cache_path_dl} {local_cache_path}',
+ shell=True,
+ check=True,
+ )
# Ok we should have a valid .tar.gz file in our cache dir at this point.
# Just expand it and it get placed wherever it belongs.
@@ -177,6 +184,7 @@ def update_cache(makefile_dirs: list[str]) -> None:
"""Given a list of directories containing Makefiles, update caches."""
import multiprocessing
+
cpus = multiprocessing.cpu_count()
fnames1: list[str] = []
fnames2: list[str] = []
@@ -186,20 +194,28 @@ def update_cache(makefile_dirs: list[str]) -> None:
# First, make sure all cache files are built.
mfpath = os.path.join(path, 'Makefile')
print(f'Building efrocache targets for {Clr.SBLU}{mfpath}{Clr.RST}...')
- subprocess.run(f'{cdp}make -j{cpus} efrocache-build',
- shell=True,
- check=True)
+ subprocess.run(
+ f'{cdp}make -j{cpus} efrocache-build', shell=True, check=True
+ )
- rawpaths = subprocess.run(f'{cdp}make efrocache-list',
- shell=True,
- check=True,
- capture_output=True).stdout.decode().split()
+ rawpaths = (
+ subprocess.run(
+ f'{cdp}make efrocache-list',
+ shell=True,
+ check=True,
+ capture_output=True,
+ )
+ .stdout.decode()
+ .split()
+ )
# Make sure the paths they gave were relative.
for rawpath in rawpaths:
if rawpath.startswith('/'):
- raise RuntimeError(f'Invalid path returned for caching '
- f'(absolute paths not allowed): {rawpath}')
+ raise RuntimeError(
+ f'Invalid path returned for caching '
+ f'(absolute paths not allowed): {rawpath}'
+ )
# Break these into 2 lists, one of which will be included in the
# starter-cache.
@@ -227,7 +243,8 @@ def update_cache(makefile_dirs: list[str]) -> None:
print(
f'{Clr.SBLU}Efrocache state unchanged;'
f' skipping cache push.{Clr.RST}',
- flush=True)
+ flush=True,
+ )
else:
_upload_cache(fnames1, fnames2, hashes, hashes_existing)
@@ -239,8 +256,12 @@ def update_cache(makefile_dirs: list[str]) -> None:
outfile.write(hashes)
-def _upload_cache(fnames1: list[str], fnames2: list[str], hashes_str: str,
- hashes_existing_str: str) -> None:
+def _upload_cache(
+ fnames1: list[str],
+ fnames2: list[str],
+ hashes_str: str,
+ hashes_existing_str: str,
+) -> None:
# First, if we've run before, print the files causing us to re-run:
if hashes_existing_str != '':
@@ -255,8 +276,10 @@ def _upload_cache(fnames1: list[str], fnames2: list[str], hashes_str: str,
for fname in hashes_existing:
if fname not in hashes:
changed_files.add(fname)
- print(f'{Clr.SBLU}Updating efrocache due to'
- f' {len(changed_files)} changes:{Clr.RST}')
+ print(
+ f'{Clr.SBLU}Updating efrocache due to'
+ f' {len(changed_files)} changes:{Clr.RST}'
+ )
for fname in sorted(changed_files):
print(f' {Clr.SBLU}{fname}{Clr.RST}')
@@ -268,8 +291,10 @@ def _upload_cache(fnames1: list[str], fnames2: list[str], hashes_str: str,
_write_cache_files(fnames1, fnames2, staging_dir, mapping_file)
- print(f'{Clr.SBLU}Starter cache includes {len(fnames1)} items;'
- f' excludes {len(fnames2)}{Clr.RST}')
+ print(
+ f'{Clr.SBLU}Starter cache includes {len(fnames1)} items;'
+ f' excludes {len(fnames2)}{Clr.RST}'
+ )
# Sync all individual cache files to the staging server.
print(f'{Clr.SBLU}Pushing cache to staging...{Clr.RST}', flush=True)
@@ -277,7 +302,8 @@ def _upload_cache(fnames1: list[str], fnames2: list[str], hashes_str: str,
'rsync --progress --recursive --human-readable build/efrocache/'
' ubuntu@staging.ballistica.net:files.ballistica.net/cache/ba1/',
shell=True,
- check=True)
+ check=True,
+ )
# Now generate the starter cache on the server..
subprocess.run(
@@ -285,7 +311,8 @@ def _upload_cache(fnames1: list[str], fnames2: list[str], hashes_str: str,
'ubuntu@staging.ballistica.net'
' "cd files.ballistica.net/cache/ba1 && python3 genstartercache.py"',
shell=True,
- check=True)
+ check=True,
+ )
def _gen_hashes(fnames: list[str]) -> str:
@@ -304,9 +331,11 @@ def _gen_hashes(fnames: list[str]) -> str:
return json.dumps(hashes, separators=(',', ':'))
-def _write_cache_files(fnames1: list[str], fnames2: list[str],
- staging_dir: str, mapping_file: str) -> None:
+def _write_cache_files(
+ fnames1: list[str], fnames2: list[str], staging_dir: str, mapping_file: str
+) -> None:
import functools
+
fhashes1: set[str] = set()
fhashes2: set[str] = set()
mapping: dict[str, str] = {}
@@ -357,10 +386,12 @@ def _write_cache_files(fnames1: list[str], fnames2: list[str],
'subprocess.run(["mv", "tmp.tar.xz", "startercache.tar.xz"],'
' check=True)\n'
'subprocess.run(["rm", "-rf", "efrocache", "genstartercache.py"])\n'
- 'print("Starter cache generation complete!", flush=True)\n')
+ 'print("Starter cache generation complete!", flush=True)\n'
+ )
- with open('build/efrocache/genstartercache.py', 'w',
- encoding='utf-8') as outfile:
+ with open(
+ 'build/efrocache/genstartercache.py', 'w', encoding='utf-8'
+ ) as outfile:
outfile.write(script)
with open(mapping_file, 'w', encoding='utf-8') as outfile:
@@ -369,6 +400,7 @@ def _write_cache_files(fnames1: list[str], fnames2: list[str],
def _write_cache_file(staging_dir: str, fname: str) -> tuple[str, str]:
import hashlib
+
print(f'Caching {fname}')
if ' ' in fname:
raise RuntimeError('Spaces in paths not supported.')
@@ -388,14 +420,17 @@ def _write_cache_file(staging_dir: str, fname: str) -> tuple[str, str]:
# with no embedded timestamps.
# Note: The 'COPYFILE_DISABLE' prevents mac tar from adding
# file attributes/resource-forks to the archive as as ._filename.
- subprocess.run(f'COPYFILE_DISABLE=1 tar cf - {fname} | gzip -n > {path}',
- shell=True,
- check=True)
+ subprocess.run(
+ f'COPYFILE_DISABLE=1 tar cf - {fname} | gzip -n > {path}',
+ shell=True,
+ check=True,
+ )
return fname, hashpath
def _check_warm_start_entry(entry: tuple[str, str]) -> None:
import hashlib
+
fname, filehash = entry
md5 = hashlib.md5()
with open(fname, 'rb') as infile:
@@ -431,15 +466,16 @@ def warm_start_cache() -> None:
f'curl --fail {BASE_URL}startercache.tar.xz'
f' --output startercache.tar.xz',
shell=True,
- check=True)
+ check=True,
+ )
print('Decompressing starter-cache...', flush=True)
subprocess.run('tar -xf startercache.tar.xz', shell=True, check=True)
- subprocess.run(f'mv efrocache {CACHE_DIR_NAME}',
- shell=True,
- check=True)
+ subprocess.run(f'mv efrocache {CACHE_DIR_NAME}', shell=True, check=True)
subprocess.run('rm startercache.tar.xz', shell=True, check=True)
- print('Starter-cache fetched successfully!'
- ' (should speed up asset builds)')
+ print(
+ 'Starter-cache fetched successfully!'
+ ' (should speed up asset builds)'
+ )
# In the public build, let's scan through all files managed by
# efrocache and update any with timestamps older than the latest
diff --git a/tools/efrotools/filecache.py b/tools/efrotools/filecache.py
index 1a4fdb69..711fb637 100644
--- a/tools/efrotools/filecache.py
+++ b/tools/efrotools/filecache.py
@@ -13,6 +13,7 @@ from typing import TYPE_CHECKING
# pylint: disable=wrong-import-order
from efro.terminal import Clr
from efrotools import get_files_hash
+
# pylint: enable=wrong-import-order
# pylint: enable=useless-suppression
@@ -46,13 +47,13 @@ class FileCache:
# First, completely prune entries for nonexistent files.
self.entries = {
path: val
- for path, val in self.entries.items() if os.path.isfile(path)
+ for path, val in self.entries.items()
+ if os.path.isfile(path)
}
# Also remove any not in our passed list.
self.entries = {
- path: val
- for path, val in self.entries.items() if path in filenames
+ path: val for path, val in self.entries.items() if path in filenames
}
# Add empty entries for files that lack them.
@@ -61,8 +62,9 @@ class FileCache:
for filename in filenames:
if filename not in self.entries:
self.entries[filename] = {}
- self.curhashes[filename] = curhash = (get_files_hash([filename],
- extrahash))
+ self.curhashes[filename] = curhash = get_files_hash(
+ [filename], extrahash
+ )
# Also store modtimes; we'll abort cache writes if
# anything changed.
self.mtimes[filename] = os.path.getmtime(filename)
@@ -92,8 +94,10 @@ class FileCache:
# if anything has been modified, don't write.
for fname, mtime in self.mtimes.items():
if os.path.getmtime(fname) != mtime:
- print(f'{Clr.MAG}File changed during run:'
- f' "{fname}"; cache not updated.{Clr.RST}')
+ print(
+ f'{Clr.MAG}File changed during run:'
+ f' "{fname}"; cache not updated.{Clr.RST}'
+ )
return
out = json.dumps(self.entries)
self._path.parent.mkdir(parents=True, exist_ok=True)
diff --git a/tools/efrotools/filecommand.py b/tools/efrotools/filecommand.py
index bbde1f8f..684c44f8 100644
--- a/tools/efrotools/filecommand.py
+++ b/tools/efrotools/filecommand.py
@@ -14,12 +14,13 @@ if TYPE_CHECKING:
class _FileBatchesRun:
-
- def __init__(self,
- paths: list[str],
- batch_size: int,
- file_filter: Callable[[str], bool] | None,
- include_mac_packages: bool = False) -> None:
+ def __init__(
+ self,
+ paths: list[str],
+ batch_size: int,
+ file_filter: Callable[[str], bool] | None,
+ include_mac_packages: bool = False,
+ ) -> None:
self.condition = Condition()
self.paths = paths
self.batches: list[list[str]] = []
@@ -36,6 +37,7 @@ class _FileBatchesRun:
# pylint: disable=no-name-in-module, import-error
# noinspection PyUnresolvedReferences
from Cocoa import NSWorkspace
+
self._shared_nsworkspace = NSWorkspace.sharedWorkspace()
# pylint: enable=useless-suppression
else:
@@ -48,8 +50,9 @@ class _FileBatchesRun:
# stuff our new results in, and inform any listeners that it has
# changed.
with self.condition:
- self.condition.wait_for(lambda: len(self.batches) < self.
- batch_buffer_size or self.done)
+ self.condition.wait_for(
+ lambda: len(self.batches) < self.batch_buffer_size or self.done
+ )
self.batches.append(self._pending_batch)
self._pending_batch = []
self.condition.notify()
@@ -85,8 +88,9 @@ class _FileBatchesRun:
if self._include_mac_packages:
for dirname in list(dirs):
fullpath = os.path.join(root, dirname)
- if (self._shared_nsworkspace.isFilePackageAtPath_(
- fullpath)):
+ if self._shared_nsworkspace.isFilePackageAtPath_(
+ fullpath
+ ):
dirs.remove(dirname)
self._possibly_add_to_pending_batch(fullpath)
@@ -116,10 +120,12 @@ def file_batches(
synchronous operations on the returned batches will not slow the gather.
"""
- run = _FileBatchesRun(paths=paths,
- batch_size=batch_size,
- file_filter=file_filter,
- include_mac_packages=include_mac_packages)
+ run = _FileBatchesRun(
+ paths=paths,
+ batch_size=batch_size,
+ file_filter=file_filter,
+ include_mac_packages=include_mac_packages,
+ )
# Spin up a bg thread to feed us batches.
thread = Thread(target=run.bg_thread)
@@ -128,8 +134,9 @@ def file_batches(
# Now spin waiting for new batches to come in or completion/errors.
while True:
with run.condition:
- run.condition.wait_for(lambda: run.done or run.errored or run.
- batches)
+ run.condition.wait_for(
+ lambda: run.done or run.errored or run.batches
+ )
try:
if run.errored:
raise RuntimeError('BG batch run errored.')
diff --git a/tools/efrotools/ios.py b/tools/efrotools/ios.py
index 64f35a64..4b2bfd3f 100644
--- a/tools/efrotools/ios.py
+++ b/tools/efrotools/ios.py
@@ -12,12 +12,8 @@ from dataclasses import dataclass
from efrotools import getlocalconfig, getconfig
MODES = {
- 'debug': {
- 'configuration': 'Debug'
- },
- 'release': {
- 'configuration': 'Release'
- }
+ 'debug': {'configuration': 'Debug'},
+ 'release': {'configuration': 'Release'},
}
@@ -59,6 +55,7 @@ def push_ipa(root: pathlib.Path, modename: str) -> None:
that is not physically near the build machine.
"""
from efrotools.xcode import project_build_path
+
# Load both the local and project config data.
cfg = Config(**getconfig(root)['push_ipa_config'])
lcfg = LocalConfig(**getlocalconfig(root)['push_ipa_local_config'])
@@ -68,11 +65,13 @@ def push_ipa(root: pathlib.Path, modename: str) -> None:
mode = MODES[modename]
xcprojpath = pathlib.Path(root, cfg.projectpath)
- app_dir = project_build_path(projroot=str(root),
- project_path=str(xcprojpath),
- scheme='BallisticaCore iOS Legacy',
- configuration=mode['configuration'],
- executable=False)
+ app_dir = project_build_path(
+ projroot=str(root),
+ project_path=str(xcprojpath),
+ scheme='BallisticaCore iOS Legacy',
+ configuration=mode['configuration'],
+ executable=False,
+ )
built_app_path = pathlib.Path(app_dir, cfg.app_bundle_name)
workdir = pathlib.Path(root, 'build', 'push_ipa')
@@ -84,21 +83,26 @@ def push_ipa(root: pathlib.Path, modename: str) -> None:
ipa_dir_path.mkdir(parents=True, exist_ok=True)
# Inject our latest build into an existing xcarchive (creating if needed).
- archivepath = _add_build_to_xcarchive(workdir, xcprojpath, built_app_path,
- cfg)
+ archivepath = _add_build_to_xcarchive(
+ workdir, xcprojpath, built_app_path, cfg
+ )
# Export an IPA from said xcarchive.
- ipa_path = _export_ipa_from_xcarchive(archivepath, exportoptionspath,
- ipa_dir_path, cfg)
+ ipa_path = _export_ipa_from_xcarchive(
+ archivepath, exportoptionspath, ipa_dir_path, cfg
+ )
# And lastly sync said IPA up to our staging server.
print('Pushing to staging server...')
sys.stdout.flush()
subprocess.run(
[
- 'rsync', '--verbose', ipa_path, '-e',
+ 'rsync',
+ '--verbose',
+ ipa_path,
+ '-e',
'ssh -oBatchMode=yes -oStrictHostKeyChecking=yes',
- f'{lcfg.sftp_host}:{lcfg.sftp_dir}'
+ f'{lcfg.sftp_host}:{lcfg.sftp_dir}',
],
check=True,
)
@@ -106,9 +110,12 @@ def push_ipa(root: pathlib.Path, modename: str) -> None:
print('iOS Package Updated Successfully!')
-def _add_build_to_xcarchive(workdir: pathlib.Path, xcprojpath: pathlib.Path,
- built_app_path: pathlib.Path,
- cfg: Config) -> pathlib.Path:
+def _add_build_to_xcarchive(
+ workdir: pathlib.Path,
+ xcprojpath: pathlib.Path,
+ built_app_path: pathlib.Path,
+ cfg: Config,
+) -> pathlib.Path:
archivepathbase = pathlib.Path(workdir, cfg.archive_name)
archivepath = pathlib.Path(workdir, cfg.archive_name + '.xcarchive')
@@ -117,10 +124,16 @@ def _add_build_to_xcarchive(workdir: pathlib.Path, xcprojpath: pathlib.Path,
print('Base archive not found; doing full build (can take a while)...')
sys.stdout.flush()
args = [
- 'xcodebuild', 'archive', '-project',
- str(xcprojpath), '-scheme', cfg.scheme, '-configuration',
- MODES['debug']['configuration'], '-archivePath',
- str(archivepathbase)
+ 'xcodebuild',
+ 'archive',
+ '-project',
+ str(xcprojpath),
+ '-scheme',
+ cfg.scheme,
+ '-configuration',
+ MODES['debug']['configuration'],
+ '-archivePath',
+ str(archivepathbase),
]
subprocess.run(args, check=True, capture_output=False)
@@ -128,19 +141,24 @@ def _add_build_to_xcarchive(workdir: pathlib.Path, xcprojpath: pathlib.Path,
print('Copying build to archive...')
sys.stdout.flush()
archive_app_path = pathlib.Path(
- archivepath, 'Products/Applications/' + cfg.app_bundle_name)
+ archivepath, 'Products/Applications/' + cfg.app_bundle_name
+ )
subprocess.run(['rm', '-rf', archive_app_path], check=True)
subprocess.run(['cp', '-r', built_app_path, archive_app_path], check=True)
return archivepath
-def _export_ipa_from_xcarchive(archivepath: pathlib.Path,
- exportoptionspath: pathlib.Path,
- ipa_dir_path: pathlib.Path,
- cfg: Config) -> pathlib.Path:
+def _export_ipa_from_xcarchive(
+ archivepath: pathlib.Path,
+ exportoptionspath: pathlib.Path,
+ ipa_dir_path: pathlib.Path,
+ cfg: Config,
+) -> pathlib.Path:
import textwrap
+
print('Exporting IPA...')
- exportoptions = textwrap.dedent("""
+ exportoptions = textwrap.dedent(
+ """
@@ -162,23 +180,30 @@ def _export_ipa_from_xcarchive(archivepath: pathlib.Path,
<none>
- """).strip()
+ """
+ ).strip()
with exportoptionspath.open('w') as outfile:
outfile.write(exportoptions)
sys.stdout.flush()
args = [
- 'xcodebuild', '-allowProvisioningUpdates', '-exportArchive',
+ 'xcodebuild',
+ '-allowProvisioningUpdates',
+ '-exportArchive',
'-archivePath',
- str(archivepath), '-exportOptionsPlist',
- str(exportoptionspath), '-exportPath',
- str(ipa_dir_path)
+ str(archivepath),
+ '-exportOptionsPlist',
+ str(exportoptionspath),
+ '-exportPath',
+ str(ipa_dir_path),
]
try:
subprocess.run(args, check=True, capture_output=True)
except Exception:
- print('Error exporting code-signed archive; '
- ' perhaps try running "security unlock-keychain login.keychain"')
+ print(
+ 'Error exporting code-signed archive; '
+ ' perhaps try running "security unlock-keychain login.keychain"'
+ )
raise
ipa_path_exported = pathlib.Path(ipa_dir_path, cfg.scheme + '.ipa')
diff --git a/tools/efrotools/jsontools.py b/tools/efrotools/jsontools.py
index d6c2c7e5..86cd1033 100644
--- a/tools/efrotools/jsontools.py
+++ b/tools/efrotools/jsontools.py
@@ -32,16 +32,17 @@ class NoIndentEncoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
import uuid
+
if isinstance(o, NoIndent):
key = uuid.uuid4().hex
self._replacement_map[key] = json.dumps(o.value, **self.kwargs)
# pylint: disable=consider-using-f-string
- return '@@%s@@' % (key, )
+ return '@@%s@@' % (key,)
return super().default(o)
def encode(self, o: Any) -> Any:
result = super().encode(o)
for k, v in self._replacement_map.items():
# pylint: disable=consider-using-f-string
- result = result.replace('"@@%s@@"' % (k, ), v)
+ result = result.replace('"@@%s@@"' % (k,), v)
return result
diff --git a/tools/efrotools/makefile.py b/tools/efrotools/makefile.py
index 96415561..a7479a9f 100644
--- a/tools/efrotools/makefile.py
+++ b/tools/efrotools/makefile.py
@@ -15,6 +15,7 @@ if TYPE_CHECKING:
@dataclass
class Section:
"""Represents a section of a Makefile."""
+
name: str | None
paragraphs: list[Paragraph]
@@ -22,6 +23,7 @@ class Section:
@dataclass
class Paragraph:
"""Represents a continuous set of non-blank lines in a Makefile."""
+
contents: str
def get_logical_lines(self) -> list[str]:
@@ -70,12 +72,16 @@ class Makefile:
# a new section whenever we come across one.
plines = paragraph.contents.splitlines()
# pylint: disable=too-many-boolean-expressions
- if (len(plines) == 5 and plines[0] == self.header_line_full
- and plines[1] == self.header_line_empty
- and len(plines[2]) == 80 and plines[2][0] == '#'
- and plines[2][-1] == '#'
- and plines[3] == self.header_line_empty
- and plines[4] == self.header_line_full):
+ if (
+ len(plines) == 5
+ and plines[0] == self.header_line_full
+ and plines[1] == self.header_line_empty
+ and len(plines[2]) == 80
+ and plines[2][0] == '#'
+ and plines[2][-1] == '#'
+ and plines[3] == self.header_line_empty
+ and plines[4] == self.header_line_full
+ ):
section = Section(name=plines[2][1:-1].strip(), paragraphs=[])
self.sections.append(section)
else:
@@ -90,8 +96,9 @@ class Makefile:
for section in self.sections:
for i, paragraph in enumerate(section.paragraphs):
if any(
- line.split('=')[0].strip() == name
- for line in paragraph.get_logical_lines()):
+ line.split('=')[0].strip() == name
+ for line in paragraph.get_logical_lines()
+ ):
found.append((section, i))
return found
@@ -103,8 +110,10 @@ class Makefile:
found: list[tuple[Section, int]] = []
for section in self.sections:
for i, paragraph in enumerate(section.paragraphs):
- if any(line.split()[0] == name + ':'
- for line in paragraph.get_logical_lines()):
+ if any(
+ line.split()[0] == name + ':'
+ for line in paragraph.get_logical_lines()
+ ):
found.append((section, i))
return found
diff --git a/tools/efrotools/message.py b/tools/efrotools/message.py
index 3c2fe396..5ea293fd 100644
--- a/tools/efrotools/message.py
+++ b/tools/efrotools/message.py
@@ -15,7 +15,6 @@ if TYPE_CHECKING:
def standard_message_sender_gen_pcommand(
- projroot: Path,
basename: str,
source_module: str,
enable_sync_sends: bool,
@@ -34,8 +33,11 @@ def standard_message_sender_gen_pcommand(
dst = sys.argv[2]
# Use wrapping-friendly form for long call names.
- get_protocol_import = (f'({get_protocol_call})' if
- len(get_protocol_call) >= 14 else get_protocol_call)
+ get_protocol_import = (
+ f'({get_protocol_call})'
+ if len(get_protocol_call) >= 14
+ else get_protocol_call
+ )
# In embedded situations we have to pass different code to import
# the protocol at build time than we do in our runtime code (where
@@ -46,19 +48,22 @@ def standard_message_sender_gen_pcommand(
protocol_module_level_import_code = (
f'\n# Dummy import for type-checking purposes.\n'
f'if bool(False):\n'
- f' from {source_module} import {get_protocol_import}')
+ f' from {source_module} import {get_protocol_import}'
+ )
protocol_create_code = f'protocol = {get_protocol_call}()'
build_time_protocol_create_code = (
f'from {source_module} import {get_protocol_import}\n'
- f'protocol = {get_protocol_call}()')
+ f'protocol = {get_protocol_call}()'
+ )
else:
protocol_module_level_import_code = None
protocol_create_code = (
f'from {source_module} import {get_protocol_import}\n'
- f'protocol = {get_protocol_call}()')
+ f'protocol = {get_protocol_call}()'
+ )
build_time_protocol_create_code = None
- module_code = efro.message.create_sender_module(
+ out = efro.message.create_sender_module(
basename,
protocol_create_code=protocol_create_code,
protocol_module_level_import_code=protocol_module_level_import_code,
@@ -66,7 +71,7 @@ def standard_message_sender_gen_pcommand(
enable_sync_sends=enable_sync_sends,
enable_async_sends=enable_async_sends,
)
- out = format_python_str(projroot, module_code)
+ out = format_python_str(out)
print(f'Meta-building {Clr.BLD}{dst}{Clr.RST}')
Path(dst).parent.mkdir(parents=True, exist_ok=True)
@@ -75,7 +80,6 @@ def standard_message_sender_gen_pcommand(
def standard_message_receiver_gen_pcommand(
- projroot: Path,
basename: str,
source_module: str,
is_async: bool,
@@ -83,7 +87,6 @@ def standard_message_receiver_gen_pcommand(
embedded: bool = False,
) -> None:
"""Used by pcommands generating efro.message receiver modules."""
- # pylint: disable=too-many-locals
import efro.message
from efro.terminal import Clr
@@ -95,8 +98,11 @@ def standard_message_receiver_gen_pcommand(
dst = sys.argv[2]
# Use wrapping-friendly form for long call names.
- get_protocol_import = (f'({get_protocol_call})' if
- len(get_protocol_call) >= 14 else get_protocol_call)
+ get_protocol_import = (
+ f'({get_protocol_call})'
+ if len(get_protocol_call) >= 14
+ else get_protocol_call
+ )
# In embedded situations we have to pass different code to import
# the protocol at build time than we do in our runtime code (where
@@ -107,27 +113,29 @@ def standard_message_receiver_gen_pcommand(
protocol_module_level_import_code = (
f'\n# Dummy import for type-checking purposes.\n'
f'if bool(False):\n'
- f' from {source_module} import {get_protocol_import}')
+ f' from {source_module} import {get_protocol_import}'
+ )
protocol_create_code = f'protocol = {get_protocol_call}()'
build_time_protocol_create_code = (
f'from {source_module} import {get_protocol_import}\n'
- f'protocol = {get_protocol_call}()')
+ f'protocol = {get_protocol_call}()'
+ )
else:
protocol_module_level_import_code = None
protocol_create_code = (
f'from {source_module} import {get_protocol_import}\n'
- f'protocol = {get_protocol_call}()')
+ f'protocol = {get_protocol_call}()'
+ )
build_time_protocol_create_code = None
- module_code = efro.message.create_receiver_module(
+ out = efro.message.create_receiver_module(
basename,
protocol_create_code=protocol_create_code,
protocol_module_level_import_code=protocol_module_level_import_code,
build_time_protocol_create_code=build_time_protocol_create_code,
is_async=is_async,
)
-
- out = format_python_str(projroot, module_code)
+ out = format_python_str(out)
print(f'Meta-building {Clr.BLD}{dst}{Clr.RST}')
Path(dst).parent.mkdir(parents=True, exist_ok=True)
diff --git a/tools/efrotools/pcommand.py b/tools/efrotools/pcommand.py
index 4524bd60..b75bf53f 100644
--- a/tools/efrotools/pcommand.py
+++ b/tools/efrotools/pcommand.py
@@ -30,9 +30,16 @@ def pcommand_main(globs: dict[str, Any]) -> None:
import types
from efro.error import CleanError
from efro.terminal import Clr
- funcs = dict(((name, obj) for name, obj in globs.items()
- if not name.startswith('_') and name != 'pcommand_main'
- and isinstance(obj, types.FunctionType)))
+
+ funcs = dict(
+ (
+ (name, obj)
+ for name, obj in globs.items()
+ if not name.startswith('_')
+ and name != 'pcommand_main'
+ and isinstance(obj, types.FunctionType)
+ )
+ )
show_help = False
retval = 0
if len(sys.argv) < 2:
@@ -48,9 +55,12 @@ def pcommand_main(globs: dict[str, Any]) -> None:
retval = 255
else:
docs = _trim_docstring(
- getattr(funcs[sys.argv[2]], '__doc__', ''))
- print(f'\n{Clr.MAG}{Clr.BLD}pcommand {sys.argv[2]}:{Clr.RST}\n'
- f'{Clr.MAG}{docs}{Clr.RST}\n')
+ getattr(funcs[sys.argv[2]], '__doc__', '')
+ )
+ print(
+ f'\n{Clr.MAG}{Clr.BLD}pcommand {sys.argv[2]}:{Clr.RST}\n'
+ f'{Clr.MAG}{docs}{Clr.RST}\n'
+ )
elif sys.argv[1] in funcs:
try:
funcs[sys.argv[1]]()
@@ -61,17 +71,25 @@ def pcommand_main(globs: dict[str, Any]) -> None:
exc.pretty_print()
sys.exit(1)
else:
- print(f'{Clr.RED}Unknown pcommand: "{sys.argv[1]}"{Clr.RST}',
- file=sys.stderr)
+ print(
+ f'{Clr.RED}Unknown pcommand: "{sys.argv[1]}"{Clr.RST}',
+ file=sys.stderr,
+ )
retval = 255
if show_help:
- print(f'The {Clr.MAG}{Clr.BLD}pcommand{Clr.RST} script encapsulates'
- f' a collection of project-related commands.')
- print(f"Run {Clr.MAG}{Clr.BLD}'pcommand [COMMAND] ...'"
- f'{Clr.RST} to run a command.')
- print(f"Run {Clr.MAG}{Clr.BLD}'pcommand help [COMMAND]'"
- f'{Clr.RST} for full documentation for a command.')
+ print(
+ f'The {Clr.MAG}{Clr.BLD}pcommand{Clr.RST} script encapsulates'
+ f' a collection of project-related commands.'
+ )
+ print(
+ f"Run {Clr.MAG}{Clr.BLD}'pcommand [COMMAND] ...'"
+ f'{Clr.RST} to run a command.'
+ )
+ print(
+ f"Run {Clr.MAG}{Clr.BLD}'pcommand help [COMMAND]'"
+ f'{Clr.RST} for full documentation for a command.'
+ )
print('Available commands:')
for func, obj in sorted(funcs.items()):
doc = getattr(obj, '__doc__', '').splitlines()[0].strip()
@@ -117,10 +135,11 @@ def _trim_docstring(docstring: str) -> str:
def _spelling(words: list[str]) -> None:
from efrotools.code import sort_jetbrains_dict
import os
+
num_modded_dictionaries = 0
for fname in [
- '.idea/dictionaries/ericf.xml',
- 'ballisticacore-cmake/.idea/dictionaries/ericf.xml'
+ '.idea/dictionaries/ericf.xml',
+ 'ballisticacore-cmake/.idea/dictionaries/ericf.xml',
]:
if not os.path.exists(fname):
continue
@@ -149,9 +168,12 @@ def spelling_all() -> None:
print('Running "make pycharm-full"...')
lines = [
- line for line in subprocess.run(
- ['make', 'pycharm-full'], check=False,
- capture_output=True).stdout.decode().splitlines()
+ line
+ for line in subprocess.run(
+ ['make', 'pycharm-full'], check=False, capture_output=True
+ )
+ .stdout.decode()
+ .splitlines()
if 'Typo: In word' in line
]
words = [line.split('Typo: In word')[1].strip() for line in lines]
@@ -173,6 +195,7 @@ def spelling() -> None:
def xcodebuild() -> None:
"""Run xcodebuild with added smarts."""
from efrotools.xcode import XCodeBuild
+
XCodeBuild(projroot=str(PROJROOT), args=sys.argv[2:]).run()
@@ -182,22 +205,27 @@ def xcoderun() -> None:
import subprocess
from efro.error import CleanError
from efrotools.xcode import project_build_path
+
if len(sys.argv) != 5:
raise CleanError(
- 'Expected 3 args: ')
+ 'Expected 3 args: '
+ )
project_path = os.path.abspath(sys.argv[2])
scheme = sys.argv[3]
configuration = sys.argv[4]
- path = project_build_path(projroot=str(PROJROOT),
- project_path=project_path,
- scheme=scheme,
- configuration=configuration)
+ path = project_build_path(
+ projroot=str(PROJROOT),
+ project_path=project_path,
+ scheme=scheme,
+ configuration=configuration,
+ )
subprocess.run(path, check=True)
def pyver() -> None:
"""Prints the Python version used by this project."""
from efrotools import PYVER
+
print(PYVER, end='')
@@ -208,10 +236,12 @@ def try_repeat() -> None:
"""
import subprocess
from efro.error import CleanError
+
# We require one number arg and at least one command arg.
if len(sys.argv) < 4:
raise CleanError(
- 'Expected a retry-count arg and at least one command arg')
+ 'Expected a retry-count arg and at least one command arg'
+ )
try:
repeats = int(sys.argv[2])
except Exception:
@@ -223,9 +253,11 @@ def try_repeat() -> None:
result = subprocess.run(cmd, check=False)
if result.returncode == 0:
return
- print(f'try_repeat attempt {i + 1} of {repeats + 1} failed for {cmd}.',
- file=sys.stderr,
- flush=True)
+ print(
+ f'try_repeat attempt {i + 1} of {repeats + 1} failed for {cmd}.',
+ file=sys.stderr,
+ flush=True,
+ )
raise CleanError(f'Command failed {repeats + 1} time(s): {cmd}')
@@ -237,19 +269,24 @@ def check_clean_safety() -> None:
"""
import os
import subprocess
+
if len(sys.argv) != 2:
raise Exception('invalid arguments')
# Make sure we wouldn't be deleting anything not tracked by git
# or ignored.
- output = subprocess.check_output(['git', 'status',
- '--porcelain=v2']).decode()
+ output = subprocess.check_output(
+ ['git', 'status', '--porcelain=v2']
+ ).decode()
if any(line.startswith('?') for line in output.splitlines()):
- print('ERROR: untracked file(s) found; aborting.'
- ' (see "git status" from "' + os.getcwd() +
- '") Either \'git add\' them, add them to .gitignore,'
- ' or remove them and try again.',
- file=sys.stderr)
+ print(
+ 'ERROR: untracked file(s) found; aborting.'
+ ' (see "git status" from "'
+ + os.getcwd()
+ + '") Either \'git add\' them, add them to .gitignore,'
+ ' or remove them and try again.',
+ file=sys.stderr,
+ )
sys.exit(255)
@@ -260,6 +297,7 @@ def gen_empty_py_init() -> None:
"""
from efro.terminal import Clr
from efro.error import CleanError
+
if len(sys.argv) != 3:
raise CleanError('Expected a single path arg.')
outpath = Path(sys.argv[2])
@@ -272,6 +310,7 @@ def gen_empty_py_init() -> None:
def formatcode() -> None:
"""Format all of our C/C++/etc. code."""
import efrotools.code
+
full = '-full' in sys.argv
efrotools.code.format_project_cpp_files(PROJROOT, full)
@@ -279,6 +318,7 @@ def formatcode() -> None:
def formatscripts() -> None:
"""Format all of our Python/etc. code."""
import efrotools.code
+
full = '-full' in sys.argv
efrotools.code.format_project_python_files(PROJROOT, full)
@@ -286,6 +326,7 @@ def formatscripts() -> None:
def formatmakefile() -> None:
"""Format the main makefile."""
from efrotools.makefile import Makefile
+
with open('Makefile', encoding='utf-8') as infile:
original = infile.read()
@@ -300,6 +341,7 @@ def formatmakefile() -> None:
def cpplint() -> None:
"""Run lint-checking on all code deemed lint-able."""
import efrotools.code
+
full = '-full' in sys.argv
efrotools.code.check_cpplint(PROJROOT, full)
@@ -310,6 +352,7 @@ def scriptfiles() -> None:
Pass -lines to use newlines as separators. The default is spaces.
"""
import efrotools.code
+
paths = efrotools.code.get_script_filenames(projroot=PROJROOT)
assert not any(' ' in path for path in paths)
if '-lines' in sys.argv:
@@ -321,8 +364,9 @@ def scriptfiles() -> None:
def pylint() -> None:
"""Run pylint checks on our scripts."""
import efrotools.code
- full = ('-full' in sys.argv)
- fast = ('-fast' in sys.argv)
+
+ full = '-full' in sys.argv
+ fast = '-fast' in sys.argv
efrotools.code.pylint(PROJROOT, full, fast)
@@ -331,6 +375,7 @@ def pylint_files() -> None:
from efro.terminal import Clr
from efro.error import CleanError
import efrotools.code
+
if len(sys.argv) < 3:
raise CleanError('Expected at least 1 filename arg.')
filenames = sys.argv[2:]
@@ -341,7 +386,8 @@ def pylint_files() -> None:
def mypy() -> None:
"""Run mypy checks on our scripts."""
import efrotools.code
- full = ('-full' in sys.argv)
+
+ full = '-full' in sys.argv
efrotools.code.mypy(PROJROOT, full)
@@ -350,6 +396,7 @@ def runmypy() -> None:
from efro.terminal import Clr
from efro.error import CleanError
import efrotools.code
+
if len(sys.argv) < 3:
raise CleanError('Expected at least 1 filename arg.')
filenames = sys.argv[2:]
@@ -363,12 +410,14 @@ def runmypy() -> None:
def dmypy() -> None:
"""Run mypy checks on our scripts using the mypy daemon."""
import efrotools.code
+
efrotools.code.dmypy(PROJROOT)
def pycharm() -> None:
"""Run PyCharm checks on our scripts."""
import efrotools.code
+
full = '-full' in sys.argv
verbose = '-v' in sys.argv
efrotools.code.check_pycharm(PROJROOT, full, verbose)
@@ -377,6 +426,7 @@ def pycharm() -> None:
def clioncode() -> None:
"""Run CLion checks on our code."""
import efrotools.code
+
full = '-full' in sys.argv
verbose = '-v' in sys.argv
efrotools.code.check_clioncode(PROJROOT, full, verbose)
@@ -385,6 +435,7 @@ def clioncode() -> None:
def androidstudiocode() -> None:
"""Run Android Studio checks on our code."""
import efrotools.code
+
full = '-full' in sys.argv
verbose = '-v' in sys.argv
efrotools.code.check_android_studio(PROJROOT, full, verbose)
@@ -394,6 +445,7 @@ def tool_config_install() -> None:
"""Install a tool config file (with some filtering)."""
from efro.terminal import Clr
from efro.error import CleanError
+
if len(sys.argv) != 4:
raise CleanError('expected 2 args')
src = Path(sys.argv[2])
@@ -412,13 +464,19 @@ def tool_config_install() -> None:
if dst.name in ['.dir-locals.el']:
comment = ';;'
elif dst.name in [
- '.mypy.ini', '.pycheckers', '.pylintrc', '.style.yapf',
- '.clang-format', '.editorconfig'
+ '.mypy.ini',
+ '.pycheckers',
+ '.pylintrc',
+ '.style.yapf',
+ '.clang-format',
+ '.editorconfig',
]:
comment = '#'
if comment is not None:
- cfg = (f'{comment} THIS FILE WAS AUTOGENERATED; DO NOT EDIT.\n'
- f'{comment} Source: {src}.\n\n' + cfg)
+ cfg = (
+ f'{comment} THIS FILE WAS AUTOGENERATED; DO NOT EDIT.\n'
+ f'{comment} Source: {src}.\n\n' + cfg
+ )
with dst.open('w', encoding='utf-8') as outfile:
outfile.write(cfg)
@@ -442,7 +500,8 @@ def _filter_tool_config(cfg: str) -> str:
shortname = short_names.get(PROJROOT.name, PROJROOT.name)
cfg = cfg.replace('__EFRO_PROJECT_SHORTNAME__', shortname)
- mypy_standard_settings = textwrap.dedent("""
+ mypy_standard_settings = textwrap.dedent(
+ """
# We don't want all of our plain scripts complaining
# about __main__ being redefined.
scripts_are_modules = True
@@ -464,10 +523,10 @@ def _filter_tool_config(cfg: str) -> str:
strict_equality = True
local_partial_types = True
no_implicit_reexport = True
- """).strip()
+ """
+ ).strip()
- cfg = cfg.replace('__EFRO_MYPY_STANDARD_SETTINGS__',
- mypy_standard_settings)
+ cfg = cfg.replace('__EFRO_MYPY_STANDARD_SETTINGS__', mypy_standard_settings)
# Gen a pylint init to set up our python paths:
pylint_init_tag = '__EFRO_PYLINT_INIT__'
@@ -494,6 +553,7 @@ def sync_all() -> None:
import concurrent.futures
from efro.error import CleanError
from efro.terminal import Clr
+
print(f'{Clr.BLD}Updating formatting for all projects...{Clr.RST}')
projects_str = os.environ.get('EFROTOOLS_SYNC_PROJECTS')
if projects_str is None:
@@ -510,7 +570,8 @@ def sync_all() -> None:
# a preflight we'll often wind up getting out-of-sync errors due to
# formatting changing after the sync.
with concurrent.futures.ThreadPoolExecutor(
- max_workers=len(projects)) as executor:
+ max_workers=len(projects)
+ ) as executor:
# Converting this to a list will propagate any errors.
list(executor.map(_format_project, projects))
@@ -525,12 +586,16 @@ def sync_all() -> None:
# Real mode
for i in range(2):
if i == 0:
- print(f'{Clr.BLD}Running sync pass 1'
- f' (ensures all changes at dsts are pushed to src):'
- f'{Clr.RST}')
+ print(
+ f'{Clr.BLD}Running sync pass 1'
+ f' (ensures all changes at dsts are pushed to src):'
+ f'{Clr.RST}'
+ )
else:
- print(f'{Clr.BLD}Running sync pass 2'
- f' (ensures latest src is pulled to all dsts):{Clr.RST}')
+ print(
+ f'{Clr.BLD}Running sync pass 2'
+ f' (ensures latest src is pulled to all dsts):{Clr.RST}'
+ )
for project in projects_str.split(':'):
cmd = f'cd "{project}" && make sync-full'
subprocess.run(cmd, shell=True, check=True)
@@ -541,6 +606,7 @@ def sync() -> None:
"""Runs standard syncs between this project and others."""
from efrotools import getconfig
from efrotools.sync import Mode, SyncItem, run_standard_syncs
+
mode = Mode(sys.argv[2]) if len(sys.argv) > 2 else Mode.PULL
# Load sync-items from project config and run them
@@ -563,13 +629,16 @@ def compile_python_files() -> None:
"""
import os
import py_compile
+
for arg in sys.argv[2:]:
mode = py_compile.PycInvalidationMode.UNCHECKED_HASH
- py_compile.compile(arg,
- dfile=os.path.basename(arg),
- doraise=True,
- optimize=1,
- invalidation_mode=mode)
+ py_compile.compile(
+ arg,
+ dfile=os.path.basename(arg),
+ doraise=True,
+ optimize=1,
+ invalidation_mode=mode,
+ )
def pytest() -> None:
@@ -597,8 +666,9 @@ def pytest() -> None:
os.environ['PYTHONDEVMODE'] = '1'
# Do the thing.
- results = subprocess.run([PYTHON_BIN, '-m', 'pytest'] + sys.argv[2:],
- check=False)
+ results = subprocess.run(
+ [PYTHON_BIN, '-m', 'pytest'] + sys.argv[2:], check=False
+ )
if results.returncode != 0:
sys.exit(results.returncode)
@@ -634,24 +704,34 @@ def makefile_target_list() -> None:
return ' - ' + doc
return doc
- print('----------------------\n'
- 'Available Make Targets\n'
- '----------------------')
+ print(
+ '----------------------\n'
+ 'Available Make Targets\n'
+ '----------------------'
+ )
entries: list[_Entry] = []
for i, line in enumerate(lines):
# Targets.
- if ':' in line and line.split(':')[0].replace('-', '').replace(
- '_', '').isalnum() and not line.startswith('_'):
+ if (
+ ':' in line
+ and line.split(':')[0].replace('-', '').replace('_', '').isalnum()
+ and not line.startswith('_')
+ ):
entries.append(
- _Entry(kind='target', line=i, title=line.split(':')[0]))
+ _Entry(kind='target', line=i, title=line.split(':')[0])
+ )
# Section titles.
- if (line.startswith('# ') and line.endswith(' #\n')
- and len(line.split()) > 2):
+ if (
+ line.startswith('# ')
+ and line.endswith(' #\n')
+ and len(line.split()) > 2
+ ):
entries.append(
- _Entry(kind='section', line=i, title=line[1:-2].strip()))
+ _Entry(kind='section', line=i, title=line[1:-2].strip())
+ )
for i, entry in enumerate(entries):
if entry.kind == 'section':
@@ -660,8 +740,13 @@ def makefile_target_list() -> None:
continue
print('\n' + entry.title + '\n' + '-' * len(entry.title))
elif entry.kind == 'target':
- print(Clr.MAG + entry.title + Clr.BLU +
- _docstr(lines, entry.line) + Clr.RST)
+ print(
+ Clr.MAG
+ + entry.title
+ + Clr.BLU
+ + _docstr(lines, entry.line)
+ + Clr.RST
+ )
def echo() -> None:
@@ -670,6 +755,7 @@ def echo() -> None:
Prints a Clr.RST at the end so that can be omitted.
"""
from efro.terminal import Clr
+
clrnames = {n for n in dir(Clr) if n.isupper() and not n.startswith('_')}
first = True
out: list[str] = []
@@ -694,7 +780,8 @@ def urandom_pretty() -> None:
if len(sys.argv) not in (3, 4):
raise CleanError(
- 'Expected one arg (count) and possibly two (line len).')
+ 'Expected one arg (count) and possibly two (line len).'
+ )
size = int(sys.argv[2])
linemax = 72 if len(sys.argv) < 4 else int(sys.argv[3])
@@ -703,7 +790,7 @@ def urandom_pretty() -> None:
line = b''
for i in range(len(val)):
- char = val[i:i + 1]
+ char = val[i : i + 1]
thislinelen = len(repr(line + char))
if thislinelen > linemax:
lines.append(repr(line))
diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py
index 72832238..4dc0f5be 100644
--- a/tools/efrotools/pybuild.py
+++ b/tools/efrotools/pybuild.py
@@ -22,10 +22,27 @@ ANDROID_PYTHON_REPO = 'https://github.com/GRRedWings/python3-android'
# Filenames we prune from Python lib dirs in source repo to cut down on size.
PRUNE_LIB_NAMES = [
- 'config-*', 'idlelib', 'lib-dynload', 'lib2to3', 'multiprocessing',
- 'pydoc_data', 'site-packages', 'ensurepip', 'tkinter', 'wsgiref',
- 'distutils', 'turtle.py', 'turtledemo', 'test', 'sqlite3/test', 'unittest',
- 'dbm', 'venv', 'ctypes/test', 'imaplib.py', '_sysconfigdata_*'
+ 'config-*',
+ 'idlelib',
+ 'lib-dynload',
+ 'lib2to3',
+ 'multiprocessing',
+ 'pydoc_data',
+ 'site-packages',
+ 'ensurepip',
+ 'tkinter',
+ 'wsgiref',
+ 'distutils',
+ 'turtle.py',
+ 'turtledemo',
+ 'test',
+ 'sqlite3/test',
+ 'unittest',
+ 'dbm',
+ 'venv',
+ 'ctypes/test',
+ 'imaplib.py',
+ '_sysconfigdata_*',
]
# Same but for DLLs dir (windows only)
@@ -43,20 +60,27 @@ def build_apple(arch: str, debug: bool = False) -> None:
# (via brew remove gettext --ignore-dependencies)
# NOTE: Should check to see if this is still necessary on Apple silicon
# since homebrew stuff is no longer in /usr/local there.
- if ('MacBook-Fro' in platform.node()
- and os.environ.get('SKIP_GETTEXT_WARNING') != '1'):
- if (subprocess.run('which gettext', shell=True,
- check=False).returncode == 0):
+ if (
+ 'MacBook-Fro' in platform.node()
+ and os.environ.get('SKIP_GETTEXT_WARNING') != '1'
+ ):
+ if (
+ subprocess.run('which gettext', shell=True, check=False).returncode
+ == 0
+ ):
raise CleanError(
- 'NEED TO TEMP-KILL GETTEXT (or set SKIP_GETTEXT_WARNING=1)')
+ 'NEED TO TEMP-KILL GETTEXT (or set SKIP_GETTEXT_WARNING=1)'
+ )
builddir = 'build/python_apple_' + arch + ('_debug' if debug else '')
subprocess.run(['rm', '-rf', builddir], check=True)
subprocess.run(['mkdir', '-p', 'build'], check=True)
subprocess.run(
[
- 'git', 'clone',
- 'https://github.com/beeware/Python-Apple-support.git', builddir
+ 'git',
+ 'clone',
+ 'https://github.com/beeware/Python-Apple-support.git',
+ builddir,
],
check=True,
)
@@ -72,10 +96,9 @@ def build_apple(arch: str, debug: bool = False) -> None:
txt = readfile('Makefile')
# Turn doc strings on; looks like it only adds a few hundred k.
- txt = replace_exact(txt,
- '--without-doc-strings',
- '--with-doc-strings',
- count=2)
+ txt = replace_exact(
+ txt, '--without-doc-strings', '--with-doc-strings', count=2
+ )
# Customize our minimum version requirements
txt = replace_exact(
@@ -106,15 +129,16 @@ def build_apple(arch: str, debug: bool = False) -> None:
)
# Debug lib has a different name.
- txt = replace_exact(txt,
- 'python$(PYTHON_VER).a',
- 'python$(PYTHON_VER)d.a',
- count=2)
+ txt = replace_exact(
+ txt, 'python$(PYTHON_VER).a', 'python$(PYTHON_VER)d.a', count=2
+ )
- txt = replace_exact(txt,
- '/include/python$(PYTHON_VER)',
- '/include/python$(PYTHON_VER)d',
- count=4)
+ txt = replace_exact(
+ txt,
+ '/include/python$(PYTHON_VER)',
+ '/include/python$(PYTHON_VER)d',
+ count=4,
+ )
# Inject our custom modifications to fire right after their normal
# Setup.local filtering and right before building (and pass the same
@@ -151,11 +175,11 @@ def build_apple(arch: str, debug: bool = False) -> None:
# (also this build seems to fail with multiple threads)
subprocess.run(
[
- 'make', '-j1', {
- 'mac': 'Python-macOS',
- 'ios': 'Python-iOS',
- 'tvos': 'Python-tvOS'
- }[arch]
+ 'make',
+ '-j1',
+ {'mac': 'Python-macOS', 'ios': 'Python-iOS', 'tvos': 'Python-tvOS'}[
+ arch
+ ],
],
check=True,
)
@@ -182,9 +206,13 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
subprocess.run(['git', 'checkout', PY_VER_EXACT_ANDROID], check=True)
# These builds require ANDROID_NDK to be set; make sure that's the case.
- os.environ['ANDROID_NDK'] = subprocess.check_output(
- [f'{rootdir}/tools/pcommand', 'android_sdk_utils',
- 'get-ndk-path']).decode().strip()
+ os.environ['ANDROID_NDK'] = (
+ subprocess.check_output(
+ [f'{rootdir}/tools/pcommand', 'android_sdk_utils', 'get-ndk-path']
+ )
+ .decode()
+ .strip()
+ )
# Disable builds for dependencies we don't use.
ftxt = readfile('Android/build_deps.py')
@@ -192,8 +220,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
ftxt,
' '
'BZip2, GDBM, LibFFI, LibUUID, OpenSSL, Readline, SQLite, XZ, ZLib,\n',
- ' '
- 'BZip2, LibUUID, OpenSSL, SQLite, XZ, ZLib,\n',
+ ' BZip2, LibUUID, OpenSSL, SQLite, XZ, ZLib,\n',
)
# Give ourselves a handle to patch the OpenSSL build.
@@ -215,15 +242,18 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
ftxt = replace_exact(ftxt, 'PYVER=3.10.5', f'PYVER={PY_VER_EXACT_ANDROID}')
ftxt = replace_exact(
- ftxt, ' popd\n', f' ../../../tools/pcommand'
- f' python_android_patch Python-{PY_VER_EXACT_ANDROID}\n popd\n')
+ ftxt,
+ ' popd\n',
+ f' ../../../tools/pcommand'
+ f' python_android_patch Python-{PY_VER_EXACT_ANDROID}\n popd\n',
+ )
writefile('build.sh', ftxt)
# Ok; let 'er rip!
exargs = ' --with-pydebug' if debug else ''
- subprocess.run(f'ARCH={arch} ANDROID_API=21 ./build.sh{exargs}',
- shell=True,
- check=True)
+ subprocess.run(
+ f'ARCH={arch} ANDROID_API=21 ./build.sh{exargs}', shell=True, check=True
+ )
print('python build complete! (android/' + arch + ')')
@@ -262,12 +292,13 @@ def android_patch_ssl() -> None:
txt = readfile(fname)
txt = replace_exact(
txt,
- ('char *ossl_safe_getenv(const char *name)\n'
- '{\n'),
- ('char *ossl_safe_getenv(const char *name)\n'
- '{\n'
- ' // ERICF TWEAK: ALWAYS ALLOW GETENV.\n'
- ' return getenv(name);\n'),
+ 'char *ossl_safe_getenv(const char *name)\n{\n',
+ (
+ 'char *ossl_safe_getenv(const char *name)\n'
+ '{\n'
+ ' // ERICF TWEAK: ALWAYS ALLOW GETENV.\n'
+ ' return getenv(name);\n'
+ ),
)
writefile(fname, txt)
@@ -363,19 +394,23 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
zlib_ex = ' -I$(prefix)/include -lz'
bz2_ex = _slrp(
' -I$(srcdir)/../Support/BZip2.xcframework/{{slice}}/Headers'
- ' -L$(srcdir)/../Support/BZip2.xcframework/{{slice}} -lbzip2')
+ ' -L$(srcdir)/../Support/BZip2.xcframework/{{slice}} -lbzip2'
+ )
ssl_ex = _slrp(
' -I$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}/Headers'
' -L$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}'
- ' -lOpenSSL -DUSE_SSL')
+ ' -lOpenSSL -DUSE_SSL'
+ )
sqlite_ex = ' -I$(srcdir)/Modules/_sqlite'
hash_ex = _slrp(
' -I$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}/Headers'
' -L$(srcdir)/../Support/OpenSSL.xcframework/{{slice}}'
- ' -lOpenSSL -DUSE_SSL')
+ ' -lOpenSSL -DUSE_SSL'
+ )
lzma_ex = _slrp(
' -I$(srcdir)/../Support/XZ.xcframework/{{slice}}/Headers'
- ' -L$(srcdir)/../Support/XZ.xcframework/{{slice}} -lxz')
+ ' -L$(srcdir)/../Support/XZ.xcframework/{{slice}} -lxz'
+ )
else:
raise RuntimeError(f'Unknown platform {platform}')
@@ -383,28 +418,100 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
# If any .so files are coming out of builds, their names should be
# added here to stop that.
cmodules = [
- '_asyncio', '_bisect', '_blake2', '_codecs_cn', '_codecs_hk',
- '_codecs_iso2022', '_codecs_jp', '_codecs_kr', '_codecs_tw',
- '_contextvars', '_crypt', '_csv', '_ctypes_test', '_ctypes',
- '_curses_panel', '_curses', '_datetime', '_decimal', '_elementtree',
- '_heapq', '_json', '_lsprof', '_lzma', '_md5', '_multibytecodec',
- '_multiprocessing', '_opcode', '_pickle', '_posixsubprocess', '_queue',
- '_random', '_sha1', '_sha3', '_sha256', '_sha512', '_socket',
- '_statistics', '_struct', '_testbuffer', '_testcapi',
- '_testimportmultiple', '_testinternalcapi', '_testmultiphase', '_uuid',
- '_xxsubinterpreters', '_xxtestfuzz', '_zoneinfo', 'array', 'audioop',
- 'binascii', 'cmath', 'fcntl', 'grp', 'math', 'mmap', 'ossaudiodev',
- 'parser', 'pyexpat', 'resource', 'select', 'syslog', 'termios',
- 'unicodedata', 'xxlimited', 'zlib'
+ '_asyncio',
+ '_bisect',
+ '_blake2',
+ '_codecs_cn',
+ '_codecs_hk',
+ '_codecs_iso2022',
+ '_codecs_jp',
+ '_codecs_kr',
+ '_codecs_tw',
+ '_contextvars',
+ '_crypt',
+ '_csv',
+ '_ctypes_test',
+ '_ctypes',
+ '_curses_panel',
+ '_curses',
+ '_datetime',
+ '_decimal',
+ '_elementtree',
+ '_heapq',
+ '_json',
+ '_lsprof',
+ '_lzma',
+ '_md5',
+ '_multibytecodec',
+ '_multiprocessing',
+ '_opcode',
+ '_pickle',
+ '_posixsubprocess',
+ '_queue',
+ '_random',
+ '_sha1',
+ '_sha3',
+ '_sha256',
+ '_sha512',
+ '_socket',
+ '_statistics',
+ '_struct',
+ '_testbuffer',
+ '_testcapi',
+ '_testimportmultiple',
+ '_testinternalcapi',
+ '_testmultiphase',
+ '_uuid',
+ '_xxsubinterpreters',
+ '_xxtestfuzz',
+ '_zoneinfo',
+ 'array',
+ 'audioop',
+ 'binascii',
+ 'cmath',
+ 'fcntl',
+ 'grp',
+ 'math',
+ 'mmap',
+ 'ossaudiodev',
+ 'parser',
+ 'pyexpat',
+ 'resource',
+ 'select',
+ 'syslog',
+ 'termios',
+ 'unicodedata',
+ 'xxlimited',
+ 'zlib',
]
# Selectively uncomment some existing modules for static compilation.
enables = [
- '_asyncio', 'array', 'cmath', 'math', '_contextvars', '_struct',
- '_random', '_elementtree', '_pickle', '_datetime', '_zoneinfo',
- '_bisect', '_heapq', '_json', '_statistics', 'unicodedata', 'fcntl',
- 'select', 'mmap', '_csv', '_socket', '_sha3', '_blake2', 'binascii',
- '_posixsubprocess'
+ '_asyncio',
+ 'array',
+ 'cmath',
+ 'math',
+ '_contextvars',
+ '_struct',
+ '_random',
+ '_elementtree',
+ '_pickle',
+ '_datetime',
+ '_zoneinfo',
+ '_bisect',
+ '_heapq',
+ '_json',
+ '_statistics',
+ 'unicodedata',
+ 'fcntl',
+ 'select',
+ 'mmap',
+ '_csv',
+ '_socket',
+ '_sha3',
+ '_blake2',
+ 'binascii',
+ '_posixsubprocess',
]
# Note that the _md5 and _sha modules are normally only built if the
# system does not have the OpenSSL libs containing an optimized
@@ -443,35 +550,41 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
ftxt += f'_ssl _ssl.c{ssl_ex}\n'
- ftxt += (f'_sqlite3'
- f' _sqlite/cache.c'
- f' _sqlite/connection.c'
- f' _sqlite/cursor.c'
- f' _sqlite/microprotocols.c'
- f' _sqlite/module.c'
- f' _sqlite/prepare_protocol.c'
- f' _sqlite/row.c'
- f' _sqlite/statement.c'
- f' _sqlite/util.c'
- f'{sqlite_ex}'
- f' -DMODULE_NAME=\'\\"sqlite3\\"\''
- f' -DSQLITE_OMIT_LOAD_EXTENSION'
- f' -lsqlite3\n')
+ ftxt += (
+ f'_sqlite3'
+ f' _sqlite/cache.c'
+ f' _sqlite/connection.c'
+ f' _sqlite/cursor.c'
+ f' _sqlite/microprotocols.c'
+ f' _sqlite/module.c'
+ f' _sqlite/prepare_protocol.c'
+ f' _sqlite/row.c'
+ f' _sqlite/statement.c'
+ f' _sqlite/util.c'
+ f'{sqlite_ex}'
+ f' -DMODULE_NAME=\'\\"sqlite3\\"\''
+ f' -DSQLITE_OMIT_LOAD_EXTENSION'
+ f' -lsqlite3\n'
+ )
# Mac needs this:
if arch == 'mac':
- ftxt += ('\n'
- '# efrotools: mac urllib needs this:\n'
- '_scproxy _scproxy.c '
- '-framework SystemConfiguration '
- '-framework CoreFoundation\n')
+ ftxt += (
+ '\n'
+ '# efrotools: mac urllib needs this:\n'
+ '_scproxy _scproxy.c '
+ '-framework SystemConfiguration '
+ '-framework CoreFoundation\n'
+ )
# Explicitly mark the remaining ones as disabled
# (so Python won't try to build them as dynamic libs).
remaining_disabled = ' '.join(cmodules)
- ftxt += ('\n# Disabled by efrotools build:\n'
- '*disabled*\n'
- f'{remaining_disabled}\n')
+ ftxt += (
+ '\n# Disabled by efrotools build:\n'
+ '*disabled*\n'
+ f'{remaining_disabled}\n'
+ )
writefile(fname, ftxt)
# Ok, this is weird.
@@ -486,35 +599,45 @@ def _patch_setup_file(platform: str, arch: str, slc: str) -> None:
fname = 'Modules/makesetup'
txt = readfile(fname)
if platform == 'android':
- txt = replace_exact(txt, ' *=*)'
- ' DEFS="$line$NL$DEFS"; continue;;',
- ' [A-Z]*=*) DEFS="$line$NL$DEFS";'
- ' continue;;')
+ txt = replace_exact(
+ txt,
+ ' *=*) DEFS="$line$NL$DEFS"; continue;;',
+ ' [A-Z]*=*) DEFS="$line$NL$DEFS"; continue;;',
+ )
assert txt.count('[A-Z]*=*') == 1
writefile(fname, txt)
def winprune() -> None:
"""Prune unneeded files from windows python dists."""
- for libdir in ('assets/src/windows/Win32/Lib',
- 'assets/src/windows/x64/Lib'):
+ for libdir in (
+ 'assets/src/windows/Win32/Lib',
+ 'assets/src/windows/x64/Lib',
+ ):
assert os.path.isdir(libdir)
assert (' ' not in name for name in PRUNE_LIB_NAMES)
- subprocess.run(f'cd "{libdir}" && rm -rf ' + ' '.join(PRUNE_LIB_NAMES),
- shell=True,
- check=True)
+ subprocess.run(
+ f'cd "{libdir}" && rm -rf ' + ' '.join(PRUNE_LIB_NAMES),
+ shell=True,
+ check=True,
+ )
# Kill python cache dirs.
subprocess.run(
f'find "{libdir}" -name __pycache__ -print0 | xargs -0 rm -rf',
shell=True,
- check=True)
- for dlldir in ('assets/src/windows/Win32/DLLs',
- 'assets/src/windows/x64/DLLs'):
+ check=True,
+ )
+ for dlldir in (
+ 'assets/src/windows/Win32/DLLs',
+ 'assets/src/windows/x64/DLLs',
+ ):
assert os.path.isdir(dlldir)
assert (' ' not in name for name in PRUNE_DLL_NAMES)
- subprocess.run(f'cd "{dlldir}" && rm -rf ' + ' '.join(PRUNE_DLL_NAMES),
- shell=True,
- check=True)
+ subprocess.run(
+ f'cd "{dlldir}" && rm -rf ' + ' '.join(PRUNE_DLL_NAMES),
+ shell=True,
+ check=True,
+ )
print('Win-prune successful.')
@@ -532,11 +655,13 @@ def gather() -> None:
# First off, clear out any existing output.
existing_dirs = [
- os.path.join('src/external', d) for d in os.listdir('src/external')
+ os.path.join('src/external', d)
+ for d in os.listdir('src/external')
if d.startswith('python-') and d != 'python-notes.txt'
]
existing_dirs += [
- os.path.join('assets/src', d) for d in os.listdir('assets/src')
+ os.path.join('assets/src', d)
+ for d in os.listdir('assets/src')
if d.startswith('pylib-')
]
if not do_android:
@@ -559,133 +684,137 @@ def gather() -> None:
'android_arm': f'build/python_android_arm{bsuffix}/build',
'android_arm64': f'build/python_android_arm64{bsuffix}/build',
'android_x86': f'build/python_android_x86{bsuffix}/build',
- 'android_x86_64': f'build/python_android_x86_64{bsuffix}/build'
+ 'android_x86_64': f'build/python_android_x86_64{bsuffix}/build',
}
bases2 = {
'android_arm': f'build/python_android_arm{bsuffix}/{apost2}',
'android_arm64': f'build/python_android_arm64{bsuffix}/{apost2}',
'android_x86': f'build/python_android_x86{bsuffix}/{apost2}',
- 'android_x86_64': f'build/python_android_x86_64{bsuffix}/{apost2}'
+ 'android_x86_64': f'build/python_android_x86_64{bsuffix}/{apost2}',
}
# Note: only need pylib for the first in each group.
mac_arch_dir = 'macos-arm64_x86_64'
ios_arch_dir = 'ios-arm64'
tvos_arch_dir = 'tvos-arm64'
- builds: list[dict[str, Any]] = [{
- 'name':
- 'macos',
- 'group':
- 'apple',
- 'headers':
- bases['mac'] +
- f'/Support/Python.xcframework/{mac_arch_dir}/Headers',
- 'libs': [
- bases['mac'] +
- f'/Support/Python.xcframework/{mac_arch_dir}/libPython.a',
- bases['mac'] +
- f'/Support/OpenSSL.xcframework/{mac_arch_dir}/libOpenSSL.a',
- bases['mac'] +
- f'/Support/XZ.xcframework/{mac_arch_dir}/libxz.a',
- bases['mac'] +
- f'/Support/BZip2.xcframework/{mac_arch_dir}/libbzip2.a',
- ],
- 'pylib':
- (bases['mac'] + f'/Python-{PY_VER_EXACT_APPLE}-macOS/lib'),
- }, {
- 'name':
- 'ios',
- 'group':
- 'apple',
- 'headers':
- bases['ios'] +
- f'/Support/Python.xcframework/{ios_arch_dir}/Headers',
- 'libs': [
- bases['ios'] +
- f'/Support/Python.xcframework/{ios_arch_dir}/libPython.a',
- bases['ios'] +
- f'/Support/OpenSSL.xcframework/{ios_arch_dir}/libOpenSSL.a',
- bases['ios'] +
- f'/Support/XZ.xcframework/{ios_arch_dir}/libxz.a',
- bases['ios'] +
- f'/Support/BZip2.xcframework/{ios_arch_dir}/libbzip2.a',
- ],
- }, {
- 'name':
- 'tvos',
- 'group':
- 'apple',
- 'headers':
- bases['tvos'] +
- f'/Support/Python.xcframework/{tvos_arch_dir}/Headers',
- 'libs': [
- bases['tvos'] +
- f'/Support/Python.xcframework/{tvos_arch_dir}/libPython.a',
- bases['tvos'] +
- f'/Support/OpenSSL.xcframework/{tvos_arch_dir}/libOpenSSL.a',
- bases['tvos'] +
- f'/Support/XZ.xcframework/{tvos_arch_dir}/libxz.a',
- bases['tvos'] +
- f'/Support/BZip2.xcframework/{tvos_arch_dir}/libbzip2.a',
- ],
- }, {
- 'name': 'android_arm',
- 'group': 'android',
- 'headers': bases['android_arm'] + f'/usr/include/{libname}',
- 'libs': [
- bases['android_arm'] + f'/usr/lib/lib{libname}.a',
- bases2['android_arm'] + '/usr/lib/libssl.a',
- bases2['android_arm'] + '/usr/lib/libcrypto.a',
- bases2['android_arm'] + '/usr/lib/liblzma.a',
- bases2['android_arm'] + '/usr/lib/libsqlite3.a',
- bases2['android_arm'] + '/usr/lib/libbz2.a',
- bases2['android_arm'] + '/usr/lib/libuuid.a',
- ],
- 'libinst': 'android_armeabi-v7a',
- 'pylib': (bases['android_arm'] + '/usr/lib/python' + PY_VER),
- }, {
- 'name': 'android_arm64',
- 'group': 'android',
- 'headers': bases['android_arm64'] + f'/usr/include/{libname}',
- 'libs': [
- bases['android_arm64'] + f'/usr/lib/lib{libname}.a',
- bases2['android_arm64'] + '/usr/lib/libssl.a',
- bases2['android_arm64'] + '/usr/lib/libcrypto.a',
- bases2['android_arm64'] + '/usr/lib/liblzma.a',
- bases2['android_arm64'] + '/usr/lib/libsqlite3.a',
- bases2['android_arm64'] + '/usr/lib/libbz2.a',
- bases2['android_arm64'] + '/usr/lib/libuuid.a',
- ],
- 'libinst': 'android_arm64-v8a',
- }, {
- 'name': 'android_x86',
- 'group': 'android',
- 'headers': bases['android_x86'] + f'/usr/include/{libname}',
- 'libs': [
- bases['android_x86'] + f'/usr/lib/lib{libname}.a',
- bases2['android_x86'] + '/usr/lib/libssl.a',
- bases2['android_x86'] + '/usr/lib/libcrypto.a',
- bases2['android_x86'] + '/usr/lib/liblzma.a',
- bases2['android_x86'] + '/usr/lib/libsqlite3.a',
- bases2['android_x86'] + '/usr/lib/libbz2.a',
- bases2['android_x86'] + '/usr/lib/libuuid.a',
- ],
- 'libinst': 'android_x86',
- }, {
- 'name': 'android_x86_64',
- 'group': 'android',
- 'headers': bases['android_x86_64'] + f'/usr/include/{libname}',
- 'libs': [
- bases['android_x86_64'] + f'/usr/lib/lib{libname}.a',
- bases2['android_x86_64'] + '/usr/lib/libssl.a',
- bases2['android_x86_64'] + '/usr/lib/libcrypto.a',
- bases2['android_x86_64'] + '/usr/lib/liblzma.a',
- bases2['android_x86_64'] + '/usr/lib/libsqlite3.a',
- bases2['android_x86_64'] + '/usr/lib/libbz2.a',
- bases2['android_x86_64'] + '/usr/lib/libuuid.a',
- ],
- 'libinst': 'android_x86_64',
- }]
+ builds: list[dict[str, Any]] = [
+ {
+ 'name': 'macos',
+ 'group': 'apple',
+ 'headers': bases['mac']
+ + f'/Support/Python.xcframework/{mac_arch_dir}/Headers',
+ 'libs': [
+ bases['mac']
+ + f'/Support/Python.xcframework/{mac_arch_dir}/libPython.a',
+ bases['mac']
+ + f'/Support/OpenSSL.xcframework/{mac_arch_dir}/'
+ f'libOpenSSL.a',
+ bases['mac']
+ + f'/Support/XZ.xcframework/{mac_arch_dir}/libxz.a',
+ bases['mac']
+ + f'/Support/BZip2.xcframework/{mac_arch_dir}/libbzip2.a',
+ ],
+ 'pylib': (
+ bases['mac'] + f'/Python-{PY_VER_EXACT_APPLE}-macOS/lib'
+ ),
+ },
+ {
+ 'name': 'ios',
+ 'group': 'apple',
+ 'headers': bases['ios']
+ + f'/Support/Python.xcframework/{ios_arch_dir}/Headers',
+ 'libs': [
+ bases['ios']
+ + f'/Support/Python.xcframework/{ios_arch_dir}/libPython.a',
+ bases['ios']
+ + f'/Support/OpenSSL.xcframework/{ios_arch_dir}/'
+ f'libOpenSSL.a',
+ bases['ios']
+ + f'/Support/XZ.xcframework/{ios_arch_dir}/libxz.a',
+ bases['ios']
+ + f'/Support/BZip2.xcframework/{ios_arch_dir}/libbzip2.a',
+ ],
+ },
+ {
+ 'name': 'tvos',
+ 'group': 'apple',
+ 'headers': bases['tvos']
+ + f'/Support/Python.xcframework/{tvos_arch_dir}/Headers',
+ 'libs': [
+ bases['tvos']
+ + f'/Support/Python.xcframework/{tvos_arch_dir}/'
+ f'libPython.a',
+ bases['tvos']
+ + f'/Support/OpenSSL.xcframework/{tvos_arch_dir}/'
+ f'libOpenSSL.a',
+ bases['tvos']
+ + f'/Support/XZ.xcframework/{tvos_arch_dir}/libxz.a',
+ bases['tvos']
+ + f'/Support/BZip2.xcframework/{tvos_arch_dir}/libbzip2.a',
+ ],
+ },
+ {
+ 'name': 'android_arm',
+ 'group': 'android',
+ 'headers': bases['android_arm'] + f'/usr/include/{libname}',
+ 'libs': [
+ bases['android_arm'] + f'/usr/lib/lib{libname}.a',
+ bases2['android_arm'] + '/usr/lib/libssl.a',
+ bases2['android_arm'] + '/usr/lib/libcrypto.a',
+ bases2['android_arm'] + '/usr/lib/liblzma.a',
+ bases2['android_arm'] + '/usr/lib/libsqlite3.a',
+ bases2['android_arm'] + '/usr/lib/libbz2.a',
+ bases2['android_arm'] + '/usr/lib/libuuid.a',
+ ],
+ 'libinst': 'android_armeabi-v7a',
+ 'pylib': (bases['android_arm'] + '/usr/lib/python' + PY_VER),
+ },
+ {
+ 'name': 'android_arm64',
+ 'group': 'android',
+ 'headers': bases['android_arm64'] + f'/usr/include/{libname}',
+ 'libs': [
+ bases['android_arm64'] + f'/usr/lib/lib{libname}.a',
+ bases2['android_arm64'] + '/usr/lib/libssl.a',
+ bases2['android_arm64'] + '/usr/lib/libcrypto.a',
+ bases2['android_arm64'] + '/usr/lib/liblzma.a',
+ bases2['android_arm64'] + '/usr/lib/libsqlite3.a',
+ bases2['android_arm64'] + '/usr/lib/libbz2.a',
+ bases2['android_arm64'] + '/usr/lib/libuuid.a',
+ ],
+ 'libinst': 'android_arm64-v8a',
+ },
+ {
+ 'name': 'android_x86',
+ 'group': 'android',
+ 'headers': bases['android_x86'] + f'/usr/include/{libname}',
+ 'libs': [
+ bases['android_x86'] + f'/usr/lib/lib{libname}.a',
+ bases2['android_x86'] + '/usr/lib/libssl.a',
+ bases2['android_x86'] + '/usr/lib/libcrypto.a',
+ bases2['android_x86'] + '/usr/lib/liblzma.a',
+ bases2['android_x86'] + '/usr/lib/libsqlite3.a',
+ bases2['android_x86'] + '/usr/lib/libbz2.a',
+ bases2['android_x86'] + '/usr/lib/libuuid.a',
+ ],
+ 'libinst': 'android_x86',
+ },
+ {
+ 'name': 'android_x86_64',
+ 'group': 'android',
+ 'headers': bases['android_x86_64'] + f'/usr/include/{libname}',
+ 'libs': [
+ bases['android_x86_64'] + f'/usr/lib/lib{libname}.a',
+ bases2['android_x86_64'] + '/usr/lib/libssl.a',
+ bases2['android_x86_64'] + '/usr/lib/libcrypto.a',
+ bases2['android_x86_64'] + '/usr/lib/liblzma.a',
+ bases2['android_x86_64'] + '/usr/lib/libsqlite3.a',
+ bases2['android_x86_64'] + '/usr/lib/libbz2.a',
+ bases2['android_x86_64'] + '/usr/lib/libuuid.a',
+ ],
+ 'libinst': 'android_x86_64',
+ },
+ ]
for build in builds:
grp = build['group']
@@ -708,20 +837,32 @@ def gather() -> None:
subprocess.run(['mkdir', '-p', assets_src_dst], check=True)
subprocess.run(
[
- 'rsync', '--recursive', '--include', '*.py',
- '--exclude', '__pycache__', '--include', '*/',
- '--exclude', '*', build['pylib'] + '/',
- assets_src_dst
+ 'rsync',
+ '--recursive',
+ '--include',
+ '*.py',
+ '--exclude',
+ '__pycache__',
+ '--include',
+ '*/',
+ '--exclude',
+ '*',
+ build['pylib'] + '/',
+ assets_src_dst,
],
check=True,
)
# Prune a bunch of modules we don't need to cut
# down on size.
- subprocess.run('cd "' + assets_src_dst + '" && rm -rf ' +
- ' '.join(PRUNE_LIB_NAMES),
- shell=True,
- check=True)
+ subprocess.run(
+ 'cd "'
+ + assets_src_dst
+ + '" && rm -rf '
+ + ' '.join(PRUNE_LIB_NAMES),
+ shell=True,
+ check=True,
+ )
# Some minor filtering to system scripts:
# on iOS/tvOS, addusersitepackages() leads to a crash
@@ -734,25 +875,29 @@ def gather() -> None:
' known_paths = addusersitepackages(known_paths)',
' # efro tweak: this craps out on ios/tvos.\n'
' # (and we don\'t use it anyway)\n'
- ' # known_paths = addusersitepackages(known_paths)')
+ ' # known_paths = addusersitepackages(known_paths)',
+ )
writefile(fname, txt)
# Copy in a base set of headers (everything in a group should
# be using the same headers)
- subprocess.run(f'cp -r "{build["headers"]}" "{header_dst}"',
- shell=True,
- check=True)
+ subprocess.run(
+ f'cp -r "{build["headers"]}" "{header_dst}"',
+ shell=True,
+ check=True,
+ )
# Clear whatever pyconfigs came across; we'll build our own
# universal one below.
- subprocess.run('rm ' + header_dst + '/pyconfig*',
- shell=True,
- check=True)
+ subprocess.run(
+ 'rm ' + header_dst + '/pyconfig*', shell=True, check=True
+ )
# Write a master pyconfig header that reroutes to each
# platform's actual header.
- with open(header_dst + '/pyconfig.h', 'w',
- encoding='utf-8') as hfile:
+ with open(
+ header_dst + '/pyconfig.h', 'w', encoding='utf-8'
+ ) as hfile:
hfile.write(
'#if BA_OSTYPE_MACOS\n'
'#include "pyconfig-macos.h"\n\n'
@@ -770,11 +915,13 @@ def gather() -> None:
'#include "pyconfig-android_x86_64.h"\n\n'
'#else\n'
'#error unknown platform\n\n'
- '#endif\n')
+ '#endif\n'
+ )
# Now copy each build's config headers in with unique names.
cfgs = [
- f for f in os.listdir(build['headers'])
+ f
+ for f in os.listdir(build['headers'])
if f.startswith('pyconfig')
]
@@ -788,15 +935,25 @@ def gather() -> None:
# others; ios for instance points to a arm64 and a
# x86_64 variant).
contents = readfile(build['headers'] + '/' + cfg)
- contents = contents.replace('pyconfig',
- 'pyconfig-' + build['name'])
+ contents = contents.replace(
+ 'pyconfig', 'pyconfig-' + build['name']
+ )
writefile(header_dst + '/' + out, contents)
else:
# other configs we just rename
- subprocess.run('cp "' + build['headers'] + '/' + cfg +
- '" "' + header_dst + '/' + out + '"',
- shell=True,
- check=True)
+ subprocess.run(
+ 'cp "'
+ + build['headers']
+ + '/'
+ + cfg
+ + '" "'
+ + header_dst
+ + '/'
+ + out
+ + '"',
+ shell=True,
+ check=True,
+ )
# Copy in libs. If the lib gave a specific install name,
# use that; otherwise use name.
diff --git a/tools/efrotools/pylintplugins.py b/tools/efrotools/pylintplugins.py
index 1193d047..325ca9ef 100644
--- a/tools/efrotools/pylintplugins.py
+++ b/tools/efrotools/pylintplugins.py
@@ -39,9 +39,11 @@ def ignore_type_check_filter(if_node: nc.NodeNG) -> nc.NodeNG:
"""Ignore stuff under 'if TYPE_CHECKING:' block at module level."""
# Look for a non-nested 'if TYPE_CHECKING:'
- if (isinstance(if_node.test, astroid.Name)
- and if_node.test.name == 'TYPE_CHECKING'
- and isinstance(if_node.parent, astroid.Module)):
+ if (
+ isinstance(if_node.test, astroid.Name)
+ and if_node.test.name == 'TYPE_CHECKING'
+ and isinstance(if_node.parent, astroid.Module)
+ ):
module_node = if_node.parent
@@ -60,9 +62,11 @@ def ignore_type_check_filter(if_node: nc.NodeNG) -> nc.NodeNG:
del module_node.locals[name]
# Now replace its children with a simple pass statement.
- passnode = astroid.Pass(parent=if_node,
- lineno=if_node.lineno + 1,
- col_offset=if_node.col_offset + 1)
+ passnode = astroid.Pass(
+ parent=if_node,
+ lineno=if_node.lineno + 1,
+ col_offset=if_node.col_offset + 1,
+ )
if_node.body = [passnode]
return if_node
@@ -87,8 +91,7 @@ def ignore_reveal_type_call(node: nc.NodeNG) -> nc.NodeNG:
"""
# Let's just replace any reveal_type(x) call with print(x)..
- if (isinstance(node.func, astroid.Name)
- and node.func.name == 'reveal_type'):
+ if isinstance(node.func, astroid.Name) and node.func.name == 'reveal_type':
node.func.name = 'print'
return node
return node
@@ -106,8 +109,11 @@ def using_future_annotations(node: nc.NodeNG) -> nc.NodeNG:
# if we should assume all annotations are defer-eval'ed.
# NOTE: this will become default at some point within a few years..
annotations_set = mnode.locals.get('annotations')
- if (annotations_set and isinstance(annotations_set[0], astroid.ImportFrom)
- and annotations_set[0].modname == '__future__'):
+ if (
+ annotations_set
+ and isinstance(annotations_set[0], astroid.ImportFrom)
+ and annotations_set[0].modname == '__future__'
+ ):
return True
return False
@@ -134,12 +140,15 @@ def func_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
# now; can get more specific if we get false positives.
if node.decorators is not None:
for dnode in node.decorators.nodes:
- if (isinstance(dnode, astroid.nodes.Name)
- and dnode.name in {'dispatchmethod', 'singledispatch'}):
+ if isinstance(dnode, astroid.nodes.Name) and dnode.name in {
+ 'dispatchmethod',
+ 'singledispatch',
+ }:
return node # Leave annotations intact.
- if (isinstance(dnode, astroid.nodes.Attribute)
- and dnode.attrname in {'register', 'handler'}):
+ if isinstance(
+ dnode, astroid.nodes.Attribute
+ ) and dnode.attrname in {'register', 'handler'}:
return node # Leave annotations intact.
node.args.annotations = [None for _ in node.args.args]
@@ -181,16 +190,20 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
for dec in fnode.decorators.nodes:
# Look for dataclassio.ioprepped.
- if (isinstance(dec, astroid.nodes.Attribute) and
- dec.attrname in {'ioprepped', 'will_ioprep'}
- and isinstance(dec.expr, astroid.nodes.Name)
- and dec.expr.name == 'dataclassio'):
+ if (
+ isinstance(dec, astroid.nodes.Attribute)
+ and dec.attrname in {'ioprepped', 'will_ioprep'}
+ and isinstance(dec.expr, astroid.nodes.Name)
+ and dec.expr.name == 'dataclassio'
+ ):
found_ioprepped = True
break
# Look for simply 'ioprepped'.
- if (isinstance(dec, astroid.nodes.Name)
- and dec.name in {'ioprepped', 'will_ioprep'}):
+ if isinstance(dec, astroid.nodes.Name) and dec.name in {
+ 'ioprepped',
+ 'will_ioprep',
+ }:
found_ioprepped = True
break
@@ -207,8 +220,9 @@ def var_annotations_filter(node: nc.NodeNG) -> nc.NodeNG:
fnode = node
willeval = True
while fnode is not None:
- if isinstance(fnode,
- (astroid.FunctionDef, astroid.AsyncFunctionDef)):
+ if isinstance(
+ fnode, (astroid.FunctionDef, astroid.AsyncFunctionDef)
+ ):
willeval = False
break
if isinstance(fnode, astroid.ClassDef):
@@ -232,8 +246,10 @@ ALLOWED_GENERICS = {'Sequence'}
def _is_strippable_subscript(node: nc.NodeNG) -> bool:
if isinstance(node, astroid.Subscript):
# We can strip if its not in our allowed list.
- if not (isinstance(node.value, astroid.Name)
- and node.value.name in ALLOWED_GENERICS):
+ if not (
+ isinstance(node.value, astroid.Name)
+ and node.value.name in ALLOWED_GENERICS
+ ):
return True
return False
@@ -286,8 +302,9 @@ def register_plugins(manager: astroid.Manager) -> None:
# Let's make Pylint understand these.
manager.register_transform(astroid.AnnAssign, var_annotations_filter)
manager.register_transform(astroid.FunctionDef, func_annotations_filter)
- manager.register_transform(astroid.AsyncFunctionDef,
- func_annotations_filter)
+ manager.register_transform(
+ astroid.AsyncFunctionDef, func_annotations_filter
+ )
# Pylint doesn't seem to support Generics much right now, and it seems
# to lead to some buggy behavior and slowdowns. So let's filter them
diff --git a/tools/efrotools/sync.py b/tools/efrotools/sync.py
index 299f75f8..daab8e1a 100644
--- a/tools/efrotools/sync.py
+++ b/tools/efrotools/sync.py
@@ -20,6 +20,7 @@ if TYPE_CHECKING:
class Mode(Enum):
"""Modes for sync operations."""
+
PULL = 'pull' # Pull updates from theirs to ours; errors if ours changed.
FULL = 'full' # Like pull but also push changes back to src if possible.
LIST = 'list' # Simply list all sync operations that would occur.
@@ -35,25 +36,36 @@ def _valid_filename(fname: str) -> bool:
if os.path.basename(fname) != fname:
raise ValueError(f'{fname} is not a simple filename.')
if fname in [
- 'requirements.txt', 'pylintrc', 'clang-format', 'pycheckers',
- 'style.yapf', 'test_task_bin', '.editorconfig', 'cloudshell',
- 'vmshell', 'editorconfig'
+ 'requirements.txt',
+ 'pylintrc',
+ 'clang-format',
+ 'pycheckers',
+ 'style.yapf',
+ 'test_task_bin',
+ '.editorconfig',
+ 'cloudshell',
+ 'vmshell',
+ 'editorconfig',
]:
return True
- return (any(fname.endswith(ext) for ext in ('.py', '.pyi'))
- and 'flycheck_' not in fname)
+ return (
+ any(fname.endswith(ext) for ext in ('.py', '.pyi'))
+ and 'flycheck_' not in fname
+ )
@dataclass
class SyncItem:
"""Defines a file or directory to be synced from another project."""
+
src_project_id: str
src_path: str
dst_path: str | None = None
-def run_standard_syncs(projectroot: Path, mode: Mode,
- syncitems: Sequence[SyncItem]) -> None:
+def run_standard_syncs(
+ projectroot: Path, mode: Mode, syncitems: Sequence[SyncItem]
+) -> None:
"""Run a standard set of syncs.
Syncitems should be a list of tuples consisting of a src project name,
@@ -61,6 +73,7 @@ def run_standard_syncs(projectroot: Path, mode: Mode,
"""
# pylint: disable=too-many-locals
from efrotools import getlocalconfig
+
localconfig = getlocalconfig(projectroot)
total_count = 0
verbose = False
@@ -68,8 +81,11 @@ def run_standard_syncs(projectroot: Path, mode: Mode,
assert isinstance(syncitem, SyncItem)
src_project = syncitem.src_project_id
src_subpath = syncitem.src_path
- dst_subpath = (syncitem.dst_path
- if syncitem.dst_path is not None else syncitem.src_path)
+ dst_subpath = (
+ syncitem.dst_path
+ if syncitem.dst_path is not None
+ else syncitem.src_path
+ )
dstname = os.path.basename(dst_subpath)
if mode == Mode.CHECK:
if verbose:
@@ -140,8 +156,10 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
if not dstfile.is_file() or mode == Mode.FORCE:
if mode == Mode.LIST:
- print(f'Would pull from {src_proj}:'
- f' {Clr.SGRN}{dstfile}{Clr.RST}')
+ print(
+ f'Would pull from {src_proj}:'
+ f' {Clr.SGRN}{dstfile}{Clr.RST}'
+ )
else:
print(f'Pulling from {src_proj}: {Clr.SGRN}{dstfile}{Clr.RST}')
@@ -157,8 +175,10 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
# do a directional sync. If they both differ then we're out of luck.
if src_hash != marker_hash and dst_hash == marker_hash:
if mode == Mode.LIST:
- print(f'Would pull from {src_proj}:'
- f' {Clr.SGRN}{dstfile}{Clr.RST}')
+ print(
+ f'Would pull from {src_proj}:'
+ f' {Clr.SGRN}{dstfile}{Clr.RST}'
+ )
else:
print(f'Pulling from {src_proj}: {Clr.SGRN}{dstfile}{Clr.RST}')
@@ -171,8 +191,10 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
# Dst has changed; we only copy backwards to src
# if we're in full mode.
if mode == Mode.LIST:
- print(f'Would push to {src_proj}:'
- f' {Clr.SBLU}{dstfile}{Clr.RST}')
+ print(
+ f'Would push to {src_proj}:'
+ f' {Clr.SBLU}{dstfile}{Clr.RST}'
+ )
elif mode == Mode.FULL:
print(f'Pushing to {src_proj}: {Clr.SBLU}{dstfile}{Clr.RST}')
with srcfile.open('w') as outfile:
@@ -194,12 +216,16 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
# but the stored hash in dst won't.
if src_hash == dst_hash:
if mode == Mode.LIST:
- print(f'Would update dst hash (both files changed'
- f' identically) from {src_proj}:'
- f' {Clr.SGRN}{dstfile}{Clr.RST}')
+ print(
+ f'Would update dst hash (both files changed'
+ f' identically) from {src_proj}:'
+ f' {Clr.SGRN}{dstfile}{Clr.RST}'
+ )
else:
- print(f'Updating hash (both files changed)'
- f' from {src_proj}: {Clr.SGRN}{dstfile}{Clr.RST}')
+ print(
+ f'Updating hash (both files changed)'
+ f' from {src_proj}: {Clr.SGRN}{dstfile}{Clr.RST}'
+ )
with dstfile.open('w') as outfile:
outfile.write(add_marker(src_proj, srcdata))
continue
@@ -209,7 +235,8 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
dstabs = os.path.abspath(dstfile)
raise RuntimeError(
f'both src and dst sync files changed: {srcabs} {dstabs}'
- '; this must be resolved manually.')
+ '; this must be resolved manually.'
+ )
# (if we got here this file should be healthy..)
assert src_hash == marker_hash and dst_hash == marker_hash
@@ -219,8 +246,11 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
killpaths: list[Path] = []
for root, dirnames, fnames in os.walk(dst):
for name in dirnames + fnames:
- if (name.startswith('.') or '__pycache__' in root
- or '__pycache__' in name):
+ if (
+ name.startswith('.')
+ or '__pycache__' in root
+ or '__pycache__' in name
+ ):
continue
dstpathfull = Path(root, name)
relpath = dstpathfull.relative_to(dst)
@@ -233,19 +263,25 @@ def sync_paths(src_proj: str, src: Path, dst: Path, mode: Mode) -> int:
for killpath in killpaths:
if os.path.exists(killpath):
if mode == Mode.LIST:
- print(f'Would remove orphaned sync path:'
- f' {Clr.SRED}{killpath}{Clr.RST}')
+ print(
+ f'Would remove orphaned sync path:'
+ f' {Clr.SRED}{killpath}{Clr.RST}'
+ )
else:
- print(f'Removing orphaned sync path:'
- f' {Clr.SRED}{killpath}{Clr.RST}')
+ print(
+ f'Removing orphaned sync path:'
+ f' {Clr.SRED}{killpath}{Clr.RST}'
+ )
os.system('rm -rf "' + str(killpath) + '"')
# Lastly throw an error if we found any changed dst files and aren't
# allowed to reverse-sync them back.
if changed_error_dst_files:
- raise RuntimeError(f'sync dst file(s) changed since last sync:'
- f' {changed_error_dst_files}; run a FULL mode'
- ' sync to push changes back to src')
+ raise RuntimeError(
+ f'sync dst file(s) changed since last sync:'
+ f' {changed_error_dst_files}; run a FULL mode'
+ ' sync to push changes back to src'
+ )
return len(allpaths)
@@ -264,7 +300,8 @@ def check_path(dst: Path) -> int:
# changed since the last sync.
if marker_hash != dst_hash:
raise RuntimeError(
- f'sync dst file changed since last sync: {dstfile}')
+ f'sync dst file changed since last sync: {dstfile}'
+ )
return len(allpaths)
@@ -281,19 +318,22 @@ def add_marker(src_proj: str, srcdata: str) -> str:
# Make sure we're not operating on an already-synced file; that's just
# asking for trouble.
- if (len(lines) > (firstline + 1)
- and ('EFRO_SYNC_HASH=' in lines[firstline + 1])):
+ if len(lines) > (firstline + 1) and (
+ 'EFRO_SYNC_HASH=' in lines[firstline + 1]
+ ):
raise RuntimeError('Attempting to sync a file that is itself synced.')
hashstr = string_hash(srcdata)
- lines.insert(firstline,
- f'# Synced from {src_proj}.\n# EFRO_SYNC_HASH={hashstr}\n#')
+ lines.insert(
+ firstline, f'# Synced from {src_proj}.\n# EFRO_SYNC_HASH={hashstr}\n#'
+ )
return '\n'.join(lines) + '\n'
def string_hash(data: str) -> str:
"""Given a string, return a hash."""
import hashlib
+
md5 = hashlib.md5()
md5.update(data.encode())
diff --git a/tools/efrotools/xcode.py b/tools/efrotools/xcode.py
index 455fd960..6cb7e3ee 100644
--- a/tools/efrotools/xcode.py
+++ b/tools/efrotools/xcode.py
@@ -73,17 +73,29 @@ class XCodeBuild:
# Getting this error sometimes after xcode updates.
if 'error: PCH file built from a different branch' in '\n'.join(
- self._output):
- print(f'{Clr.MAG}WILL CLEAN AND'
- f' RE-ATTEMPT XCODE BUILD{Clr.RST}')
- self._run_cmd([
- 'xcodebuild', '-project', self._project, '-scheme',
- self._scheme, '-configuration', self._configuration,
- 'clean'
- ])
+ self._output
+ ):
+ print(
+ f'{Clr.MAG}WILL CLEAN AND'
+ f' RE-ATTEMPT XCODE BUILD{Clr.RST}'
+ )
+ self._run_cmd(
+ [
+ 'xcodebuild',
+ '-project',
+ self._project,
+ '-scheme',
+ self._scheme,
+ '-configuration',
+ self._configuration,
+ 'clean',
+ ]
+ )
# Now re-run the original build.
- print(f'{Clr.MAG}RE-ATTEMPTING XCODE BUILD'
- f' AFTER CLEAN{Clr.RST}')
+ print(
+ f'{Clr.MAG}RE-ATTEMPTING XCODE BUILD'
+ f' AFTER CLEAN{Clr.RST}'
+ )
self._run_cmd(self._build_cmd_args())
if self._returncode != 0:
@@ -105,9 +117,9 @@ class XCodeBuild:
self._section = None
self._returncode = 0
print(f'{Clr.BLU}Running build: {Clr.BLD}{cmd}{Clr.RST}')
- with subprocess.Popen(cmd,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT) as proc:
+ with subprocess.Popen(
+ cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ ) as proc:
if proc.stdout is None:
raise RuntimeError('Error running command')
while True:
@@ -136,12 +148,14 @@ class XCodeBuild:
# Look for a few special cases regardless of the section we're in:
if line == '** BUILD SUCCEEDED **\n':
sys.stdout.write(
- f'{Clr.GRN}{Clr.BLD}XCODE BUILD SUCCEEDED{Clr.RST}\n')
+ f'{Clr.GRN}{Clr.BLD}XCODE BUILD SUCCEEDED{Clr.RST}\n'
+ )
return
if line == '** CLEAN SUCCEEDED **\n':
sys.stdout.write(
- f'{Clr.GRN}{Clr.BLD}XCODE CLEAN SUCCEEDED{Clr.RST}\n')
+ f'{Clr.GRN}{Clr.BLD}XCODE CLEAN SUCCEEDED{Clr.RST}\n'
+ )
return
if 'warning: OpenGL is deprecated.' in line:
@@ -181,58 +195,70 @@ class XCodeBuild:
self._print_compile_storyboard_line(line)
elif self._section is _Section.LINKSTORYBOARDS:
self._print_simple_section_line(
- line, ignore_line_start_tails=['/ibtool'])
+ line, ignore_line_start_tails=['/ibtool']
+ )
elif self._section is _Section.PROCESSINFOPLISTFILE:
self._print_process_info_plist_file_line(line)
elif self._section is _Section.COPYSWIFTLIBS:
self._print_simple_section_line(
- line, ignore_line_starts=['builtin-swiftStdLibTool'])
+ line, ignore_line_starts=['builtin-swiftStdLibTool']
+ )
elif self._section is _Section.REGISTEREXECUTIONPOLICYEXCEPTION:
self._print_simple_section_line(
line,
- ignore_line_starts=[
- 'builtin-RegisterExecutionPolicyException'
- ])
+ ignore_line_starts=['builtin-RegisterExecutionPolicyException'],
+ )
elif self._section is _Section.VALIDATE:
self._print_simple_section_line(
- line, ignore_line_starts=['builtin-validationUtility'])
+ line, ignore_line_starts=['builtin-validationUtility']
+ )
elif self._section is _Section.TOUCH:
self._print_simple_section_line(
- line, ignore_line_starts=['/usr/bin/touch'])
+ line, ignore_line_starts=['/usr/bin/touch']
+ )
elif self._section is _Section.REGISTERWITHLAUNCHSERVICES:
self._print_simple_section_line(
- line, ignore_line_start_tails=['lsregister'])
+ line, ignore_line_start_tails=['lsregister']
+ )
elif self._section is _Section.METALLINK:
- self._print_simple_section_line(line,
- prefix='Linking',
- ignore_line_start_tails=['/metal'])
+ self._print_simple_section_line(
+ line, prefix='Linking', ignore_line_start_tails=['/metal']
+ )
elif self._section is _Section.COMPILESWIFT:
self._print_simple_section_line(
line,
prefix='Compiling',
prefix_index=3,
- ignore_line_start_tails=['/swift-frontend', 'EmitSwiftModule'])
+ ignore_line_start_tails=['/swift-frontend', 'EmitSwiftModule'],
+ )
elif self._section is _Section.CREATEBUILDDIRECTORY:
self._print_simple_section_line(
- line, ignore_line_starts=['builtin-create-build-directory'])
+ line, ignore_line_starts=['builtin-create-build-directory']
+ )
elif self._section is _Section.COMPILEMETALFILE:
- self._print_simple_section_line(line,
- prefix='Metal-Compiling',
- ignore_line_start_tails=['/metal'])
+ self._print_simple_section_line(
+ line,
+ prefix='Metal-Compiling',
+ ignore_line_start_tails=['/metal'],
+ )
elif self._section is _Section.COPY:
self._print_simple_section_line(
- line, ignore_line_starts=['builtin-copy'])
+ line, ignore_line_starts=['builtin-copy']
+ )
elif self._section is _Section.COPYSTRINGSFILE:
- self._print_simple_section_line(line,
- ignore_line_starts=[
- 'builtin-copyStrings',
- 'CopyPNGFile',
- 'ConvertIconsetFile'
- ],
- ignore_line_start_tails=[
- '/InfoPlist.strings:1:1:',
- '/copypng', '/iconutil'
- ])
+ self._print_simple_section_line(
+ line,
+ ignore_line_starts=[
+ 'builtin-copyStrings',
+ 'CopyPNGFile',
+ 'ConvertIconsetFile',
+ ],
+ ignore_line_start_tails=[
+ '/InfoPlist.strings:1:1:',
+ '/copypng',
+ '/iconutil',
+ ],
+ )
elif self._section is _Section.WRITEAUXILIARYFILE:
# EW: this spits out our full list of entitlements line by line.
# We should make this smart enough to ignore that whole section
@@ -264,28 +290,32 @@ class XCodeBuild:
'"com.apple.security.network.server"',
'"com.apple.security.scripting-targets"',
'"com.apple.Music.library.read",',
- ])
+ ],
+ )
elif self._section is _Section.COMPILESWIFTSOURCES:
self._print_simple_section_line(
line,
prefix='Compiling Swift Sources',
prefix_index=None,
ignore_line_starts=['PrecompileSwiftBridgingHeader'],
- ignore_line_start_tails=['/swiftc', '/swift-frontend'])
+ ignore_line_start_tails=['/swiftc', '/swift-frontend'],
+ )
elif self._section is _Section.PROCESSPCH:
self._print_simple_section_line(
line,
ignore_line_starts=['Precompile of'],
- ignore_line_start_tails=['/clang'])
+ ignore_line_start_tails=['/clang'],
+ )
elif self._section is _Section.PROCESSPCHPLUSPLUS:
self._print_simple_section_line(
line,
ignore_line_starts=['Precompile of'],
- ignore_line_start_tails=['/clang'])
+ ignore_line_start_tails=['/clang'],
+ )
elif self._section is _Section.PHASESCRIPTEXECUTION:
- self._print_simple_section_line(line,
- prefix='Running Script',
- ignore_line_starts=['/bin/sh'])
+ self._print_simple_section_line(
+ line, prefix='Running Script', ignore_line_starts=['/bin/sh']
+ )
else:
assert_never(self._section)
@@ -352,7 +382,8 @@ class XCodeBuild:
if self._section_line_count == 0:
name = os.path.basename(shlex.split(line)[1])
sys.stdout.write(
- f'{Clr.BLU}Compiling Asset Catalog {Clr.BLD}{name}{Clr.RST}\n')
+ f'{Clr.BLU}Compiling Asset Catalog {Clr.BLD}{name}{Clr.RST}\n'
+ )
return
# Ignore empty lines or things we expect to be there.
@@ -366,8 +397,10 @@ class XCodeBuild:
return
if line_s == '/* com.apple.actool.compilation-results */':
return
- if (' ibtoold[' in line_s
- and 'NSFileCoordinator is doing nothing' in line_s):
+ if (
+ ' ibtoold[' in line_s
+ and 'NSFileCoordinator is doing nothing' in line_s
+ ):
return
if any(line_s.endswith(x) for x in ('.plist', '.icns', '.car')):
return
@@ -381,7 +414,8 @@ class XCodeBuild:
if self._section_line_count == 0:
name = os.path.basename(shlex.split(line)[1])
sys.stdout.write(
- f'{Clr.BLU}Compiling Storyboard {Clr.BLD}{name}{Clr.RST}\n')
+ f'{Clr.BLU}Compiling Storyboard {Clr.BLD}{name}{Clr.RST}\n'
+ )
return
# Ignore empty lines or things we expect to be there.
@@ -401,8 +435,7 @@ class XCodeBuild:
# First line of the section.
if self._section_line_count == 0:
name = os.path.basename(shlex.split(line)[1])
- sys.stdout.write(f'{Clr.BLU}Signing'
- f' {Clr.BLD}{name}{Clr.RST}\n')
+ sys.stdout.write(f'{Clr.BLU}Signing' f' {Clr.BLD}{name}{Clr.RST}\n')
return
# Ignore empty lines or things we expect to be there.
@@ -438,12 +471,13 @@ class XCodeBuild:
sys.stdout.write(line)
def _print_simple_section_line(
- self,
- line: str,
- prefix: str | None = None,
- prefix_index: int | None = 1,
- ignore_line_starts: list[str] | None = None,
- ignore_line_start_tails: list[str] | None = None) -> None:
+ self,
+ line: str,
+ prefix: str | None = None,
+ prefix_index: int | None = 1,
+ ignore_line_starts: list[str] | None = None,
+ ignore_line_start_tails: list[str] | None = None,
+ ) -> None:
if ignore_line_starts is None:
ignore_line_starts = []
@@ -457,8 +491,9 @@ class XCodeBuild:
sys.stdout.write(f'{Clr.BLU}{prefix}{Clr.RST}\n')
else:
name = os.path.basename(shlex.split(line)[prefix_index])
- sys.stdout.write(f'{Clr.BLU}{prefix}'
- f' {Clr.BLD}{name}{Clr.RST}\n')
+ sys.stdout.write(
+ f'{Clr.BLU}{prefix}' f' {Clr.BLD}{name}{Clr.RST}\n'
+ )
return
# Ignore empty lines or things we expect to be there.
@@ -469,7 +504,7 @@ class XCodeBuild:
# The start strings they pass may themselves be splittable so
# we may need to compare more than one string.
startsplits = start.split()
- if splits[:len(startsplits)] == startsplits:
+ if splits[: len(startsplits)] == startsplits:
return
if any(splits[0].endswith(tail) for tail in ignore_line_start_tails):
return
@@ -480,17 +515,21 @@ class XCodeBuild:
# have no way to know what this output relates to. Tack a bit
# on to clarify in that case.
assert self._section is not None
- sys.stdout.write(f'{Clr.YLW}Unexpected {self._section.value}'
- f' Output:{Clr.RST} {line}')
+ sys.stdout.write(
+ f'{Clr.YLW}Unexpected {self._section.value}'
+ f' Output:{Clr.RST} {line}'
+ )
else:
sys.stdout.write(line)
-def project_build_path(projroot: str,
- project_path: str,
- scheme: str,
- configuration: str,
- executable: bool = True) -> str:
+def project_build_path(
+ projroot: str,
+ project_path: str,
+ scheme: str,
+ configuration: str,
+ executable: bool = True,
+) -> str:
"""Get build paths for an xcode project (cached for efficiency)."""
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@@ -504,8 +543,11 @@ def project_build_path(projroot: str,
if os.path.exists(config_path):
with open(config_path, encoding='utf-8') as infile:
config = json.loads(infile.read())
- if (project_path in config and configuration in config[project_path]
- and scheme in config[project_path][configuration]):
+ if (
+ project_path in config
+ and configuration in config[project_path]
+ and scheme in config[project_path][configuration]
+ ):
# Ok we've found a build-dir entry for this project; now if it
# exists on disk and all timestamps within it are decently
@@ -514,17 +556,22 @@ def project_build_path(projroot: str,
# stuff there so mod times should be pretty recent; if not
# then its worth re-caching to be sure.)
cached_build_dir = config[project_path][configuration][scheme][
- 'build_dir']
+ 'build_dir'
+ ]
cached_timestamp = config[project_path][configuration][scheme][
- 'timestamp']
+ 'timestamp'
+ ]
cached_executable_path = config[project_path][configuration][
- scheme]['executable_path']
+ scheme
+ ]['executable_path']
assert isinstance(cached_build_dir, str)
assert isinstance(cached_timestamp, float)
assert isinstance(cached_executable_path, str)
now = time.time()
- if (os.path.isdir(cached_build_dir)
- and abs(now - cached_timestamp) < 60 * 60 * 24):
+ if (
+ os.path.isdir(cached_build_dir)
+ and abs(now - cached_timestamp) < 60 * 60 * 24
+ ):
build_dir = cached_build_dir
executable_path = cached_executable_path
@@ -532,28 +579,33 @@ def project_build_path(projroot: str,
if build_dir is None:
print('Caching xcode build path...', file=sys.stderr)
cmd = [
- 'xcodebuild', '-project', project_path, '-showBuildSettings',
- '-configuration', configuration, '-scheme', scheme
+ 'xcodebuild',
+ '-project',
+ project_path,
+ '-showBuildSettings',
+ '-configuration',
+ configuration,
+ '-scheme',
+ scheme,
]
- output = subprocess.run(cmd, check=True,
- capture_output=True).stdout.decode()
+ output = subprocess.run(
+ cmd, check=True, capture_output=True
+ ).stdout.decode()
prefix = 'TARGET_BUILD_DIR = '
- lines = [
- l for l in output.splitlines() if l.strip().startswith(prefix)
- ]
+ lines = [l for l in output.splitlines() if l.strip().startswith(prefix)]
if len(lines) != 1:
raise Exception(
- 'TARGET_BUILD_DIR not found in xcodebuild settings output')
+ 'TARGET_BUILD_DIR not found in xcodebuild settings output'
+ )
build_dir = lines[0].replace(prefix, '').strip()
prefix = 'EXECUTABLE_PATH = '
- lines = [
- l for l in output.splitlines() if l.strip().startswith(prefix)
- ]
+ lines = [l for l in output.splitlines() if l.strip().startswith(prefix)]
if len(lines) != 1:
raise Exception(
- 'EXECUTABLE_PATH not found in xcodebuild settings output')
+ 'EXECUTABLE_PATH not found in xcodebuild settings output'
+ )
executable_path = lines[0].replace(prefix, '').strip()
if project_path not in config:
@@ -563,7 +615,7 @@ def project_build_path(projroot: str,
config[project_path][configuration][scheme] = {
'build_dir': build_dir,
'executable_path': executable_path,
- 'timestamp': time.time()
+ 'timestamp': time.time(),
}
os.makedirs(os.path.dirname(config_path), exist_ok=True)
with open(config_path, 'w', encoding='utf-8') as outfile:
diff --git a/tools/pcommand b/tools/pcommand
index f50297df..5a7b0725 100755
--- a/tools/pcommand
+++ b/tools/pcommand
@@ -19,31 +19,101 @@ from typing import TYPE_CHECKING
# modules rather than inline here because we'll be able to load them via pyc.
# pylint: disable=unused-import
from efrotools.pcommand import (
- PROJROOT, pcommand_main, formatcode, formatscripts, formatmakefile,
- cpplint, pylint, pylint_files, mypy, runmypy, dmypy, tool_config_install,
- sync, sync_all, scriptfiles, pycharm, clioncode, androidstudiocode,
- makefile_target_list, spelling, spelling_all, pytest, echo,
- compile_python_files, pyver, try_repeat, xcodebuild, xcoderun)
+ PROJROOT,
+ pcommand_main,
+ formatcode,
+ formatscripts,
+ formatmakefile,
+ cpplint,
+ pylint,
+ pylint_files,
+ mypy,
+ runmypy,
+ dmypy,
+ tool_config_install,
+ sync,
+ sync_all,
+ scriptfiles,
+ pycharm,
+ clioncode,
+ androidstudiocode,
+ makefile_target_list,
+ spelling,
+ spelling_all,
+ pytest,
+ echo,
+ compile_python_files,
+ pyver,
+ try_repeat,
+ xcodebuild,
+ xcoderun,
+)
from batools.pcommand import (
- stage_server_file, py_examine, resize_image, check_clean_safety,
- clean_orphaned_assets, archive_old_builds, lazy_increment_build,
- get_master_asset_src_dir, androidaddr, push_ipa, printcolors,
- gen_fulltest_buildfile_android, gen_fulltest_buildfile_windows,
- gen_fulltest_buildfile_apple, gen_fulltest_buildfile_linux,
- python_version_android, python_version_apple, python_build_apple,
- python_version_build_base, python_build_apple_debug, python_build_android,
- python_build_android_debug, python_android_patch, python_android_patch_ssl,
- python_apple_patch, python_gather, python_winprune, capitalize, upper,
- efrocache_update, efrocache_get, get_modern_make, warm_start_asset_build,
- gendocs, list_pip_reqs, install_pip_reqs, checkenv, ensure_prefab_platform,
- prefab_run_var, make_prefab, lazybuild, android_archive_unstripped_libs,
- efro_gradle, stage_assets, update_assets_makefile, update_project,
- update_cmake_prefab_lib, cmake_prep_dir, gen_binding_code,
- gen_flat_data_code, wsl_path_to_win, wsl_build_check_win_drive,
- win_ci_binary_build, genchangelog, android_sdk_utils, logcat,
- update_resources_makefile, update_meta_makefile, gen_python_enums_module,
- gen_python_init_module, update_dummy_modules, win_ci_install_prereqs,
- version)
+ stage_server_file,
+ py_examine,
+ resize_image,
+ check_clean_safety,
+ clean_orphaned_assets,
+ archive_old_builds,
+ lazy_increment_build,
+ get_master_asset_src_dir,
+ androidaddr,
+ push_ipa,
+ printcolors,
+ gen_fulltest_buildfile_android,
+ gen_fulltest_buildfile_windows,
+ gen_fulltest_buildfile_apple,
+ gen_fulltest_buildfile_linux,
+ python_version_android,
+ python_version_apple,
+ python_build_apple,
+ python_version_build_base,
+ python_build_apple_debug,
+ python_build_android,
+ python_build_android_debug,
+ python_android_patch,
+ python_android_patch_ssl,
+ python_apple_patch,
+ python_gather,
+ python_winprune,
+ capitalize,
+ upper,
+ efrocache_update,
+ efrocache_get,
+ get_modern_make,
+ warm_start_asset_build,
+ gendocs,
+ list_pip_reqs,
+ install_pip_reqs,
+ checkenv,
+ ensure_prefab_platform,
+ prefab_run_var,
+ make_prefab,
+ lazybuild,
+ android_archive_unstripped_libs,
+ efro_gradle,
+ stage_assets,
+ update_assets_makefile,
+ update_project,
+ update_cmake_prefab_lib,
+ cmake_prep_dir,
+ gen_binding_code,
+ gen_flat_data_code,
+ wsl_path_to_win,
+ wsl_build_check_win_drive,
+ win_ci_binary_build,
+ genchangelog,
+ android_sdk_utils,
+ logcat,
+ update_resources_makefile,
+ update_meta_makefile,
+ gen_python_enums_module,
+ gen_python_init_module,
+ update_dummy_modules,
+ win_ci_install_prereqs,
+ version,
+)
+
# pylint: enable=unused-import
if TYPE_CHECKING: