Merge branch 'efroemling:main' into main

This commit is contained in:
Loup 2024-12-29 19:02:39 +05:30 committed by GitHub
commit d2e5baea6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 728 additions and 1162 deletions

80
.efrocachemap generated
View File

@ -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",

View File

@ -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.

View File

@ -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",

View File

@ -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 \

View File

@ -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',

View File

@ -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()

View File

@ -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()

View File

@ -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:

View File

@ -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)

View File

@ -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')

View File

@ -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(
{

View File

@ -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',

View File

@ -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'

View File

@ -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(

View File

@ -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(

View File

@ -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

View File

@ -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.

View File

@ -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,
)

View File

@ -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'),

View File

@ -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)

View File

@ -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()

View File

@ -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
),
)
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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:

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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:

View File

@ -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()

View File

@ -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

View File

@ -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
)

View File

@ -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),

View File

@ -828,7 +828,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
private:
BaseFeatureSet();
void LogVersionInfo_();
void LogStartupMessage_();
void PrintContextNonLogicThread_();
void PrintContextForCallableLabel_(const char* label);
void PrintContextUnavailable_();

View File

@ -3,6 +3,7 @@
#include "ballistica/classic/python/methods/python_methods_classic.h"
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
@ -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<char**>(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<PyMethodDef> {
@ -387,7 +420,8 @@ auto PythonMethodsClassic::GetMethods() -> std::vector<PyMethodDef> {
PyClassicAppModeHandleAppIntentDefaultDef,
PyClassicAppModeActivateDef,
PyClassicAppModeDeactivateDef,
PySetRootUIValuesDef,
PySetRootUIAccountValuesDef,
PySetRootUIHaveLiveValuesDef,
};
}

View File

@ -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,

View File

@ -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_{};

View File

@ -191,6 +191,17 @@ void ConnectionToClient::HandleGamePacket(const std::vector<uint8_t>& 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<uint8_t>& 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;
}

View File

@ -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_ = "<error>";
short_name_ = "";
// account_type_ = classic::V1AccountType::kInvalid;
short_name_ = "<error>";
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()
: "");

View File

@ -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

View File

@ -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;

View File

@ -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"

View File

@ -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<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot)
| static_cast<uint32_t>(Widget::ToolbarVisibility::kGetTokens)
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuTokens));
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuTokens)
| static_cast<uint32_t>(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;
}

View File

@ -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_{};

View File

@ -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();