paid private hosting now uses tokens instead of tickets

This commit is contained in:
Eric 2024-07-24 20:14:18 -07:00
parent 674d4a83eb
commit aceb9a1a66
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
10 changed files with 304 additions and 122 deletions

96
.efrocachemap generated
View File

@ -421,7 +421,7 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", "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/arabic.json": "c62b3037ef3364b84d4f8dd6610854f8",
"build/assets/ba_data/data/languages/belarussian.json": "3d5523d0004293aa2df02f3f6f3b84f8", "build/assets/ba_data/data/languages/belarussian.json": "3d5523d0004293aa2df02f3f6f3b84f8",
"build/assets/ba_data/data/languages/chinese.json": "2f67c6b127ae85492ac552af1a91e95a", "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/czech.json": "74219f9b06ff098387b40f85a5b0124e",
"build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7", "build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7",
"build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343", "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/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "b53910af2d3946d5ff81386e8c1d35a7", "build/assets/ba_data/data/languages/filipino.json": "b53910af2d3946d5ff81386e8c1d35a7",
"build/assets/ba_data/data/languages/french.json": "b7d11199756f0eb4f1a745ceee652b2a", "build/assets/ba_data/data/languages/french.json": "b7d11199756f0eb4f1a745ceee652b2a",
"build/assets/ba_data/data/languages/german.json": "198b9860c5b9df7b8e3e30b03d8755cb", "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/greek.json": "ad3c0d38f34d809824892d6f22808dbf",
"build/assets/ba_data/data/languages/hindi.json": "e56f0ffb57321660aa415e97c2dacfc2", "build/assets/ba_data/data/languages/hindi.json": "e56f0ffb57321660aa415e97c2dacfc2",
"build/assets/ba_data/data/languages/hungarian.json": "6b08fea24b72cc805ed0dc59e11c4cd6", "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/russian.json": "4cb481af9d4e2611e0033235a045aa33",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "3c08c748c96c71bd9e1d7291fb8817b6", "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/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a", "build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a",
"build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945", "build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945",
@ -4062,50 +4062,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "b825e0ae40e1caeaf036244e635bb78d", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "123ecb04f354552c15aa04d3d5047074",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "5068c42c3b7defafbf4bb1ba01c67a53", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "b992b3d97a1b066a1a2b74911ed4a44d",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "411349ebe46b5a4bcda7211adba5fcf8", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "2ea1e068455ae6d7c0afd3ac5c1453bf",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "b719d818a8098da1f1a034ef7a7c2150", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "7b087cf909f3a71f97abd431c9dbce60",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0273ef65559ac5f39147b875adc79c68", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "26a69101cb282d96d48d38032fe2ae73",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a3d46a432dd0bb890d657185a4ad5dea", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "2fca0da9ff9eaa5af16fb6801c4e7b81",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9625783e60e3875d66299b0479432391", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "657ee4258b874631acf4e37028e512ec",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "9dc64227710e2d9d3dd04dbe70a0a994", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "942499d3619e551deb7f5795e62e9022",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f37e40f0b5447a51cafa3c7dcdd8cf91", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5ea6ae7ac701fd0b51f3db6b0e554268",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "20e35f51e0fb5f97245741ee6167bfbc", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "be20cf570211f03f8b54c94e95caf579",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a2d8f1ec1fa12ce13eb5c3893753b9e", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c73fabf8be863554d5b899c7ea4a960e",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "e0939ffb5eaf75c1ee5ca5f8b6ad9a17", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "bf29cf0164a08a4aa139e39f20f50423",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "6e80134a263106a1a982a07857b77436", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "e2272883f9c7405402cb967f8e3b2b28",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fa100352069f1eada6a23747d52ec625", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "15f5b471a04313a13bac43c86f37eb72",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "df2dad13b9b6014a5b7c031daade4833", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "a7672fb4c4b14a1d88c3435cf2af9675",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "29abf332ae82d899f3d9bf6ac7c01171", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "2f733fe4dc422b7d0db04a5ea017ab68",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "de0fe26430c34f2935e66d7a8200d5ff", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d34cf623c2cca17fac7fdc0918de1202",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "11462b669e43541ec9b97efce0540751", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "45dbf5edad5a9a71608ac1d6d1954923",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c96b92af111f9415301073de1d11c816", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "3002abd737297bf395eab0bd60c535d8",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "c778f347ac0b71f6730254a40aacbb17", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e9bf6aaa7e18bcf482223a29a609932e",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1e87ade9bea8c12ac6cb5e531205bae5", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "ffe8631c0ccc03751b4346fcf52d70ca",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "6c92725a224d085b1d426cc03272ebea", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4241e93e1ea18414ee900046d1a62517",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1e87ade9bea8c12ac6cb5e531205bae5", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "ffe8631c0ccc03751b4346fcf52d70ca",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "6c92725a224d085b1d426cc03272ebea", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4241e93e1ea18414ee900046d1a62517",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "16783ffe62e14a64331027989da5001b", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "cd90771128eda8880ac3f9d8d9fc530b",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "2db28554169b53a560f2f824c1176dfa", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "093ec692aa3c6039ccc322d74f2d4448",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "16783ffe62e14a64331027989da5001b", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "cd90771128eda8880ac3f9d8d9fc530b",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "2db28554169b53a560f2f824c1176dfa", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "093ec692aa3c6039ccc322d74f2d4448",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "73dffd26ad8f5caad5be0659cb50df6d", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "40f3006295c57114c2b5c467f463dd79",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2852099ec1365025c290f9d7f9c4a01e", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "9049a93c01002bea0e6a580a106939c3",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "73dffd26ad8f5caad5be0659cb50df6d", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "40f3006295c57114c2b5c467f463dd79",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2852099ec1365025c290f9d7f9c4a01e", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "9049a93c01002bea0e6a580a106939c3",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "0e39192fedf0bb0926e409a1cb409833", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "07ee17e424c0ea754fd4ec9844063460",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ef6ce2f4ed73d411423d6171f5d31100", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "0f34f852bf368c787f41533230767b3a",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "356af38c464e969d04ee4a2889e9f67a", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1dcd03f0cfc75a53c3e682a3a46c96a0",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ef6ce2f4ed73d411423d6171f5d31100", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "0f34f852bf368c787f41533230767b3a",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "4f53df80210601028bdec3f22b297c18", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "4a8ae45bb9474a0d8faccd195dded56f",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d5fa0bf70723c976f0b4481429022bbc", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "8eb224db25a69e2faed1c6897f73c8b6",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "54257c1026e4296f855d2deee42ba9cb", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "25740d352aec852365430d305655f0fe",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "492f317391fdf2a69ab65347fbc5cc53", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "3e19f73203116e9db0943e0c4485f459",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "8bc3320036ca1911a8f6c85d40a59172", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "517d6fc942828b4c3de3972d6165238e",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "9e137f7c83e37f9b7b43bca9319f9752", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c681050518fde161af81ae356ec80027",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "e9c8975e57c0cfde5dc70e2a5547441c", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "6689ba552f0a3761b6acc5375343dbe5",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d81182adb2c250030d38f8fda682a610", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6", "src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d", "src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d",

