diff --git a/.efrocachemap b/.efrocachemap index f4264322..ae1b6138 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,8 +421,8 @@ "build/assets/ba_data/audio/zoeOw.ogg": "b2d705c31c9dcc1efdc71394764c3beb", "build/assets/ba_data/audio/zoePickup01.ogg": "e9366dc2d2b8ab8b0c4e2c14c02d0789", "build/assets/ba_data/audio/zoeScream01.ogg": "903e0e45ee9b3373e9d9ce20c814374e", - "build/assets/ba_data/data/langdata.json": "7cdc9f897e458e98cd0131a13b87db24", - "build/assets/ba_data/data/languages/arabic.json": "8f89f09ad168c251765efebde4c9069c", + "build/assets/ba_data/data/langdata.json": "3775cd8b6f05c2205b7653302308acf9", + "build/assets/ba_data/data/languages/arabic.json": "3c22e7b6d7b09a812a2e28b35c9e9241", "build/assets/ba_data/data/languages/belarussian.json": "0b60a9d4496d1213c2d0b647d346ce30", "build/assets/ba_data/data/languages/chinese.json": "fc45d2838b834889c06920ae7c2102fa", "build/assets/ba_data/data/languages/chinesetraditional.json": "904b35b656c53f9830e406565edd5120", @@ -430,7 +430,7 @@ "build/assets/ba_data/data/languages/czech.json": "d18b7d1c6bf51fc81af4084ef0e69e3e", "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", "build/assets/ba_data/data/languages/dutch.json": "f4e1e8e9231cda9d1bcc7e87a7f8821e", - "build/assets/ba_data/data/languages/english.json": "b5917c3b975155e35fedb655dbd7568c", + "build/assets/ba_data/data/languages/english.json": "131508c56b563b9552bee5535f107b5a", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", "build/assets/ba_data/data/languages/filipino.json": "3d9269a90a2fee164d0a7513c4f130a3", "build/assets/ba_data/data/languages/french.json": "6d20655730b1017ef187fd828b91d43c", @@ -445,7 +445,7 @@ "build/assets/ba_data/data/languages/malay.json": "0212e18e54efa202c17505376e5b82fb", "build/assets/ba_data/data/languages/persian.json": "2584895475fe62b3fe49a5ea5e69b4b1", "build/assets/ba_data/data/languages/piratespeak.json": "7c7e3b72b87c1bcd5b04c9f64d912f0c", - "build/assets/ba_data/data/languages/polish.json": "d0822d5d3bdd72ddb04dc3c43a0b1395", + "build/assets/ba_data/data/languages/polish.json": "941eb816c7db9e04d6a3b8f28a64e2e8", "build/assets/ba_data/data/languages/portuguese.json": "b4463a05d65515f6812e1177c60ac666", "build/assets/ba_data/data/languages/romanian.json": "5ae206fe0b71c4015b02b86da8931c8f", "build/assets/ba_data/data/languages/russian.json": "fc64ed6b6356ea11385ee5c20748425a", @@ -4103,42 +4103,42 @@ "build/assets/windows/Win32/ucrtbased.dll": "bfd1180c269d3950b76f35a63655e9e1", "build/assets/windows/Win32/vc_redist.x86.exe": "15a5f1f876503885adbdf5b3989b3718", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "e22300fab655f96dae4021fa82cf5130", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "a585418907ebcd55788fc7522293055d", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "58e5c912b710cb61600f3ad82efd17e1", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "867fc8d0171facc2791323b4a2fc46fc", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "d2f9014578a6d5eca3c345b7c9470812", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "56d41454f04299cfa455aba882028cc1", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "21a9ef40c5cd663a29dc8c24c2ff0f95", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "29e757036b87bef6ce6d68203bda9efb", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "66dde4c6865cfc5b8de8369b92c4a103", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "55f347328de6f3e2c8f2dc9749b57436", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0d1dbc0d04f264ceddb3ad6803105bd9", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "12e891f298f604be7592b7c7ed5b8479", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "30cb8ee5ebd69d98a3bd625774d19bf0", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e788895a7c94af2cb7817c02e061c429", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "92a3afc186b9031c7ff91ce2a6800ce7", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "7e947dea1b10297b441ffc829943f437", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c8c1f174ed35992eb71bf1ff22787f4e", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "67e1e5286f3fe64c9935e0e22d3fe443", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c8c1f174ed35992eb71bf1ff22787f4e", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "67e1e5286f3fe64c9935e0e22d3fe443", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "c6f45e134e680f696df6d5f605fb8308", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "cfb87dbb40bfd0cdf4be9da5086265f8", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "c6f45e134e680f696df6d5f605fb8308", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "cfb87dbb40bfd0cdf4be9da5086265f8", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "b39713b9f3a9102fefff1f6d62a9fd82", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "1c2d42d75e8f975514b2e30f5602516d", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "b39713b9f3a9102fefff1f6d62a9fd82", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "1c2d42d75e8f975514b2e30f5602516d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "20ffab975711f6865ea4627a1e5ed377", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "06124d291e394169ff2a50bef10f832d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "8d9d7fe4ab396118ef3ef2721372c9cf", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "18890dfa2a926a787d46f00a974eae95", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "d597aae4c2924f031d5514386ec84a63", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "04ab6918264677f49f3add66cdc722e8", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "2a4f8529288c1e8e584f8196085b840b", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "02da45dd942ae7703304750f2f6c05c1", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "13233366609a24c56012f54ded7aefc6", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "9e3b238711c4c3589a307fdc4058ba7e", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "67c4c290e7b05f8b0bfb4f9be87dfa09", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "9fbba3a76f2fc50b9951e6f3f036a32b", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "c156541560f67ea45207d18561aa96a4", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "523946201fa68a8761abd8012888a1ee", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "1e164a6146131c57bdd80650c7cd01eb", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "547282ad972d2659c7adf3cdcc0fe38c", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f8f206e99e6515d857d03a6756432916", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "1948fefc1a65e0c09229229058d7a398", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "397cdcd79c10c35f13bda51cbf0431b8", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "c1a395b445754161e96040c0a3432471", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "1c4e960c1b621d4376c3ca0f8e876aad", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "60eea6534bc325bef60f2de3de497ebb", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "67e01108249a1ae8ec6ae106c684febf", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "2dbf2871814d9ae25d38ad0ed8c44dc5", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "33a0ae6f1ea5a0b0c60055ce01478488", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "aad882eaf2230b89973e2cf4f13c9759", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "33a0ae6f1ea5a0b0c60055ce01478488", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "aad882eaf2230b89973e2cf4f13c9759", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "c20929c73caa78445525c5788b6963e0", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "0f21a43d99552df99e0d21c646e6e698", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "c20929c73caa78445525c5788b6963e0", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "0f21a43d99552df99e0d21c646e6e698", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "01dab862a43d9e7c4ee4e49212442d42", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "ae4e3f563892f6b9311c4b7284f28c11", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "01dab862a43d9e7c4ee4e49212442d42", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "ae4e3f563892f6b9311c4b7284f28c11", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "d24d48e0b6d35d91350b707b74dd95f5", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "c946a1405b62469889c1d596b2021753", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "92431ded412435040f3c0be770753060", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "72e4f88ff70048a2a49c907680c34121", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0a68d461e96190865ce3457ec7c38e22", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "f8182ea0ad5537d4cf968a0b07594cff", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "996197f97e10bcea0bed1e88e8e75bdd", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "42461cf73d82008266892bf79f0634df", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "794d258d59fd17a61752843a9a0551ad", "src/ballistica/base/mgen/pyembed/binding_base.inc": "06042d31df0ff9af96b99477162e2a91", diff --git a/CHANGELOG.md b/CHANGELOG.md index 374111e6..30f5d87b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.37 (build 22143, api 9, 2024-12-22) +### 1.7.37 (build 22150, api 9, 2024-12-28) - Bumping api version to 9. As you'll see below, there's some UI changes that will require a bit of work for any UI mods to adapt to. If your mods don't touch UI stuff at all you can simply bump your api version and call it a day. diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index ba8a9e43..84ab582b 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -420,11 +420,13 @@ "ba_data/python/bauiv1lib/account/__pycache__/__init__.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/__pycache__/link.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/__pycache__/settings.cpython-312.opt-1.pyc", + "ba_data/python/bauiv1lib/account/__pycache__/signin.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/__pycache__/unlink.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/__pycache__/v2proxy.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/__pycache__/viewer.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/account/link.py", "ba_data/python/bauiv1lib/account/settings.py", + "ba_data/python/bauiv1lib/account/signin.py", "ba_data/python/bauiv1lib/account/unlink.py", "ba_data/python/bauiv1lib/account/v2proxy.py", "ba_data/python/bauiv1lib/account/viewer.py", @@ -476,9 +478,7 @@ "ba_data/python/bauiv1lib/kiosk.py", "ba_data/python/bauiv1lib/league/__init__.py", "ba_data/python/bauiv1lib/league/__pycache__/__init__.cpython-312.opt-1.pyc", - "ba_data/python/bauiv1lib/league/__pycache__/rankbutton.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/league/__pycache__/rankwindow.cpython-312.opt-1.pyc", - "ba_data/python/bauiv1lib/league/rankbutton.py", "ba_data/python/bauiv1lib/league/rankwindow.py", "ba_data/python/bauiv1lib/mainmenu.py", "ba_data/python/bauiv1lib/party.py", @@ -570,10 +570,8 @@ "ba_data/python/bauiv1lib/store/__init__.py", "ba_data/python/bauiv1lib/store/__pycache__/__init__.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/store/__pycache__/browser.cpython-312.opt-1.pyc", - "ba_data/python/bauiv1lib/store/__pycache__/button.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/store/__pycache__/item.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/store/browser.py", - "ba_data/python/bauiv1lib/store/button.py", "ba_data/python/bauiv1lib/store/item.py", "ba_data/python/bauiv1lib/tabs.py", "ba_data/python/bauiv1lib/teamnamescolors.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index a2fd0782..414ce46a 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -341,6 +341,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__init__.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/link.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/settings.py \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/account/signin.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/unlink.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/v2proxy.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/viewer.py \ @@ -378,7 +379,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/kiosk.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/league/__init__.py \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/league/rankbutton.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/league/rankwindow.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/mainmenu.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/party.py \ @@ -432,7 +432,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/__init__.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/browser.py \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/store/button.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/item.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/tabs.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/teamnamescolors.py \ @@ -622,6 +621,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/__init__.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/link.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/settings.cpython-312.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/signin.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/unlink.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/v2proxy.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/viewer.cpython-312.opt-1.pyc \ @@ -659,7 +659,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/__pycache__/englishkeyboard.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/kiosk.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/league/__pycache__/__init__.cpython-312.opt-1.pyc \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/league/__pycache__/rankbutton.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/league/__pycache__/rankwindow.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/mainmenu.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/party.cpython-312.opt-1.pyc \ @@ -713,7 +712,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/macmusicapp.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/__init__.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/browser.cpython-312.opt-1.pyc \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/button.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/store/__pycache__/item.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/tabs.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/teamnamescolors.cpython-312.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 9ab5baf5..5aed67c3 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -175,7 +175,7 @@ from babase._general import ( get_type_name, ) from babase._language import Lstr, LanguageSubsystem -from babase._logging import balog, lifecyclelog +from babase._logging import balog, applog, lifecyclelog from babase._login import LoginAdapter, LoginInfo from babase._mgen.enums import ( @@ -214,6 +214,7 @@ __all__ = [ 'AppIntentExec', 'AppMode', 'app_instance_uuid', + 'applog', 'appname', 'appnameupper', 'AppModeSelector', diff --git a/src/assets/ba_data/python/babase/_accountv2.py b/src/assets/ba_data/python/babase/_accountv2.py index 0d96a380..b04bec64 100644 --- a/src/assets/ba_data/python/babase/_accountv2.py +++ b/src/assets/ba_data/python/babase/_accountv2.py @@ -35,6 +35,16 @@ class AccountV2Subsystem: from babase._login import LoginAdapterGPGS, LoginAdapterGameCenter + # Register to be informed when connectivity changes. + plus = _babase.app.plus + self._connectivity_changed_cb = ( + None + if plus is None + else plus.cloud.on_connectivity_changed_callbacks.register( + self._on_cloud_connectivity_changed + ) + ) + # Whether or not everything related to an initial sign in (or # lack thereof) has completed. This includes things like # workspace syncing. Completion of this is what flips the app @@ -265,7 +275,7 @@ class AccountV2Subsystem: # We may want to auto-sign-in based on this new state. self._update_auto_sign_in() - def on_cloud_connectivity_changed(self, connected: bool) -> None: + def _on_cloud_connectivity_changed(self, connected: bool) -> None: """Should be called with cloud connectivity changes.""" del connected # Unused. assert _babase.in_logic_thread() diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 5fd5d048..cd5d5744 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -25,7 +25,7 @@ from babase._appintent import AppIntentDefault, AppIntentExec from babase._stringedit import StringEditSubsystem from babase._devconsole import DevConsoleSubsystem from babase._appconfig import AppConfig -from babase._logging import lifecyclelog +from babase._logging import lifecyclelog, applog if TYPE_CHECKING: import asyncio @@ -909,6 +909,7 @@ class App: # Entering shutdown state: if self.state is not self.State.SHUTTING_DOWN: self.state = self.State.SHUTTING_DOWN + applog.info('Shutting down...') lifecyclelog.info('app-state is now %s', self.state.name) self._on_shutting_down() diff --git a/src/assets/ba_data/python/babase/_appmode.py b/src/assets/ba_data/python/babase/_appmode.py index 17d4adca..6de1d2b0 100644 --- a/src/assets/ba_data/python/babase/_appmode.py +++ b/src/assets/ba_data/python/babase/_appmode.py @@ -27,20 +27,22 @@ class AppMode: """Return whether this mode can handle the provided intent. For this to return True, the AppMode must claim to support the - provided intent (via its _supports_intent() method) AND the + provided intent (via its _can_handle_intent() method) AND the AppExperience associated with the AppMode must be supported by the current app and runtime environment. """ - # TODO: check AppExperience. - return cls._supports_intent(intent) + # TODO: check AppExperience against current environment. + return cls._can_handle_intent(intent) @classmethod - def _supports_intent(cls, intent: AppIntent) -> bool: + def _can_handle_intent(cls, intent: AppIntent) -> bool: """Return whether our mode can handle the provided intent. - AppModes should override this to define what they can handle. - Note that AppExperience does not have to be considered here; that - is handled automatically by the can_handle_intent() call.""" + AppModes should override this to communicate what they can + handle. Note that AppExperience does not have to be considered + here; that is handled automatically by the can_handle_intent() + call. + """ raise NotImplementedError('AppMode subclasses must override this.') def handle_intent(self, intent: AppIntent) -> None: diff --git a/src/assets/ba_data/python/babase/_emptyappmode.py b/src/assets/ba_data/python/babase/_emptyappmode.py index 01372b54..f1a7b8f6 100644 --- a/src/assets/ba_data/python/babase/_emptyappmode.py +++ b/src/assets/ba_data/python/babase/_emptyappmode.py @@ -26,7 +26,7 @@ class EmptyAppMode(AppMode): @override @classmethod - def _supports_intent(cls, intent: AppIntent) -> bool: + def _can_handle_intent(cls, intent: AppIntent) -> bool: # We support default and exec intents currently. return isinstance(intent, AppIntentExec | AppIntentDefault) diff --git a/src/assets/ba_data/python/babase/_logging.py b/src/assets/ba_data/python/babase/_logging.py index 946b7cbf..42d063dc 100644 --- a/src/assets/ba_data/python/babase/_logging.py +++ b/src/assets/ba_data/python/babase/_logging.py @@ -8,4 +8,5 @@ import logging # Our standard set of loggers. balog = logging.getLogger('ba') +applog = logging.getLogger('ba.app') lifecyclelog = logging.getLogger('ba.lifecycle') diff --git a/src/assets/ba_data/python/baclassic/_accountv1.py b/src/assets/ba_data/python/baclassic/_accountv1.py index c8b40cbf..2ae2b02e 100644 --- a/src/assets/ba_data/python/baclassic/_accountv1.py +++ b/src/assets/ba_data/python/baclassic/_accountv1.py @@ -208,6 +208,7 @@ class AccountV1Subsystem: 'Player Profiles' not in config or '__account__' not in config['Player Profiles'] ): + print('CREATING INITIAL') # Create a spaz with a nice default purply color. plus.add_v1_account_transaction( { diff --git a/src/assets/ba_data/python/baclassic/_appmode.py b/src/assets/ba_data/python/baclassic/_appmode.py index 2e5ad16b..63fbe0d8 100644 --- a/src/assets/ba_data/python/baclassic/_appmode.py +++ b/src/assets/ba_data/python/baclassic/_appmode.py @@ -11,6 +11,8 @@ from typing import TYPE_CHECKING, override from bacommon.app import AppExperience import babase import bauiv1 +from bauiv1lib.connectivity import wait_for_connectivity +from bauiv1lib.account.signin import show_sign_in_prompt import _baclassic @@ -30,9 +32,15 @@ class ClassicAppMode(babase.AppMode): self._on_primary_account_changed_callback: ( CallbackRegistration | None ) = None + self._on_connectivity_changed_callback: CallbackRegistration | None = ( + None + ) self._test_sub: babase.CloudSubscription | None = None self._account_data_sub: babase.CloudSubscription | None = None + self._have_account_values = False + self._have_connectivity = False + @override @classmethod def get_app_experience(cls) -> AppExperience: @@ -40,7 +48,7 @@ class ClassicAppMode(babase.AppMode): @override @classmethod - def _supports_intent(cls, intent: babase.AppIntent) -> bool: + def _can_handle_intent(cls, intent: babase.AppIntent) -> bool: # We support default and exec intents currently. return isinstance( intent, babase.AppIntentExec | babase.AppIntentDefault @@ -118,14 +126,22 @@ class ClassicAppMode(babase.AppMode): self._root_ui_chest_slot_pressed, 3 ) + # We want to be informed when connectivity changes. + self._on_connectivity_changed_callback = ( + plus.cloud.on_connectivity_changed_callbacks.register( + self._update_for_connectivity_change + ) + ) # We want to be informed when primary account changes. self._on_primary_account_changed_callback = ( plus.accounts.on_primary_account_changed_callbacks.register( - self.update_for_primary_account + self._update_for_primary_account ) ) # Establish subscriptions/etc. for any current primary account. - self.update_for_primary_account(plus.accounts.primary) + self._update_for_primary_account(plus.accounts.primary) + self._have_connectivity = plus.cloud.is_connected() + self._update_for_connectivity_change(self._have_connectivity) @override def on_deactivate(self) -> None: @@ -136,7 +152,7 @@ class ClassicAppMode(babase.AppMode): self._on_primary_account_changed_callback = None # Remove anything following any current account. - self.update_for_primary_account(None) + self._update_for_primary_account(None) # Save where we were in the UI so we return there next time. if classic is not None: @@ -152,7 +168,7 @@ class ClassicAppMode(babase.AppMode): if not babase.app.active: babase.invoke_main_menu() - def update_for_primary_account( + def _update_for_primary_account( self, account: babase.AccountV2Handle | None ) -> None: """Update subscriptions/etc. for a new primary account state.""" @@ -180,14 +196,14 @@ class ClassicAppMode(babase.AppMode): if account is None: self._account_data_sub = None - _baclassic.set_root_ui_values( - tickets_text='-', - tokens_text='-', - league_rank_text='-', + _baclassic.set_root_ui_account_values( + tickets_text='', + tokens_text='', + league_rank_text='', league_type='', - achievements_percent_text='-', - level_text='-', - xp_text='-', + achievements_percent_text='', + level_text='', + xp_text='', inbox_count_text='', gold_pass=False, chest_0_appearance='', @@ -195,6 +211,8 @@ class ClassicAppMode(babase.AppMode): chest_2_appearance='', chest_3_appearance='', ) + self._have_account_values = False + self._update_ui_live_state() else: with account: @@ -204,6 +222,22 @@ class ClassicAppMode(babase.AppMode): ) ) + def _update_for_connectivity_change(self, connected: bool) -> None: + """Update when the app's connectivity state changes.""" + self._have_connectivity = connected + self._update_ui_live_state() + + def _update_ui_live_state(self) -> None: + # We want to show ui elements faded if we don't have a live + # connection to the master-server OR if we haven't received a + # set of account values from them yet. If we just plug in raw + # connectivity state here we get UI stuff un-fading a moment or + # two before values appear (since the subscriptions have not + # sent us any values yet) which looks odd. + _baclassic.set_root_ui_have_live_values( + self._have_connectivity and self._have_account_values + ) + def _on_sub_test_update(self, val: int | None) -> None: print(f'GOT SUB TEST UPDATE: {val}') @@ -221,7 +255,7 @@ class ClassicAppMode(babase.AppMode): chest2 = val.chests.get('2') chest3 = val.chests.get('3') - _baclassic.set_root_ui_values( + _baclassic.set_root_ui_account_values( tickets_text=str(val.tickets), tokens_text=str(val.tokens), league_rank_text=( @@ -249,6 +283,10 @@ class ClassicAppMode(babase.AppMode): ), ) + # Note that we have values and updated faded state accordingly. + self._have_account_values = True + self._update_ui_live_state() + def _root_ui_menu_press(self) -> None: from babase import push_back_press @@ -265,6 +303,7 @@ class ClassicAppMode(babase.AppMode): ui.clear_main_window() return + # Otherwise push_back_press() def _root_ui_account_press(self) -> None: @@ -393,17 +432,26 @@ class ClassicAppMode(babase.AppMode): def _root_ui_achievements_press(self) -> None: from bauiv1lib.achievements import AchievementsWindow - self._auxiliary_window_nav( - win_type=AchievementsWindow, - win_create_call=lambda: AchievementsWindow( - origin_widget=bauiv1.get_special_widget('achievements_button') - ), + if not self._ensure_signed_in_v1(): + return + + wait_for_connectivity( + on_connected=lambda: self._auxiliary_window_nav( + win_type=AchievementsWindow, + win_create_call=lambda: AchievementsWindow( + origin_widget=bauiv1.get_special_widget( + 'achievements_button' + ) + ), + ) ) def _root_ui_inbox_press(self) -> None: - from bauiv1lib.connectivity import wait_for_connectivity from bauiv1lib.inbox import InboxWindow + if not self._ensure_signed_in(): + return + wait_for_connectivity( on_connected=lambda: self._auxiliary_window_nav( win_type=InboxWindow, @@ -416,11 +464,16 @@ class ClassicAppMode(babase.AppMode): def _root_ui_store_press(self) -> None: from bauiv1lib.store.browser import StoreBrowserWindow - self._auxiliary_window_nav( - win_type=StoreBrowserWindow, - win_create_call=lambda: StoreBrowserWindow( - origin_widget=bauiv1.get_special_widget('store_button') - ), + if not self._ensure_signed_in_v1(): + return + + wait_for_connectivity( + on_connected=lambda: self._auxiliary_window_nav( + win_type=StoreBrowserWindow, + win_create_call=lambda: StoreBrowserWindow( + origin_widget=bauiv1.get_special_widget('store_button') + ), + ) ) def _root_ui_tickets_meter_press(self) -> None: @@ -438,13 +491,9 @@ class ClassicAppMode(babase.AppMode): ) def _root_ui_trophy_meter_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.league.rankwindow import LeagueRankWindow - plus = bauiv1.app.plus - assert plus is not None - if plus.get_v1_account_state() != 'signed_in': - show_sign_in_prompt() + if not self._ensure_signed_in_v1(): return self._auxiliary_window_nav( @@ -464,6 +513,9 @@ class ClassicAppMode(babase.AppMode): def _root_ui_inventory_press(self) -> None: from bauiv1lib.inventory import InventoryWindow + if not self._ensure_signed_in_v1(): + return + self._auxiliary_window_nav( win_type=InventoryWindow, win_create_call=lambda: InventoryWindow( @@ -471,9 +523,36 @@ class ClassicAppMode(babase.AppMode): ), ) + def _ensure_signed_in(self) -> bool: + """Make sure we're signed in (requiring modern v2 accounts).""" + plus = bauiv1.app.plus + if plus is None: + bauiv1.screenmessage('This requires plus.', color=(1, 0, 0)) + bauiv1.getsound('error').play() + return False + if plus.accounts.primary is None: + show_sign_in_prompt() + return False + return True + + def _ensure_signed_in_v1(self) -> bool: + """Make sure we're signed in (allowing legacy v1-only accounts).""" + plus = bauiv1.app.plus + if plus is None: + bauiv1.screenmessage('This requires plus.', color=(1, 0, 0)) + bauiv1.getsound('error').play() + return False + if plus.get_v1_account_state() != 'signed_in': + show_sign_in_prompt() + return False + return True + def _root_ui_get_tokens_press(self) -> None: from bauiv1lib.gettokens import GetTokensWindow + if not self._ensure_signed_in(): + return + self._auxiliary_window_nav( win_type=GetTokensWindow, win_create_call=lambda: GetTokensWindow( @@ -488,7 +567,6 @@ class ClassicAppMode(babase.AppMode): ChestWindow2, ChestWindow3, ) - from bauiv1lib.connectivity import wait_for_connectivity widgetid: Literal[ 'chest_0_button', diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 34346718..1ed2142c 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -53,7 +53,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 22143 +TARGET_BALLISTICA_BUILD = 22150 TARGET_BALLISTICA_VERSION = '1.7.37' diff --git a/src/assets/ba_data/python/baplus/_cloud.py b/src/assets/ba_data/python/baplus/_cloud.py index d2ab5c20..fdfcdf56 100644 --- a/src/assets/ba_data/python/baplus/_cloud.py +++ b/src/assets/ba_data/python/baplus/_cloud.py @@ -7,6 +7,7 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, overload +from efro.call import CallbackSet import babase if TYPE_CHECKING: @@ -24,6 +25,12 @@ if TYPE_CHECKING: class CloudSubsystem(babase.AppSubsystem): """Manages communication with cloud components.""" + def __init__(self) -> None: + super().__init__() + self.on_connectivity_changed_callbacks: CallbackSet[ + Callable[[bool], None] + ] = CallbackSet() + @property def connected(self) -> bool: """Property equivalent of CloudSubsystem.is_connected().""" @@ -44,9 +51,12 @@ class CloudSubsystem(babase.AppSubsystem): plus = babase.app.plus assert plus is not None - # Inform things that use this. - # (TODO: should generalize this into some sort of registration system) - plus.accounts.on_cloud_connectivity_changed(connected) + # Fire any registered callbacks for this. + for call in self.on_connectivity_changed_callbacks.getcalls(): + try: + call(connected) + except Exception: + logging.exception('Error in connectivity-changed callback.') @overload def send_message_cb( diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py index 98110045..085e9111 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py @@ -19,9 +19,6 @@ from bascenev1lib.actor.zoomtext import ZoomText if TYPE_CHECKING: from typing import Any, Sequence - from bauiv1lib.store.button import StoreButton - from bauiv1lib.league.rankbutton import LeagueRankButton - class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): """Score screen showing the results of a cooperative game.""" @@ -105,10 +102,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): # Ui bits. self._corner_button_offs: tuple[float, float] | None = None - self._league_rank_button: LeagueRankButton | None = None - self._store_button_instance: StoreButton | None = None self._restart_button: bui.Widget | None = None - self._update_corner_button_positions_timer: bui.AppTimer | None = None self._next_level_error: bs.Actor | None = None # Score/gameplay bits. @@ -207,11 +201,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ) def _ui_menu(self) -> None: - # from bauiv1lib import specialoffer - - # if specialoffer.show_offer(): - # return - bui.containerwidget(edit=self._root_ui, transition='out_left') with self.context: bs.timer(0.1, bs.Call(bs.WeakCall(self.session.end))) @@ -219,11 +208,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): def _ui_restart(self) -> None: from bauiv1lib.tournamententry import TournamentEntryWindow - # from bauiv1lib import specialoffer - - # if specialoffer.show_offer(): - # return - # If we're in a tournament and it looks like there's no time left, # disallow. if self.session.tournament_id is not None: @@ -270,10 +254,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): self.end({'outcome': 'restart'}) def _ui_next(self) -> None: - # from bauiv1lib.specialoffer import show_offer - - # if show_offer(): - # return # If we didn't just complete this level but are choosing to play the # next one, set it as current (this won't happen otherwise). @@ -333,6 +313,12 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ) def _should_show_worlds_best_button(self) -> bool: + + # Old high score lists webpage for tourneys seems broken + # (looking at meteor shower at least). + if self.session.tournament_id is not None: + return False + # Link is too complicated to display with no browser. return bui.is_browser_likely_available() @@ -349,8 +335,8 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): def show_ui(self) -> None: """Show the UI for restarting, playing the next Level, etc.""" # pylint: disable=too-many-locals - from bauiv1lib.store.button import StoreButton - from bauiv1lib.league.rankbutton import LeagueRankButton + # pylint: disable=too-many-statements + # pylint: disable=too-many-branches assert bui.app.classic is not None @@ -364,7 +350,9 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): return rootc = self._root_ui = bui.containerwidget( - size=(0, 0), transition='in_right' + size=(0, 0), + transition='in_right', + toolbar_visibility='no_menu_minimal', ) h_offs = 7.0 @@ -420,38 +408,83 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): if not show_next_button: h_offs += 70 - menu_button = bui.buttonwidget( - parent=rootc, - autoselect=True, - position=(h_offs - 130 - 60, v_offs), - size=(110, 85), - label='', - on_activate_call=bui.WeakCall(self._ui_menu), - ) - bui.imagewidget( - parent=rootc, - draw_controller=menu_button, - position=(h_offs - 130 - 60 + 22, v_offs + 14), - size=(60, 60), - texture=self._menu_icon_texture, - opacity=0.8, - ) - self._restart_button = restart_button = bui.buttonwidget( - parent=rootc, - autoselect=True, - position=(h_offs - 60, v_offs), - size=(110, 85), - label='', - on_activate_call=bui.WeakCall(self._ui_restart), - ) - bui.imagewidget( - parent=rootc, - draw_controller=restart_button, - position=(h_offs - 60 + 19, v_offs + 7), - size=(70, 70), - texture=self._replay_icon_texture, - opacity=0.8, - ) + # Due to virtual-bounds changes, have to squish buttons a bit to + # avoid overlapping with tips at bottom. Could look nicer to + # rework things in the middle to get more space, but would + # rather not touch this old code more than necessary. + small_buttons = True + + if small_buttons: + menu_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs - 130 - 45, v_offs + 40), + size=(100, 50), + label='', + button_type='square', + on_activate_call=bui.WeakCall(self._ui_menu), + ) + bui.imagewidget( + parent=rootc, + draw_controller=menu_button, + position=(h_offs - 130 - 60 + 43, v_offs + 43), + size=(45, 45), + texture=self._menu_icon_texture, + opacity=0.8, + ) + else: + menu_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs - 130 - 60, v_offs), + size=(110, 85), + label='', + on_activate_call=bui.WeakCall(self._ui_menu), + ) + bui.imagewidget( + parent=rootc, + draw_controller=menu_button, + position=(h_offs - 130 - 60 + 22, v_offs + 14), + size=(60, 60), + texture=self._menu_icon_texture, + opacity=0.8, + ) + + if small_buttons: + self._restart_button = restart_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs - 60, v_offs + 40), + size=(100, 50), + label='', + button_type='square', + on_activate_call=bui.WeakCall(self._ui_restart), + ) + bui.imagewidget( + parent=rootc, + draw_controller=restart_button, + position=(h_offs - 60 + 25, v_offs + 42), + size=(47, 47), + texture=self._replay_icon_texture, + opacity=0.8, + ) + else: + self._restart_button = restart_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs - 60, v_offs), + size=(110, 85), + label='', + on_activate_call=bui.WeakCall(self._ui_restart), + ) + bui.imagewidget( + parent=rootc, + draw_controller=restart_button, + position=(h_offs - 60 + 19, v_offs + 7), + size=(70, 70), + texture=self._replay_icon_texture, + opacity=0.8, + ) next_button: bui.Widget | None = None @@ -468,24 +501,46 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): button_sound = False image_opacity = 0.2 color = (0.3, 0.3, 0.3) - next_button = bui.buttonwidget( - parent=rootc, - autoselect=True, - position=(h_offs + 130 - 60, v_offs), - size=(110, 85), - label='', - on_activate_call=call, - color=color, - enable_sound=button_sound, - ) - bui.imagewidget( - parent=rootc, - draw_controller=next_button, - position=(h_offs + 130 - 60 + 12, v_offs + 5), - size=(80, 80), - texture=self._next_level_icon_texture, - opacity=image_opacity, - ) + + if small_buttons: + next_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs + 130 - 75, v_offs + 40), + size=(100, 50), + label='', + button_type='square', + on_activate_call=call, + color=color, + enable_sound=button_sound, + ) + bui.imagewidget( + parent=rootc, + draw_controller=next_button, + position=(h_offs + 130 - 60 + 12, v_offs + 40), + size=(50, 50), + texture=self._next_level_icon_texture, + opacity=image_opacity, + ) + else: + next_button = bui.buttonwidget( + parent=rootc, + autoselect=True, + position=(h_offs + 130 - 60, v_offs), + size=(110, 85), + label='', + on_activate_call=call, + color=color, + enable_sound=button_sound, + ) + bui.imagewidget( + parent=rootc, + draw_controller=next_button, + position=(h_offs + 130 - 60 + 12, v_offs + 5), + size=(80, 80), + texture=self._next_level_icon_texture, + opacity=image_opacity, + ) x_offs_extra = 0 if show_next_button else -100 self._corner_button_offs = ( @@ -493,33 +548,6 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): v_offs + 519.0, ) - if env.demo or env.arcade: - self._league_rank_button = None - self._store_button_instance = None - else: - self._league_rank_button = LeagueRankButton( - parent=rootc, - position=(h_offs + 300 + x_offs_extra, v_offs + 519), - size=(100, 60), - scale=0.9, - color=(0.4, 0.4, 0.9), - textcolor=(0.9, 0.9, 2.0), - transition_delay=0.0, - smooth_update_delay=5.0, - ) - self._store_button_instance = StoreButton( - parent=rootc, - position=(h_offs + 400 + x_offs_extra, v_offs + 519), - show_tickets=True, - sale_scale=0.85, - size=(100, 60), - scale=0.9, - button_type='square', - color=(0.35, 0.25, 0.45), - textcolor=(0.9, 0.7, 1.0), - transition_delay=0.0, - ) - bui.containerwidget( edit=rootc, selected_child=( @@ -530,25 +558,12 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): on_cancel_call=menu_button.activate, ) - self._update_corner_button_positions() - self._update_corner_button_positions_timer = bui.AppTimer( - 1.0, bui.WeakCall(self._update_corner_button_positions), repeat=True - ) - - def _update_corner_button_positions(self) -> None: - assert self._corner_button_offs is not None - pos_x = self._corner_button_offs[0] - pos_y = self._corner_button_offs[1] - if self._league_rank_button is not None: - self._league_rank_button.set_position((pos_x, pos_y)) - if self._store_button_instance is not None: - self._store_button_instance.set_position((pos_x + 100, pos_y)) - def _player_press(self) -> None: # (Only for headless builds). - # If this activity is a good 'end point', ask server-mode just once if - # it wants to do anything special like switch sessions or kill the app. + # If this activity is a good 'end point', ask server-mode just + # once if it wants to do anything special like switch sessions + # or kill the app. if ( self._allow_server_transition and bs.app.classic is not None @@ -715,7 +730,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): color=(0.5, 1, 0.5, 1), h_align='center', scale=0.4, - position=(0, 255), + position=(0, 260), jitter=1.0, ).autoretain() Text( diff --git a/src/assets/ba_data/python/bauiv1/_appsubsystem.py b/src/assets/ba_data/python/bauiv1/_appsubsystem.py index 4ee01016..5e0aae08 100644 --- a/src/assets/ba_data/python/bauiv1/_appsubsystem.py +++ b/src/assets/ba_data/python/bauiv1/_appsubsystem.py @@ -162,40 +162,6 @@ class UIV1AppSubsystem(babase.AppSubsystem): # checks. self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True) - def auto_set_back_window(self, from_window: MainWindow) -> None: - """Sets the main menu window automatically from a parent WindowState.""" - - main_window = self._main_window() - - # This should never get called for top-level main-windows. - assert ( - main_window is None or main_window.main_window_is_top_level is False - ) - - back_state = ( - None if main_window is None else main_window.main_window_back_state - ) - if back_state is None: - raise RuntimeError( - f'Main window {main_window} provides no back-state;' - f' cannot use auto-back.' - ) - - # Valid states should have values here. - assert back_state.is_top_level is not None - assert back_state.is_auxiliary is not None - assert back_state.window_type is not None - - backwin = back_state.create_window(transition='in_left') - - self.set_main_window( - backwin, - from_window=from_window, - is_back=True, - back_state=back_state, - suppress_warning=True, - ) - def get_main_window(self) -> bauiv1.MainWindow | None: """Return main window, if any.""" return self._main_window() @@ -211,11 +177,14 @@ class UIV1AppSubsystem(babase.AppSubsystem): back_state: MainWindowState | None = None, suppress_warning: bool = False, ) -> None: - """Set the current 'main' window, replacing any existing. + """Set the current 'main' window. Generally this should not be called directly; The high level MainWindow methods main_window_replace() and main_window_back() - should be used when possible for navigation. + should be used whenever possible to implement navigation. + + The caller is responsible for cleaning up any previous main + window. """ # pylint: disable=too-many-locals # pylint: disable=too-many-branches diff --git a/src/assets/ba_data/python/bauiv1/_uitypes.py b/src/assets/ba_data/python/bauiv1/_uitypes.py index 8f974395..e8e8e6c0 100644 --- a/src/assets/ba_data/python/bauiv1/_uitypes.py +++ b/src/assets/ba_data/python/bauiv1/_uitypes.py @@ -46,7 +46,14 @@ class Window: class MainWindow(Window): - """A special window that can be used as a main window.""" + """A special type of window that can be set as 'main'. + + The UI system has at most one main window at any given time. + MainWindows support high level functionality such as saving and + restoring states, allowing them to be automatically recreated when + navigating back from other locations or when something like ui-scale + changes. + """ def __init__( self, @@ -146,11 +153,33 @@ class MainWindow(Window): if not self.main_window_has_control(): return + uiv1 = babase.app.ui_v1 + + # Get the 'back' window coming in. if not self.main_window_is_top_level: - # Get the 'back' window coming in. - babase.app.ui_v1.auto_set_back_window(self) + back_state = self.main_window_back_state + if back_state is None: + raise RuntimeError( + f'Main window {self} provides no back-state.' + ) + # Valid states should have values here. + assert back_state.is_top_level is not None + assert back_state.is_auxiliary is not None + assert back_state.window_type is not None + + backwin = back_state.create_window(transition='in_left') + + uiv1.set_main_window( + backwin, + from_window=self, + is_back=True, + back_state=back_state, + suppress_warning=True, + ) + + # Transition ourself out. self.main_window_close() def main_window_replace( @@ -203,7 +232,7 @@ class MainWindow(Window): class MainWindowState: - """Persistent state for a specific main-window and its ancestors. + """Persistent state for a specific MainWindow. This allows MainWindows to be automatically recreated for back-button purposes, when switching app-modes, etc. diff --git a/src/assets/ba_data/python/bauiv1lib/account/__init__.py b/src/assets/ba_data/python/bauiv1lib/account/__init__.py index d34e9ef5..1d25ed06 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/__init__.py +++ b/src/assets/ba_data/python/bauiv1lib/account/__init__.py @@ -1,21 +1,3 @@ # Released under the MIT License. See LICENSE for details. # """UI functionality related to accounts.""" - -from __future__ import annotations - -import bauiv1 as bui - - -def show_sign_in_prompt() -> None: - """Bring up a prompt telling the user they must sign in.""" - from bauiv1lib.confirm import ConfirmWindow - from bauiv1lib.account.settings import AccountSettingsWindow - - ConfirmWindow( - bui.Lstr(resource='notSignedInErrorText'), - lambda: AccountSettingsWindow(modal=True, close_once_signed_in=True), - ok_text=bui.Lstr(resource='accountSettingsWindow.signInText'), - width=460, - height=130, - ) diff --git a/src/assets/ba_data/python/bauiv1lib/account/link.py b/src/assets/ba_data/python/bauiv1lib/account/link.py index 58828ad8..d19ed064 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/link.py +++ b/src/assets/ba_data/python/bauiv1lib/account/link.py @@ -105,13 +105,13 @@ class AccountLinkWindow(bui.Window): ) def _generate_press(self) -> None: - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return bui.screenmessage( bui.Lstr(resource='gatherWindow.requestingAPromoCodeText'), diff --git a/src/assets/ba_data/python/bauiv1lib/account/settings.py b/src/assets/ba_data/python/bauiv1lib/account/settings.py index da582047..979780c7 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/settings.py +++ b/src/assets/ba_data/python/bauiv1lib/account/settings.py @@ -28,7 +28,6 @@ class AccountSettingsWindow(bui.MainWindow): def __init__( self, transition: str | None = 'in_right', - modal: bool = False, origin_widget: bui.Widget | None = None, close_once_signed_in: bool = False, ): @@ -49,7 +48,6 @@ class AccountSettingsWindow(bui.MainWindow): self._explicitly_signed_out_of_gpgs = False self._r = 'accountSettingsWindow' - self._modal = modal self._needs_refresh = False self._v1_signed_in = plus.get_v1_account_state() == 'signed_in' self._v1_account_state_num = plus.get_v1_account_state_num() @@ -129,22 +127,17 @@ class AccountSettingsWindow(bui.MainWindow): scale=0.8, text_scale=1.2, autoselect=True, - label=bui.Lstr( - resource='cancelText' if self._modal else 'backText' - ), - button_type='regular' if self._modal else 'back', - on_activate_call=( - self._modal_close if self._modal else self.main_window_back - ), + label=bui.Lstr(resource='backText'), + button_type='back', + on_activate_call=self.main_window_back, ) bui.containerwidget(edit=self._root_widget, cancel_button=btn) - if not self._modal: - bui.buttonwidget( - edit=btn, - button_type='backSmall', - size=(60, 56), - label=bui.charstr(bui.SpecialChar.BACK), - ) + bui.buttonwidget( + edit=btn, + button_type='backSmall', + size=(60, 56), + label=bui.charstr(bui.SpecialChar.BACK), + ) titleyoffs = -9 if uiscale is bui.UIScale.SMALL else 0 titlescale = 0.7 if uiscale is bui.UIScale.SMALL else 1.0 @@ -176,18 +169,6 @@ class AccountSettingsWindow(bui.MainWindow): self._refresh() self._restore_state() - def _modal_close(self) -> None: - assert self._modal - - # no-op if our underlying widget is dead or on its way out. - if not self._root_widget or self._root_widget.transitioning_out: - return - - bui.containerwidget( - edit=self._root_widget, - transition=('out_right'), - ) - @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. @@ -369,6 +350,9 @@ class AccountSettingsWindow(bui.MainWindow): show_manage_account_button = primary_v2_account is not None manage_account_button_space = 70.0 + show_create_account_button = show_v2_proxy_sign_in_button + create_account_button_space = 70.0 + # Apple asks us to make a delete-account button directly # available in the UI. Currently disabling this elsewhere # however as I feel that poking 'Manage Account' and scrolling @@ -445,6 +429,8 @@ class AccountSettingsWindow(bui.MainWindow): self._sub_height += sign_in_benefits_space if show_manage_account_button: self._sub_height += manage_account_button_space + if show_create_account_button: + self._sub_height += create_account_button_space if show_link_accounts_button: self._sub_height += link_accounts_button_space if show_v1_obsolete_note: @@ -856,6 +842,28 @@ class AccountSettingsWindow(bui.MainWindow): ) bui.widget(edit=btn, left_widget=bbtn) + if show_create_account_button: + button_width = 300 + v -= create_account_button_space + self._create_button = btn = bui.buttonwidget( + parent=self._subcontainer, + position=((self._sub_width - button_width) * 0.5, v - 30), + autoselect=True, + size=(button_width, 60), + # label=bui.Lstr(resource=f'{self._r}.createAccountText'), + label='Create an Account', + color=(0.55, 0.5, 0.6), + # icon=bui.gettexture('settingsIcon'), + textcolor=(0.75, 0.7, 0.8), + on_activate_call=bui.WeakCall(self._on_create_account_press), + ) + if first_selectable is None: + first_selectable = btn + bui.widget( + edit=btn, right_widget=bui.get_special_widget('squad_button') + ) + bui.widget(edit=btn, left_widget=bbtn) + # the button to go to OS-Specific leaderboards/high-score-lists/etc. if show_game_service_button: button_width = 300 @@ -1212,6 +1220,9 @@ class AccountSettingsWindow(bui.MainWindow): def _on_manage_account_press(self) -> None: self._do_manage_account_press(WebLocation.ACCOUNT_EDITOR) + def _on_create_account_press(self) -> None: + bui.open_url('https://ballistica.net/createaccount') + def _on_delete_account_press(self) -> None: self._do_manage_account_press(WebLocation.ACCOUNT_DELETE_SECTION) diff --git a/src/assets/ba_data/python/bauiv1lib/account/signin.py b/src/assets/ba_data/python/bauiv1lib/account/signin.py new file mode 100644 index 00000000..0b227232 --- /dev/null +++ b/src/assets/ba_data/python/bauiv1lib/account/signin.py @@ -0,0 +1,51 @@ +# Released under the MIT License. See LICENSE for details. +# +"""UI functionality related to accounts.""" + +from __future__ import annotations + +import bauiv1 as bui + + +def show_sign_in_prompt() -> None: + """Bring up a prompt telling the user they must sign in.""" + from bauiv1lib.confirm import ConfirmWindow + + ConfirmWindow( + bui.Lstr(resource='notSignedInErrorText'), + _show_account_settings, + ok_text=bui.Lstr(resource='accountSettingsWindow.signInText'), + width=460, + height=130, + ) + + +def _show_account_settings() -> None: + from bauiv1lib.account.settings import AccountSettingsWindow + + # NOTE TO USERS: The code below is not the proper way to do things; + # whenever possible one should use a MainWindow's + # main_window_replace() or main_window_back() methods. We just need + # to do things a bit more manually in this case. + + prev_main_window = bui.app.ui_v1.get_main_window() + + # Special-case: If it seems we're already in the account window, do + # nothing. + if isinstance(prev_main_window, AccountSettingsWindow): + return + + # Set our new main window. + bui.app.ui_v1.set_main_window( + AccountSettingsWindow( + close_once_signed_in=True, + origin_widget=bui.get_special_widget('account_button'), + ), + from_window=False, + is_auxiliary=True, + suppress_warning=True, + ) + + # Transition out any previous main window. + if prev_main_window is not None: + prev_main_window.main_window_close() diff --git a/src/assets/ba_data/python/bauiv1lib/account/v2proxy.py b/src/assets/ba_data/python/bauiv1lib/account/v2proxy.py index d91f1f04..f84b3b35 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/v2proxy.py +++ b/src/assets/ba_data/python/bauiv1lib/account/v2proxy.py @@ -34,9 +34,9 @@ class V2ProxySignInWindow(bui.Window): origin_widget.get_screen_space_center() ), scale=( - 1.25 + 1.16 if uiscale is bui.UIScale.SMALL - else 1.05 if uiscale is bui.UIScale.MEDIUM else 0.9 + else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.9 ), ) ) diff --git a/src/assets/ba_data/python/bauiv1lib/characterpicker.py b/src/assets/ba_data/python/bauiv1lib/characterpicker.py index cd8286b4..835f3f05 100644 --- a/src/assets/ba_data/python/bauiv1lib/characterpicker.py +++ b/src/assets/ba_data/python/bauiv1lib/characterpicker.py @@ -193,7 +193,7 @@ class CharacterPicker(PopupWindow): bui.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 77702a4c..0a50d1c0 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -1032,7 +1032,7 @@ class CoopBrowserWindow(bui.MainWindow): # pylint: disable=cyclic-import from bauiv1lib.confirm import ConfirmWindow from bauiv1lib.purchase import PurchaseWindow - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None @@ -1106,7 +1106,7 @@ class CoopBrowserWindow(bui.MainWindow): def run_tournament(self, tournament_button: TournamentButton) -> None: """Run the provided tournament game.""" - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.tournamententry import TournamentEntryWindow plus = bui.app.plus diff --git a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py index 7b1bdac6..b6776377 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py @@ -200,7 +200,7 @@ class AboutGatherTab(GatherTab): return scroll_widget def _invite_to_try_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.appinvite import handle_app_invites_press plus = bui.app.plus diff --git a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py index 5111405d..eb6804ca 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py @@ -1375,7 +1375,7 @@ class PublicGatherTab(GatherTab): ) def _on_start_advertizing_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None diff --git a/src/assets/ba_data/python/bauiv1lib/iconpicker.py b/src/assets/ba_data/python/bauiv1lib/iconpicker.py index 4f186b41..0a9bd4bc 100644 --- a/src/assets/ba_data/python/bauiv1lib/iconpicker.py +++ b/src/assets/ba_data/python/bauiv1lib/iconpicker.py @@ -171,7 +171,7 @@ class IconPicker(PopupWindow): bui.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None diff --git a/src/assets/ba_data/python/bauiv1lib/league/rankbutton.py b/src/assets/ba_data/python/bauiv1lib/league/rankbutton.py deleted file mode 100644 index da58d53f..00000000 --- a/src/assets/ba_data/python/bauiv1lib/league/rankbutton.py +++ /dev/null @@ -1,424 +0,0 @@ -# Released under the MIT License. See LICENSE for details. -# -"""Provides a button showing league rank.""" - -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -import bauiv1 as bui - -if TYPE_CHECKING: - from typing import Any, Callable - - -class LeagueRankButton: - """Button showing league rank.""" - - def __init__( - self, - parent: bui.Widget, - position: tuple[float, float], - size: tuple[float, float], - scale: float, - *, - on_activate_call: Callable[[], Any] | None = None, - transition_delay: float | None = None, - color: tuple[float, float, float] | None = None, - textcolor: tuple[float, float, float] | None = None, - smooth_update_delay: float | None = None, - ): - if on_activate_call is None: - on_activate_call = bui.WeakCall(self._default_on_activate_call) - self._on_activate_call = on_activate_call - if smooth_update_delay is None: - smooth_update_delay = 1.0 - self._smooth_update_delay = smooth_update_delay - self._size = size - self._scale = scale - if color is None: - color = (0.5, 0.6, 0.5) - if textcolor is None: - textcolor = (1, 1, 1) - self._color = color - self._textcolor = textcolor - self._header_color = (0.8, 0.8, 2.0) - self._parent = parent - self._position: tuple[float, float] = (0.0, 0.0) - - self._button = bui.buttonwidget( - parent=parent, - size=size, - label='', - button_type='square', - scale=scale, - autoselect=True, - on_activate_call=self._on_activate, - transition_delay=transition_delay, - color=color, - ) - - self._title_text = bui.textwidget( - parent=parent, - size=(0, 0), - draw_controller=self._button, - h_align='center', - v_align='center', - maxwidth=size[0] * scale * 0.85, - text=bui.Lstr( - resource='league.leagueRankText', - fallback_resource='coopSelectWindow.powerRankingText', - ), - color=self._header_color, - flatness=1.0, - shadow=1.0, - scale=scale * 0.5, - transition_delay=transition_delay, - ) - - self._value_text = bui.textwidget( - parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=size[0] * scale * 0.85, - text='-', - draw_controller=self._button, - big=True, - scale=scale, - transition_delay=transition_delay, - color=textcolor, - ) - - plus = bui.app.plus - assert plus is not None - - self._smooth_percent: float | None = None - self._percent: int | None = None - self._smooth_rank: float | None = None - self._rank: int | None = None - self._ticking_sound: bui.Sound | None = None - self._smooth_increase_speed = 1.0 - self._league: str | None = None - self._improvement_text: str | None = None - - self._smooth_update_timer: bui.AppTimer | None = None - - # Take note of our account state; we'll refresh later if this changes. - self._account_state_num = plus.get_v1_account_state_num() - self._last_power_ranking_query_time: float | None = None - self._doing_power_ranking_query = False - self.set_position(position) - self._bg_flash = False - self._update_timer = bui.AppTimer( - 1.0, bui.WeakCall(self._update), repeat=True - ) - self._update() - - # If we've got cached power-ranking data already, apply it. - assert bui.app.classic is not None - data = bui.app.classic.accounts.get_cached_league_rank_data() - if data is not None: - self._update_for_league_rank_data(data) - - def _on_activate(self) -> None: - bui.increment_analytics_count('League rank button press') - self._on_activate_call() - - def __del__(self) -> None: - if self._ticking_sound is not None: - self._ticking_sound.stop() - self._ticking_sound = None - - def _start_smooth_update(self) -> None: - self._smooth_update_timer = bui.AppTimer( - 0.05, bui.WeakCall(self._smooth_update), repeat=True - ) - - def _smooth_update(self) -> None: - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - try: - if not self._button: - return - if self._ticking_sound is None: - self._ticking_sound = bui.getsound('scoreIncrease') - self._ticking_sound.play() - self._bg_flash = not self._bg_flash - color_used = ( - (self._color[0] * 2, self._color[1] * 2, self._color[2] * 2) - if self._bg_flash - else self._color - ) - textcolor_used = (1, 1, 1) if self._bg_flash else self._textcolor - header_color_used = ( - (1, 1, 1) if self._bg_flash else self._header_color - ) - - if self._rank is not None: - assert self._smooth_rank is not None - self._smooth_rank -= 1.0 * self._smooth_increase_speed - finished = int(self._smooth_rank) <= self._rank - elif self._smooth_percent is not None: - self._smooth_percent += 1.0 * self._smooth_increase_speed - assert self._percent is not None - finished = int(self._smooth_percent) >= self._percent - else: - finished = True - if finished: - if self._rank is not None: - self._smooth_rank = float(self._rank) - elif self._percent is not None: - self._smooth_percent = float(self._percent) - color_used = self._color - textcolor_used = self._textcolor - self._smooth_update_timer = None - if self._ticking_sound is not None: - self._ticking_sound.stop() - self._ticking_sound = None - bui.getsound('cashRegister2').play() - assert self._improvement_text is not None - diff_text = bui.textwidget( - parent=self._parent, - size=(0, 0), - h_align='center', - v_align='center', - text='+' + self._improvement_text + '!', - position=( - self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * -0.2 * self._scale, - ), - color=(0, 1, 0), - flatness=1.0, - shadow=0.0, - scale=self._scale * 0.7, - ) - - def safe_delete(widget: bui.Widget) -> None: - if widget: - widget.delete() - - bui.apptimer(2.0, bui.Call(safe_delete, diff_text)) - status_text: str | bui.Lstr - if self._rank is not None: - assert self._smooth_rank is not None - status_text = bui.Lstr( - resource='numberText', - subs=[('${NUMBER}', str(int(self._smooth_rank)))], - ) - elif self._smooth_percent is not None: - status_text = str(int(self._smooth_percent)) + '%' - else: - status_text = '-' - bui.textwidget( - edit=self._value_text, text=status_text, color=textcolor_used - ) - bui.textwidget(edit=self._title_text, color=header_color_used) - bui.buttonwidget(edit=self._button, color=color_used) - - except Exception: - logging.exception('Error doing smooth update.') - self._smooth_update_timer = None - - def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: - # pylint: disable=too-many-branches - # pylint: disable=too-many-statements - - plus = bui.app.plus - assert plus is not None - - # If our button has died, ignore. - if not self._button: - return - - status_text: str | bui.Lstr - - in_top = data is not None and data['rank'] is not None - do_percent = False - if data is None or plus.get_v1_account_state() != 'signed_in': - self._percent = self._rank = None - status_text = '-' - elif in_top: - self._percent = None - self._rank = data['rank'] - prev_league = self._league - self._league = data['l'] - - # If this is the first set, league has changed, or rank has gotten - # worse, snap the smooth value immediately. - assert self._rank is not None - if ( - self._smooth_rank is None - or prev_league != self._league - or self._rank > int(self._smooth_rank) - ): - self._smooth_rank = float(self._rank) - status_text = bui.Lstr( - resource='numberText', - subs=[('${NUMBER}', str(int(self._smooth_rank)))], - ) - else: - try: - if not data['scores'] or data['scores'][-1][1] <= 0: - self._percent = self._rank = None - status_text = '-' - else: - assert bui.app.classic is not None - our_points = ( - bui.app.classic.accounts.get_league_rank_points(data) - ) - progress = float(our_points) / data['scores'][-1][1] - self._percent = int(progress * 100.0) - self._rank = None - do_percent = True - prev_league = self._league - self._league = data['l'] - - # If this is the first set, league has changed, or percent - # has decreased, snap the smooth value immediately. - if ( - self._smooth_percent is None - or prev_league != self._league - or self._percent < int(self._smooth_percent) - ): - self._smooth_percent = float(self._percent) - status_text = str(int(self._smooth_percent)) + '%' - - except Exception: - logging.exception('Error updating power ranking.') - self._percent = self._rank = None - status_text = '-' - - # If we're doing a smooth update, set a timer. - if ( - self._rank is not None - and self._smooth_rank is not None - and int(self._smooth_rank) != self._rank - ): - self._improvement_text = str( - -(int(self._rank) - int(self._smooth_rank)) - ) - diff = abs(self._rank - self._smooth_rank) - if diff > 100: - self._smooth_increase_speed = diff / 80.0 - elif diff > 50: - self._smooth_increase_speed = diff / 70.0 - elif diff > 25: - self._smooth_increase_speed = diff / 55.0 - else: - self._smooth_increase_speed = diff / 40.0 - self._smooth_increase_speed = max(0.4, self._smooth_increase_speed) - bui.apptimer( - self._smooth_update_delay, - bui.WeakCall(self._start_smooth_update), - ) - - if ( - self._percent is not None - and self._smooth_percent is not None - and int(self._smooth_percent) != self._percent - ): - self._improvement_text = str( - (int(self._percent) - int(self._smooth_percent)) - ) - self._smooth_increase_speed = 0.3 - bui.apptimer( - self._smooth_update_delay, - bui.WeakCall(self._start_smooth_update), - ) - - if do_percent: - bui.textwidget( - edit=self._title_text, - text=bui.Lstr(resource='coopSelectWindow.toRankedText'), - ) - else: - try: - assert data is not None - txt = bui.Lstr( - resource='league.leagueFullText', - subs=[ - ( - '${NAME}', - bui.Lstr(translate=('leagueNames', data['l']['n'])), - ), - ], - ) - t_color = data['l']['c'] - except Exception: - txt = bui.Lstr( - resource='league.leagueRankText', - fallback_resource='coopSelectWindow.powerRankingText', - ) - assert bui.app.classic is not None - t_color = bui.app.ui_v1.title_color - bui.textwidget(edit=self._title_text, text=txt, color=t_color) - bui.textwidget(edit=self._value_text, text=status_text) - - def _on_power_ranking_query_response( - self, data: dict[str, Any] | None - ) -> None: - self._doing_power_ranking_query = False - assert bui.app.classic is not None - bui.app.classic.accounts.cache_league_rank_data(data) - self._update_for_league_rank_data(data) - - def _update(self) -> None: - cur_time = bui.apptime() - - plus = bui.app.plus - assert plus is not None - - # If our account state has changed, refresh our UI. - account_state_num = plus.get_v1_account_state_num() - if account_state_num != self._account_state_num: - self._account_state_num = account_state_num - - # And power ranking too... - if not self._doing_power_ranking_query: - self._last_power_ranking_query_time = None - - # Send off a new power-ranking query if its been - # long enough or whatnot. - if not self._doing_power_ranking_query and ( - self._last_power_ranking_query_time is None - or cur_time - self._last_power_ranking_query_time > 30.0 - ): - self._last_power_ranking_query_time = cur_time - self._doing_power_ranking_query = True - plus.power_ranking_query( - callback=bui.WeakCall(self._on_power_ranking_query_response) - ) - - def _default_on_activate_call(self) -> None: - # pylint: disable=cyclic-import - # from bauiv1lib.league.rankwindow import LeagueRankWindow - - raise RuntimeError() - # LeagueRankWindow(modal=True, origin_widget=self._button) - - def set_position(self, position: tuple[float, float]) -> None: - """Set the button's position.""" - self._position = position - if not self._button: - return - bui.buttonwidget(edit=self._button, position=self._position) - bui.textwidget( - edit=self._title_text, - position=( - self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * 0.82 * self._scale, - ), - ) - bui.textwidget( - edit=self._value_text, - position=( - self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * 0.36 * self._scale, - ), - ) - - def get_button(self) -> bui.Widget: - """Return the underlying button bui.Widget>""" - return self._button diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index 3da6fc62..8495903a 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -83,7 +83,6 @@ class MainMenuWindow(bui.MainWindow): # pylint: disable=cyclic-import import bauiv1lib.getremote as _unused import bauiv1lib.confirm as _unused2 - import bauiv1lib.store.button as _unused3 import bauiv1lib.account.settings as _unused5 import bauiv1lib.store.browser as _unused6 import bauiv1lib.credits as _unused7 @@ -153,30 +152,31 @@ class MainMenuWindow(bui.MainWindow): uiscale = app.ui_v1.uiscale # Temp note about UI changes. - bui.textwidget( - parent=self._root_widget, - position=( - (-400, 400) - if uiscale is bui.UIScale.LARGE - else ( - (-270, 320) - if uiscale is bui.UIScale.MEDIUM - else (-280, 280) - ) - ), - size=(0, 0), - scale=0.4, - flatness=1.0, - text=( - 'WARNING: This build contains a revamped UI\n' - 'which is still a work-in-progress. A number\n' - 'of features are not currently functional or\n' - 'contain bugs. To go back to the stable legacy UI,\n' - 'grab version 1.7.36 from ballistica.net' - ), - h_align='left', - v_align='top', - ) + if bool(False): + bui.textwidget( + parent=self._root_widget, + position=( + (-400, 400) + if uiscale is bui.UIScale.LARGE + else ( + (-270, 320) + if uiscale is bui.UIScale.MEDIUM + else (-280, 280) + ) + ), + size=(0, 0), + scale=0.4, + flatness=1.0, + text=( + 'WARNING: This build contains a revamped UI\n' + 'which is still a work-in-progress. A number\n' + 'of features are not currently functional or\n' + 'contain bugs. To go back to the stable legacy UI,\n' + 'grab version 1.7.36 from ballistica.net' + ), + h_align='left', + v_align='top', + ) self._have_quit_button = app.classic.platform in ( 'windows', diff --git a/src/assets/ba_data/python/bauiv1lib/partyqueue.py b/src/assets/ba_data/python/bauiv1lib/partyqueue.py index 3b826331..3c4d10be 100644 --- a/src/assets/ba_data/python/bauiv1lib/partyqueue.py +++ b/src/assets/ba_data/python/bauiv1lib/partyqueue.py @@ -566,7 +566,7 @@ class PartyQueueWindow(bui.Window): def on_boost_press(self) -> None: """Boost was pressed.""" - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt # from bauiv1lib import gettickets @@ -574,7 +574,7 @@ class PartyQueueWindow(bui.Window): assert plus is not None if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return if plus.get_v1_account_ticket_count() < self._boost_tickets: diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py index b94e7840..27b05a40 100644 --- a/src/assets/ba_data/python/bauiv1lib/play.py +++ b/src/assets/ba_data/python/bauiv1lib/play.py @@ -572,7 +572,7 @@ class PlayWindow(bui.MainWindow): def _coop(self) -> None: # pylint: disable=cyclic-import - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.coop.browser import CoopBrowserWindow # no-op if we're not currently in control. diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py index 7e064d1f..bcffd6e3 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py @@ -255,7 +255,7 @@ class PlaylistAddGameWindow(bui.MainWindow): ) def _on_get_more_games_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.store.browser import StoreBrowserWindow # No-op if we're not in control. @@ -271,17 +271,12 @@ class PlaylistAddGameWindow(bui.MainWindow): self.main_window_replace( StoreBrowserWindow( - # modal=True, show_tab=StoreBrowserWindow.TabID.MINIGAMES, - # on_close_call=self._on_store_close, origin_widget=self._get_more_games_button, minimal_toolbars=True, ) ) - # def _on_store_close(self) -> None: - # self._refresh(select_get_more_games_button=True) - def _add(self) -> None: bui.lock_all_input() # Make sure no more commands happen. bui.apptimer(0.1, bui.unlock_all_input) diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py index 603cd97a..ca8cdc9e 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py @@ -283,7 +283,7 @@ class PlaylistMapSelectWindow(bui.MainWindow): ) def _on_store_press(self) -> None: - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.store.browser import StoreBrowserWindow # No-op if we're not in control. @@ -294,7 +294,7 @@ class PlaylistMapSelectWindow(bui.MainWindow): assert plus is not None if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return self._selected_get_more_maps = True diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py index 2d206135..fc23723e 100644 --- a/src/assets/ba_data/python/bauiv1lib/playoptions.py +++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py @@ -432,7 +432,7 @@ class PlayOptionsWindow(PopupWindow): self._update() def _custom_colors_names_press(self) -> None: - from bauiv1lib.account import show_sign_in_prompt + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.teamnamescolors import TeamNamesColorsWindow from bauiv1lib.purchase import PurchaseWindow diff --git a/src/assets/ba_data/python/bauiv1lib/profile/edit.py b/src/assets/ba_data/python/bauiv1lib/profile/edit.py index 447bbb72..27af5033 100644 --- a/src/assets/ba_data/python/bauiv1lib/profile/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/profile/edit.py @@ -550,7 +550,7 @@ class EditProfileWindow( def upgrade_profile(self) -> None: """Attempt to upgrade the profile to global.""" - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.profile import upgrade as pupgrade new_name = self.getname().strip() @@ -566,7 +566,7 @@ class EditProfileWindow( assert plus is not None if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return pupgrade.ProfileUpgradeWindow(self) diff --git a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py index 9ca597c1..c9d4efdf 100644 --- a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py +++ b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py @@ -53,24 +53,52 @@ class ResourceTypeInfoWindow(PopupWindow): iconscale=1.2, ) + yoffs = self._height - 150 + if resource_type == 'tickets': - rdesc = 'Will describe tickets.' + rdesc = ( + 'Use tickets to unlock characters, maps,\n' + 'minigames, and more in the store.\n' + '\n' + 'Earn tickets by completing achievements or\n' + 'by opening chests won in the game.' + ) + texname = 'tickets' elif resource_type == 'tokens': - rdesc = 'Will describe tokens.' + rdesc = ( + 'Tokens can be used to speed up chest unlocks\n' + 'and skip other waits.\n' + '\n' + 'You can buy packs of tokens or buy a Gold Pass\n' + 'to get infinite tokens forever.\n' + ) + texname = 'coin' elif resource_type == 'trophies': - rdesc = 'Will show trophies & league rankings.' + rdesc = 'TODO: Will show trophies & league rankings.' + texname = 'crossOut' elif resource_type == 'xp': - rdesc = 'Will describe xp/levels.' + rdesc = 'TODO: Will describe xp/levels.' + texname = 'crossOut' else: assert_never(resource_type) + imgsize = 100.0 + bui.imagewidget( + parent=self.root_widget, + position=(self._width * 0.5 - imgsize * 0.5, yoffs + 5.0), + size=(imgsize, imgsize), + texture=bui.gettexture(texname), + ) + bui.textwidget( parent=self.root_widget, h_align='center', - v_align='center', + v_align='top', size=(0, 0), - position=(self._width * 0.5, self._height * 0.5), - text=(f'UNDER CONSTRUCTION.\n({rdesc})'), + maxwidth=self._width * 0.8, + position=(self._width * 0.5, yoffs - 5.0), + text=rdesc, + scale=0.8, ) def _on_cancel_press(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py index 343d4a8d..096545cd 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py @@ -787,13 +787,13 @@ class AdvancedSettingsWindow(bui.MainWindow): def _on_friend_promo_code_press(self) -> None: from bauiv1lib import appinvite - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return appinvite.handle_app_invites_press() diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py index 27a2f3d7..09426875 100644 --- a/src/assets/ba_data/python/bauiv1lib/store/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py @@ -281,12 +281,12 @@ class StoreBrowserWindow(bui.MainWindow): self._restore_state() def _restore_purchases(self) -> None: - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt plus = bui.app.plus assert plus is not None if plus.accounts.primary is None: - account.show_sign_in_prompt() + show_sign_in_prompt() else: plus.restore_purchases() @@ -490,7 +490,7 @@ class StoreBrowserWindow(bui.MainWindow): def buy(self, item: str) -> None: """Attempt to purchase the provided item.""" - from bauiv1lib import account + from bauiv1lib.account.signin import show_sign_in_prompt from bauiv1lib.confirm import ConfirmWindow assert bui.app.classic is not None @@ -509,7 +509,7 @@ class StoreBrowserWindow(bui.MainWindow): bui.getsound('error').play() else: if plus.get_v1_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() else: self._last_buy_time = curtime diff --git a/src/assets/ba_data/python/bauiv1lib/store/button.py b/src/assets/ba_data/python/bauiv1lib/store/button.py deleted file mode 100644 index 7d061c90..00000000 --- a/src/assets/ba_data/python/bauiv1lib/store/button.py +++ /dev/null @@ -1,329 +0,0 @@ -# Released under the MIT License. See LICENSE for details. -# -"""UI functionality for a button leading to the store.""" -from __future__ import annotations - -import logging -from typing import TYPE_CHECKING - -from efro.util import utc_now - -import bauiv1 as bui - -if TYPE_CHECKING: - from typing import Any, Sequence, Callable - - -class StoreButton: - """A button leading to the store.""" - - def __init__( - self, - parent: bui.Widget, - position: Sequence[float], - size: Sequence[float], - scale: float, - *, - on_activate_call: Callable[[], Any] | None = None, - transition_delay: float | None = None, - color: Sequence[float] | None = None, - textcolor: Sequence[float] | None = None, - show_tickets: bool = False, - button_type: str | None = None, - sale_scale: float = 1.0, - ): - self._position = position - self._size = size - self._scale = scale - - if on_activate_call is None: - on_activate_call = bui.WeakCall(self._default_on_activate_call) - self._on_activate_call = on_activate_call - - self._button = bui.buttonwidget( - parent=parent, - size=size, - label='' if show_tickets else bui.Lstr(resource='storeText'), - scale=scale, - autoselect=True, - on_activate_call=self._on_activate, - transition_delay=transition_delay, - color=color, - button_type=button_type, - ) - - self._title_text: bui.Widget | None - self._ticket_text: bui.Widget | None - - if show_tickets: - self._title_text = bui.textwidget( - parent=parent, - position=( - position[0] + size[0] * 0.5 * scale, - position[1] + size[1] * 0.65 * scale, - ), - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=size[0] * scale * 0.65, - text=bui.Lstr(resource='storeText'), - draw_controller=self._button, - scale=scale, - transition_delay=transition_delay, - color=textcolor, - ) - self._ticket_text = bui.textwidget( - parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - maxwidth=size[0] * scale * 0.85, - text='', - color=(0.2, 1.0, 0.2), - flatness=1.0, - shadow=0.0, - scale=scale * 0.6, - transition_delay=transition_delay, - ) - else: - self._title_text = None - self._ticket_text = None - - self._circle_rad = 12 * scale - self._circle_center = (0.0, 0.0) - self._sale_circle_center = (0.0, 0.0) - - self._available_purchase_backing = bui.imagewidget( - parent=parent, - color=(1, 0, 0), - draw_controller=self._button, - size=(2.2 * self._circle_rad, 2.2 * self._circle_rad), - texture=bui.gettexture('circleShadow'), - transition_delay=transition_delay, - ) - self._available_purchase_text = bui.textwidget( - parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - text='', - draw_controller=self._button, - color=(1, 1, 1), - flatness=1.0, - shadow=1.0, - scale=0.6 * scale, - maxwidth=self._circle_rad * 1.4, - transition_delay=transition_delay, - ) - - self._sale_circle_rad = 18 * scale * sale_scale - self._sale_backing = bui.imagewidget( - parent=parent, - color=(0.5, 0, 1.0), - draw_controller=self._button, - size=(2 * self._sale_circle_rad, 2 * self._sale_circle_rad), - texture=bui.gettexture('circleZigZag'), - transition_delay=transition_delay, - ) - self._sale_title_text = bui.textwidget( - parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=self._button, - color=(0, 1, 0), - flatness=1.0, - shadow=0.0, - scale=0.5 * scale * sale_scale, - maxwidth=self._sale_circle_rad * 1.5, - transition_delay=transition_delay, - ) - self._sale_time_text = bui.textwidget( - parent=parent, - size=(0, 0), - h_align='center', - v_align='center', - draw_controller=self._button, - color=(0, 1, 0), - flatness=1.0, - shadow=0.0, - scale=0.4 * scale * sale_scale, - maxwidth=self._sale_circle_rad * 1.5, - transition_delay=transition_delay, - ) - - self.set_position(position) - self._update_timer = bui.AppTimer( - 1.0, bui.WeakCall(self._update), repeat=True - ) - self._update() - - def _on_activate(self) -> None: - bui.increment_analytics_count('Store button press') - self._on_activate_call() - - def set_position(self, position: Sequence[float]) -> None: - """Set the button position.""" - self._position = position - self._circle_center = ( - position[0] + 0.1 * self._size[0] * self._scale, - position[1] + self._size[1] * self._scale * 0.8, - ) - self._sale_circle_center = ( - position[0] + 0.07 * self._size[0] * self._scale, - position[1] + self._size[1] * self._scale * 0.8, - ) - - if not self._button: - return - bui.buttonwidget(edit=self._button, position=self._position) - if self._title_text is not None: - bui.textwidget( - edit=self._title_text, - position=( - self._position[0] + self._size[0] * 0.5 * self._scale, - self._position[1] + self._size[1] * 0.65 * self._scale, - ), - ) - if self._ticket_text is not None: - bui.textwidget( - edit=self._ticket_text, - position=( - position[0] + self._size[0] * 0.5 * self._scale, - position[1] + self._size[1] * 0.28 * self._scale, - ), - size=(0, 0), - ) - bui.imagewidget( - edit=self._available_purchase_backing, - position=( - self._circle_center[0] - self._circle_rad * 1.02, - self._circle_center[1] - self._circle_rad * 1.13, - ), - ) - bui.textwidget( - edit=self._available_purchase_text, position=self._circle_center - ) - - bui.imagewidget( - edit=self._sale_backing, - position=( - self._sale_circle_center[0] - self._sale_circle_rad, - self._sale_circle_center[1] - self._sale_circle_rad, - ), - ) - bui.textwidget( - edit=self._sale_title_text, - position=( - self._sale_circle_center[0], - self._sale_circle_center[1] + self._sale_circle_rad * 0.3, - ), - ) - bui.textwidget( - edit=self._sale_time_text, - position=( - self._sale_circle_center[0], - self._sale_circle_center[1] - self._sale_circle_rad * 0.3, - ), - ) - - def _default_on_activate_call(self) -> None: - # pylint: disable=cyclic-import - from bauiv1lib.account import show_sign_in_prompt - - # from bauiv1lib.store.browser import StoreBrowserWindow - - plus = bui.app.plus - assert plus is not None - if plus.get_v1_account_state() != 'signed_in': - show_sign_in_prompt() - return - - raise RuntimeError('no longer wired up') - - # StoreBrowserWindow(modal=True, origin_widget=self._button) - - def get_button(self) -> bui.Widget: - """Return the underlying button widget.""" - return self._button - - def _update(self) -> None: - # pylint: disable=too-many-branches - # pylint: disable=cyclic-import - from babase import SpecialChar - - plus = bui.app.plus - assert plus is not None - assert bui.app.classic is not None - store = bui.app.classic.store - - if not self._button: - return # Our instance may outlive our UI objects. - - if self._ticket_text is not None: - if plus.get_v1_account_state() == 'signed_in': - sval = bui.charstr(SpecialChar.TICKET) + str( - plus.get_v1_account_ticket_count() - ) - else: - sval = '-' - bui.textwidget(edit=self._ticket_text, text=sval) - available_purchases = store.get_available_purchase_count() - - # Old pro sale stuff.. - sale_time = store.get_available_sale_time('extras') - - # ..also look for new style sales. - if sale_time is None: - import datetime - - sales_raw = plus.get_v1_account_misc_read_val('sales', {}) - sale_times = [] - try: - # Look at the current set of sales; filter any with time - # remaining that we don't own. - for sale_item, sale_info in list(sales_raw.items()): - if not plus.get_v1_account_product_purchased(sale_item): - to_end = ( - datetime.datetime.fromtimestamp( - sale_info['e'], datetime.UTC - ) - - utc_now() - ).total_seconds() - if to_end > 0: - sale_times.append(to_end) - except Exception: - logging.exception('Error parsing sales.') - if sale_times: - sale_time = int(min(sale_times) * 1000) - - if sale_time is not None: - bui.textwidget( - edit=self._sale_title_text, - text=bui.Lstr(resource='store.saleText'), - ) - bui.textwidget( - edit=self._sale_time_text, - text=bui.timestring(sale_time / 1000.0, centi=False), - ) - bui.imagewidget(edit=self._sale_backing, opacity=1.0) - bui.imagewidget(edit=self._available_purchase_backing, opacity=1.0) - bui.textwidget(edit=self._available_purchase_text, text='') - bui.imagewidget(edit=self._available_purchase_backing, opacity=0.0) - else: - bui.imagewidget(edit=self._sale_backing, opacity=0.0) - bui.textwidget(edit=self._sale_time_text, text='') - bui.textwidget(edit=self._sale_title_text, text='') - if available_purchases > 0: - bui.textwidget( - edit=self._available_purchase_text, - text=str(available_purchases), - ) - bui.imagewidget( - edit=self._available_purchase_backing, opacity=1.0 - ) - else: - bui.textwidget(edit=self._available_purchase_text, text='') - bui.imagewidget( - edit=self._available_purchase_backing, opacity=0.0 - ) diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 34fff06f..99b62cf1 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -206,7 +206,7 @@ void BaseFeatureSet::StartApp() { called_start_app_ = true; assert(!app_started_); // Shouldn't be possible. - LogVersionInfo_(); + LogStartupMessage_(); g_core->Log(LogName::kBaLifecycle, LogLevel::kInfo, "start-app begin (main thread)"); @@ -420,7 +420,7 @@ void BaseFeatureSet::OnAppShutdownComplete() { } } -void BaseFeatureSet::LogVersionInfo_() { +void BaseFeatureSet::LogStartupMessage_() { char buffer[256]; if (g_buildconfig.headless_build()) { snprintf(buffer, sizeof(buffer), diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index 9ce9d9d5..8bc37bb7 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -828,7 +828,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent, private: BaseFeatureSet(); - void LogVersionInfo_(); + void LogStartupMessage_(); void PrintContextNonLogicThread_(); void PrintContextForCallableLabel_(const char* label); void PrintContextUnavailable_(); diff --git a/src/ballistica/classic/python/methods/python_methods_classic.cc b/src/ballistica/classic/python/methods/python_methods_classic.cc index 7d87408f..cfcf45fa 100644 --- a/src/ballistica/classic/python/methods/python_methods_classic.cc +++ b/src/ballistica/classic/python/methods/python_methods_classic.cc @@ -3,6 +3,7 @@ #include "ballistica/classic/python/methods/python_methods_classic.h" #include +#include #include #include @@ -289,10 +290,10 @@ static PyMethodDef PyClassicAppModeDeactivateDef = { "(internal)\n", }; -// -------------------------- set_root_ui_values ------------------------------- +// ---------------------- set_root_ui_account_values --------------------------- -static auto PySetRootUIValues(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { +static auto PySetRootUIAccountValues(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; const char* tickets_text; @@ -353,12 +354,12 @@ static auto PySetRootUIValues(PyObject* self, PyObject* args, PyObject* keywds) BA_PYTHON_CATCH; } -static PyMethodDef PySetRootUIValuesDef = { - "set_root_ui_values", // name - (PyCFunction)PySetRootUIValues, // method - METH_VARARGS | METH_KEYWORDS, // flags +static PyMethodDef PySetRootUIAccountValuesDef = { + "set_root_ui_account_values", // name + (PyCFunction)PySetRootUIAccountValues, // method + METH_VARARGS | METH_KEYWORDS, // flags - "set_root_ui_values(*,\n" + "set_root_ui_account_values(*,\n" " tickets_text: str,\n" " tokens_text: str,\n" " league_rank_text: str,\n" @@ -377,6 +378,38 @@ static PyMethodDef PySetRootUIValuesDef = { "(internal)", }; +// --------------------- set_root_ui_have_live_values -------------------------- + +static auto PySetRootUIHaveLiveValues(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + + int have_live_values{}; + + static const char* kwlist[] = {"have_live_values", nullptr}; + if (!PyArg_ParseTupleAndKeywords( + args, keywds, "p", const_cast(kwlist), &have_live_values)) { + return nullptr; + } + BA_PRECONDITION(g_base->InLogicThread()); + + auto* appmode = ClassicAppMode::GetActiveOrThrow(); + appmode->SetRootUIHaveLiveValues(have_live_values); + + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PySetRootUIHaveLiveValuesDef = { + "set_root_ui_have_live_values", // name + (PyCFunction)PySetRootUIHaveLiveValues, // method + METH_VARARGS | METH_KEYWORDS, // flags + + "set_root_ui_have_live_values(have_live_values: bool) -> None\n" + "\n" + "(internal)", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsClassic::GetMethods() -> std::vector { @@ -387,7 +420,8 @@ auto PythonMethodsClassic::GetMethods() -> std::vector { PyClassicAppModeHandleAppIntentDefaultDef, PyClassicAppModeActivateDef, PyClassicAppModeDeactivateDef, - PySetRootUIValuesDef, + PySetRootUIAccountValuesDef, + PySetRootUIHaveLiveValuesDef, }; } diff --git a/src/ballistica/classic/support/classic_app_mode.cc b/src/ballistica/classic/support/classic_app_mode.cc index 5d9be9d5..eb261a57 100644 --- a/src/ballistica/classic/support/classic_app_mode.cc +++ b/src/ballistica/classic/support/classic_app_mode.cc @@ -161,6 +161,7 @@ void ClassicAppMode::Reset_() { root_widget->SetChests( root_ui_chest_0_appearance_, root_ui_chest_1_appearance_, root_ui_chest_2_appearance_, root_ui_chest_3_appearance_); + root_widget->SetHaveLiveValues(root_ui_have_live_values_); } } @@ -1699,6 +1700,22 @@ void ClassicAppMode::SetRootUIGoldPass(bool enabled) { } } +void ClassicAppMode::SetRootUIHaveLiveValues(bool have_live_values) { + if (have_live_values == root_ui_have_live_values_) { + return; + } + + // Store the value. + root_ui_have_live_values_ = have_live_values; + + // Apply it to any existing UI. + if (uiv1_) { + if (auto* root_widget = uiv1_->root_widget()) { + root_widget->SetHaveLiveValues(root_ui_have_live_values_); + } + } +} + void ClassicAppMode::SetRootUIChests(const std::string& chest_0_appearance, const std::string& chest_1_appearance, const std::string& chest_2_appearance, diff --git a/src/ballistica/classic/support/classic_app_mode.h b/src/ballistica/classic/support/classic_app_mode.h index 805c5a23..edfaafe0 100644 --- a/src/ballistica/classic/support/classic_app_mode.h +++ b/src/ballistica/classic/support/classic_app_mode.h @@ -228,6 +228,7 @@ class ClassicAppMode : public base::AppMode { const std::string& chest_1_appearance, const std::string& chest_2_appearance, const std::string& chest_3_appearance); + void SetRootUIHaveLiveValues(bool val); private: ClassicAppMode(); @@ -271,6 +272,7 @@ class ClassicAppMode : public base::AppMode { bool kick_voting_enabled_{true}; bool replay_paused_{}; bool root_ui_gold_pass_{}; + bool root_ui_have_live_values_{}; ui_v1::UIV1FeatureSet* uiv1_{}; cJSON* game_roster_{}; diff --git a/src/ballistica/scene_v1/connection/connection_to_client.cc b/src/ballistica/scene_v1/connection/connection_to_client.cc index b4b05acf..64682c83 100644 --- a/src/ballistica/scene_v1/connection/connection_to_client.cc +++ b/src/ballistica/scene_v1/connection/connection_to_client.cc @@ -191,6 +191,17 @@ void ConnectionToClient::HandleGamePacket(const std::vector& data) { string_buffer[string_buffer.size() - 1] = 0; set_peer_spec(PlayerSpec(&(string_buffer[0]))); } + + // If they sent us a garbage player-spec, kick them right out. + if (!peer_spec().valid()) { + g_core->Log(LogName::kBaNetworking, LogLevel::kDebug, [] { + return std::string( + "Rejecting client for submitting invalid player-spec."); + }); + Error(""); + return; + } + // FIXME: We should maybe set some sort of 'pending' peer-spec // and fetch their actual info from the master-server. // (or at least make that an option for internet servers) @@ -198,6 +209,9 @@ void ConnectionToClient::HandleGamePacket(const std::vector& data) { // Compare this against our blocked specs.. if there's a match, reject // them. if (appmode->IsPlayerBanned(peer_spec())) { + g_core->Log(LogName::kBaNetworking, LogLevel::kDebug, [] { + return std::string("Rejecting join attempt by banned player."); + }); Error(""); return; } diff --git a/src/ballistica/scene_v1/support/player_spec.cc b/src/ballistica/scene_v1/support/player_spec.cc index 3f977b4b..b0e0ee64 100644 --- a/src/ballistica/scene_v1/support/player_spec.cc +++ b/src/ballistica/scene_v1/support/player_spec.cc @@ -18,32 +18,36 @@ PlayerSpec::PlayerSpec(const std::string& s) { cJSON* root_obj = cJSON_Parse(s.c_str()); bool success = false; if (root_obj) { - cJSON* name_obj = cJSON_GetObjectItem(root_obj, "n"); - cJSON* short_name_obj = cJSON_GetObjectItem(root_obj, "sn"); - cJSON* account_obj = cJSON_GetObjectItem(root_obj, "a"); - if (name_obj && short_name_obj && account_obj) { - name_ = Utils::GetValidUTF8(name_obj->valuestring, "psps"); - short_name_ = Utils::GetValidUTF8(short_name_obj->valuestring, "psps2"); + if (cJSON_IsObject(root_obj)) { + cJSON* name_obj = cJSON_GetObjectItem(root_obj, "n"); + cJSON* short_name_obj = cJSON_GetObjectItem(root_obj, "sn"); + cJSON* account_obj = cJSON_GetObjectItem(root_obj, "a"); + if (name_obj && short_name_obj && account_obj && cJSON_IsString(name_obj) + && cJSON_IsString(short_name_obj) && cJSON_IsString(account_obj)) { + name_ = Utils::GetValidUTF8(name_obj->valuestring, "psps"); + short_name_ = Utils::GetValidUTF8(short_name_obj->valuestring, "psps2"); - // Account type may technically be something we don't recognize, - // but that's ok.. it'll just be 'invalid' to us in that case - if (g_base->HaveClassic()) { - v1_account_type_ = g_base->classic()->GetV1AccountTypeFromString( - account_obj->valuestring); - // classic::V1Account::AccountTypeFromString(account_obj->valuestring); - } else { - v1_account_type_ = 0; // kInvalid. + // Account type may technically be something we don't recognize, + // but that's ok.. it'll just be 'invalid' to us in that case + if (g_base->HaveClassic()) { + v1_account_type_ = g_base->classic()->GetV1AccountTypeFromString( + account_obj->valuestring); + } else { + v1_account_type_ = 0; // kInvalid. + } + success = true; } - success = true; } cJSON_Delete(root_obj); } if (!success) { - g_core->Log(LogName::kBa, LogLevel::kError, + valid_ = false; + + // Only log this once in case it is used as an attack. + BA_LOG_ONCE(LogName::kBa, LogLevel::kError, "Error creating PlayerSpec from string: '" + s + "'"); name_ = ""; - short_name_ = ""; - // account_type_ = classic::V1AccountType::kInvalid; + short_name_ = ""; v1_account_type_ = 0; // kInvalid. } } @@ -54,7 +58,6 @@ auto PlayerSpec::GetDisplayString() const -> std::string { + name_; } return name_; - // return classic::V1Account::AccountTypeToIconString(account_type_) + name_; } auto PlayerSpec::GetShortName() const -> std::string { @@ -76,7 +79,6 @@ auto PlayerSpec::GetSpecString() const -> std::string { cJSON_AddStringToObject(root, "n", name_.c_str()); cJSON_AddStringToObject( root, "a", - // classic::V1Account::AccountTypeToString(account_type_).c_str() g_base->HaveClassic() ? g_base->classic()->V1AccountTypeToString(v1_account_type_).c_str() : ""); diff --git a/src/ballistica/scene_v1/support/player_spec.h b/src/ballistica/scene_v1/support/player_spec.h index 37d2b5e6..fe69f9ae 100644 --- a/src/ballistica/scene_v1/support/player_spec.h +++ b/src/ballistica/scene_v1/support/player_spec.h @@ -46,10 +46,13 @@ class PlayerSpec { /// party hosts, etc. static auto GetDummyPlayerSpec(const std::string& name) -> PlayerSpec; + auto valid() const { return valid_; } + private: std::string name_; std::string short_name_; int v1_account_type_{}; + bool valid_{true}; }; } // namespace ballistica::scene_v1 diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 005b1097..1a9ad290 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 = 22143; +const int kEngineBuildNumber = 22150; const char* kEngineVersion = "1.7.37"; const int kEngineApiVersion = 9; diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index a1767105..4ce9db70 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -1370,6 +1370,8 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds) val = Widget::ToolbarVisibility::kInherit; } else if (sval == "get_tokens") { val = Widget::ToolbarVisibility::kGetTokens; + } else if (sval == "no_menu_minimal") { + val = Widget::ToolbarVisibility::kNoMenuMinimal; } else { throw Exception("Invalid toolbar_visibility: '" + sval + "'.", PyExcType::kValue); @@ -1432,6 +1434,7 @@ static PyMethodDef PyContainerWidgetDef = { " 'menu_in_game',\n" " 'menu_tokens',\n" " 'get_tokens',\n" + " 'no_menu_minimal',\n" " 'inherit',\n" " ] | None = None,\n" " on_select_call: Callable[[], None] | None = None,\n" diff --git a/src/ballistica/ui_v1/widget/root_widget.cc b/src/ballistica/ui_v1/widget/root_widget.cc index 95e66c56..ff23221d 100644 --- a/src/ballistica/ui_v1/widget/root_widget.cc +++ b/src/ballistica/ui_v1/widget/root_widget.cc @@ -336,6 +336,13 @@ void RootWidget::AddMeter_(MeterType_ type, float h_align, float r, float g, switch (type) { case MeterType_::kTrophy: trophy_icon_ = img; + break; + case MeterType_::kTickets: + tickets_meter_icon_ = img; + break; + case MeterType_::kTokens: + tokens_meter_icon_ = img; + break; default: break; } @@ -541,14 +548,11 @@ void RootWidget::Setup() { AddMeter_(MeterType_::kLevel, 0.0f, 1.0f, 1.0f, 1.0f, false, ""); AddMeter_(MeterType_::kTrophy, 0.0f, 1.0f, 1.0f, 1.0f, false, ""); - // Menu button (only shows up when we're not in a menu). - // FIXME - this should never be visible on TV or VR UI modes { ButtonDef_ b; b.h_align = 1.0f; b.v_align = VAlign_::kTop; b.width = b.height = 65.0f; - // b.x = -36.0f; b.y = b.height * -0.48f; b.img = "menuButton"; b.call = UIV1Python::ObjID::kRootUIMenuButtonPressCall; @@ -592,7 +596,8 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot) | static_cast(Widget::ToolbarVisibility::kGetTokens) - | static_cast(Widget::ToolbarVisibility::kMenuTokens)); + | static_cast(Widget::ToolbarVisibility::kMenuTokens) + | static_cast(Widget::ToolbarVisibility::kNoMenuMinimal)); b.pre_buffer = 5.0f; b.enable_sound = false; squad_button_ = AddButton_(b); @@ -1448,6 +1453,64 @@ void RootWidget::SetXPText(const std::string& val) { xp_text_->widget->SetText(val); } +void RootWidget::SetHaveLiveValues(bool have_live_values) { + // auto cval{have_live_values ? 1.0f : 0.4f}; + auto oval{have_live_values ? 1.0f : 0.4f}; + auto oval2{have_live_values ? 1.0f : 0.4f}; + + assert(tickets_meter_text_); + assert(tickets_meter_icon_); + tickets_meter_text_->widget->set_color(1.0f, 1.0f, 1.0f, oval); + // tickets_meter_icon_->widget->set_color(cval, cval, cval); + tickets_meter_icon_->widget->set_opacity(oval2); + + assert(tokens_meter_text_); + assert(tokens_meter_icon_); + tokens_meter_text_->widget->set_color(1.0f, 1.0f, 1.0f, oval); + // tokens_meter_icon_->widget->set_color(cval, cval, cval); + tokens_meter_icon_->widget->set_opacity(oval2); + + assert(inbox_button_); + inbox_button_->widget->set_opacity(oval2); + + assert(achievements_button_); + achievements_button_->widget->set_opacity(oval2); + assert(achievement_percent_text_); + achievement_percent_text_->widget->set_color(1.0f, 1.0f, 1.0f, oval); + + assert(store_button_); + store_button_->widget->set_opacity(oval2); + + assert(inventory_button_); + inventory_button_->widget->set_opacity(oval2); + + assert(get_tokens_button_); + get_tokens_button_->widget->set_opacity(oval2); + + assert(league_rank_text_); + league_rank_text_->widget->set_color(1.0f, 1.0f, 1.0f, oval); + + assert(tickets_meter_button_); + tickets_meter_button_->widget->set_opacity(oval2); + + assert(tokens_meter_button_); + tokens_meter_button_->widget->set_opacity(oval2); + + assert(trophy_meter_button_); + trophy_meter_button_->widget->set_opacity(oval2); + + assert(trophy_icon_); + trophy_icon_->widget->set_opacity(oval2); + + for (auto* button : + {chest_0_button_, chest_1_button_, chest_2_button_, chest_3_button_}) { + assert(button); + button->widget->set_opacity(have_live_values ? 1.0f : 0.5f); + } + assert(chest_backing_); + chest_backing_->widget->set_opacity(have_live_values ? 1.0f : 0.5f); +} + void RootWidget::SetChests(const std::string& chest_0_appearance, const std::string& chest_1_appearance, const std::string& chest_2_appearance, @@ -1470,9 +1533,8 @@ void RootWidget::SetChests(const std::string& chest_0_appearance, assert(b); if (appearance == "") { b->widget->set_color(0.473f, 0.44f, 0.583f); - b->widget->set_opacity(have_chests ? 1.0f : 0.5f); b->width = b->height = 80.0f; - b->y = have_chests ? 44.0f : 0.0f; + b->y = have_chests ? 44.0f : -2.0f; { base::Assets::AssetListLock lock; b->widget->SetTexture( @@ -1481,7 +1543,6 @@ void RootWidget::SetChests(const std::string& chest_0_appearance, } else { have_chests = true; b->widget->set_color(1.0f, 1.0f, 1.0f); - b->widget->set_opacity(1.0f); b->width = b->height = 110.0f; b->y = 44.0f; { @@ -1491,8 +1552,7 @@ void RootWidget::SetChests(const std::string& chest_0_appearance, } } assert(chest_backing_); - chest_backing_->y = have_chests ? 41.0f : -10.0f; - chest_backing_->widget->set_opacity(have_chests ? 1.0f : 0.5f); + chest_backing_->y = have_chests ? 41.0f : -15.0f; child_widgets_dirty_ = true; } diff --git a/src/ballistica/ui_v1/widget/root_widget.h b/src/ballistica/ui_v1/widget/root_widget.h index ec3ad660..20e9dae6 100644 --- a/src/ballistica/ui_v1/widget/root_widget.h +++ b/src/ballistica/ui_v1/widget/root_widget.h @@ -51,6 +51,7 @@ class RootWidget : public ContainerWidget { const std::string& chest_1_appearance, const std::string& chest_2_appearance, const std::string& chest_3_appearance); + void SetHaveLiveValues(bool have_live_values); auto bottom_left_height() const { return bottom_left_height_; } @@ -102,6 +103,8 @@ class RootWidget : public ContainerWidget { Button_* chest_3_button_{}; Button_* chest_backing_{}; Image_* trophy_icon_{}; + Image_* tickets_meter_icon_{}; + Image_* tokens_meter_icon_{}; Image_* inbox_count_backing_{}; Text_* squad_size_text_{}; Text_* account_name_text_{}; diff --git a/src/ballistica/ui_v1/widget/widget.h b/src/ballistica/ui_v1/widget/widget.h index 9fb09354..a7f0ad0a 100644 --- a/src/ballistica/ui_v1/widget/widget.h +++ b/src/ballistica/ui_v1/widget/widget.h @@ -21,19 +21,20 @@ class Widget : public Object { /// the entire set of visibilities they apply to. enum class ToolbarVisibility : uint16_t { kInherit = 0, // For popups and whatnot - leave toolbar as-is. - kMenuMinimal = 1, // Squad and back buttons. - kMenuMinimalNoBack = 2, // Squad button only. - kMenuStore = 4, // Squad, level, and soft currency buttons. - kMenuStoreNoBack = 8, // Squad, level, and soft currency buttons. - kMenuReduced = 16, // Squad, account, inbox, settings, back. - kMenuReducedNoBack = 32, // Squad, account, inbox, settings. + kMenuMinimal = 1, // Menu, squad, back. + kMenuMinimalNoBack = 2, // Menu, squad. + kMenuStore = 4, // Menu, squad, level, and soft currency. + kMenuStoreNoBack = 8, // Menu, squad, level, and soft currency. + kMenuReduced = 16, // Menu, squad, account, inbox, settings, back. + kMenuReducedNoBack = 32, // Menu, squad, account, inbox, settings. kMenuFull = 64, // Everything. - kMenuFullNoBack = 128, // Everything. + kMenuFullNoBack = 128, // Everything minus back. kMenuFullRoot = 256, // Obsolete. kInGame = 512, // Menu, squad. kGetTokens = 1024, // Squad, tokens without plus. kMenuInGame = 2048, // Squad, settings. - kMenuTokens = 4096 // Squad, tokens. + kMenuTokens = 4096, // Squad, tokens. + kNoMenuMinimal = 8192, // Squad. }; Widget();