diff --git a/.efrocachemap b/.efrocachemap index f7df2d94..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": "512c99d6d32d3f7ca27de9d00b3f6bee", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "407a6d564f35c7db571702ef6996adc1", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "03c278694182a2a7cd6e39d7cf0cdde8", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "8c0f3132135f6a392b31dccce44ecc72", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "399783ec75c825a7c0ceffb6b66546a6", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "7d5f43f891ddbc0e68e1e4b924717941", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "97b462045118ca81436452c19e50ead3", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "daedf2d4f2c7012f9f1146ef5e675a51", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "ef571b31a6175b391c70cde46587afc6", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "fcbe9cb15740f5569d32daaef838bae3", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "06d8ed9ca06ed1fb12437708b117621f", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "eff858f25a173f090731401c42fcfad7", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "21401c4a1bb13c6a50fbcc2413a34bc6", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "a5f6bf1f1a675a60b43674cbe06ee740", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "4965d74db093926f890d37a2d2bcb0cf", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "527d364caa4c68cb3a5e70575a834333", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "f3e69ff8d4928b52fb301c911d6e01cb", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "82dd100621de502523d9feca94de7fc5", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "6c66e68c95330200d339ec5a44706491", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f6903fb7071440ee807963cf34fd220f", + "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": "3193670862ed84af2f6bbf3dc7265137", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "5e45916a6cfb40a5667c691e507ea81d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "419fba17d012361c3ca8174c44dffdb2", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d895b4afb3bb74dd1c94595a126a2c24", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "56a67ab9520c07bc4be68162e78534b3", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "40f942e6da490173c0e17fdf23e786e8", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "3242b64140d0b0d6f7af09825d9489e0", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5d41c8b29dc4e585b878c100e01e8af4", + "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 b08abcb7..514fe060 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.37 (build 21976, api 9, 2024-08-30) +### 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. @@ -8,6 +8,7 @@ - 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. 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/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 d8638d78..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 = 21976 +TARGET_BALLISTICA_BUILD = 21977 TARGET_BALLISTICA_VERSION = '1.7.37' 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/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/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 7fd2dea9..9fb9e776 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21976; +const int kEngineBuildNumber = 21977; const char* kEngineVersion = "1.7.37"; const int kEngineApiVersion = 9;