diff --git a/.efrocachemap b/.efrocachemap
index 300321f8..398381a0 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -421,39 +421,39 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
- "build/assets/ba_data/data/langdata.json": "bb60812044af0a8d1bdefee759ff2522",
+ "build/assets/ba_data/data/langdata.json": "750e45f2f19a94a44703e3d31b9a8e96",
"build/assets/ba_data/data/languages/arabic.json": "0db32e21b6d5337ccca478381744aa88",
"build/assets/ba_data/data/languages/belarussian.json": "a112dfca3e188387516788bd8229c5b0",
- "build/assets/ba_data/data/languages/chinese.json": "93f3ca9f90d86dc7c8d0923f5f11ef46",
+ "build/assets/ba_data/data/languages/chinese.json": "1360ffde06828b63ce4fe956c3c3cd1d",
"build/assets/ba_data/data/languages/chinesetraditional.json": "319565f8a15667488f48dbce59278e39",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
- "build/assets/ba_data/data/languages/czech.json": "c9d518a324870066b987b8f412881dd3",
+ "build/assets/ba_data/data/languages/czech.json": "7171420af6d662e3a47b64576850a384",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
- "build/assets/ba_data/data/languages/dutch.json": "5cbf1a68a9d93dee00dbc27f834d878a",
+ "build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343",
"build/assets/ba_data/data/languages/english.json": "1c4037fea1066d39d6eced419f314f35",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
- "build/assets/ba_data/data/languages/filipino.json": "0031cbb8eb6a638a94fb43c5d892346c",
- "build/assets/ba_data/data/languages/french.json": "8bc35eb4b20a0b30c3348bcc9a3844a6",
+ "build/assets/ba_data/data/languages/filipino.json": "43e838754fe013b8bac75f75aef78cb3",
+ "build/assets/ba_data/data/languages/french.json": "cc8ac601f5443dd539893728db983f5c",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "b461539243e8efe3137137b886256ba7",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
- "build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
+ "build/assets/ba_data/data/languages/hindi.json": "5b6c8e988ffa84a7e26d120b6cd8e1a4",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
- "build/assets/ba_data/data/languages/indonesian.json": "408fb026e84c24a8dd7a43cb2b794541",
+ "build/assets/ba_data/data/languages/indonesian.json": "9103845242b572aa8ba48e24f81ddb68",
"build/assets/ba_data/data/languages/italian.json": "f550810b6866ea9bcf1985b7228f8cff",
- "build/assets/ba_data/data/languages/korean.json": "03fd99d5e1155e81053fc028f69df982",
+ "build/assets/ba_data/data/languages/korean.json": "4e3524327a0174250aff5e1ef4c0c597",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "9728d631cf7d9ad3b209ae1244bb59c0",
"build/assets/ba_data/data/languages/polish.json": "3a90b2d9e2c59305580c96f8098fc839",
- "build/assets/ba_data/data/languages/portuguese.json": "0274cb9a4b7d2bd49c8eb8120144a1bf",
+ "build/assets/ba_data/data/languages/portuguese.json": "b52164747c6308fc9d054eb6c0ff3c54",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
"build/assets/ba_data/data/languages/russian.json": "30d5f3d2415088e1fb6558fcd6ccfa98",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
- "build/assets/ba_data/data/languages/spanish.json": "08d9b39a519743da9a715c2524c3b6ca",
+ "build/assets/ba_data/data/languages/spanish.json": "e3e9ac8f96f52302a480c7e955aed71f",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
- "build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
+ "build/assets/ba_data/data/languages/thai.json": "9c425b420f0488a7f883da98947657ad",
"build/assets/ba_data/data/languages/turkish.json": "2be25c89ca754341f27750e0d595f31e",
"build/assets/ba_data/data/languages/ukrainian.json": "b54a38e93deebafa5706ba2d1f626892",
"build/assets/ba_data/data/languages/venetian.json": "8e9714d98a85e428ce3543fc49188a46",
@@ -950,7 +950,7 @@
"build/assets/ba_data/python-site-packages/certifi/__main__.py": "ef02e73f8581609df189a9f61aca365b",
"build/assets/ba_data/python-site-packages/certifi/cacert.pem": "4422aed09ab445f7290df7d72a301a47",
"build/assets/ba_data/python-site-packages/certifi/core.py": "1b505388f1475fabd1b60031f985271c",
- "build/assets/ba_data/python-site-packages/typing_extensions.py": "2d974cad17a71505d86513d1322976a5",
+ "build/assets/ba_data/python-site-packages/typing_extensions.py": "188320d92e530be7ea345d3ce3be38de",
"build/assets/ba_data/python-site-packages/yaml/__init__.py": "2b747e5772c203377222afc888ac6b71",
"build/assets/ba_data/python-site-packages/yaml/composer.py": "cef871e1f5f99ba2a7c44941b70afb06",
"build/assets/ba_data/python-site-packages/yaml/constructor.py": "8a15e361e34b79491c81553bb3534062",
@@ -4060,50 +4060,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "cd19dfdf480de6e73949db674e1b02d2",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "8c08cdda59e731a3830624000de5ca7f",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "14685eca62b8540cc2a268883d0ebc5d",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "f81876d7827a10be412306c52b03fa08",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "b8370845743ebba86ed6eaa6ee1d79d5",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "cdf04825dedae8fb2c26502ec2a505db",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4036493f98646b58de8bf425bee227cb",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1cf36c63f68ecaa954fb9c48a132725e",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b757a940c5157197a0138e12e308f859",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "92a07f83fceeddf3b29cfe2ead57f7e3",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "90002c085c66be4af378d4b3fc8e0260",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "eeb363d0d48e68f5f2ac2e536a26aeeb",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "e119b480fc7e542f33ceb16e8c04585f",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "6e657aa09d052765ed891789ec60dfb2",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "2d4eab8ea8399defd1afdbe548216e9c",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "b03bb9d5d1eb695a11843f64f24906ef",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "163fbc40b479ef1db1c753f7beb73c0f",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "37291abd76871f4556348f77e12dd363",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "acca6904f2f2f952ecae99922c602b9d",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3c2dfd9cf26e77a0b803ed43c85df113",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "3af5cf00e5eb30d55030e8705b83353a",
- "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "be337c05f72235b5b486277bb1a9c259",
- "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "3af5cf00e5eb30d55030e8705b83353a",
- "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "be337c05f72235b5b486277bb1a9c259",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "17c7d0041bc7c84077bf6692b16e3988",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "4affbfea91e8a33ab62da763ffc07ddd",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "17c7d0041bc7c84077bf6692b16e3988",
- "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "4affbfea91e8a33ab62da763ffc07ddd",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "58656b49d34e6c650983fbf79b5c41ae",
- "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "3ce5652e0ff5d277e256f517dec4eb61",
- "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "58656b49d34e6c650983fbf79b5c41ae",
- "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "3ce5652e0ff5d277e256f517dec4eb61",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "b94fff3a719003c1d8f5dd16dffdb3fc",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "5d68e1957febe6053815bbee3f068e76",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "85bbca447ca8a1d0fad984afc6f0700a",
- "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "5d68e1957febe6053815bbee3f068e76",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "7f2ed141a475e051d3350d571ef6cb0c",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "93fb764531ae16a30d4886eb183c3681",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "750cd7ef428f4faf65ccbeff50c21f8e",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "104cf85df9f1f7ffbf4de5997f7c6879",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "af85fb387d755b152c42f8dfb0891ad7",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "01ce8e0619b342e4cc2cc5f18f81a727",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "f8aae2e04f95f4cfb863791da36ff931",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "66252d58a1be97db8523bd0bf8098a16",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "26eea64d4509875c9a88da74f49e675c",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "0a39319a89364641f3bb0598821b4288",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "84567063607be0227ef779027e12d19d",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "f4458855192dedd13a28d36dc3962890",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4c0679b0157c2dd63519e5225d99359d",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "335a3f06dc6dd361d6122fd9143124ae",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "041a300c9fa99c82395e1ebc66e81fe3",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "181145bf30e752991860acd0e44f972c",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "8531542c35242bcbffc0309cef10b2b8",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "48cdebbdea839f6b8fc8f5cb69d7f961",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "159003daac99048702c74120be565bad",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "51c9582a1efaae50e1c435c13c390855",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "d66c11ebe6d9035ea7e86b362f8505a1",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "1f8113ffba1d000120bf83ac268c603b",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "6f2a68c0370061a2913278d97b039ecc",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "471e7f81fac96b4db752c5cdaeed7168",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "94916e80a9d7bc7801db666beceea026",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "1bc098ae93dd18143fb64ae5cbc33c19",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "da99cef03f12a6ff2c0065f4616262f2",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "14b67157a3bf57b9de067089476f79d5",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "8709ad96140d71760c2f493ee8bd7c43",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "ee829cd5488e9750570dc6f602d65589",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "8709ad96140d71760c2f493ee8bd7c43",
+ "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "ee829cd5488e9750570dc6f602d65589",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "35fe69d96c154b97b534711dae9d8d3a",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "2db876e543b3e93128ec421ea5cbb011",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "35fe69d96c154b97b534711dae9d8d3a",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "2db876e543b3e93128ec421ea5cbb011",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "417ea0f30d203d5de0e235550fcd7ab8",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "72d071e977c88454d0623c4a9fb34361",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "417ea0f30d203d5de0e235550fcd7ab8",
+ "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "72d071e977c88454d0623c4a9fb34361",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "de1b228d95c47a7c296a853778715326",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "79117cbfdf695298e1d9ae997d990c4d",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "984f0990a8e4cca29a382d70e51cc051",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "79117cbfdf695298e1d9ae997d990c4d",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "97a0aee0716397c0394c620b0cdc8cfa",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "5edf5fd129429079b24368da6c792c44",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "e453446a36102733a1f0db636fafb704",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "dfb843bbc924daf7a2e2a2eb6b4811df",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "09bb45bcbfad7c0f63b9494ceca669cc",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c8d10517d61dc5c4d7c94a5eccecab4a",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4944d18bb54894b0488cbdaa7b2ef06f",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d17c4758367051e734601018b081f786",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "b611c090513a21e2fe90e56582724e9d",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index ebeea7ef..f88e4998 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -14,6 +14,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -31,6 +34,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -48,6 +54,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -65,6 +74,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -82,6 +94,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -99,6 +114,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -116,6 +134,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -133,6 +154,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -150,6 +174,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
@@ -167,6 +194,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0878ab4e..70bb196e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,6 +21,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install dependencies
run: tools/pcommand install_pip_reqs
- name: Run checks
@@ -35,6 +38,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install dependencies
run: tools/pcommand install_pip_reqs
- name: Assemble monolithic server build
@@ -53,6 +59,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install dependencies
run: tools/pcommand install_pip_reqs
- name: Build spinoff project with only core featureset
@@ -71,6 +80,9 @@ jobs:
uses: actions/setup-python@v4
with:
python-version: '3.11'
+ # Remove this once we upgrade to 3.12.
+ - name: Install typing_extensions (temp)
+ run: python3.11 -m pip install typing_extensions
- name: Install dependencies
run: tools/pcommand install_pip_reqs
- name: Create poo feature-set
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d07555b7..a7d70b15 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.7.33 (build 21757, api 8, 2024-01-06)
+### 1.7.33 (build 21762, api 8, 2024-01-24)
- Stress test input-devices are now a bit smarter; they won't press any buttons
while UIs are up (this could cause lots of chaos if it happened).
- Added a 'Show Demos When Idle' option in advanced settings. If enabled, the
@@ -11,6 +11,15 @@
- Players now get points for killing bots with their own bombs by catching it
and throwing it back at them. This is actually old logic but was disabled due
to a logic flaw, but should be fixed now. (Thanks VinniTR!)
+- Updated the 'Settings->Advanced->Enter Code' functionality to talk to the V2
+ master server (V1 is still used as a fallback).
+- Adopted the `@override` decorator in all Python code and set up Mypy to
+ enforce its usage. Currently `override` comes from `typing_extensions` module
+ but when we upgrade to Python 3.12 soon it will come from the standard
+ `typing` module. This decorator should be familiar to users of other
+ languages; I feel it helps keep logic more understandable and should help us
+ catch problems where a base class changes or removes a method and child
+ classes forget to adapt to the change.
- Added a reset button in the input mapping menu. (Thanks Temp!)
### 1.7.32 (build 21741, api 8, 2023-12-20)
diff --git a/ballisticakit-cmake/.idea/misc.xml b/ballisticakit-cmake/.idea/misc.xml
index f01f08b8..fbb3740e 100644
--- a/ballisticakit-cmake/.idea/misc.xml
+++ b/ballisticakit-cmake/.idea/misc.xml
@@ -49,9 +49,6 @@
-
-
-
diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json
index 8a62973f..80f06984 100644
--- a/src/assets/.asset_manifest_public.json
+++ b/src/assets/.asset_manifest_public.json
@@ -13,7 +13,6 @@
"ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc",
- "ba_data/python/babase/__pycache__/_cloud.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_devconsole.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_emptyappmode.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_env.cpython-311.opt-1.pyc",
@@ -42,7 +41,6 @@
"ba_data/python/babase/_apputils.py",
"ba_data/python/babase/_assetmanager.py",
"ba_data/python/babase/_asyncio.py",
- "ba_data/python/babase/_cloud.py",
"ba_data/python/babase/_devconsole.py",
"ba_data/python/babase/_emptyappmode.py",
"ba_data/python/babase/_env.py",
@@ -121,8 +119,10 @@
"ba_data/python/baenv.py",
"ba_data/python/baplus/__init__.py",
"ba_data/python/baplus/__pycache__/__init__.cpython-311.opt-1.pyc",
+ "ba_data/python/baplus/__pycache__/_cloud.cpython-311.opt-1.pyc",
"ba_data/python/baplus/__pycache__/_hooks.cpython-311.opt-1.pyc",
"ba_data/python/baplus/__pycache__/_subsystem.cpython-311.opt-1.pyc",
+ "ba_data/python/baplus/_cloud.py",
"ba_data/python/baplus/_hooks.py",
"ba_data/python/baplus/_subsystem.py",
"ba_data/python/bascenev1/__init__.py",
diff --git a/src/assets/Makefile b/src/assets/Makefile
index 1ee71109..a92c9ad7 100644
--- a/src/assets/Makefile
+++ b/src/assets/Makefile
@@ -171,7 +171,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/_apputils.py \
$(BUILD_DIR)/ba_data/python/babase/_assetmanager.py \
$(BUILD_DIR)/ba_data/python/babase/_asyncio.py \
- $(BUILD_DIR)/ba_data/python/babase/_cloud.py \
$(BUILD_DIR)/ba_data/python/babase/_devconsole.py \
$(BUILD_DIR)/ba_data/python/babase/_emptyappmode.py \
$(BUILD_DIR)/ba_data/python/babase/_env.py \
@@ -210,6 +209,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/osmusic.py \
$(BUILD_DIR)/ba_data/python/baenv.py \
$(BUILD_DIR)/ba_data/python/baplus/__init__.py \
+ $(BUILD_DIR)/ba_data/python/baplus/_cloud.py \
$(BUILD_DIR)/ba_data/python/baplus/_hooks.py \
$(BUILD_DIR)/ba_data/python/baplus/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bascenev1/__init__.py \
@@ -446,7 +446,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/babase/__pycache__/_cloud.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_devconsole.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_emptyappmode.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_env.cpython-311.opt-1.pyc \
@@ -485,6 +484,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/osmusic.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/__pycache__/baenv.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/__init__.cpython-311.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/baplus/__pycache__/_cloud.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/_hooks.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/_subsystem.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/__init__.cpython-311.opt-1.pyc \
diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py
index 28da01e6..e3f115a7 100644
--- a/src/assets/ba_data/python/babase/__init__.py
+++ b/src/assets/ba_data/python/babase/__init__.py
@@ -118,7 +118,6 @@ from babase._apputils import (
get_remote_app_name,
AppHealthMonitor,
)
-from babase._cloud import CloudSubsystem
from babase._devconsole import (
DevConsoleTab,
DevConsoleTabEntry,
@@ -213,7 +212,6 @@ __all__ = [
'clipboard_has_text',
'clipboard_is_supported',
'clipboard_set_text',
- 'CloudSubsystem',
'commit_app_config',
'ContextCall',
'ContextError',
diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py
index 4a021ab8..f7fb9071 100644
--- a/src/assets/ba_data/python/babase/_app.py
+++ b/src/assets/ba_data/python/babase/_app.py
@@ -1,15 +1,17 @@
# Released under the MIT License. See LICENSE for details.
#
+# pylint: disable=too-many-lines
"""Functionality related to the high level state of the app."""
from __future__ import annotations
import os
import logging
from enum import Enum
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, TypeVar
from concurrent.futures import ThreadPoolExecutor
from functools import cached_property
+from typing_extensions import override
from efro.call import tpartial
import _babase
@@ -26,7 +28,7 @@ from babase._devconsole import DevConsoleSubsystem
if TYPE_CHECKING:
import asyncio
- from typing import Any, Callable, Coroutine
+ from typing import Any, Callable, Coroutine, Generator, Awaitable
from concurrent.futures import Future
import babase
@@ -42,6 +44,8 @@ if TYPE_CHECKING:
# __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__
+T = TypeVar('T')
+
class App:
"""A class for high level app functionality and state.
@@ -124,6 +128,7 @@ class App:
statically in a spinoff project.
"""
+ @override
def app_mode_for_intent(
self, intent: AppIntent
) -> type[AppMode] | None:
@@ -199,7 +204,8 @@ class App:
self._called_on_running = False
self._subsystem_registration_ended = False
self._pending_apply_app_config = False
- self._aioloop: asyncio.AbstractEventLoop | None = None
+ self._asyncio_loop: asyncio.AbstractEventLoop | None = None
+ self._asyncio_tasks: set[asyncio.Task] = set()
self._asyncio_timer: babase.AppTimer | None = None
self._config: babase.AppConfig | None = None
self._pending_intent: AppIntent | None = None
@@ -239,18 +245,68 @@ class App:
return _babase.app_is_active()
@property
- def aioloop(self) -> asyncio.AbstractEventLoop:
+ def asyncio_loop(self) -> asyncio.AbstractEventLoop:
"""The logic thread's asyncio event loop.
This allow async tasks to be run in the logic thread.
+
+ Generally you should call App.create_async_task() to schedule
+ async code to run instead of using this directly. That will
+ handle retaining the task and logging errors automatically.
+ Only schedule tasks onto asyncio_loop yourself when you intend
+ to hold on to the returned task and await its results. Releasing
+ the task reference can lead to subtle bugs such as unreported
+ errors and garbage-collected tasks disappearing before their
+ work is done.
+
Note that, at this time, the asyncio loop is encapsulated
and explicitly stepped by the engine's logic thread loop and
- thus things like asyncio.get_running_loop() will not return this
- loop from most places in the logic thread; only from within a
- task explicitly created in this loop.
+ thus things like asyncio.get_running_loop() will unintuitively
+ *not* return this loop from most places in the logic thread;
+ only from within a task explicitly created in this loop.
+ Hopefully this situation will be improved in the future with a
+ unified event loop.
"""
- assert self._aioloop is not None
- return self._aioloop
+ assert _babase.in_logic_thread()
+ assert self._asyncio_loop is not None
+ return self._asyncio_loop
+
+ def create_async_task(
+ self,
+ coro: Generator[Any, Any, T] | Coroutine[Any, Any, T],
+ *,
+ name: str | None = None,
+ ) -> None:
+ """Create a fully managed async task.
+
+ This will automatically retain and release a reference to the task
+ and log any exceptions that occur in it. If you need to await a task
+ or otherwise need more control, schedule a task directly using
+ App.asyncio_loop.
+ """
+ assert _babase.in_logic_thread()
+ # Hold a strong reference to the task until it is done.
+ # Otherwise it is possible for it to be garbage collected and
+ # disappear midway if the caller does not hold on to the
+ # returned task, which seems like a great way to introduce
+ # hard-to-track bugs.
+ task = self.asyncio_loop.create_task(coro, name=name)
+ self._asyncio_tasks.add(task)
+ task.add_done_callback(self._on_task_done)
+ # return task
+
+ def _on_task_done(self, task: asyncio.Task) -> None:
+ # Report any errors that occurred.
+ try:
+ exc = task.exception()
+ if exc is not None:
+ logging.error(
+ "Error in async task '%s'.", task.get_name(), exc_info=exc
+ )
+ except Exception:
+ logging.exception('Error reporting async task error.')
+
+ self._asyncio_tasks.remove(task)
@property
def config(self) -> babase.AppConfig:
@@ -594,7 +650,7 @@ class App:
_env.on_app_state_initing()
- self._aioloop = _asyncio.setup_asyncio()
+ self._asyncio_loop = _asyncio.setup_asyncio()
self.health_monitor = AppHealthMonitor()
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
@@ -874,8 +930,8 @@ class App:
)
# Now kick off any async shutdown task(s).
- assert self._aioloop is not None
- self._shutdown_task = self._aioloop.create_task(self._shutdown())
+ assert self._asyncio_loop is not None
+ self._shutdown_task = self._asyncio_loop.create_task(self._shutdown())
def _on_shutdown_complete(self) -> None:
"""(internal)"""
diff --git a/src/assets/ba_data/python/babase/_appsubsystem.py b/src/assets/ba_data/python/babase/_appsubsystem.py
index 812dc600..78ba01d5 100644
--- a/src/assets/ba_data/python/babase/_appsubsystem.py
+++ b/src/assets/ba_data/python/babase/_appsubsystem.py
@@ -40,16 +40,16 @@ class AppSubsystem:
"""Called when the app reaches the running state."""
def on_app_suspend(self) -> None:
- """Called when the app enters the paused state."""
+ """Called when the app enters the suspended state."""
def on_app_unsuspend(self) -> None:
- """Called when the app exits the paused state."""
+ """Called when the app exits the suspended state."""
def on_app_shutdown(self) -> None:
- """Called when the app is shutting down."""
+ """Called when the app begins shutting down."""
def on_app_shutdown_complete(self) -> None:
- """Called when the app is done shutting down."""
+ """Called when the app completes shutting down."""
def do_apply_app_config(self) -> None:
"""Called when the app config should be applied."""
diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py
index 59d76481..ce911c90 100644
--- a/src/assets/ba_data/python/babase/_apputils.py
+++ b/src/assets/ba_data/python/babase/_apputils.py
@@ -10,9 +10,11 @@ from threading import Thread
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
from efro.call import tpartial
from efro.log import LogLevel
from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json
+
import _babase
from babase._appsubsystem import AppSubsystem
@@ -386,6 +388,7 @@ class AppHealthMonitor(AppSubsystem):
self._response = False
self._first_check = True
+ @override
def on_app_loading(self) -> None:
# If any traceback dumps happened last run, log and clear them.
log_dumped_app_state(from_previous_run=True)
@@ -449,10 +452,12 @@ class AppHealthMonitor(AppSubsystem):
self._first_check = False
+ @override
def on_app_suspend(self) -> None:
assert _babase.in_logic_thread()
self._running = False
+ @override
def on_app_unsuspend(self) -> None:
assert _babase.in_logic_thread()
self._running = True
diff --git a/src/assets/ba_data/python/babase/_devconsole.py b/src/assets/ba_data/python/babase/_devconsole.py
index fb63c9e7..47580b81 100644
--- a/src/assets/ba_data/python/babase/_devconsole.py
+++ b/src/assets/ba_data/python/babase/_devconsole.py
@@ -8,6 +8,8 @@ from typing import TYPE_CHECKING
from dataclasses import dataclass
import logging
+from typing_extensions import override
+
import _babase
if TYPE_CHECKING:
@@ -96,6 +98,7 @@ class DevConsoleTab:
class DevConsoleTabPython(DevConsoleTab):
"""The Python dev-console tab."""
+ @override
def refresh(self) -> None:
self.python_terminal()
@@ -103,6 +106,7 @@ class DevConsoleTabPython(DevConsoleTab):
class DevConsoleTabTest(DevConsoleTab):
"""Test dev-console tab."""
+ @override
def refresh(self) -> None:
import random
diff --git a/src/assets/ba_data/python/babase/_emptyappmode.py b/src/assets/ba_data/python/babase/_emptyappmode.py
index f3905b71..3573769a 100644
--- a/src/assets/ba_data/python/babase/_emptyappmode.py
+++ b/src/assets/ba_data/python/babase/_emptyappmode.py
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
from bacommon.app import AppExperience
import _babase
@@ -18,15 +19,18 @@ if TYPE_CHECKING:
class EmptyAppMode(AppMode):
"""An empty app mode that can be used as a fallback/etc."""
+ @override
@classmethod
def get_app_experience(cls) -> AppExperience:
return AppExperience.EMPTY
+ @override
@classmethod
def _supports_intent(cls, intent: AppIntent) -> bool:
# We support default and exec intents currently.
return isinstance(intent, AppIntentExec | AppIntentDefault)
+ @override
def handle_intent(self, intent: AppIntent) -> None:
if isinstance(intent, AppIntentExec):
_babase.empty_app_mode_handle_intent_exec(intent.code)
@@ -34,10 +38,12 @@ class EmptyAppMode(AppMode):
assert isinstance(intent, AppIntentDefault)
_babase.empty_app_mode_handle_intent_default()
+ @override
def on_activate(self) -> None:
# Let the native layer do its thing.
_babase.on_empty_app_mode_activate()
+ @override
def on_deactivate(self) -> None:
# Let the native layer do its thing.
_babase.on_empty_app_mode_deactivate()
diff --git a/src/assets/ba_data/python/babase/_env.py b/src/assets/ba_data/python/babase/_env.py
index 2af28c23..124caa27 100644
--- a/src/assets/ba_data/python/babase/_env.py
+++ b/src/assets/ba_data/python/babase/_env.py
@@ -9,6 +9,7 @@ import logging
import warnings
from typing import TYPE_CHECKING
+from typing_extensions import override
from efro.log import LogLevel
if TYPE_CHECKING:
@@ -216,6 +217,7 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None:
class _CustomHelper:
"""Replacement 'help' that behaves better for our setup."""
+ @override
def __repr__(self) -> str:
return 'Type help(object) for help about object.'
diff --git a/src/assets/ba_data/python/babase/_general.py b/src/assets/ba_data/python/babase/_general.py
index 206ac036..2d2738da 100644
--- a/src/assets/ba_data/python/babase/_general.py
+++ b/src/assets/ba_data/python/babase/_general.py
@@ -10,6 +10,7 @@ import logging
import inspect
from typing import TYPE_CHECKING, TypeVar, Protocol, NewType
+from typing_extensions import override
from efro.terminal import Clr
import _babase
@@ -178,6 +179,7 @@ class _WeakCall:
def __call__(self, *args_extra: Any) -> Any:
return self._call(*self._args + args_extra, **self._keywds)
+ @override
def __str__(self) -> str:
return (
''
diff --git a/src/assets/ba_data/python/babase/_language.py b/src/assets/ba_data/python/babase/_language.py
index 059e60ab..3983a7a7 100644
--- a/src/assets/ba_data/python/babase/_language.py
+++ b/src/assets/ba_data/python/babase/_language.py
@@ -8,6 +8,8 @@ import json
import logging
from typing import TYPE_CHECKING, overload
+from typing_extensions import override
+
import _babase
from babase._appsubsystem import AppSubsystem
@@ -217,6 +219,7 @@ class LanguageSubsystem(AppSubsystem):
color=(0, 1, 0),
)
+ @override
def do_apply_app_config(self) -> None:
assert _babase.in_logic_thread()
assert isinstance(_babase.app.config, dict)
@@ -598,9 +601,11 @@ class Lstr:
_error.print_exception('_get_json failed for', self.args)
return 'JSON_ERR'
+ @override
def __str__(self) -> str:
return ''
+ @override
def __repr__(self) -> str:
return ''
@@ -648,5 +653,6 @@ class AttrDict(dict):
assert not isinstance(val, bytes)
return val
+ @override
def __setattr__(self, attr: str, value: Any) -> None:
raise AttributeError()
diff --git a/src/assets/ba_data/python/babase/_login.py b/src/assets/ba_data/python/babase/_login.py
index 39cfa0ed..67968440 100644
--- a/src/assets/ba_data/python/babase/_login.py
+++ b/src/assets/ba_data/python/babase/_login.py
@@ -9,6 +9,7 @@ import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING, final
+from typing_extensions import override
from bacommon.login import LoginType
import _babase
@@ -353,6 +354,7 @@ class LoginAdapterNative(LoginAdapter):
self._sign_in_attempt_num = 123
self._sign_in_attempts: dict[int, Callable[[str | None], None]] = {}
+ @override
def get_sign_in_token(
self, completion_cb: Callable[[str | None], None]
) -> None:
@@ -363,6 +365,7 @@ class LoginAdapterNative(LoginAdapter):
self.login_type.value, attempt_id
)
+ @override
def on_back_end_active_change(self, active: bool) -> None:
_babase.login_adapter_back_end_active_change(
self.login_type.value, active
diff --git a/src/assets/ba_data/python/babase/_meta.py b/src/assets/ba_data/python/babase/_meta.py
index 76f88b76..904e354f 100644
--- a/src/assets/ba_data/python/babase/_meta.py
+++ b/src/assets/ba_data/python/babase/_meta.py
@@ -26,7 +26,7 @@ EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = {
'plugin': 'babase.Plugin',
# DEPRECATED as of 12/2023. Currently am warning if finding these
# but should take this out eventually.
- 'keyboard': 'babase.Keyboard',
+ 'keyboard': 'bauiv1.Keyboard',
}
T = TypeVar('T')
diff --git a/src/assets/ba_data/python/babase/_plugin.py b/src/assets/ba_data/python/babase/_plugin.py
index 692840b1..d83050b9 100644
--- a/src/assets/ba_data/python/babase/_plugin.py
+++ b/src/assets/ba_data/python/babase/_plugin.py
@@ -8,6 +8,8 @@ import logging
import importlib.util
from typing import TYPE_CHECKING
+from typing_extensions import override
+
import _babase
from babase._appsubsystem import AppSubsystem
@@ -158,6 +160,7 @@ class PluginSubsystem(AppSubsystem):
if config_changed:
_babase.app.config.commit()
+ @override
def on_app_running(self) -> None:
# Load up our plugins and go ahead and call their on_app_running
# calls.
@@ -170,6 +173,7 @@ class PluginSubsystem(AppSubsystem):
_error.print_exception('Error in plugin on_app_running()')
+ @override
def on_app_suspend(self) -> None:
for plugin in self.active_plugins:
try:
@@ -179,6 +183,7 @@ class PluginSubsystem(AppSubsystem):
_error.print_exception('Error in plugin on_app_suspend()')
+ @override
def on_app_unsuspend(self) -> None:
for plugin in self.active_plugins:
try:
@@ -188,6 +193,7 @@ class PluginSubsystem(AppSubsystem):
_error.print_exception('Error in plugin on_app_unsuspend()')
+ @override
def on_app_shutdown(self) -> None:
for plugin in self.active_plugins:
try:
@@ -197,6 +203,7 @@ class PluginSubsystem(AppSubsystem):
_error.print_exception('Error in plugin on_app_shutdown()')
+ @override
def on_app_shutdown_complete(self) -> None:
for plugin in self.active_plugins:
try:
diff --git a/src/assets/ba_data/python/babase/_ui.py b/src/assets/ba_data/python/babase/_ui.py
index 6f6b0595..6457eadf 100644
--- a/src/assets/ba_data/python/babase/_ui.py
+++ b/src/assets/ba_data/python/babase/_ui.py
@@ -5,6 +5,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from babase._stringedit import StringEditAdapter
import _babase
@@ -24,9 +26,11 @@ class DevConsoleStringEditAdapter(StringEditAdapter):
description, initial_text, max_length, screen_space_center
)
+ @override
def _do_apply(self, new_text: str) -> None:
_babase.set_dev_console_input_text(new_text)
_babase.dev_console_input_adapter_finish()
+ @override
def _do_cancel(self) -> None:
_babase.dev_console_input_adapter_finish()
diff --git a/src/assets/ba_data/python/baclassic/_ads.py b/src/assets/ba_data/python/baclassic/_ads.py
index 2373df36..5cd454d0 100644
--- a/src/assets/ba_data/python/baclassic/_ads.py
+++ b/src/assets/ba_data/python/baclassic/_ads.py
@@ -229,9 +229,7 @@ class AdsSubsystem:
await asyncio.sleep(1.0)
payload.run(fallback=True)
- _fallback_task = babase.app.aioloop.create_task(
- add_fallback_task()
- )
+ babase.app.create_async_task(add_fallback_task())
self.show_ad('between_game', on_completion_call=payload.run)
else:
babase.pushcall(call) # Just run the callback without the ad.
diff --git a/src/assets/ba_data/python/baclassic/_benchmark.py b/src/assets/ba_data/python/baclassic/_benchmark.py
index 42e64636..23033eef 100644
--- a/src/assets/ba_data/python/baclassic/_benchmark.py
+++ b/src/assets/ba_data/python/baclassic/_benchmark.py
@@ -7,6 +7,7 @@ import random
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import bascenev1
import _baclassic
@@ -43,6 +44,7 @@ def run_cpu_benchmark() -> None:
cfg['Graphics Quality'] = self._old_quality
cfg.apply()
+ @override
def on_player_request(self, player: bascenev1.SessionPlayer) -> bool:
return False
diff --git a/src/assets/ba_data/python/baclassic/_net.py b/src/assets/ba_data/python/baclassic/_net.py
index a76258df..04d3fb6c 100644
--- a/src/assets/ba_data/python/baclassic/_net.py
+++ b/src/assets/ba_data/python/baclassic/_net.py
@@ -9,6 +9,7 @@ import threading
from enum import Enum
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import bascenev1
@@ -68,6 +69,7 @@ class MasterServerV1CallThread(threading.Thread):
with self._context:
self._callback(arg)
+ @override
def run(self) -> None:
# pylint: disable=consider-using-with
# pylint: disable=too-many-branches
diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_subsystem.py
index c9a4cda8..f5847c71 100644
--- a/src/assets/ba_data/python/baclassic/_subsystem.py
+++ b/src/assets/ba_data/python/baclassic/_subsystem.py
@@ -8,6 +8,7 @@ import random
import logging
import weakref
+from typing_extensions import override
from efro.dataclassio import dataclass_from_dict
import babase
import bauiv1
@@ -149,6 +150,7 @@ class ClassicSubsystem(babase.AppSubsystem):
assert isinstance(self._env['legacy_user_agent_string'], str)
return self._env['legacy_user_agent_string']
+ @override
def on_app_loading(self) -> None:
from bascenev1lib.actor import spazappearance
from bascenev1lib import maps as stdmaps
@@ -230,13 +232,16 @@ class ClassicSubsystem(babase.AppSubsystem):
self.accounts.on_app_loading()
+ @override
def on_app_suspend(self) -> None:
self.accounts.on_app_suspend()
+ @override
def on_app_unsuspend(self) -> None:
self.accounts.on_app_unsuspend()
self.music.on_app_unsuspend()
+ @override
def on_app_shutdown(self) -> None:
self.music.on_app_shutdown()
diff --git a/src/assets/ba_data/python/baclassic/macmusicapp.py b/src/assets/ba_data/python/baclassic/macmusicapp.py
index 27b0e126..12fe6846 100644
--- a/src/assets/ba_data/python/baclassic/macmusicapp.py
+++ b/src/assets/ba_data/python/baclassic/macmusicapp.py
@@ -8,6 +8,7 @@ import threading
from collections import deque
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
from baclassic._music import MusicPlayer
@@ -27,6 +28,7 @@ class MacMusicAppMusicPlayer(MusicPlayer):
self._thread = _MacMusicAppThread()
self._thread.start()
+ @override
def on_select_entry(
self,
callback: Callable[[Any], None],
@@ -40,6 +42,7 @@ class MacMusicAppMusicPlayer(MusicPlayer):
callback, current_entry, selection_target_name
)
+ @override
def on_set_volume(self, volume: float) -> None:
self._thread.set_volume(volume)
@@ -47,6 +50,7 @@ class MacMusicAppMusicPlayer(MusicPlayer):
"""Asynchronously fetch the list of available iTunes playlists."""
self._thread.get_playlists(callback)
+ @override
def on_play(self, entry: Any) -> None:
assert babase.app.classic is not None
music = babase.app.classic.music
@@ -59,9 +63,11 @@ class MacMusicAppMusicPlayer(MusicPlayer):
entry_type,
)
+ @override
def on_stop(self) -> None:
self._thread.play_playlist(None)
+ @override
def on_app_shutdown(self) -> None:
self._thread.shutdown()
@@ -77,6 +83,7 @@ class _MacMusicAppThread(threading.Thread):
self._current_playlist: str | None = None
self._orig_volume: int | None = None
+ @override
def run(self) -> None:
"""Run the Music.app thread."""
babase.set_thread_name('BA_MacMusicAppThread')
diff --git a/src/assets/ba_data/python/baclassic/osmusic.py b/src/assets/ba_data/python/baclassic/osmusic.py
index d808000d..f2198ce5 100644
--- a/src/assets/ba_data/python/baclassic/osmusic.py
+++ b/src/assets/ba_data/python/baclassic/osmusic.py
@@ -9,6 +9,7 @@ import logging
import threading
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
from baclassic._music import MusicPlayer
@@ -33,6 +34,7 @@ class OSMusicPlayer(MusicPlayer):
# FIXME: should ask the C++ layer for these; just hard-coding for now.
return ['mp3', 'ogg', 'm4a', 'wav', 'flac', 'mid']
+ @override
def on_select_entry(
self,
callback: Callable[[Any], None],
@@ -48,9 +50,11 @@ class OSMusicPlayer(MusicPlayer):
callback, current_entry, selection_target_name
)
+ @override
def on_set_volume(self, volume: float) -> None:
babase.music_player_set_volume(volume)
+ @override
def on_play(self, entry: Any) -> None:
assert babase.app.classic is not None
music = babase.app.classic.music
@@ -99,11 +103,13 @@ class OSMusicPlayer(MusicPlayer):
self._actually_playing = True
babase.music_player_play(result)
+ @override
def on_stop(self) -> None:
self._want_to_play = False
self._actually_playing = False
babase.music_player_stop()
+ @override
def on_app_shutdown(self) -> None:
babase.music_player_shutdown()
@@ -120,6 +126,7 @@ class _PickFolderSongThread(threading.Thread):
self._callback = callback
self._path = path
+ @override
def run(self) -> None:
do_log_error = True
try:
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index 664ef95d..01c47c91 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21757
+TARGET_BALLISTICA_BUILD = 21762
TARGET_BALLISTICA_VERSION = '1.7.33'
diff --git a/src/assets/ba_data/python/baplus/__init__.py b/src/assets/ba_data/python/baplus/__init__.py
index dd2e4cb3..4040f7b4 100644
--- a/src/assets/ba_data/python/baplus/__init__.py
+++ b/src/assets/ba_data/python/baplus/__init__.py
@@ -16,9 +16,11 @@ from __future__ import annotations
import logging
+from baplus._cloud import CloudSubsystem
from baplus._subsystem import PlusSubsystem
__all__ = [
+ 'CloudSubsystem',
'PlusSubsystem',
]
diff --git a/src/assets/ba_data/python/babase/_cloud.py b/src/assets/ba_data/python/baplus/_cloud.py
similarity index 88%
rename from src/assets/ba_data/python/babase/_cloud.py
rename to src/assets/ba_data/python/baplus/_cloud.py
index 3f5643db..d2e51eb0 100644
--- a/src/assets/ba_data/python/babase/_cloud.py
+++ b/src/assets/ba_data/python/baplus/_cloud.py
@@ -7,8 +7,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, overload
-import _babase
-from babase._appsubsystem import AppSubsystem
+import babase
if TYPE_CHECKING:
from typing import Callable, Any
@@ -23,7 +22,7 @@ DEBUG_LOG = False
# internal protocols.
-class CloudSubsystem(AppSubsystem):
+class CloudSubsystem(babase.AppSubsystem):
"""Manages communication with cloud components."""
@property
@@ -44,7 +43,7 @@ class CloudSubsystem(AppSubsystem):
if DEBUG_LOG:
logging.debug('CloudSubsystem: Connectivity is now %s.', connected)
- plus = _babase.app.plus
+ plus = babase.app.plus
assert plus is not None
# Inform things that use this.
@@ -117,12 +116,11 @@ class CloudSubsystem(AppSubsystem):
The provided on_response call will be run in the logic thread
and passed either the response or the error that occurred.
"""
- from babase._general import Call
del msg # Unused.
- _babase.pushcall(
- Call(
+ babase.pushcall(
+ babase.Call(
on_response,
RuntimeError('Cloud functionality is not available.'),
)
@@ -153,6 +151,25 @@ class CloudSubsystem(AppSubsystem):
"""
raise RuntimeError('Cloud functionality is not available.')
+ @overload
+ async def send_message_async(
+ self, msg: bacommon.cloud.PromoCodeMessage
+ ) -> bacommon.cloud.PromoCodeResponse:
+ ...
+
+ @overload
+ async def send_message_async(
+ self, msg: bacommon.cloud.TestMessage
+ ) -> bacommon.cloud.TestResponse:
+ ...
+
+ async def send_message_async(self, msg: Message) -> Response | None:
+ """Synchronously send a message to the cloud.
+
+ Must be called from the logic thread.
+ """
+ raise RuntimeError('Cloud functionality is not available.')
+
def cloud_console_exec(code: str) -> None:
"""Called by the cloud console to run code in the logic thread."""
@@ -188,7 +205,7 @@ def cloud_console_exec(code: str) -> None:
except Exception:
import traceback
- apptime = _babase.apptime()
+ apptime = babase.apptime()
print(f'Exec error at time {apptime:.2f}.', file=sys.stderr)
traceback.print_exc()
diff --git a/src/assets/ba_data/python/baplus/_subsystem.py b/src/assets/ba_data/python/baplus/_subsystem.py
index d38ca31a..567f3cd1 100644
--- a/src/assets/ba_data/python/baplus/_subsystem.py
+++ b/src/assets/ba_data/python/baplus/_subsystem.py
@@ -5,13 +5,17 @@ from __future__ import annotations
from typing import TYPE_CHECKING
-import _baplus
+from typing_extensions import override
from babase import AppSubsystem
+import _baplus
+
if TYPE_CHECKING:
from typing import Callable, Any
- from babase import CloudSubsystem, AccountV2Subsystem
+ from babase import AccountV2Subsystem
+
+ from baplus._cloud import CloudSubsystem
class PlusSubsystem(AppSubsystem):
@@ -32,6 +36,7 @@ class PlusSubsystem(AppSubsystem):
accounts: AccountV2Subsystem
cloud: CloudSubsystem
+ @override
def on_app_loading(self) -> None:
_baplus.on_app_loading()
self.accounts.on_app_loading()
diff --git a/src/assets/ba_data/python/bascenev1/_activitytypes.py b/src/assets/ba_data/python/bascenev1/_activitytypes.py
index c0e410e1..9b46b3eb 100644
--- a/src/assets/ba_data/python/bascenev1/_activitytypes.py
+++ b/src/assets/ba_data/python/bascenev1/_activitytypes.py
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -34,11 +35,13 @@ class EndSessionActivity(Activity[EmptyPlayer, EmptyTeam]):
self.inherits_vr_camera_offset = True
self.inherits_vr_overlay_center = True
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
babase.fade_screen(False)
babase.lock_all_input()
+ @override
def on_begin(self) -> None:
# pylint: disable=cyclic-import
@@ -77,6 +80,7 @@ class JoinActivity(Activity[EmptyPlayer, EmptyTeam]):
self._tips_text: bascenev1.Actor | None = None
self._join_info: JoinInfo | None = None
+ @override
def on_transition_in(self) -> None:
# pylint: disable=cyclic-import
from bascenev1lib.actor.tipstext import TipsText
@@ -110,6 +114,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]):
super().__init__(settings)
self._background: bascenev1.Actor | None = None
+ @override
def on_transition_in(self) -> None:
# pylint: disable=cyclic-import
from bascenev1lib.actor.background import Background
@@ -119,6 +124,7 @@ class TransitionActivity(Activity[EmptyPlayer, EmptyTeam]):
fade_time=0.5, start_faded=False, show_logo=False
)
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -152,6 +158,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
self._custom_continue_message: babase.Lstr | None = None
self._server_transitioning: bool | None = None
+ @override
def on_player_join(self, player: EmptyPlayer) -> None:
super().on_player_join(player)
time_till_assign = max(
@@ -164,6 +171,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
time_till_assign, babase.WeakCall(self._safe_assign, player)
)
+ @override
def on_transition_in(self) -> None:
from bascenev1lib.actor.tipstext import TipsText
from bascenev1lib.actor.background import Background
@@ -176,6 +184,7 @@ class ScoreScreenActivity(Activity[EmptyPlayer, EmptyTeam]):
self._tips_text = TipsText()
setmusic(self.default_music)
+ @override
def on_begin(self) -> None:
# pylint: disable=cyclic-import
from bascenev1lib.actor.text import Text
diff --git a/src/assets/ba_data/python/bascenev1/_appmode.py b/src/assets/ba_data/python/bascenev1/_appmode.py
index 9408865d..481fb4e6 100644
--- a/src/assets/ba_data/python/bascenev1/_appmode.py
+++ b/src/assets/ba_data/python/bascenev1/_appmode.py
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
from bacommon.app import AppExperience
from babase import (
app,
@@ -23,15 +24,18 @@ if TYPE_CHECKING:
class SceneV1AppMode(AppMode):
"""Our app-mode."""
+ @override
@classmethod
def get_app_experience(cls) -> AppExperience:
return AppExperience.MELEE
+ @override
@classmethod
def _supports_intent(cls, intent: AppIntent) -> bool:
# We support default and exec intents currently.
return isinstance(intent, AppIntentExec | AppIntentDefault)
+ @override
def handle_intent(self, intent: AppIntent) -> None:
if isinstance(intent, AppIntentExec):
_bascenev1.handle_app_intent_exec(intent.code)
@@ -39,14 +43,17 @@ class SceneV1AppMode(AppMode):
assert isinstance(intent, AppIntentDefault)
_bascenev1.handle_app_intent_default()
+ @override
def on_activate(self) -> None:
# Let the native layer do its thing.
_bascenev1.on_app_mode_activate()
+ @override
def on_deactivate(self) -> None:
# Let the native layer do its thing.
_bascenev1.on_app_mode_deactivate()
+ @override
def on_app_active_changed(self) -> None:
# If we've gone inactive, bring up the main menu, which has the
# side effect of pausing the action (when possible).
diff --git a/src/assets/ba_data/python/bascenev1/_coopgame.py b/src/assets/ba_data/python/bascenev1/_coopgame.py
index 2c684f85..e0af5158 100644
--- a/src/assets/ba_data/python/bascenev1/_coopgame.py
+++ b/src/assets/ba_data/python/bascenev1/_coopgame.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypeVar
+from typing_extensions import override
import babase
import _bascenev1
@@ -31,6 +32,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]):
# We can assume our session is a CoopSession.
session: bascenev1.CoopSession
+ @override
@classmethod
def supports_session_type(
cls, sessiontype: type[bascenev1.Session]
@@ -49,6 +51,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]):
self._life_warning_beep_timer: bascenev1.Timer | None = None
self._warn_beeps_sound = _bascenev1.getsound('warnBeeps')
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -139,6 +142,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]):
)
vval -= 55
+ @override
def spawn_player_spaz(
self,
player: PlayerT,
diff --git a/src/assets/ba_data/python/bascenev1/_coopsession.py b/src/assets/ba_data/python/bascenev1/_coopsession.py
index 72a67919..f1d59ae4 100644
--- a/src/assets/ba_data/python/bascenev1/_coopsession.py
+++ b/src/assets/ba_data/python/bascenev1/_coopsession.py
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -97,6 +98,7 @@ class CoopSession(Session):
"""Get the game instance currently being played."""
return self._current_game_instance
+ @override
def should_allow_mid_activity_joins(
self, activity: bascenev1.Activity
) -> bool:
@@ -174,9 +176,11 @@ class CoopSession(Session):
self._tutorial_activity = _bascenev1.newactivity(TutorialActivity)
+ @override
def get_custom_menu_entries(self) -> list[dict[str, Any]]:
return self._custom_menu_ui
+ @override
def on_player_leave(self, sessionplayer: bascenev1.SessionPlayer) -> None:
super().on_player_leave(sessionplayer)
@@ -256,6 +260,7 @@ class CoopSession(Session):
activity.end(results={'outcome': 'restart'}, force=True)
# noinspection PyUnresolvedReferences
+ @override
def on_activity_end(
self, activity: bascenev1.Activity, results: Any
) -> None:
diff --git a/src/assets/ba_data/python/bascenev1/_dependency.py b/src/assets/ba_data/python/bascenev1/_dependency.py
index bbc956c0..0a2f49f2 100644
--- a/src/assets/ba_data/python/bascenev1/_dependency.py
+++ b/src/assets/ba_data/python/bascenev1/_dependency.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import weakref
from typing import Generic, TypeVar, TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -313,6 +314,7 @@ class AssetPackage(DependencyComponent):
self.package_id = entry.config
print(f'LOADING ASSET PACKAGE {self.package_id}')
+ @override
@classmethod
def dep_is_present(cls, config: Any = None) -> bool:
assert isinstance(config, str)
diff --git a/src/assets/ba_data/python/bascenev1/_dualteamsession.py b/src/assets/ba_data/python/bascenev1/_dualteamsession.py
index 4b4ab102..82807684 100644
--- a/src/assets/ba_data/python/bascenev1/_dualteamsession.py
+++ b/src/assets/ba_data/python/bascenev1/_dualteamsession.py
@@ -5,6 +5,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -32,6 +33,7 @@ class DualTeamSession(MultiTeamSession):
babase.increment_analytics_count('Teams session start')
super().__init__()
+ @override
def _switch_to_score_screen(self, results: bascenev1.GameResults) -> None:
# pylint: disable=cyclic-import
from bascenev1lib.activity.multiteamvictory import (
diff --git a/src/assets/ba_data/python/bascenev1/_freeforallsession.py b/src/assets/ba_data/python/bascenev1/_freeforallsession.py
index 6c71d1b4..2718377c 100644
--- a/src/assets/ba_data/python/bascenev1/_freeforallsession.py
+++ b/src/assets/ba_data/python/bascenev1/_freeforallsession.py
@@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -53,6 +54,7 @@ class FreeForAllSession(MultiTeamSession):
babase.increment_analytics_count('Free-for-all session start')
super().__init__()
+ @override
def _switch_to_score_screen(self, results: bascenev1.GameResults) -> None:
# pylint: disable=cyclic-import
from efro.util import asserttype
diff --git a/src/assets/ba_data/python/bascenev1/_gameactivity.py b/src/assets/ba_data/python/bascenev1/_gameactivity.py
index ad9bf0a5..1557ffc1 100644
--- a/src/assets/ba_data/python/bascenev1/_gameactivity.py
+++ b/src/assets/ba_data/python/bascenev1/_gameactivity.py
@@ -9,6 +9,7 @@ import random
import logging
from typing import TYPE_CHECKING, TypeVar
+from typing_extensions import override
import babase
import _bascenev1
@@ -377,6 +378,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
"""
return ''
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
@@ -488,6 +490,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
self.end_game()
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -536,12 +539,14 @@ class GameActivity(Activity[PlayerT, TeamT]):
max(5, data_t[0]['timeRemaining'])
)
+ @override
def on_player_join(self, player: PlayerT) -> None:
super().on_player_join(player)
# By default, just spawn a dude.
self.spawn_player(player)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, PlayerDiedMessage):
# pylint: disable=cyclic-import
@@ -835,6 +840,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
_bascenev1.timer(5.0, tnode.delete)
+ @override
def end(
self, results: Any = None, delay: float = 0.0, force: bool = False
) -> None:
diff --git a/src/assets/ba_data/python/bascenev1/_level.py b/src/assets/ba_data/python/bascenev1/_level.py
index 725c962e..2f1906e6 100644
--- a/src/assets/ba_data/python/bascenev1/_level.py
+++ b/src/assets/ba_data/python/bascenev1/_level.py
@@ -7,6 +7,7 @@ import copy
import weakref
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
if TYPE_CHECKING:
@@ -38,6 +39,7 @@ class Level:
self._index: int | None = None
self._score_version_string: str | None = None
+ @override
def __repr__(self) -> str:
cls = type(self)
return f"<{cls.__module__}.{cls.__name__} '{self._name}'>"
diff --git a/src/assets/ba_data/python/bascenev1/_map.py b/src/assets/ba_data/python/bascenev1/_map.py
index 818098fd..832632fc 100644
--- a/src/assets/ba_data/python/bascenev1/_map.py
+++ b/src/assets/ba_data/python/bascenev1/_map.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bascenev1
@@ -353,9 +354,11 @@ class Map(Actor):
return self.flag_points_default[:3]
return self.flag_points[team_index % len(self.flag_points)][:3]
+ @override
def exists(self) -> bool:
return bool(self.node)
+ @override
def handlemessage(self, msg: Any) -> Any:
from bascenev1 import _messages
diff --git a/src/assets/ba_data/python/bascenev1/_multiteamsession.py b/src/assets/ba_data/python/bascenev1/_multiteamsession.py
index 2c787915..06a5a2e1 100644
--- a/src/assets/ba_data/python/bascenev1/_multiteamsession.py
+++ b/src/assets/ba_data/python/bascenev1/_multiteamsession.py
@@ -8,7 +8,9 @@ import random
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
+
import _bascenev1
from bascenev1._session import Session
@@ -160,6 +162,7 @@ class MultiTeamSession(Session):
"""Returns which game in the series is currently being played."""
return self._game_number
+ @override
def on_team_join(self, team: bascenev1.SessionTeam) -> None:
team.customdata['previous_score'] = team.customdata['score'] = 0
@@ -178,6 +181,7 @@ class MultiTeamSession(Session):
self._next_game_spec['settings'],
)
+ @override
def on_activity_end(
self, activity: bascenev1.Activity, results: Any
) -> None:
diff --git a/src/assets/ba_data/python/bascenev1/_nodeactor.py b/src/assets/ba_data/python/bascenev1/_nodeactor.py
index 2e9a1483..7b3551e7 100644
--- a/src/assets/ba_data/python/bascenev1/_nodeactor.py
+++ b/src/assets/ba_data/python/bascenev1/_nodeactor.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bascenev1._messages import DieMessage
from bascenev1._actor import Actor
@@ -28,6 +30,7 @@ class NodeActor(Actor):
super().__init__()
self.node = node
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, DieMessage):
if self.node:
@@ -35,5 +38,6 @@ class NodeActor(Actor):
return None
return super().handlemessage(msg)
+ @override
def exists(self) -> bool:
return bool(self.node)
diff --git a/src/assets/ba_data/python/bascenev1/_teamgame.py b/src/assets/ba_data/python/bascenev1/_teamgame.py
index d438dbca..e835bd0e 100644
--- a/src/assets/ba_data/python/bascenev1/_teamgame.py
+++ b/src/assets/ba_data/python/bascenev1/_teamgame.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING, TypeVar
+from typing_extensions import override
import babase
import _bascenev1
@@ -35,6 +36,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
bascenev1.Player has their own bascenev1.Team)
"""
+ @override
@classmethod
def supports_session_type(
cls, sessiontype: type[bascenev1.Session]
@@ -57,6 +59,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
if isinstance(self.session, FreeForAllSession):
self.show_kill_points = False
+ @override
def on_transition_in(self) -> None:
# pylint: disable=cyclic-import
from bascenev1._coopsession import CoopSession
@@ -85,6 +88,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
).autoretain()
setattr(self.session, attrname, True)
+ @override
def on_begin(self) -> None:
super().on_begin()
try:
@@ -104,6 +108,7 @@ class TeamGameActivity(GameActivity[PlayerT, TeamT]):
except Exception:
logging.exception('Error in on_begin.')
+ @override
def spawn_player_spaz(
self,
player: PlayerT,
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py b/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py
index 0777e8f1..55f3fd8e 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/coopjoin.py
@@ -4,6 +4,7 @@
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
@@ -18,6 +19,7 @@ class CoopJoinActivity(bs.JoinActivity):
session = self.session
assert isinstance(session, bs.CoopSession)
+ @override
def on_transition_in(self) -> None:
from bascenev1lib.actor.controlsguide import ControlsGuide
from bascenev1lib.actor.text import Text
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
index 8b335a19..a88c418b 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
@@ -9,6 +9,7 @@ import random
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
from bacommon.login import LoginType
import bascenev1 as bs
import bauiv1 as bui
@@ -186,6 +187,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
self._victory: bool = settings['outcome'] == 'victory'
+ @override
def __del__(self) -> None:
super().__del__()
@@ -194,6 +196,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
with bui.ContextRef.empty():
bui.containerwidget(edit=self._root_ui, transition='out_left')
+ @override
def on_transition_in(self) -> None:
from bascenev1lib.actor import background # FIXME NO BSSTD
@@ -574,6 +577,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
self._player_press,
)
+ @override
def on_player_join(self, player: bs.Player) -> None:
super().on_player_join(player)
@@ -585,6 +589,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
bs.timer(time_till_assign, bs.WeakCall(self._safe_assign, player))
+ @override
def on_begin(self) -> None:
# FIXME: Clean this up.
# pylint: disable=too-many-statements
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py b/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py
index 2d260dfd..d6e1b289 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/drawscore.py
@@ -4,7 +4,9 @@
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
from bascenev1lib.actor.zoomtext import ZoomText
@@ -14,6 +16,7 @@ class DrawScoreScreenActivity(MultiTeamScoreScreenActivity):
default_music = None # Awkward silence...
+ @override
def on_begin(self) -> None:
bs.set_analytics_screen('Draw Score Screen')
super().on_begin()
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py b/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py
index c369e17b..2df370bd 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/dualteamscore.py
@@ -4,7 +4,9 @@
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
from bascenev1lib.actor.zoomtext import ZoomText
@@ -17,6 +19,7 @@ class TeamVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
self._winner: bs.SessionTeam = settings['winner']
assert isinstance(self._winner, bs.SessionTeam)
+ @override
def on_begin(self) -> None:
bs.set_analytics_screen('Teams Score Screen')
super().on_begin()
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/freeforallvictory.py b/src/assets/ba_data/python/bascenev1lib/activity/freeforallvictory.py
index 45d116ac..cf82d4ad 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/freeforallvictory.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/freeforallvictory.py
@@ -6,9 +6,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
-from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
+from typing_extensions import override
import bascenev1 as bs
+from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
+
if TYPE_CHECKING:
from typing import Any
@@ -23,6 +25,7 @@ class FreeForAllVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
self.transition_time = 0.5
self._cymbal_sound = bs.getsound('cymbal')
+ @override
def on_begin(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py
index 2870c1fd..ff8f74be 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamjoin.py
@@ -4,7 +4,9 @@
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.actor.text import Text
@@ -15,6 +17,7 @@ class MultiTeamJoinActivity(bs.JoinActivity):
super().__init__(settings)
self._next_up_text: Text | None = None
+ @override
def on_transition_in(self) -> None:
from bascenev1lib.actor.controlsguide import ControlsGuide
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py
index ef2ba012..7b754014 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamscore.py
@@ -3,7 +3,9 @@
"""Functionality related to teams mode score screen."""
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.actor.text import Text
from bascenev1lib.actor.image import Image
@@ -18,6 +20,7 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
self._show_up_next: bool = True
+ @override
def on_begin(self) -> None:
super().on_begin()
session = self.session
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py b/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py
index 203f0516..afc2a26b 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/multiteamvictory.py
@@ -4,7 +4,9 @@
from __future__ import annotations
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.activity.multiteamscore import MultiTeamScoreScreenActivity
@@ -22,6 +24,7 @@ class TeamSeriesVictoryScoreScreenActivity(MultiTeamScoreScreenActivity):
self._tips_text = None
self._default_show_tips = False
+ @override
def on_begin(self) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/background.py b/src/assets/ba_data/python/bascenev1lib/actor/background.py
index 2ad629a2..d5625a27 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/background.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/background.py
@@ -9,6 +9,7 @@ import weakref
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -104,6 +105,7 @@ class Background(bs.Actor):
timeval += random.random() * 0.1
bs.animate(cmb, 'input1', keys, loop=True)
+ @override
def __del__(self) -> None:
# Normal actors don't get sent DieMessages when their
# activity is shutting down, but we still need to do so
@@ -138,6 +140,7 @@ class Background(bs.Actor):
)
bs.timer(self.fade_time + 0.1, self.node.delete)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/bomb.py b/src/assets/ba_data/python/bascenev1lib/actor/bomb.py
index 00cb4e76..058057d0 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/bomb.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/bomb.py
@@ -10,7 +10,9 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING, TypeVar
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
@@ -661,6 +663,7 @@ class Blast(bs.Actor):
bs.timer(0.4, _extra_debris_sound)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
@@ -935,6 +938,7 @@ class Bomb(bs.Actor):
else None
)
+ @override
def on_expire(self) -> None:
super().on_expire()
@@ -1140,6 +1144,7 @@ class Bomb(bs.Actor):
if msg.srcnode:
pass
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, ExplodeMessage):
self.explode()
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/controlsguide.py b/src/assets/ba_data/python/bascenev1lib/actor/controlsguide.py
index 23c4a977..f3068f0e 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/controlsguide.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/controlsguide.py
@@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -547,9 +548,11 @@ class ControlsGuide(bs.Actor):
self._update_timer = None
self._dead = True
+ @override
def exists(self) -> bool:
return not self._dead
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/flag.py b/src/assets/ba_data/python/bascenev1lib/actor/flag.py
index 7c8f5364..cd24c9ef 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/flag.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/flag.py
@@ -7,9 +7,11 @@ from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING
-from bascenev1lib.gameutils import SharedObjects
+from typing_extensions import override
import bascenev1 as bs
+from bascenev1lib.gameutils import SharedObjects
+
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -328,6 +330,7 @@ class Flag(bs.Actor):
1.0, bs.WeakCall(self._hide_score_text)
)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/image.py b/src/assets/ba_data/python/bascenev1lib/actor/image.py
index 4fc61bfa..00e71d0c 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/image.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/image.py
@@ -7,6 +7,7 @@ from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -165,6 +166,7 @@ class Image(bs.Actor):
bs.WeakCall(self.handlemessage, bs.DieMessage()),
)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/onscreencountdown.py b/src/assets/ba_data/python/bascenev1lib/actor/onscreencountdown.py
index 7c1a9042..68c477b6 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/onscreencountdown.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/onscreencountdown.py
@@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -72,6 +73,7 @@ class OnScreenCountdown(bs.Actor):
)
self._timer = bs.Timer(1.0, self._update, repeat=True)
+ @override
def on_expire(self) -> None:
super().on_expire()
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/onscreentimer.py b/src/assets/ba_data/python/bascenev1lib/actor/onscreentimer.py
index 6f3b8dfa..192fecc2 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/onscreentimer.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/onscreentimer.py
@@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import logging
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -93,6 +94,7 @@ class OnScreenTimer(bs.Actor):
"""Shortcut for start time in seconds."""
return self.getstarttime()
+ @override
def handlemessage(self, msg: Any) -> Any:
# if we're asked to die, just kill our node/timer
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py b/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py
index 4902e57f..2ea3ead7 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/playerspaz.py
@@ -6,7 +6,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING, TypeVar, overload
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.actor.spaz import Spaz
if TYPE_CHECKING:
@@ -183,6 +185,7 @@ class PlayerSpaz(Spaz):
' non-connected player'
)
+ @override
def handlemessage(self, msg: Any) -> Any:
# FIXME: Tidy this up.
# pylint: disable=too-many-branches
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py b/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py
index c5c1dccd..6fdd41d6 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/popuptext.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -118,6 +119,7 @@ class PopupText(bs.Actor):
lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage())
)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/powerupbox.py b/src/assets/ba_data/python/bascenev1lib/actor/powerupbox.py
index 893cb2df..1b95e4df 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/powerupbox.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/powerupbox.py
@@ -7,7 +7,9 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
@@ -278,6 +280,7 @@ class PowerupBox(bs.Actor):
if self.node:
self.node.flashing = True
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/spaz.py b/src/assets/ba_data/python/bascenev1lib/actor/spaz.py
index 71a31a4c..ad914b8c 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/spaz.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/spaz.py
@@ -9,11 +9,13 @@ import random
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.bomb import Bomb, Blast
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence, Callable
@@ -228,9 +230,11 @@ class Spaz(bs.Actor):
self.punch_callback: Callable[[Spaz], Any] | None = None
self.pick_up_powerup_callback: Callable[[Spaz], Any] | None = None
+ @override
def exists(self) -> bool:
return bool(self.node)
+ @override
def on_expire(self) -> None:
super().on_expire()
@@ -249,6 +253,7 @@ class Spaz(bs.Actor):
assert not self.expired
self._dropped_bomb_callbacks.append(call)
+ @override
def is_alive(self) -> bool:
"""
Method override; returns whether ol' spaz is still kickin'.
@@ -694,6 +699,7 @@ class Spaz(bs.Actor):
else:
self.shield_decay_timer = None
+ @override
def handlemessage(self, msg: Any) -> Any:
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-statements
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/spazbot.py b/src/assets/ba_data/python/bascenev1lib/actor/spazbot.py
index 75d0eeb3..bdd8c271 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/spazbot.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/spazbot.py
@@ -10,6 +10,7 @@ import weakref
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz
@@ -489,6 +490,7 @@ class SpazBot(Spaz):
self.on_punch_press()
self.on_punch_release()
+ @override
def on_punched(self, damage: int) -> None:
"""
Method override; sends bs.SpazBotPunchedMessage
@@ -496,6 +498,7 @@ class SpazBot(Spaz):
"""
bs.getactivity().handlemessage(SpazBotPunchedMessage(self, damage))
+ @override
def on_expire(self) -> None:
super().on_expire()
@@ -503,6 +506,7 @@ class SpazBot(Spaz):
# no chance of them keeping activities or other things alive.
self.update_callback = None
+ @override
def handlemessage(self, msg: Any) -> Any:
# pylint: disable=too-many-branches
assert not self.expired
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/text.py b/src/assets/ba_data/python/bascenev1lib/actor/text.py
index 1a274d64..15698510 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/text.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/text.py
@@ -7,6 +7,7 @@ from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -221,6 +222,7 @@ class Text(bs.Actor):
bs.WeakCall(self.handlemessage, bs.DieMessage()),
)
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py b/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py
index ede00802..7a6335fc 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/tipstext.py
@@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -95,6 +96,7 @@ class TipsText(bs.Actor):
)
self.node.text = next_tip
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py b/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py
index e50a16a9..12cdb757 100644
--- a/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py
+++ b/src/assets/ba_data/python/bascenev1lib/actor/zoomtext.py
@@ -8,6 +8,7 @@ import random
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
if TYPE_CHECKING:
@@ -158,6 +159,7 @@ class ZoomText(bs.Actor):
if lifespan is not None:
bs.timer(lifespan, bs.WeakCall(self.handlemessage, bs.DieMessage()))
+ @override
def handlemessage(self, msg: Any) -> Any:
assert not self.expired
if isinstance(msg, bs.DieMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/game/assault.py b/src/assets/ba_data/python/bascenev1lib/game/assault.py
index 183a017f..12eb1229 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/assault.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/assault.py
@@ -10,11 +10,13 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -71,10 +73,12 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
bs.BoolSetting('Epic Mode', default=False),
]
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -96,16 +100,19 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH
)
+ @override
def get_instance_description(self) -> str | Sequence:
if self._score_to_win == 1:
return 'Touch the enemy flag.'
return 'Touch the enemy flag ${ARG1} times.', self._score_to_win
+ @override
def get_instance_description_short(self) -> str | Sequence:
if self._score_to_win == 1:
return 'touch 1 flag'
return 'touch ${ARG1} flags', self._score_to_win
+ @override
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
shared = SharedObjects.get()
base_pos = self.map.get_flag_position(sessionteam.id)
@@ -151,16 +158,19 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
return team
+ @override
def on_team_join(self, team: Team) -> None:
# Can't do this in create_team because the team's color/etc. have
# not been wired up yet at that point.
self._update_scoreboard()
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
self.setup_standard_powerup_drops()
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment standard.
@@ -249,6 +259,7 @@ class AssaultGame(bs.TeamGameActivity[Player, Team]):
if player_team.score >= self._score_to_win:
self.end_game()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
diff --git a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py
index 4e5da2d6..837c3be1 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py
@@ -10,6 +10,9 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.flag import (
@@ -19,7 +22,6 @@ from bascenev1lib.actor.flag import (
FlagDroppedMessage,
FlagDiedMessage,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -141,10 +143,12 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
bs.BoolSetting('Epic Mode', default=False),
]
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -173,16 +177,19 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FLAG_CATCHER
)
+ @override
def get_instance_description(self) -> str | Sequence:
if self._score_to_win == 1:
return 'Steal the enemy flag.'
return 'Steal the enemy flag ${ARG1} times.', self._score_to_win
+ @override
def get_instance_description_short(self) -> str | Sequence:
if self._score_to_win == 1:
return 'return 1 flag'
return 'return ${ARG1} flags', self._score_to_win
+ @override
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
# Create our team instance and its initial values.
@@ -272,12 +279,14 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
return team
+ @override
def on_team_join(self, team: Team) -> None:
# Can't do this in create_team because the team's color/etc. have
# not been wired up yet at that point.
self._spawn_flag_for_team(team)
self._update_scoreboard()
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
@@ -406,6 +415,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
if team.score >= self._score_to_win:
self.end_game()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -532,6 +542,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
bs.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
bs.timer(length, light.delete)
+ @override
def spawn_player_spaz(
self,
player: Player,
@@ -576,6 +587,7 @@ class CaptureTheFlagGame(bs.TeamGameActivity[Player, Team]):
team, team.score, self._score_to_win
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment standard behavior.
diff --git a/src/assets/ba_data/python/bascenev1lib/game/chosenone.py b/src/assets/ba_data/python/bascenev1lib/game/chosenone.py
index 81576190..aba373d7 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/chosenone.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/chosenone.py
@@ -10,11 +10,13 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -83,6 +85,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]):
]
scoreconfig = bs.ScoreConfig(label='Time Held')
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -121,20 +124,25 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.CHOSEN_ONE
)
+ @override
def get_instance_description(self) -> str | Sequence:
return 'There can be only one.'
+ @override
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
return Team(time_remaining=self._chosen_one_time)
+ @override
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
+ @override
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
if self._get_chosen_one_player() is player:
self._set_chosen_one_player(None)
+ @override
def on_begin(self) -> None:
super().on_begin()
shared = SharedObjects.get()
@@ -251,6 +259,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]):
logging.error('got nonexistent player as chosen one in _tick')
self._set_chosen_one_player(None)
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -335,6 +344,7 @@ class ChosenOneGame(bs.TeamGameActivity[Player, Team]):
'position', light.node, 'position'
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
diff --git a/src/assets/ba_data/python/bascenev1lib/game/conquest.py b/src/assets/ba_data/python/bascenev1lib/game/conquest.py
index 8b1589c1..d50041f6 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/conquest.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/conquest.py
@@ -10,12 +10,14 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.gameutils import SharedObjects
from bascenev1lib.actor.respawnicon import RespawnIcon
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -108,10 +110,12 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
bs.BoolSetting('Epic Mode', default=False),
]
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -143,16 +147,20 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
),
)
+ @override
def get_instance_description(self) -> str | Sequence:
return 'Secure all ${ARG1} flags.', len(self.map.flag_points)
+ @override
def get_instance_description_short(self) -> str | Sequence:
return 'secure all ${ARG1} flags', len(self.map.flag_points)
+ @override
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scores()
+ @override
def on_player_join(self, player: Player) -> None:
player.respawn_timer = None
@@ -160,6 +168,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
if player.team.flags_held > 0:
self.spawn_player(player)
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
@@ -221,6 +230,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
team, team.flags_held, len(self._flags)
)
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -272,6 +282,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
):
self.spawn_player(otherplayer)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
@@ -287,6 +298,7 @@ class ConquestGame(bs.TeamGameActivity[Player, Team]):
else:
super().handlemessage(msg)
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
# We spawn players at different places based on what flags are held.
return self.spawn_player_spaz(
diff --git a/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py b/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py
index ccbadd8d..7721b636 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py
@@ -9,9 +9,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -38,6 +40,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]):
# Print messages when players die since it matters here.
announce_player_deaths = True
+ @override
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]
@@ -87,12 +90,14 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]):
return settings
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -116,16 +121,20 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.TO_THE_DEATH
)
+ @override
def get_instance_description(self) -> str | Sequence:
return 'Crush ${ARG1} of your enemies.', self._score_to_win
+ @override
def get_instance_description_short(self) -> str | Sequence:
return 'kill ${ARG1} enemies', self._score_to_win
+ @override
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
@@ -137,6 +146,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]):
)
self._update_scoreboard()
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
@@ -197,6 +207,7 @@ class DeathMatchGame(bs.TeamGameActivity[Player, Team]):
team, team.score, self._score_to_win
)
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
diff --git a/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py b/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py
index 4eb2d5b5..e7159f8d 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py
@@ -10,6 +10,9 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.spazbot import SpazBotSet, BouncyBot, SpazBotDiedMessage
@@ -17,7 +20,6 @@ from bascenev1lib.actor.onscreencountdown import OnScreenCountdown
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.respawnicon import RespawnIcon
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any
@@ -51,11 +53,13 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]):
scoreconfig = bs.ScoreConfig(label='Score', scoretype=bs.ScoreType.POINTS)
# We're currently hard-coded for one map.
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Tower D']
# We support teams, free-for-all, and co-op sessions.
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (
@@ -93,11 +97,13 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FORWARD_MARCH
)
+ @override
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self._update_scoreboard()
# Called when our game actually starts.
+ @override
def on_begin(self) -> None:
from bascenev1lib.maps import TowerD
@@ -118,6 +124,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]):
self._spawn_evil_bunny()
# Overriding the default character spawning.
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
spaz = self.spawn_player_spaz(player)
spaz.connect_controls_to_player()
@@ -191,6 +198,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]):
self._eggs.append(Egg(position=(xpos, ypos, zpos)))
# Various high-level game events come through this method.
+ @override
def handlemessage(self, msg: Any) -> Any:
# Respawn dead players.
if isinstance(msg, bs.PlayerDiedMessage):
@@ -231,6 +239,7 @@ class EasterEggHuntGame(bs.TeamGameActivity[Player, Team]):
for team in self.teams:
self._scoreboard.set_team_value(team, team.score)
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -271,9 +280,11 @@ class Egg(bs.Actor):
},
)
+ @override
def exists(self) -> bool:
return bool(self.node)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node:
diff --git a/src/assets/ba_data/python/bascenev1lib/game/elimination.py b/src/assets/ba_data/python/bascenev1lib/game/elimination.py
index 51b09895..a7f9429f 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/elimination.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/elimination.py
@@ -10,9 +10,11 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.spazfactory import SpazFactory
from bascenev1lib.actor.scoreboard import Scoreboard
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -157,6 +159,7 @@ class Icon(bs.Actor):
if lives == 0:
bs.timer(0.6, self.update_for_lives)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
self.node.delete()
@@ -194,6 +197,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
allow_mid_activity_joins = False
+ @override
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]
@@ -238,12 +242,14 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
)
return settings
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -269,6 +275,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SURVIVAL
)
+ @override
def get_instance_description(self) -> str | Sequence:
return (
'Last team standing wins.'
@@ -276,6 +283,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
else 'Last one standing wins.'
)
+ @override
def get_instance_description_short(self) -> str | Sequence:
return (
'last team standing wins'
@@ -283,6 +291,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
else 'last one standing wins'
)
+ @override
def on_player_join(self, player: Player) -> None:
player.lives = self._lives_per_player
@@ -299,6 +308,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
if self.has_begun():
self._update_icons()
+ @override
def on_begin(self) -> None:
super().on_begin()
self._start_time = bs.time()
@@ -469,6 +479,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
return points[-1][1]
return None
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
actor = self.spawn_player_spaz(player, self._get_spawn_point(player))
if not self._solo_mode:
@@ -495,6 +506,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
position=player.node.position,
).autoretain()
+ @override
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
player.icons = []
@@ -518,6 +530,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
def _get_total_team_lives(self, team: Team) -> int:
return sum(player.lives for player in team.players)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
@@ -588,6 +601,7 @@ class EliminationGame(bs.TeamGameActivity[Player, Team]):
and any(player.lives > 0 for player in team.players)
]
+ @override
def end_game(self) -> None:
if self.has_ended():
return
diff --git a/src/assets/ba_data/python/bascenev1lib/game/football.py b/src/assets/ba_data/python/bascenev1lib/game/football.py
index 5a286a63..1c99555b 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/football.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/football.py
@@ -1,5 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
+# pylint: disable=too-many-lines
"""Implements football games (both co-op and teams varieties)."""
# ba_meta require api 8
@@ -12,6 +13,9 @@ import random
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.bomb import TNTSpawner
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
@@ -39,7 +43,6 @@ from bascenev1lib.actor.spazbot import (
StickyBot,
ExplodeyBot,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -128,11 +131,13 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
bs.BoolSetting('Epic Mode', default=False),
]
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
# We only support two-team play.
return issubclass(sessiontype, bs.DualTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -170,6 +175,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.FOOTBALL
)
+ @override
def get_instance_description(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
@@ -181,6 +187,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
return 'Score ${ARG1} touchdowns.', touchdowns
return 'Score a touchdown.'
+ @override
def get_instance_description_short(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
touchdowns = math.ceil(touchdowns)
@@ -188,6 +195,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
return 'score ${ARG1} touchdowns', touchdowns
return 'score a touchdown'
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
@@ -224,6 +232,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
self._update_scoreboard()
self._chant_sound.play()
+ @override
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
@@ -285,6 +294,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
bs.cameraflash(duration=10.0)
self._update_scoreboard()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -298,6 +308,7 @@ class FootballTeamGame(bs.TeamGameActivity[Player, Team]):
team, team.score, self._score_to_win
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, FlagPickedUpMessage):
assert isinstance(msg.flag, FootballFlag)
@@ -379,9 +390,11 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
default_music = bs.MusicType.FOOTBALL
# FIXME: Need to update co-op games to use getscoreconfig.
+ @override
def get_score_type(self) -> str:
return 'time'
+ @override
def get_instance_description(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
touchdowns = math.ceil(touchdowns)
@@ -389,6 +402,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
return 'Score ${ARG1} touchdowns.', touchdowns
return 'Score a touchdown.'
+ @override
def get_instance_description_short(self) -> str | Sequence:
touchdowns = self._score_to_win / 7
touchdowns = math.ceil(touchdowns)
@@ -444,6 +458,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
self._flag_respawn_light: bs.Actor | None = None
self._flag: FootballFlag | None = None
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
self._scoreboard = Scoreboard()
@@ -480,6 +495,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
)
self._chant_sound.play()
+ @override
def on_begin(self) -> None:
# FIXME: Split this up a bit.
# pylint: disable=too-many-statements
@@ -795,11 +811,13 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
if i == 0:
bs.cameraflash(duration=10.0)
+ @override
def end_game(self) -> None:
bs.setmusic(None)
self._bots.final_celebrate()
bs.timer(0.001, bs.Call(self.do_end, 'defeat'))
+ @override
def on_continue(self) -> None:
# Subtract one touchdown from the bots and get them moving again.
assert self._bot_team is not None
@@ -897,6 +915,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
},
)
+ @override
def handlemessage(self, msg: Any) -> Any:
"""handle high-level game messages"""
if isinstance(msg, bs.PlayerDiedMessage):
@@ -959,6 +978,7 @@ class FootballCoopGame(bs.CoopGameActivity[Player, Team]):
del player # Unused.
self._player_has_punched = True
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
spaz = self.spawn_player_spaz(
player, position=self.map.get_start_position(player.team.id)
diff --git a/src/assets/ba_data/python/bascenev1lib/game/hockey.py b/src/assets/ba_data/python/bascenev1lib/game/hockey.py
index b61b7dfd..64487e5a 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/hockey.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/hockey.py
@@ -9,11 +9,13 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.powerupbox import PowerupBoxFactory
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -58,6 +60,7 @@ class Puck(bs.Actor):
)
bs.animate(self.node, 'mesh_scale', {0: 0, 0.2: 1.3, 0.26: 1})
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
if self.node:
@@ -152,10 +155,12 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]):
bs.BoolSetting('Epic Mode', default=False),
]
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -231,16 +236,19 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.HOCKEY
)
+ @override
def get_instance_description(self) -> str | Sequence:
if self._score_to_win == 1:
return 'Score a goal.'
return 'Score ${ARG1} goals.', self._score_to_win
+ @override
def get_instance_description_short(self) -> str | Sequence:
if self._score_to_win == 1:
return 'score a goal'
return 'score ${ARG1} goals', self._score_to_win
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -281,6 +289,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]):
self._update_scoreboard()
self._chant_sound.play()
+ @override
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
@@ -364,6 +373,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]):
bs.cameraflash(duration=10.0)
self._update_scoreboard()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -375,6 +385,7 @@ class HockeyGame(bs.TeamGameActivity[Player, Team]):
for team in self.teams:
self._scoreboard.set_team_value(team, team.score, winscore)
+ @override
def handlemessage(self, msg: Any) -> Any:
# Respawn dead players if they're still in the game.
if isinstance(msg, bs.PlayerDiedMessage):
diff --git a/src/assets/ba_data/python/bascenev1lib/game/keepaway.py b/src/assets/ba_data/python/bascenev1lib/game/keepaway.py
index bc80b77d..416723e1 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/keepaway.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/keepaway.py
@@ -11,6 +11,9 @@ import logging
from enum import Enum
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.flag import (
@@ -19,7 +22,6 @@ from bascenev1lib.actor.flag import (
FlagDiedMessage,
FlagPickedUpMessage,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -86,12 +88,14 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
]
scoreconfig = bs.ScoreConfig(label='Time Held')
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.DualTeamSession) or issubclass(
sessiontype, bs.FreeForAllSession
)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -129,18 +133,23 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.KEEP_AWAY
)
+ @override
def get_instance_description(self) -> str | Sequence:
return 'Carry the flag for ${ARG1} seconds.', self._hold_time
+ @override
def get_instance_description_short(self) -> str | Sequence:
return 'carry the flag for ${ARG1} seconds', self._hold_time
+ @override
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
return Team(timeremaining=self._hold_time)
+ @override
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
+ @override
def on_begin(self) -> None:
super().on_begin()
self.setup_standard_time_limit(self._time_limit)
@@ -181,6 +190,7 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
if scoreteam.timeremaining <= 0:
self.end_game()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -268,6 +278,7 @@ class KeepAwayGame(bs.TeamGameActivity[Player, Team]):
team, team.timeremaining, self._hold_time, countdown=True
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
diff --git a/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py b/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py
index 6492d823..907b4341 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py
@@ -11,11 +11,13 @@ import weakref
from enum import Enum
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.flag import Flag
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -84,10 +86,12 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]):
]
scoreconfig = bs.ScoreConfig(label='Time Held')
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.MultiTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -144,15 +148,19 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC if self._epic_mode else bs.MusicType.SCARY
)
+ @override
def get_instance_description(self) -> str | Sequence:
return 'Secure the flag for ${ARG1} seconds.', self._hold_time
+ @override
def get_instance_description_short(self) -> str | Sequence:
return 'secure the flag for ${ARG1} seconds', self._hold_time
+ @override
def create_team(self, sessionteam: bs.SessionTeam) -> Team:
return Team(time_remaining=self._hold_time)
+ @override
def on_begin(self) -> None:
super().on_begin()
shared = SharedObjects.get()
@@ -223,6 +231,7 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]):
if scoring_team.time_remaining <= 0:
self.end_game()
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -283,6 +292,7 @@ class KingOfTheHillGame(bs.TeamGameActivity[Player, Team]):
team, team.time_remaining, self._hold_time, countdown=True
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
super().handlemessage(msg) # Augment default.
diff --git a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py
index 35e0566d..a9a70ae0 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py
@@ -10,9 +10,11 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.onscreentimer import OnScreenTimer
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -49,11 +51,13 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
allow_mid_activity_joins = False
# We're currently hard-coded for one map.
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Rampage']
# We support teams, free-for-all, and co-op sessions.
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return (
@@ -77,6 +81,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
if self._epic_mode:
self.slow_motion = True
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -100,6 +105,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
# Check for immediate end (if we've only got 1 player, etc).
bs.timer(5.0, self._check_end_game)
+ @override
def on_player_leave(self, player: Player) -> None:
# Augment default behavior.
super().on_player_leave(player)
@@ -108,6 +114,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
self._check_end_game()
# overriding the default character spawning..
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
spaz = self.spawn_player_spaz(player)
@@ -122,6 +129,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
return spaz
# Various high-level game events come through this method.
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment standard behavior.
@@ -213,6 +221,7 @@ class MeteorShowerGame(bs.TeamGameActivity[Player, Team]):
def _decrement_meteor_time(self) -> None:
self._meteor_time = max(0.01, self._meteor_time * 0.9)
+ @override
def end_game(self) -> None:
cur_time = bs.time()
assert self._timer is not None
diff --git a/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py b/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py
index 501e920b..98ca8d0b 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py
@@ -10,13 +10,15 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.spazbot import (
SpazBotSet,
ChargerBot,
SpazBotDiedMessage,
)
from bascenev1lib.actor.onscreentimer import OnScreenTimer
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any
@@ -44,6 +46,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
)
default_music = bs.MusicType.TO_THE_DEATH
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
# For now we're hard-coding spawn positions and whatnot
@@ -51,6 +54,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
# a specific map.
return ['Courtyard']
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
# We currently support Co-Op only.
@@ -67,6 +71,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
self._preset = str(settings['preset'])
# Called when our game actually begins.
+ @override
def on_begin(self) -> None:
super().on_begin()
is_pro = self._preset == 'pro'
@@ -123,6 +128,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
)
# Called for each spawning player.
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
# Let's spawn close to the center.
spawn_center = (0, 3, -2)
@@ -144,6 +150,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
self.end_game()
# Called for miscellaneous messages.
+ @override
def handlemessage(self, msg: Any) -> Any:
# A player has died.
if isinstance(msg, bs.PlayerDiedMessage):
@@ -166,6 +173,7 @@ class NinjaFightGame(bs.TeamGameActivity[Player, Team]):
# When this is called, we should fill out results and end the game
# *regardless* of whether is has been won. (this may be called due
# to a tournament ending or other external reason).
+ @override
def end_game(self) -> None:
# Stop our on-screen timer so players can see what they got.
assert self._timer is not None
diff --git a/src/assets/ba_data/python/bascenev1lib/game/onslaught.py b/src/assets/ba_data/python/bascenev1lib/game/onslaught.py
index 2e8f98e4..d4644130 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/onslaught.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/onslaught.py
@@ -17,6 +17,9 @@ from enum import Enum, unique
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.popuptext import PopupText
from bascenev1lib.actor.bomb import TNTSpawner
from bascenev1lib.actor.playerspaz import PlayerSpazHurtMessage
@@ -45,7 +48,6 @@ from bascenev1lib.actor.spazbot import (
BrawlerBotPro,
BomberBotProShielded,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -222,6 +224,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
self._land_mine_kills = 0
self._tnt_kills = 0
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
customdata = bs.getsession().customdata
@@ -286,6 +289,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
label=bs.Lstr(resource='scoreText'), score_split=0.5
)
+ @override
def on_begin(self) -> None:
super().on_begin()
player_count = len(self.players)
@@ -825,6 +829,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
break
entry_count += 1
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
# We keep track of who got hurt each wave for score purposes.
player.has_been_hurt = False
@@ -1414,6 +1419,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
assert self._scoreboard is not None
self._scoreboard.set_team_value(self.teams[0], score, max_score=None)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, PlayerSpazHurtMessage):
msg.spaz.getplayer(Player, True).has_been_hurt = True
@@ -1526,6 +1532,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
def _set_can_end_wave(self) -> None:
self._can_end_wave = True
+ @override
def end_game(self) -> None:
# Tell our bots to celebrate just to rub it in.
assert self._bots is not None
@@ -1534,6 +1541,7 @@ class OnslaughtGame(bs.CoopGameActivity[Player, Team]):
self.do_end('defeat', delay=2.0)
bs.setmusic(None)
+ @override
def on_continue(self) -> None:
for player in self.players:
if not player.is_alive():
diff --git a/src/assets/ba_data/python/bascenev1lib/game/race.py b/src/assets/ba_data/python/bascenev1lib/game/race.py
index 2dd20f73..4a35fa4c 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/race.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/race.py
@@ -12,11 +12,13 @@ import logging
from typing import TYPE_CHECKING
from dataclasses import dataclass
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.gameutils import SharedObjects
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -84,6 +86,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
label='Time', lower_is_better=True, scoretype=bs.ScoreType.MILLISECONDS
)
+ @override
@classmethod
def get_available_settings(
cls, sessiontype: type[bs.Session]
@@ -133,10 +136,12 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
)
return settings
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
return issubclass(sessiontype, bs.MultiTeamSession)
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
assert bs.app.classic is not None
@@ -179,6 +184,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
bs.MusicType.EPIC_RACE if self._epic_mode else bs.MusicType.RACE
)
+ @override
def get_instance_description(self) -> str | Sequence:
if (
isinstance(self.session, bs.DualTeamSession)
@@ -192,11 +198,13 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
return 'Run ${ARG1} laps.' + t_str, self._laps
return 'Run 1 lap.' + t_str
+ @override
def get_instance_description_short(self) -> str | Sequence:
if self._laps > 1:
return 'run ${ARG1} laps', self._laps
return 'run 1 lap'
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
shared = SharedObjects.get()
@@ -379,9 +387,11 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
except Exception:
logging.exception('Error printing lap.')
+ @override
def on_team_join(self, team: Team) -> None:
self._update_scoreboard()
+ @override
def on_player_leave(self, player: Player) -> None:
super().on_player_leave(player)
@@ -442,6 +452,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
show_value=False,
)
+ @override
def on_begin(self) -> None:
from bascenev1lib.actor.onscreentimer import OnScreenTimer
@@ -670,6 +681,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
self._flash_mine(m_index)
bs.timer(0.95, bs.Call(self._make_mine, m_index))
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
if player.team.finished:
# FIXME: This is not type-safe!
@@ -758,6 +770,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
self.end_game()
return
+ @override
def end_game(self) -> None:
# Stop updating our time text, and set it to show the exact last
# finish time if we have one. (so users don't get upset if their
@@ -787,6 +800,7 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
announce_winning_team=isinstance(self.session, bs.DualTeamSession),
)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
# Augment default behavior.
diff --git a/src/assets/ba_data/python/bascenev1lib/game/runaround.py b/src/assets/ba_data/python/bascenev1lib/game/runaround.py
index 6640601f..b970d354 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/runaround.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/runaround.py
@@ -16,6 +16,9 @@ from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.popuptext import PopupText
from bascenev1lib.actor.bomb import TNTSpawner
from bascenev1lib.actor.scoreboard import Scoreboard
@@ -40,7 +43,6 @@ from bascenev1lib.actor.spazbot import (
BomberBotPro,
BrawlerBotPro,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -194,6 +196,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
self._flawless_bonus: int | None = None
self._wave_update_timer: bs.Timer | None = None
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
self._scoreboard = Scoreboard(
@@ -211,6 +214,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
)
)
+ @override
def on_begin(self) -> None:
super().on_begin()
player_count = len(self.players)
@@ -571,6 +575,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
),
)
+ @override
def on_continue(self) -> None:
self._lives = 3
assert self._lives_text is not None
@@ -578,6 +583,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
self._lives_text.node.text = str(self._lives)
self._bots.start_moving()
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
pos = (
self._spawn_center[0] + random.uniform(-1.5, 1.5),
@@ -654,6 +660,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
),
).autoretain()
+ @override
def end_game(self) -> None:
bs.pushcall(bs.Call(self.do_end, 'defeat'))
bs.setmusic(None)
@@ -1286,6 +1293,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
# Revert to normal bot behavior otherwise..
return False
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerScoredMessage):
self._score += msg.score
diff --git a/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py b/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py
index c39f9706..fdff48f2 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py
@@ -10,11 +10,13 @@ from __future__ import annotations
import random
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.scoreboard import Scoreboard
from bascenev1lib.actor.onscreencountdown import OnScreenCountdown
from bascenev1lib.actor.bomb import Bomb
from bascenev1lib.actor.popuptext import PopupText
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -49,10 +51,12 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]):
]
default_music = bs.MusicType.FORWARD_MARCH
+ @override
@classmethod
def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]:
return ['Doom Shroom']
+ @override
@classmethod
def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
# We support any teams or versus sessions.
@@ -70,10 +74,12 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]):
self._enable_impact_bombs = bool(settings['Enable Impact Bombs'])
self._enable_triple_bombs = bool(settings['Enable Triple Bombs'])
+ @override
def on_team_join(self, team: Team) -> None:
if self.has_begun():
self.update_scoreboard()
+ @override
def on_begin(self) -> None:
super().on_begin()
self.update_scoreboard()
@@ -86,6 +92,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]):
self._countdown = OnScreenCountdown(60, endcall=self.end_game)
bs.timer(4.0, self._countdown.start)
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
spawn_center = (0, 3, -5)
pos = (
@@ -169,6 +176,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]):
# Clear out targets that have died.
self._targets = [t for t in self._targets if t]
+ @override
def handlemessage(self, msg: Any) -> Any:
# When players die, respawn them.
if isinstance(msg, bs.PlayerDiedMessage):
@@ -188,6 +196,7 @@ class TargetPracticeGame(bs.TeamGameActivity[Player, Team]):
for team in self.teams:
self._scoreboard.set_team_value(team, team.score)
+ @override
def end_game(self) -> None:
results = bs.GameResults()
for team in self.teams:
@@ -252,9 +261,11 @@ class Target(bs.Actor):
bs.animate_array(loc3, 'size', 1, {0.1: [0.0], 0.3: [self._r3 * 2.0]})
bs.getsound('laserReverse').play()
+ @override
def exists(self) -> bool:
return bool(self._nodes)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.DieMessage):
for node in self._nodes:
diff --git a/src/assets/ba_data/python/bascenev1lib/game/thelaststand.py b/src/assets/ba_data/python/bascenev1lib/game/thelaststand.py
index 5eb32fac..7752c3fa 100644
--- a/src/assets/ba_data/python/bascenev1lib/game/thelaststand.py
+++ b/src/assets/ba_data/python/bascenev1lib/game/thelaststand.py
@@ -9,6 +9,9 @@ import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
+import bascenev1 as bs
+
from bascenev1lib.actor.playerspaz import PlayerSpaz
from bascenev1lib.actor.bomb import TNTSpawner
from bascenev1lib.actor.scoreboard import Scoreboard
@@ -29,7 +32,6 @@ from bascenev1lib.actor.spazbot import (
StickyBot,
ExplodeyBot,
)
-import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any, Sequence
@@ -109,6 +111,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]):
ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002),
}
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
bs.timer(1.3, self._new_wave_sound.play)
@@ -116,6 +119,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]):
label=bs.Lstr(resource='scoreText'), score_split=0.5
)
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -129,6 +133,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]):
position=self._tntspawnpos, respawn_time=10.0
)
+ @override
def spawn_player(self, player: Player) -> bs.Actor:
pos = (
self._spawn_center[0] + random.uniform(-1.5, 1.5),
@@ -290,6 +295,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]):
assert self._scoreboard is not None
self._scoreboard.set_team_value(self.teams[0], score, max_score=None)
+ @override
def handlemessage(self, msg: Any) -> Any:
if isinstance(msg, bs.PlayerDiedMessage):
player = msg.getplayer(Player)
@@ -327,6 +333,7 @@ class TheLastStandGame(bs.CoopGameActivity[Player, Team]):
else:
super().handlemessage(msg)
+ @override
def end_game(self) -> None:
# Tell our bots to celebrate just to rub it in.
self._bots.final_celebrate()
diff --git a/src/assets/ba_data/python/bascenev1lib/mainmenu.py b/src/assets/ba_data/python/bascenev1lib/mainmenu.py
index 7b7acfbf..8041176e 100644
--- a/src/assets/ba_data/python/bascenev1lib/mainmenu.py
+++ b/src/assets/ba_data/python/bascenev1lib/mainmenu.py
@@ -10,6 +10,7 @@ import random
import weakref
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
import bauiv1 as bui
@@ -44,6 +45,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._news: NewsDisplay | None = None
self._attract_mode_timer: bs.Timer | None = None
+ @override
def on_transition_in(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
@@ -1139,6 +1141,7 @@ class MainMenuSession(bs.Session):
self._locked = False
self.setactivity(bs.newactivity(MainMenuActivity))
+ @override
def on_activity_end(self, activity: bs.Activity, results: Any) -> None:
if self._locked:
bui.unlock_all_input()
@@ -1146,6 +1149,7 @@ class MainMenuSession(bs.Session):
# Any ending activity leads us into the main menu one.
self.setactivity(bs.newactivity(MainMenuActivity))
+ @override
def on_player_request(self, player: bs.SessionPlayer) -> bool:
# Reject all player requests.
return False
diff --git a/src/assets/ba_data/python/bascenev1lib/maps.py b/src/assets/ba_data/python/bascenev1lib/maps.py
index 26349f56..d4731145 100644
--- a/src/assets/ba_data/python/bascenev1lib/maps.py
+++ b/src/assets/ba_data/python/bascenev1lib/maps.py
@@ -7,7 +7,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
+
from bascenev1lib.gameutils import SharedObjects
if TYPE_CHECKING:
@@ -22,15 +24,18 @@ class HockeyStadium(bs.Map):
name = 'Hockey Stadium'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'hockey', 'team_flag', 'keep_away']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'hockeyStadiumPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -114,15 +119,18 @@ class FootballStadium(bs.Map):
name = 'Football Stadium'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'football', 'team_flag', 'keep_away']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'footballStadiumPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -164,6 +172,7 @@ class FootballStadium(bs.Map):
gnode.vr_camera_offset = (0, -0.8, -1.1)
gnode.vr_near_clip = 0.5
+ @override
def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool:
box_position = self.defs.boxes['edge_box'][0:3]
box_scale = self.defs.boxes['edge_box'][6:9]
@@ -181,16 +190,19 @@ class Bridgit(bs.Map):
name = 'Bridgit'
dataname = 'bridgit'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
# print('getting playtypes', cls._getdata()['play_types'])
return ['melee', 'team_flag', 'keep_away']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'bridgitPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -286,6 +298,7 @@ class BigG(bs.Map):
name = 'Big G'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
@@ -298,10 +311,12 @@ class BigG(bs.Map):
'conquest',
]
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'bigGPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -397,15 +412,18 @@ class Roundabout(bs.Map):
name = 'Roundabout'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'roundaboutPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -502,15 +520,18 @@ class MonkeyFace(bs.Map):
name = 'Monkey Face'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'monkeyFacePreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -607,6 +628,7 @@ class ZigZag(bs.Map):
name = 'Zigzag'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
@@ -618,10 +640,12 @@ class ZigZag(bs.Map):
'king_of_the_hill',
]
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'zigzagPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -715,15 +739,18 @@ class ThePad(bs.Map):
name = 'The Pad'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'thePadPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -804,15 +831,18 @@ class DoomShroom(bs.Map):
name = 'Doom Shroom'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'doomShroomPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -881,6 +911,7 @@ class DoomShroom(bs.Map):
gnode.vignette_outer = (0.76, 0.76, 0.76)
gnode.vignette_inner = (0.95, 0.95, 0.99)
+ @override
def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool:
xpos = point.x
zpos = point.z
@@ -900,15 +931,18 @@ class LakeFrigid(bs.Map):
name = 'Lake Frigid'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag', 'race']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'lakeFrigidPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -987,15 +1021,18 @@ class TipTop(bs.Map):
name = 'Tip Top'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag', 'king_of_the_hill']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'tipTopPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1065,15 +1102,18 @@ class CragCastle(bs.Map):
name = 'Crag Castle'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag', 'conquest']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'cragCastlePreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1158,15 +1198,18 @@ class TowerD(bs.Map):
name = 'Tower D'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return []
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'towerDPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1256,6 +1299,7 @@ class TowerD(bs.Map):
gnode.vignette_outer = (0.7, 0.73, 0.7)
gnode.vignette_inner = (0.95, 0.95, 0.95)
+ @override
def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool:
# see if we're within edge_box
boxes = self.defs.boxes
@@ -1281,6 +1325,7 @@ class HappyThoughts(bs.Map):
name = 'Happy Thoughts'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
@@ -1292,10 +1337,12 @@ class HappyThoughts(bs.Map):
'king_of_the_hill',
]
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'alwaysLandPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1310,6 +1357,7 @@ class HappyThoughts(bs.Map):
}
return data
+ @override
@classmethod
def get_music_type(cls) -> bs.MusicType:
return bs.MusicType.FLYING
@@ -1397,15 +1445,18 @@ class StepRightUp(bs.Map):
name = 'Step Right Up'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag', 'conquest']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'stepRightUpPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1477,15 +1528,18 @@ class Courtyard(bs.Map):
name = 'Courtyard'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'courtyardPreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1576,6 +1630,7 @@ class Courtyard(bs.Map):
gnode.vignette_outer = (0.6, 0.6, 0.64)
gnode.vignette_inner = (0.95, 0.95, 0.93)
+ @override
def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool:
# count anything off our ground level as safe (for our platforms)
# see if we're within edge_box
@@ -1593,15 +1648,18 @@ class Rampage(bs.Map):
name = 'Rampage'
+ @override
@classmethod
def get_play_types(cls) -> list[str]:
"""Return valid play types for this map."""
return ['melee', 'keep_away', 'team_flag']
+ @override
@classmethod
def get_preview_texture_name(cls) -> str:
return 'rampagePreview'
+ @override
@classmethod
def on_preload(cls) -> Any:
data: dict[str, Any] = {
@@ -1681,6 +1739,7 @@ class Rampage(bs.Map):
gnode.vignette_outer = (0.62, 0.64, 0.69)
gnode.vignette_inner = (0.97, 0.95, 0.93)
+ @override
def is_point_near_edge(self, point: bs.Vec3, running: bool = False) -> bool:
box_position = self.defs.boxes['edge_box'][0:3]
box_scale = self.defs.boxes['edge_box'][6:9]
diff --git a/src/assets/ba_data/python/bascenev1lib/tutorial.py b/src/assets/ba_data/python/bascenev1lib/tutorial.py
index e51c4a8d..86aa6411 100644
--- a/src/assets/ba_data/python/bascenev1lib/tutorial.py
+++ b/src/assets/ba_data/python/bascenev1lib/tutorial.py
@@ -19,6 +19,7 @@ import logging
from collections import deque
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
from bascenev1lib.actor.spaz import Spaz
@@ -234,11 +235,13 @@ class TutorialActivity(bs.Activity[Player, Team]):
self._read_entries_timer: bs.Timer | None = None
self._entry_timer: bs.Timer | None = None
+ @override
def on_transition_in(self) -> None:
super().on_transition_in()
bs.setmusic(bs.MusicType.CHAR_SELECT, continuous=True)
self.map = self._map_type()
+ @override
def on_begin(self) -> None:
super().on_begin()
@@ -2513,6 +2516,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
self._skip_text.color = (1, 1, 1)
self._issued_warning = False
+ @override
def on_player_join(self, player: Player) -> None:
super().on_player_join(player)
@@ -2527,6 +2531,7 @@ class TutorialActivity(bs.Activity[Player, Team]):
bs.Call(self._player_pressed_button, player),
)
+ @override
def on_player_leave(self, player: Player) -> None:
if not all(self.players):
logging.error(
diff --git a/src/assets/ba_data/python/bauiv1/_subsystem.py b/src/assets/ba_data/python/bauiv1/_subsystem.py
index 3c6b1e77..34d7f5af 100644
--- a/src/assets/ba_data/python/bauiv1/_subsystem.py
+++ b/src/assets/ba_data/python/bauiv1/_subsystem.py
@@ -8,7 +8,9 @@ import logging
import inspect
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
+
import _bauiv1
if TYPE_CHECKING:
@@ -82,6 +84,7 @@ class UIV1Subsystem(babase.AppSubsystem):
"""Current ui scale for the app."""
return self._uiscale
+ @override
def on_app_loading(self) -> None:
from bauiv1._uitypes import UIController, ui_upkeep
diff --git a/src/assets/ba_data/python/bauiv1/_uitypes.py b/src/assets/ba_data/python/bauiv1/_uitypes.py
index 5cbd14e2..2f93f227 100644
--- a/src/assets/ba_data/python/bauiv1/_uitypes.py
+++ b/src/assets/ba_data/python/bauiv1/_uitypes.py
@@ -9,6 +9,7 @@ import weakref
from dataclasses import dataclass
from typing import TYPE_CHECKING
+from typing_extensions import override
import babase
import _bauiv1
@@ -264,12 +265,14 @@ class TextWidgetStringEditAdapter(babase.StringEditAdapter):
description, initial_text, max_length, screen_space_center
)
+ @override
def _do_apply(self, new_text: str) -> None:
if self.widget:
_bauiv1.textwidget(
edit=self.widget, text=new_text, adapter_finished=True
)
+ @override
def _do_cancel(self) -> None:
if self.widget:
_bauiv1.textwidget(edit=self.widget, adapter_finished=True)
diff --git a/src/assets/ba_data/python/bauiv1lib/account/viewer.py b/src/assets/ba_data/python/bauiv1lib/account/viewer.py
index 914bd930..9046a805 100644
--- a/src/assets/ba_data/python/bauiv1lib/account/viewer.py
+++ b/src/assets/ba_data/python/bauiv1lib/account/viewer.py
@@ -7,9 +7,11 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import logging
-from bauiv1lib.popup import PopupWindow, PopupMenuWindow
+from typing_extensions import override
import bauiv1 as bui
+from bauiv1lib.popup import PopupWindow, PopupMenuWindow
+
if TYPE_CHECKING:
from typing import Any
@@ -596,6 +598,7 @@ class AccountViewerWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/achievements.py b/src/assets/ba_data/python/bauiv1lib/achievements.py
index 1c8cc673..df2345d5 100644
--- a/src/assets/ba_data/python/bauiv1lib/achievements.py
+++ b/src/assets/ba_data/python/bauiv1lib/achievements.py
@@ -4,6 +4,8 @@
from __future__ import annotations
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -229,6 +231,7 @@ class AchievementsWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/characterpicker.py b/src/assets/ba_data/python/bauiv1lib/characterpicker.py
index b4bdd635..92194e2b 100644
--- a/src/assets/ba_data/python/bauiv1lib/characterpicker.py
+++ b/src/assets/ba_data/python/bauiv1lib/characterpicker.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -208,6 +210,7 @@ class CharacterPicker(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/colorpicker.py b/src/assets/ba_data/python/bauiv1lib/colorpicker.py
index 904c9e33..30887432 100644
--- a/src/assets/ba_data/python/bauiv1lib/colorpicker.py
+++ b/src/assets/ba_data/python/bauiv1lib/colorpicker.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -170,6 +172,7 @@ class ColorPicker(PopupWindow):
self._delegate.color_picker_closing(self)
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
if not self._transitioning_out:
bui.getsound('swish').play()
@@ -338,6 +341,7 @@ class ColorPickerExact(PopupWindow):
self._delegate.color_picker_closing(self)
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
if not self._transitioning_out:
bui.getsound('swish').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/fileselector.py b/src/assets/ba_data/python/bauiv1lib/fileselector.py
index 17263119..e9cf0ca6 100644
--- a/src/assets/ba_data/python/bauiv1lib/fileselector.py
+++ b/src/assets/ba_data/python/bauiv1lib/fileselector.py
@@ -10,6 +10,8 @@ import logging
from threading import Thread
from typing import TYPE_CHECKING
+from typing_extensions import override
+
import bauiv1 as bui
if TYPE_CHECKING:
@@ -204,6 +206,7 @@ class FileSelectorWindow(bui.Window):
self._callback = callback
self._path = path
+ @override
def run(self) -> None:
try:
starttime = time.time()
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
index b61ed339..1257c4bd 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.gather import GatherTab
import bauiv1 as bui
@@ -16,6 +18,7 @@ if TYPE_CHECKING:
class AboutGatherTab(GatherTab):
"""The about tab in the gather UI"""
+ @override
def on_activate(
self,
parent_widget: bui.Widget,
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
index e09968b1..d7f3205e 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
@@ -6,13 +6,13 @@
from __future__ import annotations
import logging
-from threading import Thread
-from typing import TYPE_CHECKING, cast
-
from enum import Enum
+from threading import Thread
from dataclasses import dataclass
+from typing import TYPE_CHECKING, cast
from bauiv1lib.gather import GatherTab
+from typing_extensions import override
import bauiv1 as bui
import bascenev1 as bs
@@ -42,6 +42,7 @@ class _HostLookupThread(Thread):
self._port = port
self._call = call
+ @override
def run(self) -> None:
result: str | None
try:
@@ -101,6 +102,7 @@ class ManualGatherTab(GatherTab):
self._party_edit_port_text: bui.Widget | None = None
self._no_parties_added_text: bui.Widget | None = None
+ @override
def on_activate(
self,
parent_widget: bui.Widget,
@@ -180,10 +182,12 @@ class ManualGatherTab(GatherTab):
return self._container
+ @override
def save_state(self) -> None:
assert bui.app.classic is not None
bui.app.ui_v1.window_states[type(self)] = State(sub_tab=self._sub_tab)
+ @override
def restore_state(self) -> None:
assert bui.app.classic is not None
state = bui.app.ui_v1.window_states.get(type(self))
@@ -771,6 +775,7 @@ class ManualGatherTab(GatherTab):
text=bui.Lstr(resource='gatherWindow.noPartiesAddedText'),
)
+ @override
def on_deactivate(self) -> None:
self._access_check_timer = None
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/nearbytab.py b/src/assets/ba_data/python/bauiv1lib/gather/nearbytab.py
index 146fcc7a..7393f6bd 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/nearbytab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/nearbytab.py
@@ -7,10 +7,12 @@ from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
-from bauiv1lib.gather import GatherTab
+from typing_extensions import override
import bauiv1 as bui
import bascenev1 as bs
+from bauiv1lib.gather import GatherTab
+
if TYPE_CHECKING:
from typing import Any
@@ -104,6 +106,7 @@ class NearbyGatherTab(GatherTab):
self._net_scanner: NetScanner | None = None
self._container: bui.Widget | None = None
+ @override
def on_activate(
self,
parent_widget: bui.Widget,
@@ -156,5 +159,6 @@ class NearbyGatherTab(GatherTab):
bui.widget(edit=scrollw, autoselect=True, up_widget=tab_button)
return self._container
+ @override
def on_deactivate(self) -> None:
self._net_scanner = None
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
index e66cd6c1..8b6cf7fb 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
@@ -13,6 +13,7 @@ from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
+from typing_extensions import override
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
from bacommon.net import (
PrivateHostingState,
@@ -81,6 +82,7 @@ class PrivateGatherTab(GatherTab):
logging.exception('Error building hosting config.')
self._hostingconfig = PrivateHostingConfig()
+ @override
def on_activate(
self,
parent_widget: bui.Widget,
@@ -253,6 +255,7 @@ class PrivateGatherTab(GatherTab):
return hcfg
+ @override
def on_deactivate(self) -> None:
self._update_timer = None
@@ -995,10 +998,12 @@ class PrivateGatherTab(GatherTab):
self._debug_server_comm('got connect response error')
bui.getsound('error').play()
+ @override
def save_state(self) -> None:
assert bui.app.classic is not None
bui.app.ui_v1.window_states[type(self)] = copy.deepcopy(self._state)
+ @override
def restore_state(self) -> None:
assert bui.app.classic is not None
state = bui.app.ui_v1.window_states.get(type(self))
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
index e1619436..83b3b534 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
@@ -13,6 +13,7 @@ from enum import Enum
from dataclasses import dataclass
from typing import TYPE_CHECKING, cast
+from typing_extensions import override
from bauiv1lib.gather import GatherTab
import bauiv1 as bui
import bascenev1 as bs
@@ -247,6 +248,7 @@ class AddrFetchThread(Thread):
super().__init__()
self._call = call
+ @override
def run(self) -> None:
sock: socket.socket | None = None
try:
@@ -284,6 +286,7 @@ class PingThread(Thread):
self._port = port
self._call = call
+ @override
def run(self) -> None:
assert bui.app.classic is not None
bui.app.classic.ping_thread_count += 1
@@ -392,6 +395,7 @@ class PublicGatherTab(GatherTab):
self._pending_party_infos: list[dict[str, Any]] = []
self._last_sub_scroll_height = 0.0
+ @override
def on_activate(
self,
parent_widget: bui.Widget,
@@ -478,9 +482,11 @@ class PublicGatherTab(GatherTab):
)
return self._container
+ @override
def on_deactivate(self) -> None:
self._update_timer = None
+ @override
def save_state(self) -> None:
# Save off a small number of parties with the lowest ping; we'll
# display these immediately when our UI comes back up which should
@@ -496,6 +502,7 @@ class PublicGatherTab(GatherTab):
have_valid_server_list=self._have_valid_server_list,
)
+ @override
def restore_state(self) -> None:
assert bui.app.classic is not None
state = bui.app.ui_v1.window_states.get(type(self))
diff --git a/src/assets/ba_data/python/bauiv1lib/getremote.py b/src/assets/ba_data/python/bauiv1lib/getremote.py
index 194725f7..0cea25ae 100644
--- a/src/assets/ba_data/python/bauiv1lib/getremote.py
+++ b/src/assets/ba_data/python/bauiv1lib/getremote.py
@@ -4,6 +4,8 @@
from __future__ import annotations
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -77,6 +79,7 @@ class GetBSRemoteWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/iconpicker.py b/src/assets/ba_data/python/bauiv1lib/iconpicker.py
index e3cace22..260bbdec 100644
--- a/src/assets/ba_data/python/bauiv1lib/iconpicker.py
+++ b/src/assets/ba_data/python/bauiv1lib/iconpicker.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import math
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -186,6 +188,7 @@ class IconPicker(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/share.py b/src/assets/ba_data/python/bauiv1lib/playlist/share.py
index d52d1e7f..480aba52 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/share.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/share.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import time
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.promocode import PromoCodeWindow
import bauiv1 as bui
@@ -55,6 +57,7 @@ class SharePlaylistImportWindow(PromoCodeWindow):
edit=self._root_widget, transition=self._transition_out
)
+ @override
def _do_enter(self) -> None:
plus = bui.app.plus
assert plus is not None
diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py
index ea58e4d6..b2d42b21 100644
--- a/src/assets/ba_data/python/bauiv1lib/playoptions.py
+++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
import bascenev1 as bs
import bauiv1 as bui
@@ -440,6 +441,7 @@ class PlayOptionsWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition=transition)
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/popup.py b/src/assets/ba_data/python/bauiv1lib/popup.py
index 34c591ca..b53cfc93 100644
--- a/src/assets/ba_data/python/bauiv1lib/popup.py
+++ b/src/assets/ba_data/python/bauiv1lib/popup.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import weakref
from typing import TYPE_CHECKING
+from typing_extensions import override
+
import bauiv1 as bui
if TYPE_CHECKING:
@@ -275,6 +277,7 @@ class PopupMenuWindow(PopupWindow):
delegate.popup_menu_closing(self)
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
if not self._transitioning_out:
bui.getsound('swish').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/promocode.py b/src/assets/ba_data/python/bauiv1lib/promocode.py
index 3cf745b5..09bcf86a 100644
--- a/src/assets/ba_data/python/bauiv1lib/promocode.py
+++ b/src/assets/ba_data/python/bauiv1lib/promocode.py
@@ -5,9 +5,14 @@
from __future__ import annotations
import time
+import logging
+from typing import TYPE_CHECKING
import bauiv1 as bui
+if TYPE_CHECKING:
+ from typing import Any
+
class PromoCodeWindow(bui.Window):
"""Window for entering promo codes."""
@@ -167,9 +172,6 @@ class PromoCodeWindow(bui.Window):
if not self._root_widget or self._root_widget.transitioning_out:
return
- plus = bui.app.plus
- assert plus is not None
-
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@@ -179,11 +181,43 @@ class PromoCodeWindow(bui.Window):
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
+
+ code: Any = bui.textwidget(query=self._text_field)
+ assert isinstance(code, str)
+
+ bui.app.create_async_task(_run_code(code))
+
+
+async def _run_code(code: str) -> None:
+ from bacommon.cloud import PromoCodeMessage
+
+ plus = bui.app.plus
+ assert plus is not None
+
+ try:
+ # If we're signed in with a V2 account, ship this to V2 server.
+ if plus.accounts.primary is not None:
+ with plus.accounts.primary:
+ response = await plus.cloud.send_message_async(
+ PromoCodeMessage(code)
+ )
+ # If V2 handled it, we're done.
+ if response.valid:
+ # Support simple message printing from v2 server.
+ if response.message is not None:
+ bui.screenmessage(response.message, color=(0, 1, 0))
+ return
+
+ # If V2 didn't accept it (or isn't signed in) kick it over to V1.
plus.add_v1_account_transaction(
{
'type': 'PROMO_CODE',
'expire_time': time.time() + 5,
- 'code': bui.textwidget(query=self._text_field),
+ 'code': code,
}
)
plus.run_v1_account_transactions()
+ except Exception:
+ logging.exception('Error sending promo code.')
+ bui.screenmessage('Error sending code (see log).', color=(1, 0, 0))
+ bui.getsound('error').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/qrcode.py b/src/assets/ba_data/python/bauiv1lib/qrcode.py
index 3d92eaf8..467bab7f 100644
--- a/src/assets/ba_data/python/bauiv1lib/qrcode.py
+++ b/src/assets/ba_data/python/bauiv1lib/qrcode.py
@@ -3,9 +3,11 @@
"""Provides functionality for displaying QR codes."""
from __future__ import annotations
-from bauiv1lib.popup import PopupWindow
+from typing_extensions import override
import bauiv1 as bui
+from bauiv1lib.popup import PopupWindow
+
class QRCodeWindow(PopupWindow):
"""Popup window that shows a QR code."""
@@ -58,6 +60,7 @@ class QRCodeWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
index c40b708d..6c3fc587 100644
--- a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
+++ b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
@@ -4,6 +4,8 @@
from __future__ import annotations
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -53,6 +55,7 @@ class ResourceTypeInfoWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/teamnamescolors.py b/src/assets/ba_data/python/bauiv1lib/teamnamescolors.py
index 2334e09b..3fe95459 100644
--- a/src/assets/ba_data/python/bauiv1lib/teamnamescolors.py
+++ b/src/assets/ba_data/python/bauiv1lib/teamnamescolors.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING, cast
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
from bauiv1lib.colorpicker import ColorPicker
import bauiv1 as bui
@@ -217,6 +219,7 @@ class TeamNamesColorsWindow(PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition=transition)
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/tournamententry.py b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
index d00c37dd..90996bc0 100644
--- a/src/assets/ba_data/python/bauiv1lib/tournamententry.py
+++ b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import logging
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -749,6 +751,7 @@ class TournamentEntryWindow(PopupWindow):
if self._on_close_call is not None:
self._on_close_call()
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._on_cancel()
diff --git a/src/assets/ba_data/python/bauiv1lib/tournamentscores.py b/src/assets/ba_data/python/bauiv1lib/tournamentscores.py
index 9d756318..ebd44de7 100644
--- a/src/assets/ba_data/python/bauiv1lib/tournamentscores.py
+++ b/src/assets/ba_data/python/bauiv1lib/tournamentscores.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
@@ -244,6 +246,7 @@ class TournamentScoresWindow(PopupWindow):
if self._on_close_call is not None:
self._on_close_call()
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/trophies.py b/src/assets/ba_data/python/bauiv1lib/trophies.py
index b746d05c..e605ad7c 100644
--- a/src/assets/ba_data/python/bauiv1lib/trophies.py
+++ b/src/assets/ba_data/python/bauiv1lib/trophies.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
+from typing_extensions import override
+
from bauiv1lib import popup
import bauiv1 as bui
@@ -213,6 +215,7 @@ class TrophiesWindow(popup.PopupWindow):
self._transitioning_out = True
bui.containerwidget(edit=self.root_widget, transition='out_scale')
+ @override
def on_popup_cancel(self) -> None:
bui.getsound('swish').play()
self._transition_out()
diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc
index 4badfe1f..75f4b12f 100644
--- a/src/ballistica/shared/ballistica.cc
+++ b/src/ballistica/shared/ballistica.cc
@@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kEngineBuildNumber = 21757;
+const int kEngineBuildNumber = 21762;
const char* kEngineVersion = "1.7.33";
const int kEngineApiVersion = 8;
diff --git a/tests/test_efro/test_dataclassio.py b/tests/test_efro/test_dataclassio.py
index 28e53eb8..1c91d47d 100644
--- a/tests/test_efro/test_dataclassio.py
+++ b/tests/test_efro/test_dataclassio.py
@@ -10,6 +10,7 @@ import datetime
from dataclasses import field, dataclass
from typing import TYPE_CHECKING, Any, Sequence, Annotated
+from typing_extensions import override
import pytest
from efro.util import utc_now
@@ -855,10 +856,12 @@ def test_extended_data() -> None:
class _TestClass2(IOExtendedData):
vals: tuple[int, int]
+ @override
@classmethod
def will_input(cls, data: dict) -> None:
data['vals'] = data['vals'][:2]
+ @override
def will_output(self) -> None:
self.vals = (0, 0)
diff --git a/tests/test_efro/test_message.py b/tests/test_efro/test_message.py
index f29c5fd8..7027dfe6 100644
--- a/tests/test_efro/test_message.py
+++ b/tests/test_efro/test_message.py
@@ -11,6 +11,7 @@ import asyncio
from typing import TYPE_CHECKING, overload, assert_type
from dataclasses import dataclass
+from typing_extensions import override
import pytest
from efro.error import CleanError, RemoteError, CommunicationError
from efro.dataclassio import ioprepped
@@ -39,6 +40,7 @@ class _TMsg1(Message):
ival: int
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [_TResp1]
@@ -51,6 +53,7 @@ class _TMsg2(Message):
sval: str
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [_TResp1, _TResp2]
diff --git a/tools/bacommon/build.py b/tools/bacommon/build.py
index 0ffcd3fb..7e26ed55 100644
--- a/tools/bacommon/build.py
+++ b/tools/bacommon/build.py
@@ -21,7 +21,7 @@ class BuildInfoSet:
@dataclass
class Entry:
- """Info about a particular build."""
+ """Info about a particular app build."""
filename: Annotated[str, IOAttrs('fname')]
size: Annotated[int, IOAttrs('size')]
diff --git a/tools/bacommon/cloud.py b/tools/bacommon/cloud.py
index df621b09..dd14bb5b 100644
--- a/tools/bacommon/cloud.py
+++ b/tools/bacommon/cloud.py
@@ -7,6 +7,7 @@ from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated
from enum import Enum
+from typing_extensions import override
from efro.message import Message, Response
from efro.dataclassio import ioprepped, IOAttrs
from bacommon.transfer import DirectoryManifest
@@ -21,6 +22,7 @@ if TYPE_CHECKING:
class LoginProxyRequestMessage(Message):
"""Request send to the cloud to ask for a login-proxy."""
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyRequestResponse]
@@ -49,6 +51,7 @@ class LoginProxyStateQueryMessage(Message):
proxyid: Annotated[str, IOAttrs('p')]
proxykey: Annotated[str, IOAttrs('k')]
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [LoginProxyStateQueryResponse]
@@ -85,6 +88,7 @@ class LoginProxyCompleteMessage(Message):
class PingMessage(Message):
"""Standard ping."""
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [PingResponse]
@@ -103,6 +107,7 @@ class TestMessage(Message):
testfoo: Annotated[int, IOAttrs('f')]
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [TestResponse]
@@ -116,6 +121,28 @@ class TestResponse(Response):
testfoo: Annotated[int, IOAttrs('f')]
+@ioprepped
+@dataclass
+class PromoCodeMessage(Message):
+ """User is entering a promo code"""
+
+ code: Annotated[str, IOAttrs('c')]
+
+ @override
+ @classmethod
+ def get_response_types(cls) -> list[type[Response] | None]:
+ return [PromoCodeResponse]
+
+
+@ioprepped
+@dataclass
+class PromoCodeResponse(Response):
+ """Applied that promo code for ya, boss."""
+
+ valid: Annotated[bool, IOAttrs('v')]
+ message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
+
+
@ioprepped
@dataclass
class WorkspaceFetchState:
@@ -136,6 +163,7 @@ class WorkspaceFetchMessage(Message):
workspaceid: Annotated[str, IOAttrs('w')]
state: Annotated[WorkspaceFetchState, IOAttrs('s')]
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [WorkspaceFetchResponse]
@@ -162,6 +190,7 @@ class WorkspaceFetchResponse(Response):
class MerchAvailabilityMessage(Message):
"""Can we show merch link?"""
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [MerchAvailabilityResponse]
@@ -187,6 +216,7 @@ class SignInMessage(Message):
description: Annotated[str, IOAttrs('d', soft_default='-')]
apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)]
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [SignInResponse]
@@ -205,6 +235,7 @@ class SignInResponse(Response):
class ManageAccountMessage(Message):
"""Message asking for a manage-account url."""
+ @override
@classmethod
def get_response_types(cls) -> list[type[Response] | None]:
return [ManageAccountResponse]
diff --git a/tools/bacommon/transfer.py b/tools/bacommon/transfer.py
index f6704b26..a53c6153 100644
--- a/tools/bacommon/transfer.py
+++ b/tools/bacommon/transfer.py
@@ -31,7 +31,7 @@ class DirectoryManifest:
files: Annotated[dict[str, DirectoryManifestFile], IOAttrs('f')]
- _empty_hash: str | None = None
+ # _empty_hash: str | None = None
@classmethod
def create_from_disk(cls, path: Path) -> DirectoryManifest:
@@ -92,12 +92,12 @@ class DirectoryManifest:
)
break # 1 error is enough for now.
- @classmethod
- def get_empty_hash(cls) -> str:
- """Return the hash for an empty file."""
- if cls._empty_hash is None:
- import hashlib
+ # @classmethod
+ # def get_empty_hash(cls) -> str:
+ # """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
+ # sha = hashlib.sha256()
+ # cls._empty_hash = sha.hexdigest()
+ # return cls._empty_hash
diff --git a/tools/batools/build.py b/tools/batools/build.py
index e9bd8b2d..1bc59689 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -43,25 +43,25 @@ class PyRequirement:
# remove our custom module based stuff soon if nobody complains, which
# would free us to theoretically move to a requirements.txt based setup.
PY_REQUIREMENTS = [
- PyRequirement(pipname='mypy', minversion=[1, 7, 0]),
- PyRequirement(pipname='pylint', minversion=[3, 0, 2]),
+ PyRequirement(pipname='mypy', minversion=[1, 8, 0]),
+ PyRequirement(pipname='pylint', minversion=[3, 0, 3]),
PyRequirement(pipname='cpplint', minversion=[1, 6, 1]),
- PyRequirement(pipname='pytest', minversion=[7, 4, 2]),
+ PyRequirement(pipname='pytest', minversion=[7, 4, 4]),
PyRequirement(pipname='pytz', minversion=[2023, 3]),
PyRequirement(pipname='ansiwrap', minversion=[0, 8, 4]),
PyRequirement(pipname='requests', minversion=[2, 31, 0]),
- PyRequirement(pipname='pdoc', minversion=[14, 1, 0]),
+ PyRequirement(pipname='pdoc', minversion=[14, 4, 0]),
PyRequirement(pipname='PyYAML', minversion=[6, 0, 1]),
- PyRequirement(pipname='black', minversion=[23, 9, 1]),
- PyRequirement(pipname='typing_extensions', minversion=[4, 8, 0]),
+ PyRequirement(pipname='black', minversion=[23, 12, 1]),
+ PyRequirement(pipname='typing_extensions', minversion=[4, 9, 0]),
PyRequirement(pipname='types-filelock', minversion=[3, 2, 7]),
- PyRequirement(pipname='types-requests', minversion=[2, 31, 0, 6]),
+ PyRequirement(pipname='types-requests', minversion=[2, 31, 0, 20240106]),
PyRequirement(pipname='types-pytz', minversion=[2023, 3, 1, 1]),
PyRequirement(pipname='types-PyYAML', minversion=[6, 0, 12, 12]),
- PyRequirement(pipname='certifi', minversion=[2023, 7, 22]),
+ PyRequirement(pipname='certifi', minversion=[2023, 11, 17]),
PyRequirement(pipname='types-certifi', minversion=[2021, 10, 8, 3]),
- PyRequirement(pipname='pbxproj', minversion=[3, 5, 0]),
- PyRequirement(pipname='filelock', minversion=[3, 12, 4]),
+ PyRequirement(pipname='pbxproj', minversion=[4, 0, 0]),
+ PyRequirement(pipname='filelock', minversion=[3, 13, 1]),
PyRequirement(pipname='python-daemon', minversion=[3, 0, 1]),
]
diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py
index 7fbf618e..5a29b870 100755
--- a/tools/batools/dummymodule.py
+++ b/tools/batools/dummymodule.py
@@ -408,13 +408,16 @@ def _special_class_cases(classname: str) -> str:
' return self\n'
'\n'
' # (for index access)\n'
+ ' @override\n'
' def __getitem__(self, typeargs: Any) -> Any:\n'
' return 0.0\n'
'\n'
+ ' @override\n'
' def __len__(self) -> int:\n'
' return 3\n'
'\n'
' # (for iterator access)\n'
+ ' @override\n'
' def __iter__(self) -> Any:\n'
' return self\n'
'\n'
@@ -886,6 +889,8 @@ class Generator:
'\n'
f'from typing import {typing_imports}\n'
'\n'
+ f'from typing_extensions import override\n'
+ '\n'
f'{enum_import_lines}'
'if TYPE_CHECKING:\n'
f' from typing import {typing_imports_tc}\n'
diff --git a/tools/efro/dataclassio/_base.py b/tools/efro/dataclassio/_base.py
index 6ef37105..afe19deb 100644
--- a/tools/efro/dataclassio/_base.py
+++ b/tools/efro/dataclassio/_base.py
@@ -70,6 +70,13 @@ class IOExtendedData:
Can be overridden to migrate old data formats to new, etc.
"""
+ def did_input(self) -> None:
+ """Called on a class instance after created from data.
+
+ Can be useful to correct values from the db, etc. in the
+ type-safe form.
+ """
+
def _is_valid_for_codec(obj: Any, codec: Codec) -> bool:
"""Return whether a value consists solely of json-supported types.
diff --git a/tools/efro/dataclassio/_inputter.py b/tools/efro/dataclassio/_inputter.py
index d6650a65..97075528 100644
--- a/tools/efro/dataclassio/_inputter.py
+++ b/tools/efro/dataclassio/_inputter.py
@@ -64,11 +64,23 @@ class _Inputter(Generic[T]):
# For special extended data types, call their 'will_output' callback.
tcls = self._cls
+
if issubclass(tcls, IOExtendedData):
+ is_ext = True
tcls.will_input(values)
+ else:
+ is_ext = False
out = self._dataclass_from_input(self._cls, '', values)
assert isinstance(out, self._cls)
+
+ if is_ext:
+ # mypy complains that we're no longer returning a T
+ # if we operate on out directly.
+ out2 = out
+ assert isinstance(out2, IOExtendedData)
+ out2.did_input()
+
return out
def _value_from_input(
diff --git a/tools/efro/dataclassio/extras.py b/tools/efro/dataclassio/extras.py
index 327f829e..c54b0c0d 100644
--- a/tools/efro/dataclassio/extras.py
+++ b/tools/efro/dataclassio/extras.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import dataclasses
from typing import TYPE_CHECKING
+from typing_extensions import override
+
if TYPE_CHECKING:
from typing import Any
@@ -32,6 +34,7 @@ class DataclassDiff:
self._obj1 = obj1
self._obj2 = obj2
+ @override
def __repr__(self) -> str:
return dataclass_diff(self._obj1, self._obj2)
diff --git a/tools/efro/error.py b/tools/efro/error.py
index 7f902162..f0e561b7 100644
--- a/tools/efro/error.py
+++ b/tools/efro/error.py
@@ -6,6 +6,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING
import errno
+from typing_extensions import override
+
if TYPE_CHECKING:
from typing import Any
@@ -82,6 +84,7 @@ class RemoteError(Exception):
super().__init__(msg)
self._peer_desc = peer_desc
+ @override
def __str__(self) -> str:
s = ''.join(str(arg) for arg in self.args)
# Indent so we can more easily tell what is the remote part when
diff --git a/tools/efro/log.py b/tools/efro/log.py
index 77b89996..680d94e8 100644
--- a/tools/efro/log.py
+++ b/tools/efro/log.py
@@ -15,6 +15,7 @@ from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated
from threading import Thread, current_thread, Lock
+from typing_extensions import override
from efro.util import utc_now
from efro.call import tpartial
from efro.terminal import Clr
@@ -306,6 +307,7 @@ class LogHandler(logging.Handler):
"""Submit a call to be run in the logging background thread."""
self._event_loop.call_soon_threadsafe(call)
+ @override
def emit(self, record: logging.LogRecord) -> None:
# pylint: disable=too-many-branches
if __debug__:
diff --git a/tools/efro/message/_protocol.py b/tools/efro/message/_protocol.py
index 04c1dea5..8daa8d88 100644
--- a/tools/efro/message/_protocol.py
+++ b/tools/efro/message/_protocol.py
@@ -386,6 +386,7 @@ class MessageProtocol:
f'\n'
f'from typing import TYPE_CHECKING{ovld}{ovld2}\n'
f'\n'
+ # f'from typing_extensions import override\n'
f'{import_lines}'
f'\n'
f'if TYPE_CHECKING:\n'
diff --git a/tools/efro/util.py b/tools/efro/util.py
index 8bc4542a..f5815429 100644
--- a/tools/efro/util.py
+++ b/tools/efro/util.py
@@ -174,17 +174,26 @@ def empty_weakref(objtype: type[T]) -> weakref.ref[T]:
# Just create an object and let it die. Is there a cleaner way to do this?
# return weakref.ref(_EmptyObj()) # type: ignore
+ # Sharing a single ones seems at least a bit better.
return _g_empty_weak_ref # type: ignore
-def data_size_str(bytecount: int) -> str:
+def data_size_str(bytecount: int, compact: bool = False) -> str:
"""Given a size in bytes, returns a short human readable string.
- This should be 6 or fewer chars for most all sane file sizes.
+ In compact mode this should be 6 or fewer chars for most all
+ sane file sizes.
"""
# pylint: disable=too-many-return-statements
+
+ # Special case: handle negatives.
+ if bytecount < 0:
+ val = data_size_str(-bytecount, compact=compact)
+ return f'-{val}'
+
if bytecount <= 999:
- return f'{bytecount} B'
+ suffix = 'B' if compact else 'bytes'
+ return f'{bytecount} {suffix}'
kbytecount = bytecount / 1024
if round(kbytecount, 1) < 10.0:
return f'{kbytecount:.1f} KB'
@@ -197,7 +206,7 @@ def data_size_str(bytecount: int) -> str:
return f'{mbytecount:.0f} MB'
gbytecount = bytecount / (1024 * 1024 * 1024)
if round(gbytecount, 1) < 10.0:
- return f'{mbytecount:.1f} GB'
+ return f'{gbytecount:.1f} GB'
return f'{gbytecount:.0f} GB'
@@ -623,7 +632,7 @@ def check_non_optional(obj: T | None) -> T:
Use assert_non_optional for a more efficient (but less safe) equivalent.
"""
if obj is None:
- raise TypeError('Got None value in check_non_optional.')
+ raise ValueError('Got None value in check_non_optional.')
return obj
diff --git a/tools/efrotools/jsontools.py b/tools/efrotools/jsontools.py
index 86cd1033..708bf382 100644
--- a/tools/efrotools/jsontools.py
+++ b/tools/efrotools/jsontools.py
@@ -7,6 +7,8 @@ from __future__ import annotations
import json
from typing import TYPE_CHECKING
+from typing_extensions import override
+
if TYPE_CHECKING:
from typing import Any
@@ -30,6 +32,7 @@ class NoIndentEncoder(json.JSONEncoder):
del self.kwargs['indent']
self._replacement_map: dict = {}
+ @override
def default(self, o: Any) -> Any:
import uuid
@@ -40,6 +43,7 @@ class NoIndentEncoder(json.JSONEncoder):
return '@@%s@@' % (key,)
return super().default(o)
+ @override
def encode(self, o: Any) -> Any:
result = super().encode(o)
for k, v in self._replacement_map.items():
diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py
index 50d5ac8c..1177ec79 100644
--- a/tools/efrotools/pybuild.py
+++ b/tools/efrotools/pybuild.py
@@ -46,7 +46,7 @@ XZ_VER_APPLE = '5.4.4'
# Android repo doesn't seem to be getting updated much so manually
# bumping various versions to keep things up to date.
-ZLIB_VER_ANDROID = '1.3'
+ZLIB_VER_ANDROID = '1.3.1'
XZ_VER_ANDROID = '5.4.5'
BZIP2_VER_ANDROID = '1.0.8'
GDBM_VER_ANDROID = '1.23'
diff --git a/tools/efrotools/toolconfig.py b/tools/efrotools/toolconfig.py
index deb43613..82690847 100644
--- a/tools/efrotools/toolconfig.py
+++ b/tools/efrotools/toolconfig.py
@@ -183,7 +183,7 @@ def _filter_tool_config(projroot: Path, cfg: str) -> str:
no_implicit_reexport = True
enable_error_code = redundant-expr, truthy-bool, \
-truthy-function, unused-awaitable
+truthy-function, unused-awaitable, explicit-override
"""
).strip()