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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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