# Released under the MIT License. See LICENSE for details. # """Provides a popup for displaying info about any account.""" from __future__ import annotations from typing import TYPE_CHECKING import ba import ba.internal from bastd.ui import popup if TYPE_CHECKING: from typing import Any class AccountViewerWindow(popup.PopupWindow): """Popup window that displays info for an account.""" def __init__( self, account_id: str, profile_id: str | None = None, position: tuple[float, float] = (0.0, 0.0), scale: float | None = None, offset: tuple[float, float] = (0.0, 0.0), ): from ba.internal import is_browser_likely_available, master_server_get self._account_id = account_id self._profile_id = profile_id uiscale = ba.app.ui.uiscale if scale is None: scale = ( 2.6 if uiscale is ba.UIScale.SMALL else 1.8 if uiscale is ba.UIScale.MEDIUM else 1.4 ) self._transitioning_out = False self._width = 400 self._height = ( 300 if uiscale is ba.UIScale.SMALL else 400 if uiscale is ba.UIScale.MEDIUM else 450 ) self._subcontainer: ba.Widget | None = None bg_color = (0.5, 0.4, 0.6) # Creates our _root_widget. popup.PopupWindow.__init__( self, position=position, size=(self._width, self._height), scale=scale, bg_color=bg_color, offset=offset, ) self._cancel_button = ba.buttonwidget( parent=self.root_widget, position=(50, self._height - 30), size=(50, 50), scale=0.5, label='', color=bg_color, on_activate_call=self._on_cancel_press, autoselect=True, icon=ba.gettexture('crossOut'), iconscale=1.2, ) self._title_text = ba.textwidget( parent=self.root_widget, position=(self._width * 0.5, self._height - 20), size=(0, 0), h_align='center', v_align='center', scale=0.6, text=ba.Lstr(resource='playerInfoText'), maxwidth=200, color=(0.7, 0.7, 0.7, 0.7), ) self._scrollwidget = ba.scrollwidget( parent=self.root_widget, size=(self._width - 60, self._height - 70), position=(30, 30), capture_arrows=True, simple_culling_v=10, ) ba.widget(edit=self._scrollwidget, autoselect=True) self._loading_text = ba.textwidget( parent=self._scrollwidget, scale=0.5, text=ba.Lstr( value='${A}...', subs=[('${A}', ba.Lstr(resource='loadingText'))], ), size=(self._width - 60, 100), h_align='center', v_align='center', ) # In cases where the user most likely has a browser/email, lets # offer a 'report this user' button. if ( is_browser_likely_available() and ba.internal.get_v1_account_misc_read_val( 'showAccountExtrasMenu', False ) ): self._extras_menu_button = ba.buttonwidget( parent=self.root_widget, size=(20, 20), position=(self._width - 60, self._height - 30), autoselect=True, label='...', button_type='square', color=(0.64, 0.52, 0.69), textcolor=(0.57, 0.47, 0.57), on_activate_call=self._on_extras_menu_press, ) ba.containerwidget( edit=self.root_widget, cancel_button=self._cancel_button ) master_server_get( 'bsAccountInfo', { 'buildNumber': ba.app.build_number, 'accountID': self._account_id, 'profileID': self._profile_id, }, callback=ba.WeakCall(self._on_query_response), ) def popup_menu_selected_choice( self, window: popup.PopupMenu, choice: str ) -> None: """Called when a menu entry is selected.""" del window # Unused arg. if choice == 'more': self._on_more_press() elif choice == 'report': self._on_report_press() elif choice == 'ban': self._on_ban_press() else: print('ERROR: unknown account info extras menu item:', choice) def popup_menu_closing(self, window: popup.PopupMenu) -> None: """Called when the popup menu is closing.""" def _on_extras_menu_press(self) -> None: choices = ['more', 'report'] choices_display = [ ba.Lstr(resource='coopSelectWindow.seeMoreText'), ba.Lstr(resource='reportThisPlayerText'), ] is_admin = False if is_admin: ba.screenmessage('TEMP FORCING ADMIN ON') choices.append('ban') choices_display.append(ba.Lstr(resource='banThisPlayerText')) uiscale = ba.app.ui.uiscale popup.PopupMenuWindow( position=self._extras_menu_button.get_screen_space_center(), scale=( 2.3 if uiscale is ba.UIScale.SMALL else 1.65 if uiscale is ba.UIScale.MEDIUM else 1.23 ), choices=choices, choices_display=choices_display, current_choice='more', delegate=self, ) def _on_ban_press(self) -> None: ba.internal.add_transaction( {'type': 'BAN_ACCOUNT', 'account': self._account_id} ) ba.internal.run_transactions() def _on_report_press(self) -> None: from bastd.ui import report report.ReportPlayerWindow( self._account_id, origin_widget=self._extras_menu_button ) def _on_more_press(self) -> None: ba.open_url( ba.internal.get_master_server_address() + '/highscores?profile=' + self._account_id ) def _on_query_response(self, data: dict[str, Any] | None) -> None: # FIXME: Tidy this up. # pylint: disable=too-many-locals # pylint: disable=too-many-branches # pylint: disable=too-many-statements # pylint: disable=too-many-nested-blocks if data is None: ba.textwidget( edit=self._loading_text, text=ba.Lstr(resource='internal.unavailableNoConnectionText'), ) else: try: self._loading_text.delete() trophystr = '' try: trophystr = data['trophies'] num = 10 chunks = [ trophystr[i : i + num] for i in range(0, len(trophystr), num) ] trophystr = '\n\n'.join(chunks) if trophystr == '': trophystr = '-' except Exception: ba.print_exception('Error displaying trophies.') account_name_spacing = 15 tscale = 0.65 ts_height = ba.internal.get_string_height( trophystr, suppress_warning=True ) sub_width = self._width - 80 sub_height = ( 200 + ts_height * tscale + account_name_spacing * len(data['accountDisplayStrings']) ) self._subcontainer = ba.containerwidget( parent=self._scrollwidget, size=(sub_width, sub_height), background=False, ) v = sub_height - 20 title_scale = 0.37 center = 0.3 maxwidth_scale = 0.45 showing_character = False if data['profileDisplayString'] is not None: tint_color = (1, 1, 1) try: if data['profile'] is not None: profile = data['profile'] character = ba.app.spaz_appearances.get( profile['character'], None ) if character is not None: tint_color = ( profile['color'] if 'color' in profile else (1, 1, 1) ) tint2_color = ( profile['highlight'] if 'highlight' in profile else (1, 1, 1) ) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture mask_texture = ba.gettexture( 'characterIconMask' ) ba.imagewidget( parent=self._subcontainer, position=(sub_width * center - 40, v - 80), size=(80, 80), color=(1, 1, 1), mask_texture=mask_texture, texture=ba.gettexture(icon_tex), tint_texture=ba.gettexture(tint_tex), tint_color=tint_color, tint2_color=tint2_color, ) v -= 95 except Exception: ba.print_exception('Error displaying character.') ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), h_align='center', v_align='center', scale=0.9, color=ba.safecolor(tint_color, 0.7), shadow=1.0, text=ba.Lstr(value=data['profileDisplayString']), maxwidth=sub_width * maxwidth_scale * 0.75, ) showing_character = True v -= 33 center = 0.75 if showing_character else 0.5 maxwidth_scale = 0.45 if showing_character else 0.9 v = sub_height - 20 if len(data['accountDisplayStrings']) <= 1: account_title = ba.Lstr( resource='settingsWindow.accountText' ) else: account_title = ba.Lstr( resource='accountSettingsWindow.accountsText', fallback_resource='settingsWindow.accountText', ) ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), flatness=1.0, h_align='center', v_align='center', scale=title_scale, color=ba.app.ui.infotextcolor, text=account_title, maxwidth=sub_width * maxwidth_scale, ) draw_small = ( showing_character or len(data['accountDisplayStrings']) > 1 ) v -= 14 if draw_small else 20 for account_string in data['accountDisplayStrings']: ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), h_align='center', v_align='center', scale=0.55 if draw_small else 0.8, text=account_string, maxwidth=sub_width * maxwidth_scale, ) v -= account_name_spacing v += account_name_spacing v -= 25 if showing_character else 29 ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), flatness=1.0, h_align='center', v_align='center', scale=title_scale, color=ba.app.ui.infotextcolor, text=ba.Lstr(resource='rankText'), maxwidth=sub_width * maxwidth_scale, ) v -= 14 if data['rank'] is None: rank_str = '-' suffix_offset = None else: str_raw = ba.Lstr( resource='league.rankInLeagueText' ).evaluate() # FIXME: Would be nice to not have to eval this. rank_str = ba.Lstr( resource='league.rankInLeagueText', subs=[ ('${RANK}', str(data['rank'][2])), ( '${NAME}', ba.Lstr( translate=('leagueNames', data['rank'][0]) ), ), ('${SUFFIX}', ''), ], ).evaluate() rank_str_width = min( sub_width * maxwidth_scale, ba.internal.get_string_width( rank_str, suppress_warning=True ) * 0.55, ) # Only tack our suffix on if its at the end and only for # non-diamond leagues. if ( str_raw.endswith('${SUFFIX}') and data['rank'][0] != 'Diamond' ): suffix_offset = rank_str_width * 0.5 + 2 else: suffix_offset = None ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), h_align='center', v_align='center', scale=0.55, text=rank_str, maxwidth=sub_width * maxwidth_scale, ) if suffix_offset is not None: assert data['rank'] is not None ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center + suffix_offset, v + 3), h_align='left', v_align='center', scale=0.29, flatness=1.0, text='[' + str(data['rank'][1]) + ']', ) v -= 14 str_raw = ba.Lstr(resource='league.rankInLeagueText').evaluate() old_offs = -50 prev_ranks_shown = 0 for prev_rank in data['prevRanks']: rank_str = ba.Lstr( value='${S}: ${I}', subs=[ ( '${S}', ba.Lstr( resource='league.seasonText', subs=[('${NUMBER}', str(prev_rank[0]))], ), ), ( '${I}', ba.Lstr( resource='league.rankInLeagueText', subs=[ ('${RANK}', str(prev_rank[3])), ( '${NAME}', ba.Lstr( translate=( 'leagueNames', prev_rank[1], ) ), ), ('${SUFFIX}', ''), ], ), ), ], ).evaluate() rank_str_width = min( sub_width * maxwidth_scale, ba.internal.get_string_width( rank_str, suppress_warning=True ) * 0.3, ) # Only tack our suffix on if its at the end and only for # non-diamond leagues. if ( str_raw.endswith('${SUFFIX}') and prev_rank[1] != 'Diamond' ): suffix_offset = rank_str_width + 2 else: suffix_offset = None ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center + old_offs, v), h_align='left', v_align='center', scale=0.3, text=rank_str, flatness=1.0, maxwidth=sub_width * maxwidth_scale, ) if suffix_offset is not None: ba.textwidget( parent=self._subcontainer, size=(0, 0), position=( sub_width * center + old_offs + suffix_offset, v + 1, ), h_align='left', v_align='center', scale=0.20, flatness=1.0, text='[' + str(prev_rank[2]) + ']', ) prev_ranks_shown += 1 v -= 10 v -= 13 ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), flatness=1.0, h_align='center', v_align='center', scale=title_scale, color=ba.app.ui.infotextcolor, text=ba.Lstr(resource='achievementsText'), maxwidth=sub_width * maxwidth_scale, ) v -= 14 ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), h_align='center', v_align='center', scale=0.55, text=str(data['achievementsCompleted']) + ' / ' + str(len(ba.app.ach.achievements)), maxwidth=sub_width * maxwidth_scale, ) v -= 25 if prev_ranks_shown == 0 and showing_character: v -= 20 elif prev_ranks_shown == 1 and showing_character: v -= 10 center = 0.5 maxwidth_scale = 0.9 ba.textwidget( parent=self._subcontainer, size=(0, 0), position=(sub_width * center, v), h_align='center', v_align='center', scale=title_scale, color=ba.app.ui.infotextcolor, flatness=1.0, text=ba.Lstr( resource='trophiesThisSeasonText', fallback_resource='trophiesText', ), maxwidth=sub_width * maxwidth_scale, ) v -= 19 ba.textwidget( parent=self._subcontainer, size=(0, ts_height), position=(sub_width * 0.5, v - ts_height * tscale), h_align='center', v_align='top', corner_scale=tscale, text=trophystr, ) except Exception: ba.print_exception('Error displaying account info.') def _on_cancel_press(self) -> None: self._transition_out() def _transition_out(self) -> None: if not self._transitioning_out: self._transitioning_out = True ba.containerwidget(edit=self.root_widget, transition='out_scale') def on_popup_cancel(self) -> None: ba.playsound(ba.getsound('swish')) self._transition_out()