View File

@ -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 - 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 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, other places in the game soon. For a full explanation on why these were added,
see https://ballistica.net/whataretokens 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. - 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 commands are now a bit smarter; you can now do things like
`bacloud workspace put .` or even just `bacloud workspace put` and it will `bacloud workspace put .` or even just `bacloud workspace put` and it will

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21936 TARGET_BALLISTICA_BUILD = 21939
TARGET_BALLISTICA_VERSION = '1.7.36' TARGET_BALLISTICA_VERSION = '1.7.36'

View File

@ -109,6 +109,15 @@ class CloudSubsystem(babase.AppSubsystem):
], ],
) -> None: ... ) -> None: ...
@overload
def send_message_cb(
self,
msg: bacommon.cloud.BSPrivatePartyMessage,
on_response: Callable[
[bacommon.cloud.BSPrivatePartyResponse | Exception], None
],
) -> None: ...
def send_message_cb( def send_message_cb(
self, self,
msg: Message, msg: Message,

View File

@ -13,14 +13,17 @@ from enum import Enum
from dataclasses import dataclass from dataclasses import dataclass
from typing import TYPE_CHECKING, cast, override from typing import TYPE_CHECKING, cast, override
from efro.error import CommunicationError
from efro.dataclassio import dataclass_from_dict, dataclass_to_dict from efro.dataclassio import dataclass_from_dict, dataclass_to_dict
import bacommon.cloud
from bacommon.net import ( from bacommon.net import (
PrivateHostingState, PrivateHostingState,
PrivateHostingConfig, PrivateHostingConfig,
PrivatePartyConnectResult, PrivatePartyConnectResult,
) )
from bauiv1lib.gather import GatherTab 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 bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
@ -55,7 +58,9 @@ class PrivateGatherTab(GatherTab):
super().__init__(window) super().__init__(window)
self._container: bui.Widget | None = None self._container: bui.Widget | None = None
self._state: State = State() self._state: State = State()
self._last_datacode_refresh_time: float | None = None
self._hostingstate = PrivateHostingState() self._hostingstate = PrivateHostingState()
self._v2state: bacommon.cloud.BSPrivatePartyResponse | None = None
self._join_sub_tab_text: bui.Widget | None = None self._join_sub_tab_text: bui.Widget | None = None
self._host_sub_tab_text: bui.Widget | None = None self._host_sub_tab_text: bui.Widget | None = None
self._update_timer: bui.AppTimer | None = None self._update_timer: bui.AppTimer | None = None
@ -63,14 +68,15 @@ class PrivateGatherTab(GatherTab):
self._c_width: float = 0.0 self._c_width: float = 0.0
self._c_height: float = 0.0 self._c_height: float = 0.0
self._last_hosting_state_query_time: float | None = None 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_initial_state = True
self._waiting_for_start_stop_response = True self._waiting_for_start_stop_response = True
self._host_playlist_button: bui.Widget | None = None self._host_playlist_button: bui.Widget | None = None
self._host_copy_button: bui.Widget | None = None self._host_copy_button: bui.Widget | None = None
self._host_connect_button: bui.Widget | None = None self._host_connect_button: bui.Widget | None = None
self._host_start_stop_button: bui.Widget | None = None self._host_start_stop_button: bui.Widget | None = None
self._get_tickets_button: bui.Widget | None = None self._get_tokens_button: bui.Widget | None = None
self._ticket_count_text: bui.Widget | None = None self._token_count_text: bui.Widget | None = None
self._showing_not_signed_in_screen = False self._showing_not_signed_in_screen = False
self._create_time = time.time() self._create_time = time.time()
self._last_action_send_time: float | None = None 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. # Prevent taking any action until we've updated our state.
self._waiting_for_initial_state = True 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_action_send_time = None # Ensure we don't ignore response.
self._last_hosting_state_query_time = None self._last_hosting_state_query_time = None
self._update() self._update()
@ -263,19 +271,20 @@ class PrivateGatherTab(GatherTab):
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
try: if self._v2state is not None:
t_str = str(plus.get_v1_account_ticket_count()) t_str = str(self._v2state.tokens)
except Exception: else:
t_str = '?' t_str = '-'
if self._get_tickets_button:
if self._get_tokens_button:
bui.buttonwidget( bui.buttonwidget(
edit=self._get_tickets_button, edit=self._get_tokens_button,
label=bui.charstr(bui.SpecialChar.TICKET) + t_str, label=bui.charstr(bui.SpecialChar.TOKEN) + t_str,
) )
if self._ticket_count_text: if self._token_count_text:
bui.textwidget( bui.textwidget(
edit=self._ticket_count_text, edit=self._token_count_text,
text=bui.charstr(bui.SpecialChar.TICKET) + t_str, text=bui.charstr(bui.SpecialChar.TOKEN) + t_str,
) )
def _update(self) -> None: def _update(self) -> None:
@ -292,11 +301,11 @@ class PrivateGatherTab(GatherTab):
# If we're not signed in, just refresh to show that. # If we're not signed in, just refresh to show that.
if ( if (
plus.get_v1_account_state() != 'signed_in' 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() self._refresh_sub_tab()
else: else:
# Query an updated state periodically. # Query an updated v1 state periodically.
if ( if (
self._last_hosting_state_query_time is None self._last_hosting_state_query_time is None
or now - self._last_hosting_state_query_time > 15.0 or now - self._last_hosting_state_query_time > 15.0
@ -309,23 +318,78 @@ class PrivateGatherTab(GatherTab):
'expire_time': time.time() + 20, 'expire_time': time.time() + 20,
}, },
callback=bui.WeakCall( callback=bui.WeakCall(
self._hosting_state_idle_response self._idle_hosting_state_response
), ),
) )
plus.run_v1_account_transactions() plus.run_v1_account_transactions()
else: else:
self._hosting_state_idle_response(None) self._idle_hosting_state_response(None)
self._last_hosting_state_query_time = now 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 self, result: dict[str, Any] | None
) -> None: ) -> None:
# This simply passes through to our standard response handler. # This simply passes through to our standard response handler.
# The one exception is if we've recently sent an action to the # 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 # server (start/stop hosting/etc.) In that case we want to
# idle background updates and wait for the response to our action. # ignore idle background updates and wait for the response to
# (this keeps the button showing 'one moment...' until the change # our action. (this keeps the button showing 'one moment...'
# takes effect, etc.) # until the change takes effect, etc.)
if ( if (
self._last_action_send_time is not None self._last_action_send_time is not None
and time.time() - self._last_action_send_time < 5.0 and time.time() - self._last_action_send_time < 5.0
@ -354,8 +418,8 @@ class PrivateGatherTab(GatherTab):
else: else:
self._debug_server_comm('private party state response errored') self._debug_server_comm('private party state response errored')
# Hmm I guess let's just ignore failed responses?... # Hmm I guess let's just ignore failed responses?... Or should
# Or should we show some sort of error state to the user?... # we show some sort of error state to the user?...
if result is None or state is None: if result is None or state is None:
return return
@ -369,14 +433,17 @@ class PrivateGatherTab(GatherTab):
if playsound: if playsound:
bui.getsound('click01').play() 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: 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 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_hosting_state_query_time = None
self._last_action_send_time = None # So we don't ignore response. 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._update()
self._state.sub_tab = value self._state.sub_tab = value
@ -403,14 +470,14 @@ class PrivateGatherTab(GatherTab):
self._host_copy_button, self._host_copy_button,
self._host_connect_button, self._host_connect_button,
self._host_start_stop_button, self._host_start_stop_button,
self._get_tickets_button, self._get_tokens_button,
] ]
def _refresh_sub_tab(self) -> None: def _refresh_sub_tab(self) -> None:
assert self._container assert self._container
# Store an index for our current selection so we can # Store an index for our current selection so we can reselect
# reselect the equivalent recreated widget if possible. # the equivalent recreated widget if possible.
selindex: int | None = None selindex: int | None = None
selchild = self._container.get_selected_child() selchild = self._container.get_selected_child()
if selchild is not None: if selchild is not None:
@ -481,13 +548,13 @@ class PrivateGatherTab(GatherTab):
edit=self._join_party_code_text, on_return_press_call=btn.activate 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: if self._waiting_for_start_stop_response:
return return
# Bring up get-tickets window and then kill ourself (we're on the # Bring up get-tickets window and then kill ourself (we're on
# overlay layer so we'd show up above it). # the overlay layer so we'd show up above it).
GetTicketsWindow(modal=True, origin_widget=self._get_tickets_button) GetTokensWindow(origin_widget=self._get_tokens_button)
def _build_host_tab(self) -> None: def _build_host_tab(self) -> None:
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
@ -498,13 +565,20 @@ class PrivateGatherTab(GatherTab):
assert plus is not None assert plus is not None
hostingstate = self._hostingstate 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': if plus.get_v1_account_state() != 'signed_in':
bui.textwidget( bui.textwidget(
parent=self._container, parent=self._container,
size=(0, 0), size=(0, 0),
h_align='center', h_align='center',
v_align='center', v_align='center',
maxwidth=200, maxwidth=self._c_width * 0.8,
scale=0.8, scale=0.8,
color=(0.6, 0.56, 0.6), color=(0.6, 0.56, 0.6),
position=(self._c_width * 0.5, self._c_height * 0.5), 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 self._showing_not_signed_in_screen = True
return 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 self._showing_not_signed_in_screen = False
# At first we don't want to show anything until we've gotten a state. # At first we don't want to show anything until we've gotten a
# Update: In this situation we now simply show our existing state # state. Update: In this situation we now simply show our
# but give the start/stop button a loading message and disallow its # existing state but give the start/stop button a loading
# use. This keeps things a lot less jumpy looking and allows selecting # message and disallow its use. This keeps things a lot less
# playlists/etc without having to wait for the server each time # jumpy looking and allows selecting playlists/etc without
# back to the ui. # having to wait for the server each time back to the ui.
if self._waiting_for_initial_state and bool(False): if self._waiting_for_initial_state and bool(False):
bui.textwidget( bui.textwidget(
parent=self._container, parent=self._container,
@ -537,16 +628,21 @@ class PrivateGatherTab(GatherTab):
) )
return 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). # Show our count (possibly with a link to purchase more).
if ( if (
not self._waiting_for_initial_state not self._waiting_for_initial_state
and hostingstate.party_code is None and hostingstate.party_code is None
and hostingstate.tickets_to_host_now != 0 and hostingstate.tickets_to_host_now != 0
and not havegoldpass
): ):
if not bui.app.ui_v1.use_toolbars: 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, parent=self._container,
position=( position=(
self._c_width - 210 + 125, self._c_width - 210 + 125,
@ -555,24 +651,25 @@ class PrivateGatherTab(GatherTab):
autoselect=True, autoselect=True,
scale=0.6, scale=0.6,
size=(120, 60), size=(120, 60),
textcolor=(0.2, 1, 0.2), textcolor=(1.0, 0.6, 0.0),
label=bui.charstr(bui.SpecialChar.TICKET), label=bui.charstr(bui.SpecialChar.TOKEN),
color=(0.65, 0.5, 0.8), color=(0.65, 0.5, 0.8),
on_activate_call=self._on_get_tickets_press, on_activate_call=self._on_get_tokens_press,
) )
else: else:
self._ticket_count_text = bui.textwidget( self._token_count_text = bui.textwidget(
parent=self._container, parent=self._container,
scale=0.6, scale=0.6,
position=( position=(
self._c_width - 210 + 125, self._c_width - 210 + 125,
self._c_height - 44, self._c_height - 44,
), ),
color=(0.2, 1, 0.2), color=(1.0, 0.6, 0.0),
h_align='center', h_align='center',
v_align='center', v_align='center',
) )
# Set initial ticket count.
# Set initial token count.
self._update_currency_ui() self._update_currency_ui()
v = self._c_height - 90 v = self._c_height - 90
@ -594,7 +691,8 @@ class PrivateGatherTab(GatherTab):
v -= 100 v -= 100
if hostingstate.party_code is None: 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( bui.textwidget(
parent=self._container, parent=self._container,
size=(0, 0), 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: 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( bui.textwidget(
parent=self._container, parent=self._container,
size=(0, 0), size=(0, 0),
@ -811,12 +915,12 @@ class PrivateGatherTab(GatherTab):
resource='gatherWindow.hostingUnavailableText' resource='gatherWindow.hostingUnavailableText'
) )
elif hostingstate.party_code is None: elif hostingstate.party_code is None:
ticon = bui.charstr(bui.SpecialChar.TICKET) ticon = bui.charstr(bui.SpecialChar.TOKEN)
nowtickets = hostingstate.tickets_to_host_now nowtokens = hostingstate.tokens_to_host_now
if nowtickets > 0: if nowtokens > 0 and not havegoldpass:
btnlabel = bui.Lstr( btnlabel = bui.Lstr(
resource='gatherWindow.startHostingPaidText', resource='gatherWindow.startHostingPaidText',
subs=[('${COST}', f'{ticon}{nowtickets}')], subs=[('${COST}', f'{ticon}{nowtokens}')],
) )
else: else:
btnlabel = bui.Lstr( btnlabel = bui.Lstr(
@ -867,8 +971,8 @@ class PrivateGatherTab(GatherTab):
) )
def _connect_to_party_code(self, code: str) -> None: def _connect_to_party_code(self, code: str) -> None:
# Ignore attempted followup sends for a few seconds. # Ignore attempted followup sends for a few seconds. (this will
# (this will reset if we get a response) # reset if we get a response)
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -915,21 +1019,30 @@ class PrivateGatherTab(GatherTab):
bui.getsound('click01').play() 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 we're not hosting, start.
if self._hostingstate.party_code is None: if self._hostingstate.party_code is None:
# If there's a ticket cost, make sure we have enough tickets. # If there's a token cost, make sure we have enough tokens
if self._hostingstate.tickets_to_host_now > 0: # or a gold pass.
ticket_count: int | None if self._hostingstate.tokens_to_host_now > 0:
try:
ticket_count = plus.get_v1_account_ticket_count() if (
except Exception: not self._v2state.gold_pass
# FIXME: should add a bui.NotSignedInError we can use here. and self._v2state.tokens
ticket_count = None < self._hostingstate.tokens_to_host_now
ticket_cost = self._hostingstate.tickets_to_host_now ):
if ticket_count is not None and ticket_count < ticket_cost: show_get_tokens_prompt()
show_get_tickets_prompt()
bui.getsound('error').play() bui.getsound('error').play()
return return
self._last_action_send_time = time.time() self._last_action_send_time = time.time()
plus.add_v1_account_transaction( plus.add_v1_account_transaction(
{ {
@ -937,6 +1050,7 @@ class PrivateGatherTab(GatherTab):
'config': dataclass_to_dict(self._hostingconfig), 'config': dataclass_to_dict(self._hostingconfig),
'region_pings': bui.app.net.zone_pings, 'region_pings': bui.app.net.zone_pings,
'expire_time': time.time() + 20, 'expire_time': time.time() + 20,
'datacode': self._v2state.datacode,
}, },
callback=bui.WeakCall(self._hosting_state_response), callback=bui.WeakCall(self._hosting_state_response),
) )

View File

@ -779,3 +779,31 @@ class GetTokensWindow(bui.Window):
def _on_learn_more_press(self, url: str) -> None: def _on_learn_more_press(self, url: str) -> None:
bui.open_url(url) 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,
)

View File

@ -80,6 +80,11 @@ TextGraphics::TextGraphics() {
g.y_size *= 0.55f; g.y_size *= 0.55f;
} }
} }
// Special handling of tokens icon.
if (index == 29) {
extra_advance += 0.12f;
}
// Special case for v2 logo. // Special case for v2 logo.
if (index == 99) { if (index == 99) {
g.pen_offset_y += 0.25f; g.pen_offset_y += 0.25f;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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 char* kEngineVersion = "1.7.36";
const int kEngineApiVersion = 8; const int kEngineApiVersion = 8;

View File

@ -288,3 +288,27 @@ class StoreQueryResponse(Response):
available_purchases: Annotated[list[Purchase], IOAttrs('p')] available_purchases: Annotated[list[Purchase], IOAttrs('p')]
token_info_url: Annotated[str, IOAttrs('tiu')] 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')]

View File

@ -64,6 +64,7 @@ class PrivateHostingState:
unavailable_error: str | None = None unavailable_error: str | None = None
party_code: str | None = None party_code: str | None = None
tickets_to_host_now: int = 0 tickets_to_host_now: int = 0
tokens_to_host_now: int = 0
minutes_until_free_host: float | None = None minutes_until_free_host: float | None = None
free_host_minutes_remaining: float | None = None free_host_minutes_remaining: float | None = None