diff --git a/.efrocachemap b/.efrocachemap index 87cd4596..b5f38fa3 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4096,26 +4096,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "0a166c970637de5e318e4f07e8c0aeae", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "71fee3398234820d038235f02ea7ed6a", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "682d622e82fbdf7d41f170146ae8cf6f", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ed9a7e820ab3a59d6326b898973d5cff", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4efa411d0d43debb7c8db1db1d9a4658", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "8892f774f2d079604d77241adaf3e529", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "c479f3ab633f0b19f3e733943b3c1c3b", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2797e7b22388c68b2e41a1eb7c820a62", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f58b978d2978fdf6e2f7f0cc13fbb2c7", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "1ad711461c2a65ecb1fbab1aac2aacae", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ec90ed3ec92a6db3f8b9651f2464d8d4", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "12cc0539e86a408b01ac3b3a74cff98a", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1f59835f71e01827f056f503302c45c4", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c747103d9fe3206f94b54ed9ae5d4aa4", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "603f77208cb0df9161b84387631b8778", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "00a72972b6b0531b176406ad0e8d243f", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "af0063f60bb84a65ca71df83d00b9507", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "817b90e953f655e83cd8e69d1feb5526", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c132d48c3b560ac0dcf090a3bce8ba26", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b3c44cd6282d4100b730e997baa3f28e", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d6246c930e7e2d2d9a6aff6788f33b69", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "001c67c4d4d33e20755399e0b2ed1593", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "6f6bfaf19daf6e866f4fecbc889b8854", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ebc7449903d7868c631c504aed10f371", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "91f03e7dbfc0d7eb75568704f681fba5", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0b32b6eb05df0a7e23e55ff2e7235a8f", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4ed4b63cc506815f759412a295bfe088", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1890731fb8a2fb43c4cde72af9d7a4e6", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1a4764a504dcb20591ac3472c48db8d9", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "9bfd6234aac4ffd7d7601a40017a73be", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "941f8fb79d54522ca5df97a735c3babe", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "290a3d9840efc5e88532e13b76b3ae6b", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "621a6684b54b0a9101f808209bcca1ee", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "43c8b0b29e5fb257e4cde0f7fce1c680", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "9a30c6b88ffc11bdf6765780616d8ba1", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8a8f63850fc296b060a994f4e001d74f", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "2864e9ae1d2d566def85f1cdf6e863fe", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "dc0db7ca99661a896634fc05187a5c75", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f56bb0fafe0a45cecdcd06a10a6924c9", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e3a58b09fea193d78187f97cf922717d", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c", @@ -4132,14 +4132,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "d4aca724c3184c6c2c82e7b11c1a352a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f4fbab89e11f52329d65a9afcabc2e5e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "09733e1291806ece13f108ad6fb72b68", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "099e20f57cfa2c36bf7b44e389d01d9a", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "aae5ce60761c700d23a480cac83a2df5", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1fdc6ee85373af9df3a0b5eb601990af", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "809e62f1d2ac54c239a3a13c2d2c8ee7", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "68b49b66a76b9ab0d1b17de2bab19c04", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6b6aae30362b1e9aaf3f1a8daae0b0b4", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "25db08ee2b4c79ca631e517b622c741d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2ea419644782c4c52d1fa784d0ced86d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "c40627a46ee127997153df041e47eda5", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "fff81b29e2d86031ec25c4a672a243e7", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2a87e2f53c658d0be3918476cd8a04ee", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1e91536979b318b9a8325ab0e871ecec", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c6506399a93ef93e8322425f78e2e6b0", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6", "src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d", diff --git a/CHANGELOG.md b/CHANGELOG.md index 98301646..514fe060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ -### 1.7.37 (build 21968, api 8, 2024-08-29) +### 1.7.37 (build 21977, api 9, 2024-08-30) +- 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. + I'm hopeful that api version won't need to be bumped again for along time (if + ever). - Playlist customization no longer requires pro. - Soundtrack customization no longer requires pro. +- Campaign hard mode no longer requires pro. +- Full player profile color customization no longer requires pro. +- Removed nag screens for purchasing pro or bundle offers. - Switching over to the new 'toolbar mode' UI that has been in the works for several years. This includes a number of handy things such as consistent buttons and widgets for league status, currencies, inventory, and the store. @@ -34,7 +42,7 @@ back-button handling are more automatic and windows don't have to hard-code where they came from. There are also other benefits such as better state saving/restoring. When writing a MainWindow, pretty much all navigation should - only need too use methods: `can_change_main_window()`, `main_window_back()`, + only need too use methods: `main_window_has_control()`, `main_window_back()`, and `main_window_replace()`. - Finally got things updated so language testing works again, and made it a bit spiffier while at it. You now simply point the game at your test language and diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index a65c4972..efae5d4a 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -399,7 +399,6 @@ "ba_data/python/bauiv1lib/__pycache__/resourcetypeinfo.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/__pycache__/sendinfo.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/__pycache__/serverdialog.cpython-312.opt-1.pyc", - "ba_data/python/bauiv1lib/__pycache__/specialoffer.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/__pycache__/tabs.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/__pycache__/teamnamescolors.cpython-312.opt-1.pyc", "ba_data/python/bauiv1lib/__pycache__/tournamententry.cpython-312.opt-1.pyc", @@ -558,7 +557,6 @@ "ba_data/python/bauiv1lib/soundtrack/edit.py", "ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py", "ba_data/python/bauiv1lib/soundtrack/macmusicapp.py", - "ba_data/python/bauiv1lib/specialoffer.py", "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", diff --git a/src/assets/Makefile b/src/assets/Makefile index 1e474a92..5440f665 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -425,7 +425,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/edit.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/specialoffer.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 \ @@ -704,7 +703,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/edit.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/entrytypeselect.cpython-312.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1lib/soundtrack/__pycache__/macmusicapp.cpython-312.opt-1.pyc \ - $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/specialoffer.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 \ diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 50a08100..aca78d41 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -62,6 +62,7 @@ from _babase import ( have_chars, have_permission, in_logic_thread, + in_main_menu, increment_analytics_count, invoke_main_menu, is_os_playing_music, @@ -262,6 +263,7 @@ __all__ = [ 'have_chars', 'have_permission', 'in_logic_thread', + 'in_main_menu', 'increment_analytics_count', 'InputDeviceNotFoundError', 'InputType', diff --git a/src/assets/ba_data/python/baclassic/__init__.py b/src/assets/ba_data/python/baclassic/__init__.py index 8d9bbb81..3367c4b7 100644 --- a/src/assets/ba_data/python/baclassic/__init__.py +++ b/src/assets/ba_data/python/baclassic/__init__.py @@ -16,7 +16,7 @@ classic submodules directly, it is much harder to make it cleanly handle classic not being present. """ -# ba_meta require api 8 +# ba_meta require api 9 # Note: Code relying on classic should import things from here *only* # for type-checking and use the versions in ba*.app.classic at runtime; diff --git a/src/assets/ba_data/python/baclassic/_appmode.py b/src/assets/ba_data/python/baclassic/_appmode.py index bcb3b3c0..0bfed722 100644 --- a/src/assets/ba_data/python/baclassic/_appmode.py +++ b/src/assets/ba_data/python/baclassic/_appmode.py @@ -15,6 +15,7 @@ from babase import ( AppIntentDefault, invoke_main_menu, screenmessage, + in_main_menu, ) import _baclassic @@ -120,20 +121,28 @@ class ClassicAppMode(AppMode): def _jump_to_main_window(self, window: MainWindow) -> None: """Jump to a window with the main menu as its parent.""" from bauiv1lib.mainmenu import MainMenuWindow + from bauiv1lib.ingamemenu import InGameMenuWindow ui = app.ui_v1 old_window = ui.get_main_window() - if isinstance(old_window, MainMenuWindow): + + if isinstance(old_window, (MainMenuWindow, InGameMenuWindow)): + # If we're currently in the top level menu window, just push + # our mainwindow on to the end. old_window.main_window_replace(window) else: - # Blow away the window stack. + # Blow away the window stack and build a fresh one. ui.clear_main_window() ui.set_main_window( window, from_window=False, # Disable from-check. - back_state=MainMenuWindow.do_get_main_window_state(), + back_state=( + MainMenuWindow.do_get_main_window_state() + if in_main_menu() + else InGameMenuWindow.do_get_main_window_state() + ), ) def _root_ui_menu_press(self) -> None: diff --git a/src/assets/ba_data/python/baclassic/_appsubsystem.py b/src/assets/ba_data/python/baclassic/_appsubsystem.py index a0fcb72c..99559170 100644 --- a/src/assets/ba_data/python/baclassic/_appsubsystem.py +++ b/src/assets/ba_data/python/baclassic/_appsubsystem.py @@ -216,24 +216,6 @@ class ClassicAppSubsystem(babase.AppSubsystem): cfg['launchCount'] = launch_count cfg.commit() - # Run a test in a few seconds to see if we should pop up an existing - # pending special offer. - def check_special_offer() -> None: - assert plus is not None - - from bauiv1lib.specialoffer import show_offer - - if ( - 'pendingSpecialOffer' in cfg - and plus.get_v1_account_public_login_id() - == cfg['pendingSpecialOffer']['a'] - ): - self.special_offer = cfg['pendingSpecialOffer']['o'] - show_offer() - - if babase.app.env.gui: - babase.apptimer(3.0, check_special_offer) - # If there's a leftover log file, attempt to upload it to the # master-server and/or get rid of it. babase.handle_leftover_v1_cloud_log_file() @@ -832,7 +814,7 @@ class ClassicAppSubsystem(babase.AppSubsystem): app = bauiv1.app env = app.env with bascenev1.ContextRef.empty(): - from bauiv1lib import specialoffer + # from bauiv1lib import specialoffer assert app.classic is not None if app.env.headless: @@ -927,11 +909,11 @@ class ClassicAppSubsystem(babase.AppSubsystem): # (we may not have heard back from the server) # ..if that doesn't work they'll just have to wait # until the next opportunity. - if not specialoffer.show_offer(): + # if not specialoffer.show_offer(): - def try_again() -> None: - if not specialoffer.show_offer(): - # Try one last time.. - bauiv1.apptimer(2.0, specialoffer.show_offer) + # def try_again() -> None: + # if not specialoffer.show_offer(): + # # Try one last time.. + # bauiv1.apptimer(2.0, specialoffer.show_offer) - bauiv1.apptimer(2.0, try_again) + # bauiv1.apptimer(2.0, try_again) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index c8940edb..eb72351a 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21968 +TARGET_BALLISTICA_BUILD = 21977 TARGET_BALLISTICA_VERSION = '1.7.37' diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index 43c6550a..873e4f77 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -2,7 +2,7 @@ # """Gameplay-centric api for classic BombSquad.""" -# ba_meta require api 8 +# ba_meta require api 9 # The stuff we expose here at the top level is our 'public' api for use # from other modules/packages. Code *within* this package should import diff --git a/src/assets/ba_data/python/bascenev1/_gameutils.py b/src/assets/ba_data/python/bascenev1/_gameutils.py index 814e4bb1..36a1f31d 100644 --- a/src/assets/ba_data/python/bascenev1/_gameutils.py +++ b/src/assets/ba_data/python/bascenev1/_gameutils.py @@ -4,6 +4,7 @@ from __future__ import annotations +import random from dataclasses import dataclass from typing import TYPE_CHECKING, NewType @@ -243,7 +244,6 @@ def cameraflash(duration: float = 999.0) -> None: Duration is in seconds. """ # pylint: disable=too-many-locals - import random from bascenev1._nodeactor import NodeActor x_spread = 10 diff --git a/src/assets/ba_data/python/bascenev1lib/__init__.py b/src/assets/ba_data/python/bascenev1lib/__init__.py index 4e8f5377..b1fa37ee 100644 --- a/src/assets/ba_data/python/bascenev1lib/__init__.py +++ b/src/assets/ba_data/python/bascenev1lib/__init__.py @@ -2,4 +2,4 @@ # """Library of stuff using the bascenev1 api: games, actors, etc.""" -# ba_meta require api 8 +# ba_meta require api 9 diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py index 646b60a1..a5958874 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py @@ -207,20 +207,22 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): ) def _ui_menu(self) -> None: - from bauiv1lib import specialoffer + # from bauiv1lib import specialoffer + + # if specialoffer.show_offer(): + # return - 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))) def _ui_restart(self) -> None: from bauiv1lib.tournamententry import TournamentEntryWindow - from bauiv1lib import specialoffer - if specialoffer.show_offer(): - return + # 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. @@ -268,10 +270,10 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): self.end({'outcome': 'restart'}) def _ui_next(self) -> None: - from bauiv1lib.specialoffer import show_offer + # from bauiv1lib.specialoffer import show_offer - if show_offer(): - return + # 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). diff --git a/src/assets/ba_data/python/bascenev1lib/game/assault.py b/src/assets/ba_data/python/bascenev1lib/game/assault.py index 051cb7f0..4f8fb252 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/assault.py +++ b/src/assets/ba_data/python/bascenev1lib/game/assault.py @@ -2,7 +2,7 @@ # """Defines assault minigame.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py index a5e65ed0..b97c6aa4 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py +++ b/src/assets/ba_data/python/bascenev1lib/game/capturetheflag.py @@ -2,7 +2,7 @@ # """Defines a capture-the-flag game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/chosenone.py b/src/assets/ba_data/python/bascenev1lib/game/chosenone.py index 17160109..1617f87f 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/chosenone.py +++ b/src/assets/ba_data/python/bascenev1lib/game/chosenone.py @@ -2,7 +2,7 @@ # """Provides the chosen-one mini-game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/conquest.py b/src/assets/ba_data/python/bascenev1lib/game/conquest.py index 7c14011a..0002fabd 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/conquest.py +++ b/src/assets/ba_data/python/bascenev1lib/game/conquest.py @@ -2,7 +2,7 @@ # """Provides the Conquest game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py b/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py index 85ff1423..ee331ed1 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py +++ b/src/assets/ba_data/python/bascenev1lib/game/deathmatch.py @@ -2,7 +2,7 @@ # """DeathMatch game and support classes.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py b/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py index 260628a0..50a1f00d 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py +++ b/src/assets/ba_data/python/bascenev1lib/game/easteregghunt.py @@ -2,7 +2,7 @@ # """Provides an easter egg hunt game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/elimination.py b/src/assets/ba_data/python/bascenev1lib/game/elimination.py index 37cec73f..7df1d7da 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/elimination.py +++ b/src/assets/ba_data/python/bascenev1lib/game/elimination.py @@ -2,7 +2,7 @@ # """Elimination mini-game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/football.py b/src/assets/ba_data/python/bascenev1lib/game/football.py index 3555794b..dc1bdcbe 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/football.py +++ b/src/assets/ba_data/python/bascenev1lib/game/football.py @@ -3,7 +3,7 @@ # pylint: disable=too-many-lines """Implements football games (both co-op and teams varieties).""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/hockey.py b/src/assets/ba_data/python/bascenev1lib/game/hockey.py index c8b84494..63b4cef9 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/hockey.py +++ b/src/assets/ba_data/python/bascenev1lib/game/hockey.py @@ -2,7 +2,7 @@ # """Hockey game and support classes.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/keepaway.py b/src/assets/ba_data/python/bascenev1lib/game/keepaway.py index 874b03e4..555e2623 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/keepaway.py +++ b/src/assets/ba_data/python/bascenev1lib/game/keepaway.py @@ -2,7 +2,7 @@ # """Defines a keep-away game type.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py b/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py index 24244f22..083e1b17 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py +++ b/src/assets/ba_data/python/bascenev1lib/game/kingofthehill.py @@ -2,7 +2,7 @@ # """Defines the King of the Hill game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py index 7596632c..f2a01ecc 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py +++ b/src/assets/ba_data/python/bascenev1lib/game/meteorshower.py @@ -2,7 +2,7 @@ # """Defines a bomb-dodging mini-game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py b/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py index 67658cfe..28a0e4db 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py +++ b/src/assets/ba_data/python/bascenev1lib/game/ninjafight.py @@ -2,7 +2,7 @@ # """Provides Ninja Fight mini-game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/onslaught.py b/src/assets/ba_data/python/bascenev1lib/game/onslaught.py index 6933a0ce..8635d31e 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/onslaught.py +++ b/src/assets/ba_data/python/bascenev1lib/game/onslaught.py @@ -5,7 +5,7 @@ # Yes this is a long one.. # pylint: disable=too-many-lines -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/race.py b/src/assets/ba_data/python/bascenev1lib/game/race.py index c5556161..bbdd1225 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/race.py +++ b/src/assets/ba_data/python/bascenev1lib/game/race.py @@ -2,7 +2,7 @@ # """Defines Race mini-game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/runaround.py b/src/assets/ba_data/python/bascenev1lib/game/runaround.py index 8d0b993b..2d886418 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/runaround.py +++ b/src/assets/ba_data/python/bascenev1lib/game/runaround.py @@ -5,7 +5,7 @@ # We wear the cone of shame. # pylint: disable=too-many-lines -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py b/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py index 18d94109..b78ad813 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py +++ b/src/assets/ba_data/python/bascenev1lib/game/targetpractice.py @@ -2,7 +2,7 @@ # """Implements Target Practice game.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/batemplatefs/__init__.py b/src/assets/ba_data/python/batemplatefs/__init__.py index 379f75e3..ed097645 100644 --- a/src/assets/ba_data/python/batemplatefs/__init__.py +++ b/src/assets/ba_data/python/batemplatefs/__init__.py @@ -2,7 +2,7 @@ # """Ballistica Template Feature Set - just an example.""" -# ba_meta require api 8 +# ba_meta require api 9 # Package up various private bits (including stuff from our native # module) into a nice clean public API. diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py index d3a454d2..31b0de18 100644 --- a/src/assets/ba_data/python/bauiv1/__init__.py +++ b/src/assets/ba_data/python/bauiv1/__init__.py @@ -2,7 +2,7 @@ # """Ballistica user interface api version 1""" -# ba_meta require api 8 +# ba_meta require api 9 # The stuff we expose here at the top level is our 'public' api. # It should only be imported by code outside of this package or @@ -60,6 +60,7 @@ from babase import ( getclass, have_permission, in_logic_thread, + in_main_menu, increment_analytics_count, is_browser_likely_available, is_xcode_build, @@ -188,6 +189,7 @@ __all__ = [ 'hscrollwidget', 'imagewidget', 'in_logic_thread', + 'in_main_menu', 'increment_analytics_count', 'is_browser_likely_available', 'is_party_icon_visible', diff --git a/src/assets/ba_data/python/bauiv1/_uitypes.py b/src/assets/ba_data/python/bauiv1/_uitypes.py index 0106213c..496d32fc 100644 --- a/src/assets/ba_data/python/bauiv1/_uitypes.py +++ b/src/assets/ba_data/python/bauiv1/_uitypes.py @@ -99,7 +99,7 @@ class MainWindow(Window): edit=self._root_widget, transition=self._main_window_transition_out ) - def can_change_main_window(self) -> bool: + def main_window_has_control(self) -> bool: """Is this MainWindow allowed to change the global main window? It is a good idea to make sure this is True before calling @@ -118,12 +118,12 @@ class MainWindow(Window): def main_window_back(self) -> None: """Move back in the main window stack.""" - # Users should always check can_change_main_window() before + # Users should always check main_window_has_control() before # calling us. Error if it seems they did not. - if not self.can_change_main_window(): + if not self.main_window_has_control(): raise RuntimeError( 'main_window_back() should only be called' - ' if can_change_main_window() returns True' + ' if main_window_has_control() returns True' ' (it currently is False).' ) @@ -137,13 +137,13 @@ class MainWindow(Window): ) -> None: """Replace ourself with a new MainWindow.""" - # Users should always check can_change_main_window() before + # Users should always check main_window_has_control() before # creating new MainWindows and passing them in here. Error if it # seems they did not. - if not self.can_change_main_window(): + if not self.main_window_has_control(): raise RuntimeError( 'main_window_replace() should only be called' - ' if can_change_main_window() returns True' + ' if main_window_has_control() returns True' ' (it currently is False).' ) diff --git a/src/assets/ba_data/python/bauiv1lib/__init__.py b/src/assets/ba_data/python/bauiv1lib/__init__.py index f7720bdc..454e03f2 100644 --- a/src/assets/ba_data/python/bauiv1lib/__init__.py +++ b/src/assets/ba_data/python/bauiv1lib/__init__.py @@ -2,4 +2,4 @@ # """Library of stuff using the bauiv1 api: windows, custom controls, etc.""" -# ba_meta require api 8 +# ba_meta require api 9 diff --git a/src/assets/ba_data/python/bauiv1lib/colorpicker.py b/src/assets/ba_data/python/bauiv1lib/colorpicker.py index 2896427f..0067f684 100644 --- a/src/assets/ba_data/python/bauiv1lib/colorpicker.py +++ b/src/assets/ba_data/python/bauiv1lib/colorpicker.py @@ -12,6 +12,8 @@ import bauiv1 as bui if TYPE_CHECKING: from typing import Any, Sequence +REQUIRE_PRO = False + class ColorPicker(PopupWindow): """A popup UI to select from a set of colors. @@ -105,9 +107,8 @@ class ColorPicker(PopupWindow): on_activate_call=bui.WeakCall(self._select_other), ) - # Custom colors are limited to pro currently. assert bui.app.classic is not None - if not bui.app.classic.accounts.have_pro(): + if REQUIRE_PRO and not bui.app.classic.accounts.have_pro(): bui.imagewidget( parent=self.root_widget, position=(50, 12), @@ -137,7 +138,7 @@ class ColorPicker(PopupWindow): # Requires pro. assert bui.app.classic is not None - if not bui.app.classic.accounts.have_pro(): + if REQUIRE_PRO and not bui.app.classic.accounts.have_pro(): purchase.PurchaseWindow(items=['pro']) self._transition_out() return diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 7e914fdd..fa7fe785 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -16,6 +16,8 @@ if TYPE_CHECKING: from bauiv1lib.coop.tournamentbutton import TournamentButton +HARD_REQUIRES_PRO = False + class CoopBrowserWindow(bui.MainWindow): """Window for browsing co-op levels/games/etc.""" @@ -90,6 +92,7 @@ class CoopBrowserWindow(bui.MainWindow): if ( self._campaign_difficulty == 'hard' + and HARD_REQUIRES_PRO and not classic.accounts.have_pro_options() ): plus.add_v1_account_transaction( @@ -430,7 +433,12 @@ class CoopBrowserWindow(bui.MainWindow): bui.imagewidget( edit=self._hard_button_lock_image, opacity=( - 0.0 if bui.app.classic.accounts.have_pro_options() else 1.0 + 0.0 + if ( + (not HARD_REQUIRES_PRO) + or bui.app.classic.accounts.have_pro_options() + ) + else 1.0 ), ) except Exception: @@ -490,6 +498,7 @@ class CoopBrowserWindow(bui.MainWindow): if difficulty != self._campaign_difficulty: if ( difficulty == 'hard' + and HARD_REQUIRES_PRO and not bui.app.classic.accounts.have_pro_options() ): PurchaseWindow(items=['pro']) diff --git a/src/assets/ba_data/python/bauiv1lib/ingamemenu.py b/src/assets/ba_data/python/bauiv1lib/ingamemenu.py index abb04027..f72477f9 100644 --- a/src/assets/ba_data/python/bauiv1lib/ingamemenu.py +++ b/src/assets/ba_data/python/bauiv1lib/ingamemenu.py @@ -49,7 +49,11 @@ class InGameMenuWindow(bui.MainWindow): @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. - cls = type(self) + return self.do_get_main_window_state() + + @classmethod + def do_get_main_window_state(cls) -> bui.MainWindowState: + """Classmethod to gen a windowstate for the main menu.""" return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget diff --git a/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py b/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py index 3de74f87..e8b65073 100644 --- a/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py +++ b/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py @@ -2,7 +2,7 @@ # """Defines a default keyboards.""" -# ba_meta require api 8 +# ba_meta require api 9 # (see https://ballistica.net/wiki/meta-tag-system) from __future__ import annotations diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index ea7fd0e0..f4904fbd 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -153,6 +153,33 @@ class MainMenuWindow(bui.MainWindow): app = bui.app assert app.classic is not None + 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', + ) self._have_quit_button = app.classic.platform in ( 'windows', @@ -183,7 +210,6 @@ class MainMenuWindow(bui.MainWindow): side_button_2_y_offs = 10.0 side_button_2_scale = 0.5 - uiscale = bui.app.ui_v1.uiscale if uiscale is bui.UIScale.SMALL: root_widget_scale = 1.3 button_y_offs = -20.0 @@ -204,6 +230,24 @@ class MainMenuWindow(bui.MainWindow): scale=root_widget_scale, ) + # Version/copyright info. + bui.textwidget( + parent=self._root_widget, + position=(self._width * 0.5, button_y_offs - 10), + size=(0, 0), + scale=0.4, + flatness=1.0, + color=(1, 1, 1, 0.3), + text=( + f'{app.env.engine_version}' + f' build {app.env.engine_build_number}.' + f' Copyright 2024 Eric Froemling.' + ), + h_align='center', + v_align='center', + transition_delay=self._t_delay_play, + ) + # In kiosk mode, provide a button to get back to the kiosk menu. if bui.app.env.demo or bui.app.env.arcade: # h, v, scale = positions[self._p_index] @@ -443,7 +487,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.confirm import QuitWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return # Note: Normally we should go through bui.quit(confirm=True) but @@ -456,7 +500,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.credits import CreditsWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self.main_window_replace( @@ -469,7 +513,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.helpui import HelpWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self.main_window_replace( @@ -536,7 +580,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.gather import GatherWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self.main_window_replace( @@ -548,7 +592,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.watch import WatchWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self.main_window_replace( @@ -560,7 +604,7 @@ class MainMenuWindow(bui.MainWindow): from bauiv1lib.play import PlayWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return classic = bui.app.classic diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py index 5ec63a2c..4bce5070 100644 --- a/src/assets/ba_data/python/bauiv1lib/play.py +++ b/src/assets/ba_data/python/bauiv1lib/play.py @@ -541,7 +541,7 @@ class PlayWindow(bui.MainWindow): # pylint: disable=cyclic-import # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return if self._is_main_menu: @@ -567,7 +567,7 @@ class PlayWindow(bui.MainWindow): from bauiv1lib.coop.browser import CoopBrowserWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return plus = bui.app.plus @@ -588,7 +588,7 @@ class PlayWindow(bui.MainWindow): from bauiv1lib.playlist.browser import PlaylistBrowserWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self._save_state() @@ -604,7 +604,7 @@ class PlayWindow(bui.MainWindow): from bauiv1lib.playlist.browser import PlaylistBrowserWindow # no-op if we're not currently in control. - if not self.can_change_main_window(): + if not self.main_window_has_control(): return self._save_state() diff --git a/src/assets/ba_data/python/bauiv1lib/specialoffer.py b/src/assets/ba_data/python/bauiv1lib/specialoffer.py deleted file mode 100644 index 8d092e3d..00000000 --- a/src/assets/ba_data/python/bauiv1lib/specialoffer.py +++ /dev/null @@ -1,569 +0,0 @@ -# Released under the MIT License. See LICENSE for details. -# -"""UI for presenting sales/etc.""" - -from __future__ import annotations - -import copy -import logging -from typing import TYPE_CHECKING - -import bauiv1 as bui - -if TYPE_CHECKING: - from typing import Any - - -class SpecialOfferWindow(bui.Window): - """Window for presenting sales/etc.""" - - def __init__(self, offer: dict[str, Any], transition: str = 'in_right'): - # pylint: disable=too-many-statements - # pylint: disable=too-many-branches - # pylint: disable=too-many-locals - from babase import SpecialChar - from bauiv1lib.store import item as storeitemui - - plus = bui.app.plus - assert plus is not None - - assert bui.app.classic is not None - store = bui.app.classic.store - - self._cancel_delay = offer.get('cancelDelay', 0) - - # First thing: if we're offering pro or an IAP, see if we have a - # price for it. - # If not, abort and go into zombie mode (the user should never see - # us that way). - - real_price: str | None - - # Misnomer: 'pro' actually means offer 'pro_sale'. - if offer['item'] in ['pro', 'pro_fullprice']: - real_price = plus.get_price( - 'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale' - ) - if real_price is None and bui.app.env.debug: - print('NOTE: Faking prices for debug build.') - real_price = '$1.23' - zombie = real_price is None - elif isinstance(offer['price'], str): - # (a string price implies IAP id) - real_price = plus.get_price(offer['price']) - if real_price is None and bui.app.env.debug: - print('NOTE: Faking price for debug build.') - real_price = '$1.23' - zombie = real_price is None - else: - real_price = None - zombie = False - if real_price is None: - real_price = '?' - - if offer['item'] in ['pro', 'pro_fullprice']: - self._offer_item = 'pro' - else: - self._offer_item = offer['item'] - - # If we wanted a real price but didn't find one, go zombie. - if zombie: - return - - # This can pop up suddenly, so lets block input for 1 second. - bui.lock_all_input() - bui.apptimer(1.0, bui.unlock_all_input) - bui.getsound('ding').play() - bui.apptimer(0.3, bui.getsound('ooh').play) - self._offer = copy.deepcopy(offer) - self._width = 580 - self._height = 590 - uiscale = bui.app.ui_v1.uiscale - super().__init__( - root_widget=bui.containerwidget( - size=(self._width, self._height), - transition=transition, - scale=( - 1.2 - if uiscale is bui.UIScale.SMALL - else 1.15 if uiscale is bui.UIScale.MEDIUM else 1.0 - ), - stack_offset=( - (0, -15) if uiscale is bui.UIScale.SMALL else (0, 0) - ), - ) - ) - self._is_bundle_sale = False - try: - if offer['item'] in ['pro', 'pro_fullprice']: - original_price_str = plus.get_price('pro') - if original_price_str is None: - original_price_str = '?' - new_price_str = plus.get_price('pro_sale') - if new_price_str is None: - new_price_str = '?' - percent_off_text = '' - else: - # If the offer includes bonus tickets, it's a bundle-sale. - if ( - 'bonusTickets' in offer - and offer['bonusTickets'] is not None - ): - self._is_bundle_sale = True - original_price = plus.get_v1_account_misc_read_val( - 'price.' + self._offer_item, 9999 - ) - - # For pure ticket prices we can show a percent-off. - if isinstance(offer['price'], int): - new_price = offer['price'] - tchar = bui.charstr(SpecialChar.TICKET) - original_price_str = tchar + str(original_price) - new_price_str = tchar + str(new_price) - percent_off = int( - round( - 100.0 - (float(new_price) / original_price) * 100.0 - ) - ) - percent_off_text = ' ' + bui.Lstr( - resource='store.salePercentText' - ).evaluate().replace('${PERCENT}', str(percent_off)) - else: - original_price_str = new_price_str = '?' - percent_off_text = '' - - except Exception: - logging.exception('Error setting up special-offer: %s.', offer) - original_price_str = new_price_str = '?' - percent_off_text = '' - - # If its a bundle sale, change the title. - if self._is_bundle_sale: - sale_text = bui.Lstr( - resource='store.saleBundleText', - fallback_resource='store.saleText', - ).evaluate() - else: - # For full pro we say 'Upgrade?' since its not really a sale. - if offer['item'] == 'pro_fullprice': - sale_text = bui.Lstr( - resource='store.upgradeQuestionText', - fallback_resource='store.saleExclaimText', - ).evaluate() - else: - sale_text = bui.Lstr( - resource='store.saleExclaimText', - fallback_resource='store.saleText', - ).evaluate() - - self._title_text = bui.textwidget( - parent=self._root_widget, - position=(self._width * 0.5, self._height - 40), - size=(0, 0), - text=sale_text - + ( - (' ' + bui.Lstr(resource='store.oneTimeOnlyText').evaluate()) - if self._offer['oneTimeOnly'] - else '' - ) - + percent_off_text, - h_align='center', - v_align='center', - maxwidth=self._width * 0.9 - 220, - scale=1.4, - color=(0.3, 1, 0.3), - ) - - self._flash_on = False - self._flashing_timer: bui.AppTimer | None = bui.AppTimer( - 0.05, bui.WeakCall(self._flash_cycle), repeat=True - ) - bui.apptimer(0.6, bui.WeakCall(self._stop_flashing)) - - size = store.get_store_item_display_size(self._offer_item) - display: dict[str, Any] = {} - storeitemui.instantiate_store_item_display( - self._offer_item, - display, - parent_widget=self._root_widget, - b_pos=( - self._width * 0.5 - - size[0] * 0.5 - + 10 - - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0), - self._height * 0.5 - - size[1] * 0.5 - + 20 - + (20 if self._is_bundle_sale else 0), - ), - b_width=size[0], - b_height=size[1], - button=not self._is_bundle_sale, - ) - - # Wire up the parts we need. - if self._is_bundle_sale: - self._plus_text = bui.textwidget( - parent=self._root_widget, - position=(self._width * 0.5, self._height * 0.5 + 50), - size=(0, 0), - text='+', - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=1.4, - color=(0.5, 0.5, 0.5), - ) - self._plus_tickets = bui.textwidget( - parent=self._root_widget, - position=(self._width * 0.5 + 120, self._height * 0.5 + 50), - size=(0, 0), - text=bui.charstr(SpecialChar.TICKET_BACKING) - + str(offer['bonusTickets']), - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=2.5, - color=(0.2, 1, 0.2), - ) - self._price_text = bui.textwidget( - parent=self._root_widget, - position=(self._width * 0.5, 150), - size=(0, 0), - text=real_price, - h_align='center', - v_align='center', - maxwidth=self._width * 0.9, - scale=1.4, - color=(0.2, 1, 0.2), - ) - # Total-value if they supplied it. - total_worth_item = offer.get('valueItem', None) - if total_worth_item is not None: - price = plus.get_price(total_worth_item) - total_worth_price = ( - store.get_clean_price(price) if price is not None else None - ) - if total_worth_price is not None: - total_worth_text = bui.Lstr( - resource='store.totalWorthText', - subs=[('${TOTAL_WORTH}', total_worth_price)], - ) - self._total_worth_text = bui.textwidget( - parent=self._root_widget, - text=total_worth_text, - position=(self._width * 0.5, 210), - scale=0.9, - maxwidth=self._width * 0.7, - size=(0, 0), - h_align='center', - v_align='center', - shadow=1.0, - flatness=1.0, - color=(0.3, 1, 1), - ) - - elif offer['item'] == 'pro_fullprice': - # for full-price pro we simply show full price - bui.textwidget(edit=display['price_widget'], text=real_price) - bui.buttonwidget( - edit=display['button'], on_activate_call=self._purchase - ) - else: - # Show old/new prices otherwise (for pro sale). - bui.buttonwidget( - edit=display['button'], on_activate_call=self._purchase - ) - bui.imagewidget(edit=display['price_slash_widget'], opacity=1.0) - bui.textwidget( - edit=display['price_widget_left'], text=original_price_str - ) - bui.textwidget( - edit=display['price_widget_right'], text=new_price_str - ) - - # Add ticket button only if this is ticket-purchasable. - # if isinstance(offer.get('price'), int): - # self._get_tickets_button = bui.buttonwidget( - # parent=self._root_widget, - # position=(self._width - 125, self._height - 68), - # size=(90, 55), - # scale=1.0, - # button_type='square', - # color=(0.7, 0.5, 0.85), - # textcolor=(0.2, 1, 0.2), - # autoselect=True, - # label=bui.Lstr(resource='getTicketsWindow.titleText'), - # on_activate_call=self._on_get_more_tickets_press, - # ) - - # self._ticket_text_update_timer = bui.AppTimer( - # 1.0, bui.WeakCall(self._update_tickets_text), repeat=True - # ) - # self._update_tickets_text() - - self._update_timer = bui.AppTimer( - 1.0, bui.WeakCall(self._update), repeat=True - ) - - self._cancel_button = bui.buttonwidget( - parent=self._root_widget, - position=( - (50, 40) - if self._is_bundle_sale - else (self._width * 0.5 - 75, 40) - ), - size=(150, 60), - scale=1.0, - on_activate_call=self._cancel, - autoselect=True, - label=bui.Lstr(resource='noThanksText'), - ) - self._cancel_countdown_text = bui.textwidget( - parent=self._root_widget, - text='', - position=( - (50 + 150 + 20, 40 + 27) - if self._is_bundle_sale - else (self._width * 0.5 - 75 + 150 + 20, 40 + 27) - ), - scale=1.1, - size=(0, 0), - h_align='left', - v_align='center', - shadow=1.0, - flatness=1.0, - color=(0.6, 0.5, 0.5), - ) - self._update_cancel_button_graphics() - - if self._is_bundle_sale: - self._purchase_button = bui.buttonwidget( - parent=self._root_widget, - position=(self._width - 200, 40), - size=(150, 60), - scale=1.0, - on_activate_call=self._purchase, - autoselect=True, - label=bui.Lstr(resource='store.purchaseText'), - ) - - bui.containerwidget( - edit=self._root_widget, - cancel_button=self._cancel_button, - start_button=( - self._purchase_button if self._is_bundle_sale else None - ), - selected_child=( - self._purchase_button - if self._is_bundle_sale - else display['button'] - ), - ) - - def _stop_flashing(self) -> None: - self._flashing_timer = None - bui.textwidget(edit=self._title_text, color=(0.3, 1, 0.3)) - - def _flash_cycle(self) -> None: - if not self._root_widget: - return - self._flash_on = not self._flash_on - bui.textwidget( - edit=self._title_text, - color=(0.3, 1, 0.3) if self._flash_on else (1, 0.5, 0), - ) - - def _update_cancel_button_graphics(self) -> None: - bui.buttonwidget( - edit=self._cancel_button, - color=( - (0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.7, 0.4, 0.34) - ), - textcolor=( - (0.5, 0.5, 0.5) if self._cancel_delay > 0 else (0.9, 0.9, 1.0) - ), - ) - bui.textwidget( - edit=self._cancel_countdown_text, - text=str(self._cancel_delay) if self._cancel_delay > 0 else '', - ) - - def _update(self) -> None: - plus = bui.app.plus - assert plus is not None - - # If we've got seconds left on our countdown, update it. - if self._cancel_delay > 0: - self._cancel_delay = max(0, self._cancel_delay - 1) - self._update_cancel_button_graphics() - - can_die = False - - # We go away if we see that our target item is owned. - if self._offer_item == 'pro': - assert bui.app.classic is not None - if bui.app.classic.accounts.have_pro(): - can_die = True - else: - if plus.get_purchased(self._offer_item): - can_die = True - - if can_die: - self._transition_out('out_left') - - def _transition_out(self, transition: str = 'out_left') -> None: - # Also clear any pending-special-offer we've stored at this point. - cfg = bui.app.config - if 'pendingSpecialOffer' in cfg: - del cfg['pendingSpecialOffer'] - cfg.commit() - - bui.containerwidget(edit=self._root_widget, transition=transition) - - # def _update_tickets_text(self) -> None: - # from babase import SpecialChar - - # plus = bui.app.plus - # assert plus is not None - - # if not self._root_widget: - # return - # sval: str | bui.Lstr - # if plus.get_v1_account_state() == 'signed_in': - # sval = bui.charstr(SpecialChar.TICKET) + str( - # plus.get_v1_account_ticket_count() - # ) - # else: - # sval = bui.Lstr(resource='getTicketsWindow.titleText') - # bui.buttonwidget(edit=self._get_tickets_button, label=sval) - - # def _on_get_more_tickets_press(self) -> None: - # from bauiv1lib import account - # from bauiv1lib import gettickets - - # plus = bui.app.plus - # assert plus is not None - - # if plus.get_v1_account_state() != 'signed_in': - # account.show_sign_in_prompt() - # return - # gettickets.GetTicketsWindow(modal=True).get_root_widget() - - def _purchase(self) -> None: - # from bauiv1lib import gettickets - from bauiv1lib import confirm - - plus = bui.app.plus - assert plus is not None - - assert bui.app.classic is not None - store = bui.app.classic.store - - if self._offer['item'] == 'pro': - plus.purchase('pro_sale') - elif self._offer['item'] == 'pro_fullprice': - plus.purchase('pro') - elif self._is_bundle_sale: - # With bundle sales, the price is the name of the IAP. - plus.purchase(self._offer['price']) - else: - ticket_count: int | None - try: - ticket_count = plus.get_v1_account_ticket_count() - except Exception: - ticket_count = None - if ticket_count is not None and ticket_count < self._offer['price']: - # gettickets.show_get_tickets_prompt() - bui.getsound('error').play() - return - - def do_it() -> None: - assert plus is not None - - plus.in_game_purchase( - 'offer:' + str(self._offer['id']), self._offer['price'] - ) - - bui.getsound('swish').play() - confirm.ConfirmWindow( - bui.Lstr( - resource='store.purchaseConfirmText', - subs=[ - ( - '${ITEM}', - store.get_store_item_name_translated( - self._offer['item'] - ), - ) - ], - ), - width=400, - height=120, - action=do_it, - ok_text=bui.Lstr( - resource='store.purchaseText', fallback_resource='okText' - ), - ) - - def _cancel(self) -> None: - if self._cancel_delay > 0: - bui.getsound('error').play() - return - self._transition_out('out_right') - - -def show_offer() -> bool: - """(internal)""" - try: - from bauiv1lib import feedback - - plus = bui.app.plus - assert plus is not None - - app = bui.app - if app.classic is None: - raise RuntimeError( - 'Classic feature-set is required to show offers.' - ) - - # Space things out a bit so we don't hit the poor user with an - # ad and then an in-game offer. - has_been_long_enough_since_ad = True - if app.classic.ads.last_ad_completion_time is not None and ( - bui.apptime() - app.classic.ads.last_ad_completion_time < 30.0 - ): - has_been_long_enough_since_ad = False - - if ( - app.classic.special_offer is not None - and has_been_long_enough_since_ad - ): - # Special case: for pro offers, store this in our prefs so - # we can re-show it if the user kills us (set phasers to - # 'NAG'!!!). - if app.classic.special_offer.get('item') == 'pro_fullprice': - cfg = app.config - cfg['pendingSpecialOffer'] = { - 'a': plus.get_v1_account_public_login_id(), - 'o': app.classic.special_offer, - } - cfg.commit() - - if app.classic.special_offer['item'] == 'rating': - # Go with a native thing if we've got one. - if bui.native_review_request_supported(): - bui.native_review_request() - else: - if app.ui_v1.available: - feedback.ask_for_rating() - else: - if app.ui_v1.available: - SpecialOfferWindow(app.classic.special_offer) - - app.classic.special_offer = None - return True - except Exception: - logging.exception('Error showing offer.') - - return False diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py index 6a41feed..f176e7c8 100644 --- a/src/assets/ba_data/python/bauiv1lib/store/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py @@ -125,55 +125,11 @@ class StoreBrowserWindow(bui.MainWindow): bui.containerwidget( edit=self._root_widget, on_cancel_call=self._back ) - # backbutton = bui.get_special_widget('back_button') backbuttonspecial = True else: bui.containerwidget(edit=self._root_widget, cancel_button=btn) - # backbutton = self._back_button backbuttonspecial = False - # self._ticket_count_text: bui.Widget | None = None - # self._get_tickets_button: bui.Widget | None = None - - # if bool(False): - # if app.classic.allow_ticket_purchases: - # self._get_tickets_button = bui.buttonwidget( - # parent=self._root_widget, - # size=(210, 65), - # on_activate_call=self._on_get_more_tickets_press, - # autoselect=True, - # scale=0.9, - # text_scale=1.4, - # left_widget=backbutton, - # color=(0.7, 0.5, 0.85), - # textcolor=(0.2, 1.0, 0.2), - # label=bui.Lstr(resource='getTicketsWindow.titleText'), - # ) - # else: - # self._ticket_count_text = bui.textwidget( - # parent=self._root_widget, - # size=(210, 64), - # color=(0.2, 1.0, 0.2), - # h_align='center', - # v_align='center', - # ) - - # Move this dynamically to keep it out of the way of the party icon. - # self._update_get_tickets_button_pos() - # self._get_ticket_pos_update_timer = bui.AppTimer( - # 1.0, - # bui.WeakCall(self._update_get_tickets_button_pos), - # repeat=True, - # ) - # if self._get_tickets_button and not backbuttonspecial: - # bui.widget( - # edit=self._back_button, right_widget=self._get_tickets_button - # ) - # self._ticket_text_update_timer = bui.AppTimer( - # 1.0, bui.WeakCall(self._update_tickets_text), repeat=True - # ) - # self._update_tickets_text() - if ( app.classic.platform in ['mac', 'ios'] and app.classic.subplatform == 'appstore' @@ -316,17 +272,6 @@ class StoreBrowserWindow(bui.MainWindow): ) self._update_tabs() - # if self._get_tickets_button: - # last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button - # bui.widget( - # edit=self._get_tickets_button, down_widget=last_tab_button - # ) - # bui.widget( - # edit=last_tab_button, - # up_widget=self._get_tickets_button, - # right_widget=self._get_tickets_button, - # ) - if uiscale is bui.UIScale.SMALL: first_tab_button = self._tab_row.tabs[tabs_def[0][0]].button last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button @@ -346,28 +291,6 @@ class StoreBrowserWindow(bui.MainWindow): self._status_textwidget: bui.Widget | None = None self._restore_state() - # def _update_get_tickets_button_pos(self) -> None: - # assert bui.app.classic is not None - # uiscale = bui.app.ui_v1.uiscale - # pos = ( - # self._width - # - 252 - # - ( - # self._x_inset - # + ( - # 47 - # if uiscale is bui.UIScale.SMALL - # and bui.is_party_icon_visible() - # else 0 - # ) - # ), - # self._height - 70, - # ) - # if self._get_tickets_button: - # bui.buttonwidget(edit=self._get_tickets_button, position=pos) - # if self._ticket_count_text: - # bui.textwidget(edit=self._ticket_count_text, position=pos) - def _restore_purchases(self) -> None: from bauiv1lib import account @@ -411,25 +334,6 @@ class StoreBrowserWindow(bui.MainWindow): bui.textwidget(edit=tab_data['text'], text='') bui.imagewidget(edit=tab_data['img'], opacity=0.0) - # def _update_tickets_text(self) -> None: - # from bauiv1 import SpecialChar - - # if not self._root_widget: - # return - # plus = bui.app.plus - # assert plus is not None - # sval: str | bui.Lstr - # if plus.get_v1_account_state() == 'signed_in': - # sval = bui.charstr(SpecialChar.TICKET) + str( - # plus.get_v1_account_ticket_count() - # ) - # else: - # sval = bui.Lstr(resource='getTicketsWindow.titleText') - # if self._get_tickets_button: - # bui.buttonwidget(edit=self._get_tickets_button, label=sval) - # if self._ticket_count_text: - # bui.textwidget(edit=self._ticket_count_text, text=sval) - def _set_tab(self, tab_id: TabID) -> None: if self._current_tab is tab_id: return @@ -491,7 +395,6 @@ class StoreBrowserWindow(bui.MainWindow): window = self._window() if window is not None and (window.request is self): window.request = None - # noinspection PyProtectedMember window._on_response(data) # Kick off a server request. @@ -601,8 +504,6 @@ class StoreBrowserWindow(bui.MainWindow): from bauiv1lib import account from bauiv1lib.confirm import ConfirmWindow - # from bauiv1lib import gettickets - assert bui.app.classic is not None store = bui.app.classic.store @@ -1292,8 +1193,6 @@ class StoreBrowserWindow(bui.MainWindow): for tab_id, tab in self._tab_row.tabs.items() if sel == tab.button ] - # if sel == self._get_tickets_button: - # sel_name = 'GetTickets' if sel == self._scrollwidget: sel_name = 'Scroll' elif sel == self._back_button: @@ -1327,8 +1226,6 @@ class StoreBrowserWindow(bui.MainWindow): if self._show_tab is not None: current_tab = self._show_tab - # if sel_name == 'GetTickets' and self._get_tickets_button: - # sel = self._get_tickets_button if sel_name == 'Back': sel = self._back_button elif sel_name == 'Scroll': @@ -1342,7 +1239,7 @@ class StoreBrowserWindow(bui.MainWindow): else: sel = self._tab_row.tabs[current_tab].button - # If we were requested to show a tab, select it too.. + # If we were requested to show a tab, select it too. if ( self._show_tab is not None and self._show_tab in self._tab_row.tabs @@ -1354,63 +1251,21 @@ class StoreBrowserWindow(bui.MainWindow): except Exception: logging.exception('Error restoring state for %s.', self) - # def _on_get_more_tickets_press(self) -> None: - # # pylint: disable=cyclic-import - # from bauiv1lib.account import show_sign_in_prompt - # from bauiv1lib.gettickets import GetTicketsWindow - - # # 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 - - # plus = bui.app.plus - # assert plus is not None - - # if plus.get_v1_account_state() != 'signed_in': - # show_sign_in_prompt() - # return - # self._save_state() - # bui.containerwidget(edit=self._root_widget, transition='out_left') - # window = GetTicketsWindow( - # from_modal_store=self._modal, - # store_back_location=self._back_location, - # ) - # if not self._modal: - # assert bui.app.classic is not None - # bui.app.ui_v1.set_main_window(window, from_window=self) - def _back(self) -> None: - # pylint: disable=cyclic-import - # from bauiv1lib.coop.browser import CoopBrowserWindow - # from bauiv1lib.mainmenu import MainMenuWindow - - # 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 - - self._save_state() if 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 + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) else: + # no-op if we're not currently in control. + if not self.main_window_has_control(): + return self.main_window_back() - # if not self._modal: - # assert bui.app.classic is not None - # if self._back_location == 'CoopBrowserWindow': - # bui.app.ui_v1.set_main_window( - # CoopBrowserWindow(transition='in_left'), - # from_window=self, - # is_back=True, - # ) - # else: - # bui.app.ui_v1.set_main_window( - # MainMenuWindow(transition='in_left'), - # from_window=self, - # is_back=True, - # is_top_level=True, - # ) if self._on_close_call is not None: self._on_close_call() diff --git a/src/ballistica/base/app_mode/app_mode.cc b/src/ballistica/base/app_mode/app_mode.cc index 4f1650fc..b4361428 100644 --- a/src/ballistica/base/app_mode/app_mode.cc +++ b/src/ballistica/base/app_mode/app_mode.cc @@ -66,6 +66,6 @@ void AppMode::LanguageChanged() {} auto AppMode::LastClientJoinTime() const -> millisecs_t { return -1; } -auto AppMode::InClassicMainMenuSession() const -> bool { return false; } +auto AppMode::IsInMainMenu() const -> bool { return false; } } // namespace ballistica::base diff --git a/src/ballistica/base/app_mode/app_mode.h b/src/ballistica/base/app_mode/app_mode.h index 7f981b5a..5f848367 100644 --- a/src/ballistica/base/app_mode/app_mode.h +++ b/src/ballistica/base/app_mode/app_mode.h @@ -68,8 +68,8 @@ class AppMode { /// Called when language changes. virtual void LanguageChanged(); - /// Are we currently in a classic 'main menu' session? - virtual auto InClassicMainMenuSession() const -> bool; + /// Are we currently in a 'main menu' situation (as opposed to gameplay)? + virtual auto IsInMainMenu() const -> bool; /// Get current party size (for legacy parties). virtual auto GetPartySize() const -> int; diff --git a/src/ballistica/base/python/methods/python_methods_base_3.cc b/src/ballistica/base/python/methods/python_methods_base_3.cc index 831031dd..b0f79be9 100644 --- a/src/ballistica/base/python/methods/python_methods_base_3.cc +++ b/src/ballistica/base/python/methods/python_methods_base_3.cc @@ -6,6 +6,7 @@ #include #include "ballistica/base/app_adapter/app_adapter.h" +#include "ballistica/base/app_mode/app_mode.h" #include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/input/input.h" #include "ballistica/base/platform/base_platform.h" @@ -350,6 +351,37 @@ static PyMethodDef PyInLogicThreadDef = { "Returns whether or not the current thread is the logic thread.", }; +// ------------------------------ in_main_menu --------------------------------- + +static auto PyInMainMenu(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + static const char* kwlist[] = {nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "", + const_cast(kwlist))) { + return nullptr; + } + BA_PRECONDITION(g_base->InLogicThread()); + if (g_base->app_mode()->IsInMainMenu()) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyInMainMenuDef = { + "in_main_menu", // name + (PyCFunction)PyInMainMenu, // method + METH_VARARGS | METH_KEYWORDS, // flags + + "in_main_menu() -> bool\n" + "\n" + "(internal)\n" + "\n" + "Returns whether or not the app-mode is currently in a main menu\n" + "situation (as opposed to gameplay).", +}; + // ----------------------------- set_thread_name ------------------------------- static auto PySetThreadName(PyObject* self, PyObject* args, @@ -1949,6 +1981,7 @@ auto PythonMoethodsBase3::GetMethods() -> std::vector { PyGetThreadNameDef, PySetThreadNameDef, PyInLogicThreadDef, + PyInMainMenuDef, PyRequestPermissionDef, PyHavePermissionDef, PyUnlockAllInputDef, diff --git a/src/ballistica/classic/support/classic_app_mode.cc b/src/ballistica/classic/support/classic_app_mode.cc index 364c03eb..17c4e31d 100644 --- a/src/ballistica/classic/support/classic_app_mode.cc +++ b/src/ballistica/classic/support/classic_app_mode.cc @@ -67,7 +67,7 @@ base::InputDeviceDelegate* ClassicAppMode::CreateInputDeviceDelegate( // Go with 5 minute ban. const int kKickBanSeconds = 5 * 60; -bool ClassicAppMode::InClassicMainMenuSession() const { +bool ClassicAppMode::IsInMainMenu() const { scene_v1::HostSession* hostsession = scene_v1::ContextRefSceneV1::FromAppForegroundContext().GetHostSession(); return (hostsession && hostsession->is_main_menu()); diff --git a/src/ballistica/classic/support/classic_app_mode.h b/src/ballistica/classic/support/classic_app_mode.h index 0fa75159..8e8e8d0b 100644 --- a/src/ballistica/classic/support/classic_app_mode.h +++ b/src/ballistica/classic/support/classic_app_mode.h @@ -155,7 +155,7 @@ class ClassicAppMode : public base::AppMode { void OnAppStart() override; void OnAppSuspend() override; void OnAppUnsuspend() override; - auto InClassicMainMenuSession() const -> bool override; + auto IsInMainMenu() const -> bool override; auto CreateInputDeviceDelegate(base::InputDevice* device) -> base::InputDeviceDelegate* override; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 55bc92dd..9fb9e776 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,9 +39,9 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21968; +const int kEngineBuildNumber = 21977; const char* kEngineVersion = "1.7.37"; -const int kEngineApiVersion = 8; +const int kEngineApiVersion = 9; #if BA_MONOLITHIC_BUILD diff --git a/src/ballistica/ui_v1/widget/root_widget.cc b/src/ballistica/ui_v1/widget/root_widget.cc index 33bac53b..b82ac95f 100644 --- a/src/ballistica/ui_v1/widget/root_widget.cc +++ b/src/ballistica/ui_v1/widget/root_widget.cc @@ -3,7 +3,9 @@ #include "ballistica/ui_v1/widget/root_widget.h" #include "ballistica/base/app_mode/app_mode.h" -#include "ballistica/base/graphics/renderer/renderer.h" +#include "ballistica/base/assets/assets.h" +#include "ballistica/base/graphics/renderer/render_pass.h" +#include "ballistica/base/graphics/support/frame_def.h" #include "ballistica/shared/foundation/inline.h" #include "ballistica/ui_v1/python/ui_v1_python.h" #include "ballistica/ui_v1/widget/button_widget.h" @@ -43,6 +45,8 @@ struct RootWidget::ButtonDef { uint32_t visibility_mask{}; bool selectable{true}; bool enable_sound{true}; + bool allow_in_main_menu{true}; + bool allow_in_game{true}; float h_align{}; float x{}; float y{}; @@ -81,6 +85,8 @@ struct RootWidget::Button { bool selectable{true}; bool fully_offscreen{}; bool enabled{}; + bool allow_in_main_menu{true}; + bool allow_in_game{true}; uint32_t visibility_mask{}; }; @@ -222,6 +228,8 @@ void RootWidget::AddMeter_(MeterType type, float h_align, float r, float g, | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); + bd.allow_in_game = false; + // Show some in store mode. if (type == MeterType::kLevel || type == MeterType::kTickets) { bd.visibility_mask |= @@ -491,6 +499,8 @@ void RootWidget::AddMeter_(MeterType type, float h_align, float r, float g, } bd.pre_buffer = -10.0f; + bd.allow_in_game = false; + Button* btn = AddButton_(bd); if (type == MeterType::kTokens) { get_tokens_button_ = btn; @@ -662,11 +672,7 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); - // on desktop, stick this in the top left corner - // if (g_ui->scale() == UIScale::kLarge) { - // bd.h_align = 0.0f; - // bd.x = 120.0f; - // } + bd.allow_in_game = false; Button* b = account_button_ = AddButton_(bd); top_left_buttons_.push_back(b); @@ -675,9 +681,9 @@ void RootWidget::Setup() { { TextDef td; td.button = b; - td.y = 9.0f; - td.width = bd.width * 0.9f; - td.text = "Player Name"; + td.y = 0.0f; + td.width = bd.width * 0.8f; + td.text = "AccountName"; td.scale = 1.2f; td.depth_min = 0.3f; td.color_r = 0.5f; @@ -688,19 +694,19 @@ void RootWidget::Setup() { } // Clan. - { - TextDef td; - td.button = b; - td.y = -12.0f; - td.width = bd.width * 0.9f; - td.depth_min = 0.3f; - td.text = "Clan Name"; - td.color_a = 0.6f; - td.scale = 0.6f; - td.flatness = 1.0f; - td.shadow = 0.0f; - AddText_(td); - } + // { + // TextDef td; + // td.button = b; + // td.y = -12.0f; + // td.width = bd.width * 0.9f; + // td.depth_min = 0.3f; + // td.text = "Clan Name"; + // td.color_a = 0.6f; + // td.scale = 0.6f; + // td.flatness = 1.0f; + // td.shadow = 0.0f; + // AddText_(td); + // } } // float anchorx = 0.0f; @@ -737,6 +743,7 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); b.pre_buffer = 5.0f; b.enable_sound = false; + b.allow_in_main_menu = false; menu_button_ = AddButton_(b); top_right_buttons_.push_back(menu_button_); } @@ -795,6 +802,7 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); b.pre_buffer = 20.0f; + b.allow_in_game = false; inbox_button_ = AddButton_(b); bottom_left_buttons_.push_back(inbox_button_); } @@ -819,6 +827,7 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); b.pre_buffer = 20.0f; + b.allow_in_game = false; achievements_button_ = AddButton_(b); bottom_left_buttons_.push_back(achievements_button_); bx += 80.0f; @@ -1007,7 +1016,7 @@ void RootWidget::Setup() { (static_cast(Widget::ToolbarVisibility::kMenuFull) | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); - + bd.allow_in_game = false; AddButton_(bd); } @@ -1026,6 +1035,7 @@ void RootWidget::Setup() { float spacing = 130.0f; b.x = -1.5f * spacing; b.call = UIV1Python::ObjID::kRootUIChestSlot1PressCall; + b.allow_in_game = false; AddButton_(b); b.x = -0.5f * spacing; @@ -1090,6 +1100,7 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); b.disable_offset_scale = 1.5f; b.pre_buffer = 20.0f; + b.allow_in_game = false; inventory_button_ = AddButton_(b); bottom_right_buttons_.push_back(inventory_button_); } @@ -1109,6 +1120,8 @@ void RootWidget::Setup() { | static_cast(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast(Widget::ToolbarVisibility::kMenuFullRoot)); b.pre_buffer = 20.0f; + b.allow_in_game = false; + store_button_ = AddButton_(b); bottom_right_buttons_.push_back(store_button_); } @@ -1147,6 +1160,8 @@ auto RootWidget::AddButton_(const ButtonDef& def) -> RootWidget::Button* { b.h_align = def.h_align; b.v_align = def.v_align; b.selectable = def.selectable; + b.allow_in_game = def.allow_in_game; + b.allow_in_main_menu = def.allow_in_main_menu; b.widget = Object::New(); b.widget->SetColor(def.color_r, def.color_g, def.color_b); b.widget->set_opacity(def.opacity); @@ -1246,11 +1261,8 @@ void RootWidget::UpdateForFocusedWindow() { } void RootWidget::UpdateForFocusedWindow_(Widget* widget) { - // Take note if the current session is the main menu; we do a few things - // differently there. - // - // FIXME - need a more generalized way to determine this. - in_main_menu_ = g_base->app_mode()->InClassicMainMenuSession(); + // Take note whether we're currently in a main menu vs gameplay. + in_main_menu_ = g_base->app_mode()->IsInMainMenu(); if (widget == nullptr) { toolbar_visibility_ = ToolbarVisibility::kInGame; @@ -1278,9 +1290,23 @@ void RootWidget::StepPositions_(float dt) { // When we're in the main menu, always disable the menu button and // shift the party button a bit to the right if (in_main_menu_) { - if (&b == menu_button_) { + if (!b.allow_in_main_menu) { enable_button = false; } + // Disallow menu button in the main menu. + // if (&b == menu_button_) { + // enable_button = false; + // } + } else { + if (!b.allow_in_game) { + enable_button = false; + } + // Disallow most buttons in-game. + // if (&b == inbox_button_ || &b == achievements_button_ + // || &b == account_button_ || &b == level_meter_button_ + // || &b == trophy_meter_button_ || &b == store_button_ || &b == ) { + // enable_button = false; + // } } if (&b == back_button_) { // Back button is always disabled in medium/large UI.