diff --git a/.efrocachemap b/.efrocachemap index b62c0dc1..7b3bbb27 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,7 +421,7 @@ "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "e6331203538a066bb79a755ab3a2ddd7", + "build/assets/ba_data/data/langdata.json": "e9c687b8c06c4b1df34a933a365ed9e7", "build/assets/ba_data/data/languages/arabic.json": "c62b3037ef3364b84d4f8dd6610854f8", "build/assets/ba_data/data/languages/belarussian.json": "3d5523d0004293aa2df02f3f6f3b84f8", "build/assets/ba_data/data/languages/chinese.json": "2f67c6b127ae85492ac552af1a91e95a", @@ -430,12 +430,12 @@ "build/assets/ba_data/data/languages/czech.json": "74219f9b06ff098387b40f85a5b0124e", "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", "build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343", - "build/assets/ba_data/data/languages/english.json": "d8aabdfb3c00c0a6b35a8a1f53a02394", + "build/assets/ba_data/data/languages/english.json": "ef2b554d37c256132ee9dcb4f6478c4e", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", "build/assets/ba_data/data/languages/filipino.json": "b53910af2d3946d5ff81386e8c1d35a7", "build/assets/ba_data/data/languages/french.json": "b7d11199756f0eb4f1a745ceee652b2a", "build/assets/ba_data/data/languages/german.json": "198b9860c5b9df7b8e3e30b03d8755cb", - "build/assets/ba_data/data/languages/gibberish.json": "99db538eeed34be968166075346155cf", + "build/assets/ba_data/data/languages/gibberish.json": "fd68975cb527ddd7b2b97e84a7ba22d7", "build/assets/ba_data/data/languages/greek.json": "ad3c0d38f34d809824892d6f22808dbf", "build/assets/ba_data/data/languages/hindi.json": "e56f0ffb57321660aa415e97c2dacfc2", "build/assets/ba_data/data/languages/hungarian.json": "6b08fea24b72cc805ed0dc59e11c4cd6", @@ -450,7 +450,7 @@ "build/assets/ba_data/data/languages/russian.json": "4cb481af9d4e2611e0033235a045aa33", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "3c08c748c96c71bd9e1d7291fb8817b6", - "build/assets/ba_data/data/languages/spanish.json": "fe339007d6e631d601bf826f949828e3", + "build/assets/ba_data/data/languages/spanish.json": "485356283e4375a6b8b7cd06614319db", "build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac", "build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a", "build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945", @@ -4062,50 +4062,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "b825e0ae40e1caeaf036244e635bb78d", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "5068c42c3b7defafbf4bb1ba01c67a53", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "411349ebe46b5a4bcda7211adba5fcf8", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "b719d818a8098da1f1a034ef7a7c2150", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0273ef65559ac5f39147b875adc79c68", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a3d46a432dd0bb890d657185a4ad5dea", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9625783e60e3875d66299b0479432391", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "9dc64227710e2d9d3dd04dbe70a0a994", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f37e40f0b5447a51cafa3c7dcdd8cf91", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "20e35f51e0fb5f97245741ee6167bfbc", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a2d8f1ec1fa12ce13eb5c3893753b9e", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "e0939ffb5eaf75c1ee5ca5f8b6ad9a17", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "6e80134a263106a1a982a07857b77436", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fa100352069f1eada6a23747d52ec625", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "df2dad13b9b6014a5b7c031daade4833", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "29abf332ae82d899f3d9bf6ac7c01171", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "de0fe26430c34f2935e66d7a8200d5ff", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "11462b669e43541ec9b97efce0540751", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c96b92af111f9415301073de1d11c816", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "c778f347ac0b71f6730254a40aacbb17", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1e87ade9bea8c12ac6cb5e531205bae5", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "6c92725a224d085b1d426cc03272ebea", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1e87ade9bea8c12ac6cb5e531205bae5", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "6c92725a224d085b1d426cc03272ebea", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "16783ffe62e14a64331027989da5001b", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "2db28554169b53a560f2f824c1176dfa", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "16783ffe62e14a64331027989da5001b", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "2db28554169b53a560f2f824c1176dfa", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "73dffd26ad8f5caad5be0659cb50df6d", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2852099ec1365025c290f9d7f9c4a01e", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "73dffd26ad8f5caad5be0659cb50df6d", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2852099ec1365025c290f9d7f9c4a01e", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "0e39192fedf0bb0926e409a1cb409833", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ef6ce2f4ed73d411423d6171f5d31100", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "356af38c464e969d04ee4a2889e9f67a", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ef6ce2f4ed73d411423d6171f5d31100", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "4f53df80210601028bdec3f22b297c18", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d5fa0bf70723c976f0b4481429022bbc", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "54257c1026e4296f855d2deee42ba9cb", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "492f317391fdf2a69ab65347fbc5cc53", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "8bc3320036ca1911a8f6c85d40a59172", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "9e137f7c83e37f9b7b43bca9319f9752", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "e9c8975e57c0cfde5dc70e2a5547441c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d81182adb2c250030d38f8fda682a610", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "123ecb04f354552c15aa04d3d5047074", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "b992b3d97a1b066a1a2b74911ed4a44d", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "2ea1e068455ae6d7c0afd3ac5c1453bf", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "7b087cf909f3a71f97abd431c9dbce60", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "26a69101cb282d96d48d38032fe2ae73", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "2fca0da9ff9eaa5af16fb6801c4e7b81", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "657ee4258b874631acf4e37028e512ec", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "942499d3619e551deb7f5795e62e9022", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5ea6ae7ac701fd0b51f3db6b0e554268", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "be20cf570211f03f8b54c94e95caf579", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c73fabf8be863554d5b899c7ea4a960e", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "bf29cf0164a08a4aa139e39f20f50423", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "e2272883f9c7405402cb967f8e3b2b28", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "15f5b471a04313a13bac43c86f37eb72", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "a7672fb4c4b14a1d88c3435cf2af9675", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "2f733fe4dc422b7d0db04a5ea017ab68", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d34cf623c2cca17fac7fdc0918de1202", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "45dbf5edad5a9a71608ac1d6d1954923", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "3002abd737297bf395eab0bd60c535d8", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e9bf6aaa7e18bcf482223a29a609932e", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "ffe8631c0ccc03751b4346fcf52d70ca", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4241e93e1ea18414ee900046d1a62517", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "ffe8631c0ccc03751b4346fcf52d70ca", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4241e93e1ea18414ee900046d1a62517", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "cd90771128eda8880ac3f9d8d9fc530b", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "093ec692aa3c6039ccc322d74f2d4448", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "cd90771128eda8880ac3f9d8d9fc530b", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "093ec692aa3c6039ccc322d74f2d4448", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "40f3006295c57114c2b5c467f463dd79", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "9049a93c01002bea0e6a580a106939c3", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "40f3006295c57114c2b5c467f463dd79", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "9049a93c01002bea0e6a580a106939c3", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "07ee17e424c0ea754fd4ec9844063460", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "0f34f852bf368c787f41533230767b3a", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1dcd03f0cfc75a53c3e682a3a46c96a0", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "0f34f852bf368c787f41533230767b3a", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "4a8ae45bb9474a0d8faccd195dded56f", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "8eb224db25a69e2faed1c6897f73c8b6", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "25740d352aec852365430d305655f0fe", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "3e19f73203116e9db0943e0c4485f459", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "517d6fc942828b4c3de3972d6165238e", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c681050518fde161af81ae356ec80027", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "6689ba552f0a3761b6acc5375343dbe5", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "3a6f2f59a965348b5e9b54562954da89", "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 5ab2d812..2ac7013d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ -### 1.7.36 (build 21936, api 8, 2024-07-24) +### 1.7.36 (build 21939, api 8, 2024-07-24) - Wired up Tokens, BombSquad's new purchasable currency. The first thing these can be used for is storage packs on ballistica.net, but this will expand to other places in the game soon. For a full explanation on why these were added, see https://ballistica.net/whataretokens +- Paid private hosting now uses tokens instead of tickets. - Wired up initial support for using asset-packages for bundled assets. - bacloud workspace commands are now a bit smarter; you can now do things like `bacloud workspace put .` or even just `bacloud workspace put` and it will diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 46f260b0..99cb1d8a 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 = 21936 +TARGET_BALLISTICA_BUILD = 21939 TARGET_BALLISTICA_VERSION = '1.7.36' diff --git a/src/assets/ba_data/python/baplus/_cloud.py b/src/assets/ba_data/python/baplus/_cloud.py index bc8593a2..0fcc0ae8 100644 --- a/src/assets/ba_data/python/baplus/_cloud.py +++ b/src/assets/ba_data/python/baplus/_cloud.py @@ -109,6 +109,15 @@ class CloudSubsystem(babase.AppSubsystem): ], ) -> None: ... + @overload + def send_message_cb( + self, + msg: bacommon.cloud.BSPrivatePartyMessage, + on_response: Callable[ + [bacommon.cloud.BSPrivatePartyResponse | Exception], None + ], + ) -> None: ... + def send_message_cb( self, msg: Message, diff --git a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py index ac10cfb5..223d6d2c 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py @@ -13,14 +13,17 @@ from enum import Enum from dataclasses import dataclass from typing import TYPE_CHECKING, cast, override +from efro.error import CommunicationError from efro.dataclassio import dataclass_from_dict, dataclass_to_dict +import bacommon.cloud from bacommon.net import ( PrivateHostingState, PrivateHostingConfig, PrivatePartyConnectResult, ) from bauiv1lib.gather import GatherTab -from bauiv1lib.gettickets import GetTicketsWindow, show_get_tickets_prompt + +from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_prompt import bascenev1 as bs import bauiv1 as bui @@ -55,7 +58,9 @@ class PrivateGatherTab(GatherTab): super().__init__(window) self._container: bui.Widget | None = None self._state: State = State() + self._last_datacode_refresh_time: float | None = None self._hostingstate = PrivateHostingState() + self._v2state: bacommon.cloud.BSPrivatePartyResponse | None = None self._join_sub_tab_text: bui.Widget | None = None self._host_sub_tab_text: bui.Widget | None = None self._update_timer: bui.AppTimer | None = None @@ -63,14 +68,15 @@ class PrivateGatherTab(GatherTab): self._c_width: float = 0.0 self._c_height: float = 0.0 self._last_hosting_state_query_time: float | None = None + self._last_v2_state_query_time: float | None = None self._waiting_for_initial_state = True self._waiting_for_start_stop_response = True self._host_playlist_button: bui.Widget | None = None self._host_copy_button: bui.Widget | None = None self._host_connect_button: bui.Widget | None = None self._host_start_stop_button: bui.Widget | None = None - self._get_tickets_button: bui.Widget | None = None - self._ticket_count_text: bui.Widget | None = None + self._get_tokens_button: bui.Widget | None = None + self._token_count_text: bui.Widget | None = None self._showing_not_signed_in_screen = False self._create_time = time.time() self._last_action_send_time: float | None = None @@ -159,7 +165,9 @@ class PrivateGatherTab(GatherTab): # Prevent taking any action until we've updated our state. self._waiting_for_initial_state = True - # This will get a state query sent out immediately. + # Force some immediate refreshes. + self._last_datacode_refresh_time = None + self._last_v2_state_query_time = None self._last_action_send_time = None # Ensure we don't ignore response. self._last_hosting_state_query_time = None self._update() @@ -263,19 +271,20 @@ class PrivateGatherTab(GatherTab): plus = bui.app.plus assert plus is not None - try: - t_str = str(plus.get_v1_account_ticket_count()) - except Exception: - t_str = '?' - if self._get_tickets_button: + if self._v2state is not None: + t_str = str(self._v2state.tokens) + else: + t_str = '-' + + if self._get_tokens_button: bui.buttonwidget( - edit=self._get_tickets_button, - label=bui.charstr(bui.SpecialChar.TICKET) + t_str, + edit=self._get_tokens_button, + label=bui.charstr(bui.SpecialChar.TOKEN) + t_str, ) - if self._ticket_count_text: + if self._token_count_text: bui.textwidget( - edit=self._ticket_count_text, - text=bui.charstr(bui.SpecialChar.TICKET) + t_str, + edit=self._token_count_text, + text=bui.charstr(bui.SpecialChar.TOKEN) + t_str, ) def _update(self) -> None: @@ -292,11 +301,11 @@ class PrivateGatherTab(GatherTab): # If we're not signed in, just refresh to show that. if ( plus.get_v1_account_state() != 'signed_in' - and self._showing_not_signed_in_screen - ): + or plus.accounts.primary is None + ) and not self._showing_not_signed_in_screen: self._refresh_sub_tab() else: - # Query an updated state periodically. + # Query an updated v1 state periodically. if ( self._last_hosting_state_query_time is None or now - self._last_hosting_state_query_time > 15.0 @@ -309,23 +318,78 @@ class PrivateGatherTab(GatherTab): 'expire_time': time.time() + 20, }, callback=bui.WeakCall( - self._hosting_state_idle_response + self._idle_hosting_state_response ), ) plus.run_v1_account_transactions() else: - self._hosting_state_idle_response(None) + self._idle_hosting_state_response(None) self._last_hosting_state_query_time = now - def _hosting_state_idle_response( + # Query an updated v2 state periodically. + if ( + self._last_v2_state_query_time is None + or now - self._last_v2_state_query_time > 12.0 + ): + self._debug_server_comm('querying pp v2 state') + + if plus.accounts.primary is not None: + with plus.accounts.primary: + plus.cloud.send_message_cb( + bacommon.cloud.BSPrivatePartyMessage( + need_datacode=( + self._last_datacode_refresh_time is None + or time.monotonic() + - self._last_datacode_refresh_time + > 30.0 + ) + ), + on_response=bui.WeakCall( + self._on_private_party_query_response + ), + ) + + self._last_v2_state_query_time = now + + def _on_private_party_query_response( + self, response: bacommon.cloud.BSPrivatePartyResponse | Exception + ) -> None: + if isinstance(response, Exception): + self._debug_server_comm('got pp v2 state response (err)') + # We expect comm errors sometimes. Make noise on anything else. + if not isinstance(response, CommunicationError): + logging.exception('Error on private-party-query-response') + return + + # Ignore if something went wrong server-side. + if not response.success: + self._debug_server_comm('got pp v2 state response (serverside err)') + return + + self._debug_server_comm('got pp v2 state response') + + existing_datacode = ( + None if self._v2state is None else self._v2state.datacode + ) + + self._v2state = response + if self._v2state.datacode is None: + # We don't fetch datacode each time; preserve our existing + # if we didn't. + self._v2state.datacode = existing_datacode + else: + # If we *did* fetch it, note the time. + self._last_datacode_refresh_time = time.monotonic() + + def _idle_hosting_state_response( self, result: dict[str, Any] | None ) -> None: # This simply passes through to our standard response handler. # The one exception is if we've recently sent an action to the - # server (start/stop hosting/etc.) In that case we want to ignore - # idle background updates and wait for the response to our action. - # (this keeps the button showing 'one moment...' until the change - # takes effect, etc.) + # server (start/stop hosting/etc.) In that case we want to + # ignore idle background updates and wait for the response to + # our action. (this keeps the button showing 'one moment...' + # until the change takes effect, etc.) if ( self._last_action_send_time is not None and time.time() - self._last_action_send_time < 5.0 @@ -354,8 +418,8 @@ class PrivateGatherTab(GatherTab): else: self._debug_server_comm('private party state response errored') - # Hmm I guess let's just ignore failed responses?... - # Or should we show some sort of error state to the user?... + # Hmm I guess let's just ignore failed responses?... Or should + # we show some sort of error state to the user?... if result is None or state is None: return @@ -369,14 +433,17 @@ class PrivateGatherTab(GatherTab): if playsound: bui.getsound('click01').play() - # If switching from join to host, do a fresh state query. + # If switching from join to host, force some refreshes. if self._state.sub_tab is SubTabType.JOIN and value is SubTabType.HOST: - # Prevent taking any action until we've gotten a fresh state. + # Prevent taking any action until we've gotten a fresh + # state. self._waiting_for_initial_state = True - # This will get a state query sent out immediately. + # Get some refreshes going immediately. self._last_hosting_state_query_time = None self._last_action_send_time = None # So we don't ignore response. + self._last_datacode_refresh_time = None + self._last_v2_state_query_time = None self._update() self._state.sub_tab = value @@ -403,14 +470,14 @@ class PrivateGatherTab(GatherTab): self._host_copy_button, self._host_connect_button, self._host_start_stop_button, - self._get_tickets_button, + self._get_tokens_button, ] def _refresh_sub_tab(self) -> None: assert self._container - # Store an index for our current selection so we can - # reselect the equivalent recreated widget if possible. + # Store an index for our current selection so we can reselect + # the equivalent recreated widget if possible. selindex: int | None = None selchild = self._container.get_selected_child() if selchild is not None: @@ -481,13 +548,13 @@ class PrivateGatherTab(GatherTab): edit=self._join_party_code_text, on_return_press_call=btn.activate ) - def _on_get_tickets_press(self) -> None: + def _on_get_tokens_press(self) -> None: if self._waiting_for_start_stop_response: return - # Bring up get-tickets window and then kill ourself (we're on the - # overlay layer so we'd show up above it). - GetTicketsWindow(modal=True, origin_widget=self._get_tickets_button) + # Bring up get-tickets window and then kill ourself (we're on + # the overlay layer so we'd show up above it). + GetTokensWindow(origin_widget=self._get_tokens_button) def _build_host_tab(self) -> None: # pylint: disable=too-many-branches @@ -498,13 +565,20 @@ class PrivateGatherTab(GatherTab): assert plus is not None hostingstate = self._hostingstate + + havegoldpass = self._v2state is not None and self._v2state.gold_pass + + # We use both v1 and v2 account functionality here (sigh). So + # make sure we're signed in on both ends. + + # Make sure the V1 side is good to go. if plus.get_v1_account_state() != 'signed_in': bui.textwidget( parent=self._container, size=(0, 0), h_align='center', v_align='center', - maxwidth=200, + maxwidth=self._c_width * 0.8, scale=0.8, color=(0.6, 0.56, 0.6), position=(self._c_width * 0.5, self._c_height * 0.5), @@ -512,14 +586,31 @@ class PrivateGatherTab(GatherTab): ) self._showing_not_signed_in_screen = True return + + # Make sure the V2 side is good to go. + if plus.accounts.primary is None: + bui.textwidget( + parent=self._container, + size=(0, 0), + h_align='center', + v_align='center', + maxwidth=self._c_width * 0.8, + scale=0.8, + color=(0.6, 0.56, 0.6), + position=(self._c_width * 0.5, self._c_height * 0.5), + text=bui.Lstr(resource='v2AccountRequiredText'), + ) + self._showing_not_signed_in_screen = True + return + self._showing_not_signed_in_screen = False - # At first we don't want to show anything until we've gotten a state. - # Update: In this situation we now simply show our existing state - # but give the start/stop button a loading message and disallow its - # use. This keeps things a lot less jumpy looking and allows selecting - # playlists/etc without having to wait for the server each time - # back to the ui. + # At first we don't want to show anything until we've gotten a + # state. Update: In this situation we now simply show our + # existing state but give the start/stop button a loading + # message and disallow its use. This keeps things a lot less + # jumpy looking and allows selecting playlists/etc without + # having to wait for the server each time back to the ui. if self._waiting_for_initial_state and bool(False): bui.textwidget( parent=self._container, @@ -537,16 +628,21 @@ class PrivateGatherTab(GatherTab): ) return - # If we're not currently hosting and hosting requires tickets, + # If we're not currently hosting and hosting requires tokens, # Show our count (possibly with a link to purchase more). if ( not self._waiting_for_initial_state and hostingstate.party_code is None and hostingstate.tickets_to_host_now != 0 + and not havegoldpass ): if not bui.app.ui_v1.use_toolbars: - if bui.app.classic.allow_ticket_purchases: - self._get_tickets_button = bui.buttonwidget( + + # Currently have no allow_token_purchases value like + # we had with tickets; just assuming we always allow. + if bool(True): + # if bui.app.classic.allow_ticket_purchases: + self._get_tokens_button = bui.buttonwidget( parent=self._container, position=( self._c_width - 210 + 125, @@ -555,24 +651,25 @@ class PrivateGatherTab(GatherTab): autoselect=True, scale=0.6, size=(120, 60), - textcolor=(0.2, 1, 0.2), - label=bui.charstr(bui.SpecialChar.TICKET), + textcolor=(1.0, 0.6, 0.0), + label=bui.charstr(bui.SpecialChar.TOKEN), color=(0.65, 0.5, 0.8), - on_activate_call=self._on_get_tickets_press, + on_activate_call=self._on_get_tokens_press, ) else: - self._ticket_count_text = bui.textwidget( + self._token_count_text = bui.textwidget( parent=self._container, scale=0.6, position=( self._c_width - 210 + 125, self._c_height - 44, ), - color=(0.2, 1, 0.2), + color=(1.0, 0.6, 0.0), h_align='center', v_align='center', ) - # Set initial ticket count. + + # Set initial token count. self._update_currency_ui() v = self._c_height - 90 @@ -594,7 +691,8 @@ class PrivateGatherTab(GatherTab): v -= 100 if hostingstate.party_code is None: - # We've got no current party running; show options to set one up. + # We've got no current party running; show options to set + # one up. bui.textwidget( parent=self._container, size=(0, 0), @@ -713,8 +811,14 @@ class PrivateGatherTab(GatherTab): ) ), ) + elif havegoldpass: + # If we have a gold pass, none of the + # timing/free-server-availability info below is relevant to + # us. + pass elif hostingstate.free_host_minutes_remaining is not None: - # If we've been pre-approved to start/stop for free, show that. + # If we've been pre-approved to start/stop for free, show + # that. bui.textwidget( parent=self._container, size=(0, 0), @@ -811,12 +915,12 @@ class PrivateGatherTab(GatherTab): resource='gatherWindow.hostingUnavailableText' ) elif hostingstate.party_code is None: - ticon = bui.charstr(bui.SpecialChar.TICKET) - nowtickets = hostingstate.tickets_to_host_now - if nowtickets > 0: + ticon = bui.charstr(bui.SpecialChar.TOKEN) + nowtokens = hostingstate.tokens_to_host_now + if nowtokens > 0 and not havegoldpass: btnlabel = bui.Lstr( resource='gatherWindow.startHostingPaidText', - subs=[('${COST}', f'{ticon}{nowtickets}')], + subs=[('${COST}', f'{ticon}{nowtokens}')], ) else: btnlabel = bui.Lstr( @@ -867,8 +971,8 @@ class PrivateGatherTab(GatherTab): ) def _connect_to_party_code(self, code: str) -> None: - # Ignore attempted followup sends for a few seconds. - # (this will reset if we get a response) + # Ignore attempted followup sends for a few seconds. (this will + # reset if we get a response) plus = bui.app.plus assert plus is not None @@ -915,21 +1019,30 @@ class PrivateGatherTab(GatherTab): bui.getsound('click01').play() + # We need our v2 info for this. + if self._v2state is None or self._v2state.datacode is None: + bui.screenmessage( + bui.Lstr(resource='internal.unavailableNoConnectionText'), + color=(1, 0, 0), + ) + bui.getsound('error').play() + return + # If we're not hosting, start. if self._hostingstate.party_code is None: - # If there's a ticket cost, make sure we have enough tickets. - if self._hostingstate.tickets_to_host_now > 0: - ticket_count: int | None - try: - ticket_count = plus.get_v1_account_ticket_count() - except Exception: - # FIXME: should add a bui.NotSignedInError we can use here. - ticket_count = None - ticket_cost = self._hostingstate.tickets_to_host_now - if ticket_count is not None and ticket_count < ticket_cost: - show_get_tickets_prompt() + # If there's a token cost, make sure we have enough tokens + # or a gold pass. + if self._hostingstate.tokens_to_host_now > 0: + + if ( + not self._v2state.gold_pass + and self._v2state.tokens + < self._hostingstate.tokens_to_host_now + ): + show_get_tokens_prompt() bui.getsound('error').play() return + self._last_action_send_time = time.time() plus.add_v1_account_transaction( { @@ -937,6 +1050,7 @@ class PrivateGatherTab(GatherTab): 'config': dataclass_to_dict(self._hostingconfig), 'region_pings': bui.app.net.zone_pings, 'expire_time': time.time() + 20, + 'datacode': self._v2state.datacode, }, callback=bui.WeakCall(self._hosting_state_response), ) diff --git a/src/assets/ba_data/python/bauiv1lib/gettokens.py b/src/assets/ba_data/python/bauiv1lib/gettokens.py index 501c0bf4..22bff444 100644 --- a/src/assets/ba_data/python/bauiv1lib/gettokens.py +++ b/src/assets/ba_data/python/bauiv1lib/gettokens.py @@ -779,3 +779,31 @@ class GetTokensWindow(bui.Window): def _on_learn_more_press(self, url: str) -> None: bui.open_url(url) + + +def show_get_tokens_prompt() -> None: + """Show a 'not enough tokens' prompt with an option to purchase more. + + Note that the purchase option may not always be available + depending on the build of the game. + """ + from bauiv1lib.confirm import ConfirmWindow + + assert bui.app.classic is not None + + # Currently always allowing token purchases. + if bool(True): + ConfirmWindow( + bui.Lstr(resource='tokens.notEnoughTokensText'), + GetTokensWindow, + ok_text=bui.Lstr(resource='tokens.getTokensText'), + width=460, + height=130, + ) + else: + ConfirmWindow( + bui.Lstr(resource='tokens.notEnoughTokensText'), + cancel_button=False, + width=460, + height=130, + ) diff --git a/src/ballistica/base/graphics/text/text_graphics.cc b/src/ballistica/base/graphics/text/text_graphics.cc index a4d9aa90..daddd6b4 100644 --- a/src/ballistica/base/graphics/text/text_graphics.cc +++ b/src/ballistica/base/graphics/text/text_graphics.cc @@ -80,6 +80,11 @@ TextGraphics::TextGraphics() { g.y_size *= 0.55f; } } + // Special handling of tokens icon. + if (index == 29) { + extra_advance += 0.12f; + } + // Special case for v2 logo. if (index == 99) { g.pen_offset_y += 0.25f; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index eb4499e3..feb05206 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 = 21936; +const int kEngineBuildNumber = 21939; const char* kEngineVersion = "1.7.36"; const int kEngineApiVersion = 8; diff --git a/tools/bacommon/cloud.py b/tools/bacommon/cloud.py index ec02e027..0990dd7c 100644 --- a/tools/bacommon/cloud.py +++ b/tools/bacommon/cloud.py @@ -288,3 +288,27 @@ class StoreQueryResponse(Response): available_purchases: Annotated[list[Purchase], IOAttrs('p')] token_info_url: Annotated[str, IOAttrs('tiu')] + + +@ioprepped +@dataclass +class BSPrivatePartyMessage(Message): + """Message asking about info we need for private-party UI.""" + + need_datacode: Annotated[bool, IOAttrs('d')] + + @override + @classmethod + def get_response_types(cls) -> list[type[Response] | None]: + return [BSPrivatePartyResponse] + + +@ioprepped +@dataclass +class BSPrivatePartyResponse(Response): + """Here's that private party UI info you asked for, boss.""" + + success: Annotated[bool, IOAttrs('s')] + tokens: Annotated[int, IOAttrs('t')] + gold_pass: Annotated[bool, IOAttrs('g')] + datacode: Annotated[str | None, IOAttrs('d')] diff --git a/tools/bacommon/net.py b/tools/bacommon/net.py index 0f146026..c4411063 100644 --- a/tools/bacommon/net.py +++ b/tools/bacommon/net.py @@ -64,6 +64,7 @@ class PrivateHostingState: unavailable_error: str | None = None party_code: str | None = None tickets_to_host_now: int = 0 + tokens_to_host_now: int = 0 minutes_until_free_host: float | None = None free_host_minutes_remaining: float | None = None