# Released under the MIT License. See LICENSE for details.
#
"""UI for player profile upgrades."""
from __future__ import annotations
import time
import weakref
from typing import TYPE_CHECKING
import bacommon.bs
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any
from bauiv1lib.profile.edit import EditProfileWindow
[docs]
class ProfileUpgradeWindow(bui.Window):
"""Window for player profile upgrades to global."""
def __init__(
self,
edit_profile_window: EditProfileWindow,
transition: str = 'in_right',
):
# if bui.app.classic is None:
# raise RuntimeError('This requires classic.')
plus = bui.app.plus
assert plus is not None
self._r = 'editProfileWindow'
self._cost: int | None = None
uiscale = bui.app.ui_v1.uiscale
self._width = 750 if uiscale is bui.UIScale.SMALL else 680
self._height = 450 if uiscale is bui.UIScale.SMALL else 350
self._base_scale = (
1.92
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.2
)
yoffs = -60.0 if uiscale is bui.UIScale.SMALL else 0
self._upgrade_start_time: float | None = None
self._name = edit_profile_window.getname()
self._edit_profile_window = weakref.ref(edit_profile_window)
top_extra = 15 if uiscale is bui.UIScale.SMALL else 15
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
toolbar_visibility='menu_store_no_back',
transition=transition,
scale=self._base_scale,
stack_offset=(
(0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
),
)
)
cancel_button = bui.buttonwidget(
parent=self._root_widget,
position=(52, self._height - 290 + yoffs),
size=(155, 60),
scale=0.8,
autoselect=True,
label=bui.Lstr(resource='cancelText'),
on_activate_call=self._cancel,
)
self._upgrade_button = bui.buttonwidget(
parent=self._root_widget,
position=(self._width - 190, self._height - 290 + yoffs),
size=(155, 60),
scale=0.8,
autoselect=True,
label=bui.Lstr(resource='upgradeText'),
on_activate_call=self._on_upgrade_press,
)
bui.containerwidget(
edit=self._root_widget,
cancel_button=cancel_button,
start_button=self._upgrade_button,
selected_child=self._upgrade_button,
)
# assert bui.app.classic is not None
bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 38 + yoffs),
size=(0, 0),
text=bui.Lstr(resource=f'{self._r}.upgradeToGlobalProfileText'),
color=bui.app.ui_v1.title_color,
maxwidth=self._width * 0.45,
scale=1.0,
h_align='center',
v_align='center',
)
# assert bui.app.classic is not None
bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 100 + yoffs),
size=(0, 0),
text=bui.Lstr(resource=f'{self._r}.upgradeProfileInfoText'),
color=bui.app.ui_v1.infotextcolor,
maxwidth=self._width * 0.8,
scale=0.7,
h_align='center',
v_align='center',
)
self._status_text = bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 160 + yoffs),
size=(0, 0),
text=bui.Lstr(
resource=f'{self._r}.checkingAvailabilityText',
subs=[('${NAME}', self._name)],
),
color=(0.8, 0.4, 0.0),
maxwidth=self._width * 0.8,
scale=0.65,
h_align='center',
v_align='center',
)
self._price_text = bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 230 + yoffs),
size=(0, 0),
text='',
color=(0.2, 1, 0.2),
maxwidth=self._width * 0.8,
scale=1.5,
h_align='center',
v_align='center',
)
assert plus.accounts.primary is not None
with plus.accounts.primary:
plus.cloud.send_message_cb(
bacommon.bs.GlobalProfileCheckMessage(self._name),
on_response=bui.WeakCall(
self._on_global_profile_check_response
),
)
self._status: str | None = 'waiting'
self._update_timer = bui.AppTimer(
1.023, bui.WeakCall(self._update), repeat=True
)
self._update()
def _on_global_profile_check_response(
self, response: bacommon.bs.GlobalProfileCheckResponse | Exception
) -> None:
if isinstance(response, Exception):
bui.textwidget(
edit=self._status_text,
text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
color=(1, 0, 0),
)
self._status = 'error'
bui.buttonwidget(
edit=self._upgrade_button,
color=(0.4, 0.4, 0.4),
textcolor=(0.5, 0.5, 0.5),
)
else:
self._cost = response.ticket_cost
if response.available:
bui.textwidget(
edit=self._status_text,
text=bui.Lstr(
resource=f'{self._r}.availableText',
subs=[('${NAME}', self._name)],
),
color=(0, 1, 0),
)
bui.textwidget(
edit=self._price_text,
text=bui.charstr(bui.SpecialChar.TICKET) + str(self._cost),
)
self._status = None
else:
bui.textwidget(
edit=self._status_text,
text=bui.Lstr(
resource=f'{self._r}.unavailableText',
subs=[('${NAME}', self._name)],
),
color=(1, 0, 0),
)
self._status = 'unavailable'
bui.buttonwidget(
edit=self._upgrade_button,
color=(0.4, 0.4, 0.4),
textcolor=(0.5, 0.5, 0.5),
)
def _on_upgrade_press(self) -> None:
if self._status is None:
plus = bui.app.plus
classic = bui.app.classic
assert plus is not None
assert classic is not None
assert self._cost is not None
# tickets = plus.get_v1_account_ticket_count()
tickets = classic.tickets
if tickets < self._cost:
bui.getsound('error').play()
bui.screenmessage(
bui.Lstr(resource='notEnoughTicketsText'),
color=(1, 0, 0),
)
return
bui.screenmessage(
bui.Lstr(resource='purchasingText'), color=(0, 1, 0)
)
self._status = 'pre_upgrading'
# Now we tell the original editor to save the profile, add
# an upgrade transaction, and then sit and wait for
# everything to go through.
edit_profile_window = self._edit_profile_window()
if edit_profile_window is None:
print('profile upgrade: original edit window gone')
return
success = edit_profile_window.save(transition_out=False)
if not success:
print('profile upgrade: error occurred saving profile')
bui.screenmessage(
bui.Lstr(resource='errorText'), color=(1, 0, 0)
)
bui.getsound('error').play()
return
plus.add_v1_account_transaction(
{'type': 'UPGRADE_PROFILE', 'name': self._name}
)
plus.run_v1_account_transactions()
self._status = 'upgrading'
self._upgrade_start_time = time.time()
else:
bui.getsound('error').play()
def _update(self) -> None:
plus = bui.app.plus
assert plus is not None
# If our originating window dies at any point, cancel.
edit_profile_window = self._edit_profile_window()
if edit_profile_window is None:
self._cancel()
return
# Once we've kicked off an upgrade attempt and all transactions
# go through, we're done.
if (
self._status == 'upgrading'
and not plus.have_outstanding_v1_account_transactions()
):
self._status = 'exiting'
bui.containerwidget(edit=self._root_widget, transition='out_right')
edit_profile_window = self._edit_profile_window()
if edit_profile_window is None:
print(
'profile upgrade transition out:'
' original edit window gone'
)
return
bui.getsound('gunCocking').play()
edit_profile_window.reload_window()
def _cancel(self) -> None:
# If we recently sent out an upgrade request, disallow canceling
# for a bit.
if (
self._upgrade_start_time is not None
and time.time() - self._upgrade_start_time < 10.0
):
bui.getsound('error').play()
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
# Docs-generation hack; import some stuff that we likely only forward-declared
# in our actual source code so that docs tools can find it.
from typing import (Coroutine, Any, Literal, Callable,
Generator, Awaitable, Sequence, Self)
import asyncio
from concurrent.futures import Future
from pathlib import Path
from enum import Enum