mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2026-08-15 13:04:30 +00:00
459 lines
15 KiB
Python
459 lines
15 KiB
Python
# Released under the MIT License. See LICENSE for details.
|
|
#
|
|
"""UI functionality for entering promo codes."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
import logging
|
|
from typing import TYPE_CHECKING, override
|
|
|
|
import bauiv1 as bui
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any
|
|
|
|
|
|
class SendInfoWindow(bui.MainWindow):
|
|
"""Window for sending info to the developer."""
|
|
|
|
def __init__(
|
|
self,
|
|
transition: str | None = 'in_scale',
|
|
origin_widget: bui.Widget | None = None,
|
|
):
|
|
|
|
# Need to wrangle our own transition-out in modal mode.
|
|
if origin_widget is not None:
|
|
self._transition_out = 'out_scale'
|
|
else:
|
|
self._transition_out = 'out_right'
|
|
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
|
|
width = 1200 if uiscale is bui.UIScale.SMALL else 600
|
|
height = 600 if uiscale is bui.UIScale.SMALL else 300
|
|
|
|
self._r = 'promoCodeWindow'
|
|
|
|
# Do some fancy math to fill all available screen area up to the
|
|
# size of our backing container. This lets us fit to the exact
|
|
# screen shape at small ui scale.
|
|
screensize = bui.get_virtual_screen_size()
|
|
scale = (
|
|
2.0
|
|
if uiscale is bui.UIScale.SMALL
|
|
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
|
|
)
|
|
# Calc screen size in our local container space and clamp to a
|
|
# bit smaller than our container size.
|
|
# target_width = min(width - 80, screensize[0] / scale)
|
|
target_height = min(height - 80, screensize[1] / scale)
|
|
|
|
# To get top/left coords, go to the center of our window and
|
|
# offset by half the width/height of our target area.
|
|
yoffs = 0.5 * height + 0.5 * target_height + 20.0
|
|
|
|
assert bui.app.classic is not None
|
|
super().__init__(
|
|
root_widget=bui.containerwidget(
|
|
size=(width, height),
|
|
toolbar_visibility=(
|
|
'menu_full' if bui.in_main_menu() else 'menu_minimal'
|
|
),
|
|
scale=scale,
|
|
),
|
|
transition=transition,
|
|
origin_widget=origin_widget,
|
|
# We're affected by screen size only at small ui-scale.
|
|
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
|
|
)
|
|
|
|
if uiscale is not bui.UIScale.SMALL:
|
|
close_button = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
id=f'{self.main_window_id_prefix}|close',
|
|
position=(25, yoffs - 35),
|
|
size=(60, 60),
|
|
scale=0.7,
|
|
autoselect=True,
|
|
label=bui.charstr(bui.SpecialChar.BACK),
|
|
button_type='backSmall',
|
|
on_activate_call=self.main_window_back,
|
|
)
|
|
|
|
else:
|
|
close_button = None
|
|
|
|
v = yoffs - 74
|
|
|
|
v += -30 if uiscale is bui.UIScale.SMALL else 10
|
|
bui.textwidget(
|
|
parent=self._root_widget,
|
|
text=bui.Lstr(resource='sendInfoDescriptionText'),
|
|
maxwidth=width * 0.9,
|
|
position=(width * 0.5, v),
|
|
color=(0.7, 0.7, 0.7, 1.0),
|
|
size=(0, 0),
|
|
scale=0.8,
|
|
h_align='center',
|
|
v_align='center',
|
|
)
|
|
v -= 100
|
|
|
|
txoffs = -270
|
|
bui.textwidget(
|
|
parent=self._root_widget,
|
|
text=bui.Lstr(resource='descriptionText'),
|
|
position=(width * 0.5 + txoffs + 22, v),
|
|
color=(0.8, 0.8, 0.8, 1.0),
|
|
size=(90, 30),
|
|
h_align='right',
|
|
maxwidth=100,
|
|
)
|
|
v -= 8
|
|
|
|
self._text_field = bui.textwidget(
|
|
parent=self._root_widget,
|
|
id=f'{self.main_window_id_prefix}|desc',
|
|
position=(width * 0.5 + txoffs + 125, v),
|
|
size=(380, 46),
|
|
text='',
|
|
h_align='left',
|
|
v_align='center',
|
|
max_chars=64,
|
|
color=(0.9, 0.9, 0.9, 1.0),
|
|
description=bui.Lstr(resource='descriptionText'),
|
|
editable=True,
|
|
autoselect=True,
|
|
padding=4,
|
|
on_return_press_call=self._activate_enter_button,
|
|
)
|
|
if close_button is not None:
|
|
bui.widget(edit=close_button, down_widget=self._text_field)
|
|
|
|
v -= 79
|
|
b_width = 200
|
|
self._enter_button = btn2 = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
id=f'{self.main_window_id_prefix}|enter',
|
|
position=(width * 0.5 - b_width * 0.5, v),
|
|
size=(b_width, 60),
|
|
scale=1.0,
|
|
label=bui.Lstr(
|
|
resource='submitText', fallback_resource=f'{self._r}.enterText'
|
|
),
|
|
on_activate_call=self._do_enter,
|
|
autoselect=True,
|
|
)
|
|
bui.containerwidget(
|
|
edit=self._root_widget,
|
|
start_button=btn2,
|
|
selected_child=self._text_field,
|
|
)
|
|
if close_button is not None:
|
|
bui.containerwidget(
|
|
edit=self._root_widget,
|
|
cancel_button=close_button,
|
|
)
|
|
else:
|
|
bui.containerwidget(
|
|
edit=self._root_widget, on_cancel_call=self.main_window_back
|
|
)
|
|
|
|
@override
|
|
def get_main_window_state(self) -> bui.MainWindowState:
|
|
# Support recreating our window for back/refresh purposes.
|
|
cls = type(self)
|
|
|
|
return bui.BasicMainWindowState(
|
|
create_call=lambda transition, origin_widget: cls(
|
|
transition=transition,
|
|
origin_widget=origin_widget,
|
|
)
|
|
)
|
|
|
|
@override
|
|
def main_window_should_preserve_selection(self) -> bool:
|
|
return True
|
|
|
|
def _activate_enter_button(self) -> None:
|
|
self._enter_button.activate()
|
|
|
|
def _do_enter(self) -> None:
|
|
|
|
plus = bui.app.plus
|
|
assert plus is not None
|
|
|
|
description: Any = bui.textwidget(query=self._text_field)
|
|
assert isinstance(description, str)
|
|
|
|
# no-op if we're not in control.
|
|
if not self.main_window_has_control():
|
|
return
|
|
self.main_window_back()
|
|
|
|
# Used for things like unlocking shared playlists or linking
|
|
# accounts: talk directly to V1 server via transactions.
|
|
bui.app.create_async_task(_send_info(description))
|
|
|
|
|
|
class SendInfoWindowLegacyModal(bui.Window):
|
|
"""Window for sending info to the developer."""
|
|
|
|
def __init__(
|
|
self,
|
|
transition: str | None = 'in_scale',
|
|
origin_widget: bui.Widget | None = None,
|
|
):
|
|
# pylint: disable=too-many-locals
|
|
|
|
# Need to wrangle our own transition-out in modal mode.
|
|
if origin_widget is not None:
|
|
self._transition_out = 'out_scale'
|
|
else:
|
|
self._transition_out = 'out_right'
|
|
|
|
uiscale = bui.app.ui_v1.uiscale
|
|
|
|
width = 450
|
|
height = 200
|
|
|
|
self._r = 'promoCodeWindow'
|
|
|
|
# Do some fancy math to fill all available screen area up to the
|
|
# size of our backing container. This lets us fit to the exact
|
|
# screen shape at small ui scale.
|
|
screensize = bui.get_virtual_screen_size()
|
|
scale = (
|
|
2.0
|
|
if uiscale is bui.UIScale.SMALL
|
|
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
|
|
)
|
|
# Calc screen size in our local container space and clamp to a
|
|
# bit smaller than our container size.
|
|
# target_width = min(width - 80, screensize[0] / scale)
|
|
target_height = min(height - 80, screensize[1] / scale)
|
|
|
|
# To get top/left coords, go to the center of our window and
|
|
# offset by half the width/height of our target area.
|
|
yoffs = 0.5 * height + 0.5 * target_height + 20.0
|
|
|
|
scale_origin = (
|
|
None
|
|
if origin_widget is None
|
|
else origin_widget.get_screen_space_center()
|
|
)
|
|
|
|
assert bui.app.classic is not None
|
|
super().__init__(
|
|
root_widget=bui.containerwidget(
|
|
size=(width, height),
|
|
toolbar_visibility=('menu_minimal_no_back'),
|
|
transition=transition,
|
|
scale_origin_stack_offset=scale_origin,
|
|
scale=scale,
|
|
darken_behind=True,
|
|
),
|
|
)
|
|
|
|
close_button = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
scale=0.5,
|
|
position=(30, yoffs - 30),
|
|
size=(60, 60),
|
|
on_activate_call=self._do_back,
|
|
autoselect=True,
|
|
color=(0.55, 0.5, 0.6),
|
|
label=bui.charstr(bui.SpecialChar.CLOSE),
|
|
textcolor=(1, 1, 1),
|
|
)
|
|
|
|
v = yoffs - 74
|
|
|
|
txoffs = -200
|
|
bui.textwidget(
|
|
parent=self._root_widget,
|
|
text=bui.Lstr(resource=f'{self._r}.codeText'),
|
|
position=(width * 0.5 + txoffs + 22, v),
|
|
color=(0.8, 0.8, 0.8, 1.0),
|
|
size=(90, 30),
|
|
h_align='right',
|
|
maxwidth=100,
|
|
)
|
|
v -= 8
|
|
|
|
self._text_field = bui.textwidget(
|
|
parent=self._root_widget,
|
|
position=(width * 0.5 + txoffs + 125, v),
|
|
size=(280, 46),
|
|
text='',
|
|
h_align='left',
|
|
v_align='center',
|
|
max_chars=64,
|
|
color=(0.9, 0.9, 0.9, 1.0),
|
|
description=bui.Lstr(resource=f'{self._r}.codeText'),
|
|
editable=True,
|
|
autoselect=True,
|
|
padding=4,
|
|
on_return_press_call=self._activate_enter_button,
|
|
)
|
|
if close_button is not None:
|
|
bui.widget(edit=close_button, down_widget=self._text_field)
|
|
|
|
v -= 79
|
|
b_width = 200
|
|
self._enter_button = btn2 = bui.buttonwidget(
|
|
parent=self._root_widget,
|
|
position=(width * 0.5 - b_width * 0.5, v),
|
|
size=(b_width, 60),
|
|
scale=1.0,
|
|
label=bui.Lstr(
|
|
resource='submitText', fallback_resource=f'{self._r}.enterText'
|
|
),
|
|
on_activate_call=self._do_enter,
|
|
autoselect=True,
|
|
)
|
|
bui.containerwidget(
|
|
edit=self._root_widget,
|
|
start_button=btn2,
|
|
selected_child=self._text_field,
|
|
)
|
|
if close_button is not None:
|
|
bui.containerwidget(
|
|
edit=self._root_widget,
|
|
cancel_button=close_button,
|
|
)
|
|
|
|
def _do_back(self) -> None:
|
|
# pylint: disable=cyclic-import
|
|
|
|
# Handle modal case:
|
|
|
|
# no-op if our underlying widget is dead or on its way out.
|
|
if not self._root_widget or self._root_widget.transitioning_out:
|
|
return
|
|
|
|
bui.containerwidget(
|
|
edit=self._root_widget, transition=self._transition_out
|
|
)
|
|
|
|
def _activate_enter_button(self) -> None:
|
|
self._enter_button.activate()
|
|
|
|
def _do_enter(self) -> None:
|
|
# pylint: disable=cyclic-import
|
|
# from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
|
|
|
plus = bui.app.plus
|
|
assert plus is not None
|
|
|
|
description: Any = bui.textwidget(query=self._text_field)
|
|
assert isinstance(description, str)
|
|
|
|
# no-op if our underlying widget is dead or on its way out.
|
|
if not self._root_widget or self._root_widget.transitioning_out:
|
|
return
|
|
bui.containerwidget(
|
|
edit=self._root_widget, transition=self._transition_out
|
|
)
|
|
|
|
# Used for things like unlocking shared playlists or linking
|
|
# accounts: talk directly to V1 server via transactions.
|
|
if plus.get_v1_account_state() != 'signed_in':
|
|
bui.screenmessage(
|
|
bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
|
|
)
|
|
bui.getsound('error').play()
|
|
else:
|
|
plus.add_v1_account_transaction(
|
|
{
|
|
'type': 'PROMO_CODE',
|
|
'expire_time': time.time() + 5,
|
|
'code': description,
|
|
}
|
|
)
|
|
plus.run_v1_account_transactions()
|
|
|
|
|
|
async def _send_info(description: str) -> None:
|
|
from bacommon.classic import SendInfoMessage
|
|
|
|
plus = bui.app.plus
|
|
assert plus is not None
|
|
|
|
classic = bui.app.classic
|
|
assert classic is not None
|
|
|
|
ui_pause: bui.RootUIUpdatePause | None = None # pylint: disable=W0612
|
|
|
|
try:
|
|
# Don't allow *anything* if our V2 transport connection isn't up.
|
|
if not plus.cloud.connected:
|
|
bui.screenmessage(
|
|
bui.Lstr(resource='internal.unavailableNoConnectionText'),
|
|
color=(1, 0, 0),
|
|
)
|
|
bui.getsound('error').play()
|
|
return
|
|
|
|
# Pause root ui updates so stuff like token counts don't change
|
|
# automatically until we've run any client-effect animations
|
|
# resulting from this message.
|
|
ui_pause = bui.RootUIUpdatePause()
|
|
|
|
# Ship to V2 server, with or without account info.
|
|
if plus.accounts.primary is not None:
|
|
with plus.accounts.primary:
|
|
response = await plus.cloud.send_message_async(
|
|
SendInfoMessage(description)
|
|
)
|
|
else:
|
|
response = await plus.cloud.send_message_async(
|
|
SendInfoMessage(description)
|
|
)
|
|
|
|
# Support simple message printing from v2 server.
|
|
if response.message is not None:
|
|
bui.screenmessage(
|
|
bui.Lstr(translate=('serverResponses', response.message)),
|
|
color=(0, 1, 0),
|
|
)
|
|
# As of newer builds we support client-effects too.
|
|
if response.effects:
|
|
classic.run_bs_client_effects(response.effects)
|
|
|
|
# If V2 handled it, we're done.
|
|
if response.handled:
|
|
return
|
|
|
|
# Ok; V2 didn't handle it. Try V1 if we're signed in there.
|
|
if plus.get_v1_account_state() != 'signed_in':
|
|
bui.screenmessage(
|
|
bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
|
|
)
|
|
bui.getsound('error').play()
|
|
return
|
|
|
|
# Push it along to v1 as an old style code. Allow v2 response to
|
|
# sub in its own code.
|
|
plus.add_v1_account_transaction(
|
|
{
|
|
'type': 'PROMO_CODE',
|
|
'expire_time': time.time() + 5,
|
|
'code': (
|
|
description
|
|
if response.legacy_code is None
|
|
else response.legacy_code
|
|
),
|
|
}
|
|
)
|
|
plus.run_v1_account_transactions()
|
|
except Exception:
|
|
logging.exception('Error sending promo code.')
|
|
bui.screenmessage('Error sending code (see log).', color=(1, 0, 0))
|
|
bui.getsound('error').play()
|
|
finally:
|
|
# Make sure ui-pause is dead even if something is holding
|
|
# on to this stack frame.
|
|
ui_pause = None
|