bastd.ui.league.rankwindow

UI related to league rank.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""UI related to league rank."""
  4
  5from __future__ import annotations
  6
  7import copy
  8from typing import TYPE_CHECKING
  9
 10import _ba
 11import ba
 12from bastd.ui import popup as popup_ui
 13
 14if TYPE_CHECKING:
 15    from typing import Any
 16
 17
 18class LeagueRankWindow(ba.Window):
 19    """Window for showing league rank."""
 20
 21    def __init__(self,
 22                 transition: str = 'in_right',
 23                 modal: bool = False,
 24                 origin_widget: ba.Widget | None = None):
 25        ba.set_analytics_screen('League Rank Window')
 26
 27        self._league_rank_data: dict[str, Any] | None = None
 28        self._modal = modal
 29
 30        # If they provided an origin-widget, scale up from that.
 31        scale_origin: tuple[float, float] | None
 32        if origin_widget is not None:
 33            self._transition_out = 'out_scale'
 34            scale_origin = origin_widget.get_screen_space_center()
 35            transition = 'in_scale'
 36        else:
 37            self._transition_out = 'out_right'
 38            scale_origin = None
 39
 40        uiscale = ba.app.ui.uiscale
 41        self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120
 42        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 43        self._height = (657 if uiscale is ba.UIScale.SMALL else
 44                        710 if uiscale is ba.UIScale.MEDIUM else 800)
 45        self._r = 'coopSelectWindow'
 46        self._rdict = ba.app.lang.get_resource(self._r)
 47        top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
 48
 49        self._league_url_arg = ''
 50
 51        self._is_current_season = False
 52        self._can_do_more_button = True
 53
 54        super().__init__(root_widget=ba.containerwidget(
 55            size=(self._width, self._height + top_extra),
 56            stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (
 57                0, 10) if uiscale is ba.UIScale.MEDIUM else (0, 0),
 58            transition=transition,
 59            scale_origin_stack_offset=scale_origin,
 60            scale=(1.2 if uiscale is ba.UIScale.SMALL else
 61                   0.93 if uiscale is ba.UIScale.MEDIUM else 0.8)))
 62
 63        self._back_button = btn = ba.buttonwidget(
 64            parent=self._root_widget,
 65            position=(75 + x_inset, self._height - 87 -
 66                      (4 if uiscale is ba.UIScale.SMALL else 0)),
 67            size=(120, 60),
 68            scale=1.2,
 69            autoselect=True,
 70            label=ba.Lstr(resource='doneText' if self._modal else 'backText'),
 71            button_type=None if self._modal else 'back',
 72            on_activate_call=self._back)
 73
 74        self._title_text = ba.textwidget(
 75            parent=self._root_widget,
 76            position=(self._width * 0.5, self._height - 56),
 77            size=(0, 0),
 78            text=ba.Lstr(
 79                resource='league.leagueRankText',
 80                fallback_resource='coopSelectWindow.powerRankingText'),
 81            h_align='center',
 82            color=ba.app.ui.title_color,
 83            scale=1.4,
 84            maxwidth=600,
 85            v_align='center')
 86
 87        ba.buttonwidget(edit=btn,
 88                        button_type='backSmall',
 89                        position=(75 + x_inset, self._height - 87 -
 90                                  (2 if uiscale is ba.UIScale.SMALL else 0)),
 91                        size=(60, 55),
 92                        label=ba.charstr(ba.SpecialChar.BACK))
 93
 94        self._scroll_width = self._width - (130 + 2 * x_inset)
 95        self._scroll_height = self._height - 160
 96        self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
 97                                             highlight=False,
 98                                             position=(65 + x_inset, 70),
 99                                             size=(self._scroll_width,
100                                                   self._scroll_height),
101                                             center_small_content=True)
102        ba.widget(edit=self._scrollwidget, autoselect=True)
103        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
104        ba.containerwidget(edit=self._root_widget,
105                           cancel_button=self._back_button,
106                           selected_child=self._back_button)
107
108        self._last_power_ranking_query_time: float | None = None
109        self._doing_power_ranking_query = False
110
111        self._subcontainer: ba.Widget | None = None
112        self._subcontainerwidth = 800
113        self._subcontainerheight = 483
114        self._power_ranking_score_widgets: list[ba.Widget] = []
115
116        self._season_popup_menu: popup_ui.PopupMenu | None = None
117        self._requested_season: str | None = None
118        self._season: str | None = None
119
120        # take note of our account state; we'll refresh later if this changes
121        self._account_state = _ba.get_v1_account_state()
122
123        self._refresh()
124        self._restore_state()
125
126        # if we've got cached power-ranking data already, display it
127        info = ba.app.accounts_v1.get_cached_league_rank_data()
128        if info is not None:
129            self._update_for_league_rank_data(info)
130
131        self._update_timer = ba.Timer(1.0,
132                                      ba.WeakCall(self._update),
133                                      timetype=ba.TimeType.REAL,
134                                      repeat=True)
135        self._update(show=(info is None))
136
137    def _on_achievements_press(self) -> None:
138        from bastd.ui import achievements
139        # only allow this for all-time or the current season
140        # (we currently don't keep specific achievement data for old seasons)
141        if self._season == 'a' or self._is_current_season:
142            achievements.AchievementsWindow(
143                position=(self._power_ranking_achievements_button.
144                          get_screen_space_center()))
145        else:
146            ba.screenmessage(ba.Lstr(
147                resource='achievementsUnavailableForOldSeasonsText',
148                fallback_resource='unavailableText'),
149                             color=(1, 0, 0))
150            ba.playsound(ba.getsound('error'))
151
152    def _on_activity_mult_press(self) -> None:
153        from bastd.ui import confirm
154        txt = ba.Lstr(
155            resource='coopSelectWindow.activenessAllTimeInfoText'
156            if self._season == 'a' else 'coopSelectWindow.activenessInfoText',
157            subs=[('${MAX}',
158                   str(_ba.get_v1_account_misc_read_val('activenessMax',
159                                                        1.0)))])
160        confirm.ConfirmWindow(txt,
161                              cancel_button=False,
162                              width=460,
163                              height=150,
164                              origin_widget=self._activity_mult_button)
165
166    def _on_pro_mult_press(self) -> None:
167        from bastd.ui import confirm
168        txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText',
169                      subs=[('${PERCENT}',
170                             str(
171                                 _ba.get_v1_account_misc_read_val(
172                                     'proPowerRankingBoost', 10))),
173                            ('${PRO}',
174                             ba.Lstr(resource='store.bombSquadProNameText',
175                                     subs=[('${APP_NAME}',
176                                            ba.Lstr(resource='titleText'))]))])
177        confirm.ConfirmWindow(txt,
178                              cancel_button=False,
179                              width=460,
180                              height=130,
181                              origin_widget=self._pro_mult_button)
182
183    def _on_trophies_press(self) -> None:
184        from bastd.ui.trophies import TrophiesWindow
185        info = self._league_rank_data
186        if info is not None:
187            TrophiesWindow(position=self._power_ranking_trophies_button.
188                           get_screen_space_center(),
189                           data=info)
190        else:
191            ba.playsound(ba.getsound('error'))
192
193    def _on_power_ranking_query_response(self,
194                                         data: dict[str, Any] | None) -> None:
195        self._doing_power_ranking_query = False
196        # important: *only* cache this if we requested the current season..
197        if data is not None and data.get('s', None) is None:
198            ba.app.accounts_v1.cache_league_rank_data(data)
199        # always store a copy locally though (even for other seasons)
200        self._league_rank_data = copy.deepcopy(data)
201        self._update_for_league_rank_data(data)
202
203    def _restore_state(self) -> None:
204        pass
205
206    def _update(self, show: bool = False) -> None:
207
208        cur_time = ba.time(ba.TimeType.REAL)
209
210        # if our account state has changed, refresh our UI
211        account_state = _ba.get_v1_account_state()
212        if account_state != self._account_state:
213            self._account_state = account_state
214            self._save_state()
215            self._refresh()
216
217            # and power ranking too...
218            if not self._doing_power_ranking_query:
219                self._last_power_ranking_query_time = None
220
221        # send off a new power-ranking query if its been long enough or our
222        # requested season has changed or whatnot..
223        if not self._doing_power_ranking_query and (
224                self._last_power_ranking_query_time is None
225                or cur_time - self._last_power_ranking_query_time > 30.0):
226            try:
227                if show:
228                    ba.textwidget(edit=self._league_title_text, text='')
229                    ba.textwidget(edit=self._league_text, text='')
230                    ba.textwidget(edit=self._league_number_text, text='')
231                    ba.textwidget(
232                        edit=self._your_power_ranking_text,
233                        text=ba.Lstr(value='${A}...',
234                                     subs=[('${A}',
235                                            ba.Lstr(resource='loadingText'))]))
236                    ba.textwidget(edit=self._to_ranked_text, text='')
237                    ba.textwidget(edit=self._power_ranking_rank_text, text='')
238                    ba.textwidget(edit=self._season_ends_text, text='')
239                    ba.textwidget(edit=self._trophy_counts_reset_text, text='')
240            except Exception:
241                ba.print_exception('Error showing updated rank info.')
242
243            self._last_power_ranking_query_time = cur_time
244            self._doing_power_ranking_query = True
245            _ba.power_ranking_query(season=self._requested_season,
246                                    callback=ba.WeakCall(
247                                        self._on_power_ranking_query_response))
248
249    def _refresh(self) -> None:
250        # pylint: disable=too-many-statements
251
252        # (re)create the sub-container if need be..
253        if self._subcontainer is not None:
254            self._subcontainer.delete()
255        self._subcontainer = ba.containerwidget(
256            parent=self._scrollwidget,
257            size=(self._subcontainerwidth, self._subcontainerheight),
258            background=False)
259
260        w_parent = self._subcontainer
261        v = self._subcontainerheight - 20
262
263        v -= 0
264
265        h2 = 80
266        v2 = v - 60
267        worth_color = (0.6, 0.6, 0.65)
268        tally_color = (0.5, 0.6, 0.8)
269        spc = 43
270
271        h_offs_tally = 150
272        tally_maxwidth = 120
273        v2 -= 70
274
275        ba.textwidget(parent=w_parent,
276                      position=(h2 - 60, v2 + 106),
277                      size=(0, 0),
278                      flatness=1.0,
279                      shadow=0.0,
280                      text=ba.Lstr(resource='coopSelectWindow.pointsText'),
281                      h_align='left',
282                      v_align='center',
283                      scale=0.8,
284                      color=(1, 1, 1, 0.3),
285                      maxwidth=200)
286
287        self._power_ranking_achievements_button = ba.buttonwidget(
288            parent=w_parent,
289            position=(h2 - 60, v2 + 10),
290            size=(200, 80),
291            icon=ba.gettexture('achievementsIcon'),
292            autoselect=True,
293            on_activate_call=ba.WeakCall(self._on_achievements_press),
294            up_widget=self._back_button,
295            left_widget=self._back_button,
296            color=(0.5, 0.5, 0.6),
297            textcolor=(0.7, 0.7, 0.8),
298            label='')
299
300        self._power_ranking_achievement_total_text = ba.textwidget(
301            parent=w_parent,
302            position=(h2 + h_offs_tally, v2 + 45),
303            size=(0, 0),
304            flatness=1.0,
305            shadow=0.0,
306            text='-',
307            h_align='left',
308            v_align='center',
309            scale=0.8,
310            color=tally_color,
311            maxwidth=tally_maxwidth)
312
313        v2 -= 80
314
315        self._power_ranking_trophies_button = ba.buttonwidget(
316            parent=w_parent,
317            position=(h2 - 60, v2 + 10),
318            size=(200, 80),
319            icon=ba.gettexture('medalSilver'),
320            autoselect=True,
321            on_activate_call=ba.WeakCall(self._on_trophies_press),
322            left_widget=self._back_button,
323            color=(0.5, 0.5, 0.6),
324            textcolor=(0.7, 0.7, 0.8),
325            label='')
326        self._power_ranking_trophies_total_text = ba.textwidget(
327            parent=w_parent,
328            position=(h2 + h_offs_tally, v2 + 45),
329            size=(0, 0),
330            flatness=1.0,
331            shadow=0.0,
332            text='-',
333            h_align='left',
334            v_align='center',
335            scale=0.8,
336            color=tally_color,
337            maxwidth=tally_maxwidth)
338
339        v2 -= 100
340
341        ba.textwidget(
342            parent=w_parent,
343            position=(h2 - 60, v2 + 86),
344            size=(0, 0),
345            flatness=1.0,
346            shadow=0.0,
347            text=ba.Lstr(resource='coopSelectWindow.multipliersText'),
348            h_align='left',
349            v_align='center',
350            scale=0.8,
351            color=(1, 1, 1, 0.3),
352            maxwidth=200)
353
354        self._activity_mult_button: ba.Widget | None
355        if _ba.get_v1_account_misc_read_val('act', False):
356            self._activity_mult_button = ba.buttonwidget(
357                parent=w_parent,
358                position=(h2 - 60, v2 + 10),
359                size=(200, 60),
360                icon=ba.gettexture('heart'),
361                icon_color=(0.5, 0, 0.5),
362                label=ba.Lstr(resource='coopSelectWindow.activityText'),
363                autoselect=True,
364                on_activate_call=ba.WeakCall(self._on_activity_mult_press),
365                left_widget=self._back_button,
366                color=(0.5, 0.5, 0.6),
367                textcolor=(0.7, 0.7, 0.8))
368
369            self._activity_mult_text = ba.textwidget(
370                parent=w_parent,
371                position=(h2 + h_offs_tally, v2 + 40),
372                size=(0, 0),
373                flatness=1.0,
374                shadow=0.0,
375                text='-',
376                h_align='left',
377                v_align='center',
378                scale=0.8,
379                color=tally_color,
380                maxwidth=tally_maxwidth)
381            v2 -= 65
382        else:
383            self._activity_mult_button = None
384
385        self._pro_mult_button = ba.buttonwidget(
386            parent=w_parent,
387            position=(h2 - 60, v2 + 10),
388            size=(200, 60),
389            icon=ba.gettexture('logo'),
390            icon_color=(0.3, 0, 0.3),
391            label=ba.Lstr(resource='store.bombSquadProNameText',
392                          subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
393                                ]),
394            autoselect=True,
395            on_activate_call=ba.WeakCall(self._on_pro_mult_press),
396            left_widget=self._back_button,
397            color=(0.5, 0.5, 0.6),
398            textcolor=(0.7, 0.7, 0.8))
399
400        self._pro_mult_text = ba.textwidget(parent=w_parent,
401                                            position=(h2 + h_offs_tally,
402                                                      v2 + 40),
403                                            size=(0, 0),
404                                            flatness=1.0,
405                                            shadow=0.0,
406                                            text='-',
407                                            h_align='left',
408                                            v_align='center',
409                                            scale=0.8,
410                                            color=tally_color,
411                                            maxwidth=tally_maxwidth)
412        v2 -= 30
413
414        v2 -= spc
415        ba.textwidget(parent=w_parent,
416                      position=(h2 + h_offs_tally - 10 - 40, v2 + 35),
417                      size=(0, 0),
418                      flatness=1.0,
419                      shadow=0.0,
420                      text=ba.Lstr(resource='finalScoreText'),
421                      h_align='right',
422                      v_align='center',
423                      scale=0.9,
424                      color=worth_color,
425                      maxwidth=150)
426        self._power_ranking_total_text = ba.textwidget(
427            parent=w_parent,
428            position=(h2 + h_offs_tally - 40, v2 + 35),
429            size=(0, 0),
430            flatness=1.0,
431            shadow=0.0,
432            text='-',
433            h_align='left',
434            v_align='center',
435            scale=0.9,
436            color=tally_color,
437            maxwidth=tally_maxwidth)
438
439        self._season_show_text = ba.textwidget(
440            parent=w_parent,
441            position=(390 - 15, v - 20),
442            size=(0, 0),
443            color=(0.6, 0.6, 0.7),
444            maxwidth=200,
445            text=ba.Lstr(resource='showText'),
446            h_align='right',
447            v_align='center',
448            scale=0.8,
449            shadow=0,
450            flatness=1.0)
451
452        self._league_title_text = ba.textwidget(parent=w_parent,
453                                                position=(470, v - 97),
454                                                size=(0, 0),
455                                                color=(0.6, 0.6, 0.7),
456                                                maxwidth=230,
457                                                text='',
458                                                h_align='center',
459                                                v_align='center',
460                                                scale=0.9,
461                                                shadow=0,
462                                                flatness=1.0)
463
464        self._league_text_scale = 1.8
465        self._league_text_maxwidth = 210
466        self._league_text = ba.textwidget(parent=w_parent,
467                                          position=(470, v - 140),
468                                          size=(0, 0),
469                                          color=(1, 1, 1),
470                                          maxwidth=self._league_text_maxwidth,
471                                          text='-',
472                                          h_align='center',
473                                          v_align='center',
474                                          scale=self._league_text_scale,
475                                          shadow=1.0,
476                                          flatness=1.0)
477        self._league_number_base_pos = (470, v - 140)
478        self._league_number_text = ba.textwidget(parent=w_parent,
479                                                 position=(470, v - 140),
480                                                 size=(0, 0),
481                                                 color=(1, 1, 1),
482                                                 maxwidth=100,
483                                                 text='',
484                                                 h_align='left',
485                                                 v_align='center',
486                                                 scale=0.8,
487                                                 shadow=1.0,
488                                                 flatness=1.0)
489
490        self._your_power_ranking_text = ba.textwidget(parent=w_parent,
491                                                      position=(470,
492                                                                v - 142 - 70),
493                                                      size=(0, 0),
494                                                      color=(0.6, 0.6, 0.7),
495                                                      maxwidth=230,
496                                                      text='',
497                                                      h_align='center',
498                                                      v_align='center',
499                                                      scale=0.9,
500                                                      shadow=0,
501                                                      flatness=1.0)
502
503        self._to_ranked_text = ba.textwidget(parent=w_parent,
504                                             position=(470, v - 250 - 70),
505                                             size=(0, 0),
506                                             color=(0.6, 0.6, 0.7),
507                                             maxwidth=230,
508                                             text='',
509                                             h_align='center',
510                                             v_align='center',
511                                             scale=0.8,
512                                             shadow=0,
513                                             flatness=1.0)
514
515        self._power_ranking_rank_text = ba.textwidget(parent=w_parent,
516                                                      position=(473,
517                                                                v - 210 - 70),
518                                                      size=(0, 0),
519                                                      big=False,
520                                                      text='-',
521                                                      h_align='center',
522                                                      v_align='center',
523                                                      scale=1.0)
524
525        self._season_ends_text = ba.textwidget(parent=w_parent,
526                                               position=(470, v - 380),
527                                               size=(0, 0),
528                                               color=(0.6, 0.6, 0.6),
529                                               maxwidth=230,
530                                               text='',
531                                               h_align='center',
532                                               v_align='center',
533                                               scale=0.9,
534                                               shadow=0,
535                                               flatness=1.0)
536        self._trophy_counts_reset_text = ba.textwidget(
537            parent=w_parent,
538            position=(470, v - 410),
539            size=(0, 0),
540            color=(0.5, 0.5, 0.5),
541            maxwidth=230,
542            text='Trophy counts will reset next season.',
543            h_align='center',
544            v_align='center',
545            scale=0.8,
546            shadow=0,
547            flatness=1.0)
548
549        self._power_ranking_score_widgets = []
550
551        self._power_ranking_score_v = v - 56
552
553        h = 707
554        v -= 451
555
556        self._see_more_button = ba.buttonwidget(parent=w_parent,
557                                                label=self._rdict.seeMoreText,
558                                                position=(h, v),
559                                                color=(0.5, 0.5, 0.6),
560                                                textcolor=(0.7, 0.7, 0.8),
561                                                size=(230, 60),
562                                                autoselect=True,
563                                                on_activate_call=ba.WeakCall(
564                                                    self._on_more_press))
565
566    def _on_more_press(self) -> None:
567        our_login_id = _ba.get_public_login_id()
568        # our_login_id = _bs.get_account_misc_read_val_2(
569        #     'resolvedAccountID', None)
570        if not self._can_do_more_button or our_login_id is None:
571            ba.playsound(ba.getsound('error'))
572            ba.screenmessage(ba.Lstr(resource='unavailableText'),
573                             color=(1, 0, 0))
574            return
575        if self._season is None:
576            season_str = ''
577        else:
578            season_str = (
579                '&season=' +
580                ('all_time' if self._season == 'a' else self._season))
581        if self._league_url_arg != '':
582            league_str = '&league=' + self._league_url_arg
583        else:
584            league_str = ''
585        ba.open_url(_ba.get_master_server_address() +
586                    '/highscores?list=powerRankings&v=2' + league_str +
587                    season_str + '&player=' + our_login_id)
588
589    def _update_for_league_rank_data(self,
590                                     data: dict[str, Any] | None) -> None:
591        # pylint: disable=too-many-statements
592        # pylint: disable=too-many-branches
593        # pylint: disable=too-many-locals
594        if not self._root_widget:
595            return
596        accounts = ba.app.accounts_v1
597        in_top = (data is not None and data['rank'] is not None)
598        eq_text = self._rdict.powerRankingPointsEqualsText
599        pts_txt = self._rdict.powerRankingPointsText
600        num_text = ba.Lstr(resource='numberText').evaluate()
601        do_percent = False
602        finished_season_unranked = False
603        self._can_do_more_button = True
604        extra_text = ''
605        if _ba.get_v1_account_state() != 'signed_in':
606            status_text = '(' + ba.Lstr(
607                resource='notSignedInText').evaluate() + ')'
608        elif in_top:
609            assert data is not None
610            status_text = num_text.replace('${NUMBER}', str(data['rank']))
611        elif data is not None:
612            try:
613                # handle old seasons where we didn't wind up ranked
614                # at the end..
615                if not data['scores']:
616                    status_text = (
617                        self._rdict.powerRankingFinishedSeasonUnrankedText)
618                    extra_text = ''
619                    finished_season_unranked = True
620                    self._can_do_more_button = False
621                else:
622                    our_points = accounts.get_league_rank_points(data)
623                    progress = float(our_points) / max(1,
624                                                       data['scores'][-1][1])
625                    status_text = str(int(progress * 100.0)) + '%'
626                    extra_text = (
627                        '\n' +
628                        self._rdict.powerRankingPointsToRankedText.replace(
629                            '${CURRENT}', str(our_points)).replace(
630                                '${REMAINING}', str(data['scores'][-1][1])))
631                    do_percent = True
632            except Exception:
633                ba.print_exception('Error updating power ranking.')
634                status_text = self._rdict.powerRankingNotInTopText.replace(
635                    '${NUMBER}', str(data['listSize']))
636                extra_text = ''
637        else:
638            status_text = '-'
639
640        self._season = data['s'] if data is not None else None
641
642        v = self._subcontainerheight - 20
643        popup_was_selected = False
644        if self._season_popup_menu is not None:
645            btn = self._season_popup_menu.get_button()
646            assert self._subcontainer
647            if self._subcontainer.get_selected_child() == btn:
648                popup_was_selected = True
649            btn.delete()
650        season_choices = []
651        season_choices_display = []
652        did_first = False
653        self._is_current_season = False
654        if data is not None:
655            # build our list of seasons we have available
656            for ssn in data['sl']:
657                season_choices.append(ssn)
658                if ssn != 'a' and not did_first:
659                    season_choices_display.append(
660                        ba.Lstr(resource='league.currentSeasonText',
661                                subs=[('${NUMBER}', ssn)]))
662                    did_first = True
663                    # if we either did not specify a season or specified the
664                    # first, we're looking at the current..
665                    if self._season in [ssn, None]:
666                        self._is_current_season = True
667                elif ssn == 'a':
668                    season_choices_display.append(
669                        ba.Lstr(resource='league.allTimeText'))
670                else:
671                    season_choices_display.append(
672                        ba.Lstr(resource='league.seasonText',
673                                subs=[('${NUMBER}', ssn)]))
674            assert self._subcontainer
675            self._season_popup_menu = popup_ui.PopupMenu(
676                parent=self._subcontainer,
677                position=(390, v - 45),
678                width=150,
679                button_size=(200, 50),
680                choices=season_choices,
681                on_value_change_call=ba.WeakCall(self._on_season_change),
682                choices_display=season_choices_display,
683                current_choice=self._season)
684            if popup_was_selected:
685                ba.containerwidget(
686                    edit=self._subcontainer,
687                    selected_child=self._season_popup_menu.get_button())
688            ba.widget(edit=self._see_more_button, show_buffer_bottom=100)
689            ba.widget(edit=self._season_popup_menu.get_button(),
690                      up_widget=self._back_button)
691            ba.widget(edit=self._back_button,
692                      down_widget=self._power_ranking_achievements_button,
693                      right_widget=self._season_popup_menu.get_button())
694
695        ba.textwidget(edit=self._league_title_text,
696                      text='' if self._season == 'a' else ba.Lstr(
697                          resource='league.leagueText'))
698
699        if data is None:
700            lname = ''
701            lnum = ''
702            lcolor = (1, 1, 1)
703            self._league_url_arg = ''
704        elif self._season == 'a':
705            lname = ba.Lstr(resource='league.allTimeText').evaluate()
706            lnum = ''
707            lcolor = (1, 1, 1)
708            self._league_url_arg = ''
709        else:
710            lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else ''
711            lname = ba.Lstr(translate=('leagueNames',
712                                       data['l']['n'])).evaluate()
713            lcolor = data['l']['c']
714            self._league_url_arg = (data['l']['n'] + '_' +
715                                    str(data['l']['i'])).lower()
716
717        to_end_string: ba.Lstr | str
718        if data is None or self._season == 'a' or data['se'] is None:
719            to_end_string = ''
720            show_season_end = False
721        else:
722            show_season_end = True
723            days_to_end = data['se'][0]
724            minutes_to_end = data['se'][1]
725            if days_to_end > 0:
726                to_end_string = ba.Lstr(resource='league.seasonEndsDaysText',
727                                        subs=[('${NUMBER}', str(days_to_end))])
728            elif days_to_end == 0 and minutes_to_end >= 60:
729                to_end_string = ba.Lstr(resource='league.seasonEndsHoursText',
730                                        subs=[('${NUMBER}',
731                                               str(minutes_to_end // 60))])
732            elif days_to_end == 0 and minutes_to_end >= 0:
733                to_end_string = ba.Lstr(
734                    resource='league.seasonEndsMinutesText',
735                    subs=[('${NUMBER}', str(minutes_to_end))])
736            else:
737                to_end_string = ba.Lstr(
738                    resource='league.seasonEndedDaysAgoText',
739                    subs=[('${NUMBER}', str(-(days_to_end + 1)))])
740
741        ba.textwidget(edit=self._season_ends_text, text=to_end_string)
742        ba.textwidget(edit=self._trophy_counts_reset_text,
743                      text=ba.Lstr(resource='league.trophyCountsResetText')
744                      if self._is_current_season and show_season_end else '')
745
746        ba.textwidget(edit=self._league_text, text=lname, color=lcolor)
747        l_text_width = min(
748            self._league_text_maxwidth,
749            _ba.get_string_width(lname, suppress_warning=True) *
750            self._league_text_scale)
751        ba.textwidget(
752            edit=self._league_number_text,
753            text=lnum,
754            color=lcolor,
755            position=(self._league_number_base_pos[0] + l_text_width * 0.5 + 8,
756                      self._league_number_base_pos[1] + 10))
757        ba.textwidget(
758            edit=self._to_ranked_text,
759            text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate() +
760            '' + extra_text if do_percent else '')
761
762        ba.textwidget(
763            edit=self._your_power_ranking_text,
764            text=ba.Lstr(
765                resource='rankText',
766                fallback_resource='coopSelectWindow.yourPowerRankingText') if
767            (not do_percent) else '')
768
769        ba.textwidget(edit=self._power_ranking_rank_text,
770                      position=(473, v - 70 - (170 if do_percent else 220)),
771                      text=status_text,
772                      big=(in_top or do_percent),
773                      scale=3.0 if (in_top or do_percent) else
774                      0.7 if finished_season_unranked else 1.0)
775
776        if self._activity_mult_button is not None:
777            if data is None or data['act'] is None:
778                ba.buttonwidget(edit=self._activity_mult_button,
779                                textcolor=(0.7, 0.7, 0.8, 0.5),
780                                icon_color=(0.5, 0, 0.5, 0.3))
781                ba.textwidget(edit=self._activity_mult_text, text='     -')
782            else:
783                ba.buttonwidget(edit=self._activity_mult_button,
784                                textcolor=(0.7, 0.7, 0.8, 1.0),
785                                icon_color=(0.5, 0, 0.5, 1.0))
786                # pylint: disable=consider-using-f-string
787                ba.textwidget(edit=self._activity_mult_text,
788                              text='x ' + ('%.2f' % data['act']))
789
790        have_pro = False if data is None else data['p']
791        pro_mult = 1.0 + float(
792            _ba.get_v1_account_misc_read_val('proPowerRankingBoost',
793                                             0.0)) * 0.01
794        # pylint: disable=consider-using-f-string
795        ba.textwidget(edit=self._pro_mult_text,
796                      text='     -' if
797                      (data is None or not have_pro) else 'x ' +
798                      ('%.2f' % pro_mult))
799        ba.buttonwidget(edit=self._pro_mult_button,
800                        textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)),
801                        icon_color=(0.5, 0, 0.5) if have_pro else
802                        (0.5, 0, 0.5, 0.2))
803        ba.buttonwidget(edit=self._power_ranking_achievements_button,
804                        label=('' if data is None else
805                               (str(data['a']) + ' ')) +
806                        ba.Lstr(resource='achievementsText').evaluate())
807
808        # for the achievement value, use the number they gave us for
809        # non-current seasons; otherwise calc our own
810        total_ach_value = 0
811        for ach in ba.app.ach.achievements:
812            if ach.complete:
813                total_ach_value += ach.power_ranking_value
814        if self._season != 'a' and not self._is_current_season:
815            if data is not None and 'at' in data:
816                total_ach_value = data['at']
817
818        ba.textwidget(edit=self._power_ranking_achievement_total_text,
819                      text='-' if data is None else
820                      ('+ ' +
821                       pts_txt.replace('${NUMBER}', str(total_ach_value))))
822
823        total_trophies_count = (accounts.get_league_rank_points(
824            data, 'trophyCount'))
825        total_trophies_value = (accounts.get_league_rank_points(
826            data, 'trophies'))
827        ba.buttonwidget(edit=self._power_ranking_trophies_button,
828                        label=('' if data is None else
829                               (str(total_trophies_count) + ' ')) +
830                        ba.Lstr(resource='trophiesText').evaluate())
831        ba.textwidget(
832            edit=self._power_ranking_trophies_total_text,
833            text='-' if data is None else
834            ('+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value))))
835
836        ba.textwidget(
837            edit=self._power_ranking_total_text,
838            text='-' if data is None else eq_text.replace(
839                '${NUMBER}', str(accounts.get_league_rank_points(data))))
840        for widget in self._power_ranking_score_widgets:
841            widget.delete()
842        self._power_ranking_score_widgets = []
843
844        scores = data['scores'] if data is not None else []
845        tally_color = (0.5, 0.6, 0.8)
846        w_parent = self._subcontainer
847        v2 = self._power_ranking_score_v
848
849        for score in scores:
850            h2 = 680
851            is_us = score[3]
852            self._power_ranking_score_widgets.append(
853                ba.textwidget(parent=w_parent,
854                              position=(h2 - 20, v2),
855                              size=(0, 0),
856                              color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
857                              maxwidth=40,
858                              flatness=1.0,
859                              shadow=0.0,
860                              text=num_text.replace('${NUMBER}',
861                                                    str(score[0])),
862                              h_align='right',
863                              v_align='center',
864                              scale=0.5))
865            self._power_ranking_score_widgets.append(
866                ba.textwidget(parent=w_parent,
867                              position=(h2 + 20, v2),
868                              size=(0, 0),
869                              color=(1, 1, 1) if is_us else tally_color,
870                              maxwidth=60,
871                              text=str(score[1]),
872                              flatness=1.0,
873                              shadow=0.0,
874                              h_align='center',
875                              v_align='center',
876                              scale=0.7))
877            txt = ba.textwidget(parent=w_parent,
878                                position=(h2 + 60, v2 - (28 * 0.5) / 0.9),
879                                size=(210 / 0.9, 28),
880                                color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
881                                maxwidth=210,
882                                flatness=1.0,
883                                shadow=0.0,
884                                autoselect=True,
885                                selectable=True,
886                                click_activate=True,
887                                text=score[2],
888                                h_align='left',
889                                v_align='center',
890                                scale=0.9)
891            self._power_ranking_score_widgets.append(txt)
892            ba.textwidget(edit=txt,
893                          on_activate_call=ba.Call(self._show_account_info,
894                                                   score[4], txt))
895            assert self._season_popup_menu is not None
896            ba.widget(edit=txt,
897                      left_widget=self._season_popup_menu.get_button())
898            v2 -= 28
899
900    def _show_account_info(self, account_id: str,
901                           textwidget: ba.Widget) -> None:
902        from bastd.ui.account import viewer
903        ba.playsound(ba.getsound('swish'))
904        viewer.AccountViewerWindow(
905            account_id=account_id,
906            position=textwidget.get_screen_space_center())
907
908    def _on_season_change(self, value: str) -> None:
909        self._requested_season = value
910        self._last_power_ranking_query_time = None  # make sure we update asap
911        self._update(show=True)
912
913    def _save_state(self) -> None:
914        pass
915
916    def _back(self) -> None:
917        from bastd.ui.coop.browser import CoopBrowserWindow
918        self._save_state()
919        ba.containerwidget(edit=self._root_widget,
920                           transition=self._transition_out)
921        if not self._modal:
922            ba.app.ui.set_main_menu_window(
923                CoopBrowserWindow(transition='in_left').get_root_widget())
class LeagueRankWindow(ba.ui.Window):
 19class LeagueRankWindow(ba.Window):
 20    """Window for showing league rank."""
 21
 22    def __init__(self,
 23                 transition: str = 'in_right',
 24                 modal: bool = False,
 25                 origin_widget: ba.Widget | None = None):
 26        ba.set_analytics_screen('League Rank Window')
 27
 28        self._league_rank_data: dict[str, Any] | None = None
 29        self._modal = modal
 30
 31        # If they provided an origin-widget, scale up from that.
 32        scale_origin: tuple[float, float] | None
 33        if origin_widget is not None:
 34            self._transition_out = 'out_scale'
 35            scale_origin = origin_widget.get_screen_space_center()
 36            transition = 'in_scale'
 37        else:
 38            self._transition_out = 'out_right'
 39            scale_origin = None
 40
 41        uiscale = ba.app.ui.uiscale
 42        self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120
 43        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 44        self._height = (657 if uiscale is ba.UIScale.SMALL else
 45                        710 if uiscale is ba.UIScale.MEDIUM else 800)
 46        self._r = 'coopSelectWindow'
 47        self._rdict = ba.app.lang.get_resource(self._r)
 48        top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
 49
 50        self._league_url_arg = ''
 51
 52        self._is_current_season = False
 53        self._can_do_more_button = True
 54
 55        super().__init__(root_widget=ba.containerwidget(
 56            size=(self._width, self._height + top_extra),
 57            stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (
 58                0, 10) if uiscale is ba.UIScale.MEDIUM else (0, 0),
 59            transition=transition,
 60            scale_origin_stack_offset=scale_origin,
 61            scale=(1.2 if uiscale is ba.UIScale.SMALL else
 62                   0.93 if uiscale is ba.UIScale.MEDIUM else 0.8)))
 63
 64        self._back_button = btn = ba.buttonwidget(
 65            parent=self._root_widget,
 66            position=(75 + x_inset, self._height - 87 -
 67                      (4 if uiscale is ba.UIScale.SMALL else 0)),
 68            size=(120, 60),
 69            scale=1.2,
 70            autoselect=True,
 71            label=ba.Lstr(resource='doneText' if self._modal else 'backText'),
 72            button_type=None if self._modal else 'back',
 73            on_activate_call=self._back)
 74
 75        self._title_text = ba.textwidget(
 76            parent=self._root_widget,
 77            position=(self._width * 0.5, self._height - 56),
 78            size=(0, 0),
 79            text=ba.Lstr(
 80                resource='league.leagueRankText',
 81                fallback_resource='coopSelectWindow.powerRankingText'),
 82            h_align='center',
 83            color=ba.app.ui.title_color,
 84            scale=1.4,
 85            maxwidth=600,
 86            v_align='center')
 87
 88        ba.buttonwidget(edit=btn,
 89                        button_type='backSmall',
 90                        position=(75 + x_inset, self._height - 87 -
 91                                  (2 if uiscale is ba.UIScale.SMALL else 0)),
 92                        size=(60, 55),
 93                        label=ba.charstr(ba.SpecialChar.BACK))
 94
 95        self._scroll_width = self._width - (130 + 2 * x_inset)
 96        self._scroll_height = self._height - 160
 97        self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
 98                                             highlight=False,
 99                                             position=(65 + x_inset, 70),
100                                             size=(self._scroll_width,
101                                                   self._scroll_height),
102                                             center_small_content=True)
103        ba.widget(edit=self._scrollwidget, autoselect=True)
104        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
105        ba.containerwidget(edit=self._root_widget,
106                           cancel_button=self._back_button,
107                           selected_child=self._back_button)
108
109        self._last_power_ranking_query_time: float | None = None
110        self._doing_power_ranking_query = False
111
112        self._subcontainer: ba.Widget | None = None
113        self._subcontainerwidth = 800
114        self._subcontainerheight = 483
115        self._power_ranking_score_widgets: list[ba.Widget] = []
116
117        self._season_popup_menu: popup_ui.PopupMenu | None = None
118        self._requested_season: str | None = None
119        self._season: str | None = None
120
121        # take note of our account state; we'll refresh later if this changes
122        self._account_state = _ba.get_v1_account_state()
123
124        self._refresh()
125        self._restore_state()
126
127        # if we've got cached power-ranking data already, display it
128        info = ba.app.accounts_v1.get_cached_league_rank_data()
129        if info is not None:
130            self._update_for_league_rank_data(info)
131
132        self._update_timer = ba.Timer(1.0,
133                                      ba.WeakCall(self._update),
134                                      timetype=ba.TimeType.REAL,
135                                      repeat=True)
136        self._update(show=(info is None))
137
138    def _on_achievements_press(self) -> None:
139        from bastd.ui import achievements
140        # only allow this for all-time or the current season
141        # (we currently don't keep specific achievement data for old seasons)
142        if self._season == 'a' or self._is_current_season:
143            achievements.AchievementsWindow(
144                position=(self._power_ranking_achievements_button.
145                          get_screen_space_center()))
146        else:
147            ba.screenmessage(ba.Lstr(
148                resource='achievementsUnavailableForOldSeasonsText',
149                fallback_resource='unavailableText'),
150                             color=(1, 0, 0))
151            ba.playsound(ba.getsound('error'))
152
153    def _on_activity_mult_press(self) -> None:
154        from bastd.ui import confirm
155        txt = ba.Lstr(
156            resource='coopSelectWindow.activenessAllTimeInfoText'
157            if self._season == 'a' else 'coopSelectWindow.activenessInfoText',
158            subs=[('${MAX}',
159                   str(_ba.get_v1_account_misc_read_val('activenessMax',
160                                                        1.0)))])
161        confirm.ConfirmWindow(txt,
162                              cancel_button=False,
163                              width=460,
164                              height=150,
165                              origin_widget=self._activity_mult_button)
166
167    def _on_pro_mult_press(self) -> None:
168        from bastd.ui import confirm
169        txt = ba.Lstr(resource='coopSelectWindow.proMultInfoText',
170                      subs=[('${PERCENT}',
171                             str(
172                                 _ba.get_v1_account_misc_read_val(
173                                     'proPowerRankingBoost', 10))),
174                            ('${PRO}',
175                             ba.Lstr(resource='store.bombSquadProNameText',
176                                     subs=[('${APP_NAME}',
177                                            ba.Lstr(resource='titleText'))]))])
178        confirm.ConfirmWindow(txt,
179                              cancel_button=False,
180                              width=460,
181                              height=130,
182                              origin_widget=self._pro_mult_button)
183
184    def _on_trophies_press(self) -> None:
185        from bastd.ui.trophies import TrophiesWindow
186        info = self._league_rank_data
187        if info is not None:
188            TrophiesWindow(position=self._power_ranking_trophies_button.
189                           get_screen_space_center(),
190                           data=info)
191        else:
192            ba.playsound(ba.getsound('error'))
193
194    def _on_power_ranking_query_response(self,
195                                         data: dict[str, Any] | None) -> None:
196        self._doing_power_ranking_query = False
197        # important: *only* cache this if we requested the current season..
198        if data is not None and data.get('s', None) is None:
199            ba.app.accounts_v1.cache_league_rank_data(data)
200        # always store a copy locally though (even for other seasons)
201        self._league_rank_data = copy.deepcopy(data)
202        self._update_for_league_rank_data(data)
203
204    def _restore_state(self) -> None:
205        pass
206
207    def _update(self, show: bool = False) -> None:
208
209        cur_time = ba.time(ba.TimeType.REAL)
210
211        # if our account state has changed, refresh our UI
212        account_state = _ba.get_v1_account_state()
213        if account_state != self._account_state:
214            self._account_state = account_state
215            self._save_state()
216            self._refresh()
217
218            # and power ranking too...
219            if not self._doing_power_ranking_query:
220                self._last_power_ranking_query_time = None
221
222        # send off a new power-ranking query if its been long enough or our
223        # requested season has changed or whatnot..
224        if not self._doing_power_ranking_query and (
225                self._last_power_ranking_query_time is None
226                or cur_time - self._last_power_ranking_query_time > 30.0):
227            try:
228                if show:
229                    ba.textwidget(edit=self._league_title_text, text='')
230                    ba.textwidget(edit=self._league_text, text='')
231                    ba.textwidget(edit=self._league_number_text, text='')
232                    ba.textwidget(
233                        edit=self._your_power_ranking_text,
234                        text=ba.Lstr(value='${A}...',
235                                     subs=[('${A}',
236                                            ba.Lstr(resource='loadingText'))]))
237                    ba.textwidget(edit=self._to_ranked_text, text='')
238                    ba.textwidget(edit=self._power_ranking_rank_text, text='')
239                    ba.textwidget(edit=self._season_ends_text, text='')
240                    ba.textwidget(edit=self._trophy_counts_reset_text, text='')
241            except Exception:
242                ba.print_exception('Error showing updated rank info.')
243
244            self._last_power_ranking_query_time = cur_time
245            self._doing_power_ranking_query = True
246            _ba.power_ranking_query(season=self._requested_season,
247                                    callback=ba.WeakCall(
248                                        self._on_power_ranking_query_response))
249
250    def _refresh(self) -> None:
251        # pylint: disable=too-many-statements
252
253        # (re)create the sub-container if need be..
254        if self._subcontainer is not None:
255            self._subcontainer.delete()
256        self._subcontainer = ba.containerwidget(
257            parent=self._scrollwidget,
258            size=(self._subcontainerwidth, self._subcontainerheight),
259            background=False)
260
261        w_parent = self._subcontainer
262        v = self._subcontainerheight - 20
263
264        v -= 0
265
266        h2 = 80
267        v2 = v - 60
268        worth_color = (0.6, 0.6, 0.65)
269        tally_color = (0.5, 0.6, 0.8)
270        spc = 43
271
272        h_offs_tally = 150
273        tally_maxwidth = 120
274        v2 -= 70
275
276        ba.textwidget(parent=w_parent,
277                      position=(h2 - 60, v2 + 106),
278                      size=(0, 0),
279                      flatness=1.0,
280                      shadow=0.0,
281                      text=ba.Lstr(resource='coopSelectWindow.pointsText'),
282                      h_align='left',
283                      v_align='center',
284                      scale=0.8,
285                      color=(1, 1, 1, 0.3),
286                      maxwidth=200)
287
288        self._power_ranking_achievements_button = ba.buttonwidget(
289            parent=w_parent,
290            position=(h2 - 60, v2 + 10),
291            size=(200, 80),
292            icon=ba.gettexture('achievementsIcon'),
293            autoselect=True,
294            on_activate_call=ba.WeakCall(self._on_achievements_press),
295            up_widget=self._back_button,
296            left_widget=self._back_button,
297            color=(0.5, 0.5, 0.6),
298            textcolor=(0.7, 0.7, 0.8),
299            label='')
300
301        self._power_ranking_achievement_total_text = ba.textwidget(
302            parent=w_parent,
303            position=(h2 + h_offs_tally, v2 + 45),
304            size=(0, 0),
305            flatness=1.0,
306            shadow=0.0,
307            text='-',
308            h_align='left',
309            v_align='center',
310            scale=0.8,
311            color=tally_color,
312            maxwidth=tally_maxwidth)
313
314        v2 -= 80
315
316        self._power_ranking_trophies_button = ba.buttonwidget(
317            parent=w_parent,
318            position=(h2 - 60, v2 + 10),
319            size=(200, 80),
320            icon=ba.gettexture('medalSilver'),
321            autoselect=True,
322            on_activate_call=ba.WeakCall(self._on_trophies_press),
323            left_widget=self._back_button,
324            color=(0.5, 0.5, 0.6),
325            textcolor=(0.7, 0.7, 0.8),
326            label='')
327        self._power_ranking_trophies_total_text = ba.textwidget(
328            parent=w_parent,
329            position=(h2 + h_offs_tally, v2 + 45),
330            size=(0, 0),
331            flatness=1.0,
332            shadow=0.0,
333            text='-',
334            h_align='left',
335            v_align='center',
336            scale=0.8,
337            color=tally_color,
338            maxwidth=tally_maxwidth)
339
340        v2 -= 100
341
342        ba.textwidget(
343            parent=w_parent,
344            position=(h2 - 60, v2 + 86),
345            size=(0, 0),
346            flatness=1.0,
347            shadow=0.0,
348            text=ba.Lstr(resource='coopSelectWindow.multipliersText'),
349            h_align='left',
350            v_align='center',
351            scale=0.8,
352            color=(1, 1, 1, 0.3),
353            maxwidth=200)
354
355        self._activity_mult_button: ba.Widget | None
356        if _ba.get_v1_account_misc_read_val('act', False):
357            self._activity_mult_button = ba.buttonwidget(
358                parent=w_parent,
359                position=(h2 - 60, v2 + 10),
360                size=(200, 60),
361                icon=ba.gettexture('heart'),
362                icon_color=(0.5, 0, 0.5),
363                label=ba.Lstr(resource='coopSelectWindow.activityText'),
364                autoselect=True,
365                on_activate_call=ba.WeakCall(self._on_activity_mult_press),
366                left_widget=self._back_button,
367                color=(0.5, 0.5, 0.6),
368                textcolor=(0.7, 0.7, 0.8))
369
370            self._activity_mult_text = ba.textwidget(
371                parent=w_parent,
372                position=(h2 + h_offs_tally, v2 + 40),
373                size=(0, 0),
374                flatness=1.0,
375                shadow=0.0,
376                text='-',
377                h_align='left',
378                v_align='center',
379                scale=0.8,
380                color=tally_color,
381                maxwidth=tally_maxwidth)
382            v2 -= 65
383        else:
384            self._activity_mult_button = None
385
386        self._pro_mult_button = ba.buttonwidget(
387            parent=w_parent,
388            position=(h2 - 60, v2 + 10),
389            size=(200, 60),
390            icon=ba.gettexture('logo'),
391            icon_color=(0.3, 0, 0.3),
392            label=ba.Lstr(resource='store.bombSquadProNameText',
393                          subs=[('${APP_NAME}', ba.Lstr(resource='titleText'))
394                                ]),
395            autoselect=True,
396            on_activate_call=ba.WeakCall(self._on_pro_mult_press),
397            left_widget=self._back_button,
398            color=(0.5, 0.5, 0.6),
399            textcolor=(0.7, 0.7, 0.8))
400
401        self._pro_mult_text = ba.textwidget(parent=w_parent,
402                                            position=(h2 + h_offs_tally,
403                                                      v2 + 40),
404                                            size=(0, 0),
405                                            flatness=1.0,
406                                            shadow=0.0,
407                                            text='-',
408                                            h_align='left',
409                                            v_align='center',
410                                            scale=0.8,
411                                            color=tally_color,
412                                            maxwidth=tally_maxwidth)
413        v2 -= 30
414
415        v2 -= spc
416        ba.textwidget(parent=w_parent,
417                      position=(h2 + h_offs_tally - 10 - 40, v2 + 35),
418                      size=(0, 0),
419                      flatness=1.0,
420                      shadow=0.0,
421                      text=ba.Lstr(resource='finalScoreText'),
422                      h_align='right',
423                      v_align='center',
424                      scale=0.9,
425                      color=worth_color,
426                      maxwidth=150)
427        self._power_ranking_total_text = ba.textwidget(
428            parent=w_parent,
429            position=(h2 + h_offs_tally - 40, v2 + 35),
430            size=(0, 0),
431            flatness=1.0,
432            shadow=0.0,
433            text='-',
434            h_align='left',
435            v_align='center',
436            scale=0.9,
437            color=tally_color,
438            maxwidth=tally_maxwidth)
439
440        self._season_show_text = ba.textwidget(
441            parent=w_parent,
442            position=(390 - 15, v - 20),
443            size=(0, 0),
444            color=(0.6, 0.6, 0.7),
445            maxwidth=200,
446            text=ba.Lstr(resource='showText'),
447            h_align='right',
448            v_align='center',
449            scale=0.8,
450            shadow=0,
451            flatness=1.0)
452
453        self._league_title_text = ba.textwidget(parent=w_parent,
454                                                position=(470, v - 97),
455                                                size=(0, 0),
456                                                color=(0.6, 0.6, 0.7),
457                                                maxwidth=230,
458                                                text='',
459                                                h_align='center',
460                                                v_align='center',
461                                                scale=0.9,
462                                                shadow=0,
463                                                flatness=1.0)
464
465        self._league_text_scale = 1.8
466        self._league_text_maxwidth = 210
467        self._league_text = ba.textwidget(parent=w_parent,
468                                          position=(470, v - 140),
469                                          size=(0, 0),
470                                          color=(1, 1, 1),
471                                          maxwidth=self._league_text_maxwidth,
472                                          text='-',
473                                          h_align='center',
474                                          v_align='center',
475                                          scale=self._league_text_scale,
476                                          shadow=1.0,
477                                          flatness=1.0)
478        self._league_number_base_pos = (470, v - 140)
479        self._league_number_text = ba.textwidget(parent=w_parent,
480                                                 position=(470, v - 140),
481                                                 size=(0, 0),
482                                                 color=(1, 1, 1),
483                                                 maxwidth=100,
484                                                 text='',
485                                                 h_align='left',
486                                                 v_align='center',
487                                                 scale=0.8,
488                                                 shadow=1.0,
489                                                 flatness=1.0)
490
491        self._your_power_ranking_text = ba.textwidget(parent=w_parent,
492                                                      position=(470,
493                                                                v - 142 - 70),
494                                                      size=(0, 0),
495                                                      color=(0.6, 0.6, 0.7),
496                                                      maxwidth=230,
497                                                      text='',
498                                                      h_align='center',
499                                                      v_align='center',
500                                                      scale=0.9,
501                                                      shadow=0,
502                                                      flatness=1.0)
503
504        self._to_ranked_text = ba.textwidget(parent=w_parent,
505                                             position=(470, v - 250 - 70),
506                                             size=(0, 0),
507                                             color=(0.6, 0.6, 0.7),
508                                             maxwidth=230,
509                                             text='',
510                                             h_align='center',
511                                             v_align='center',
512                                             scale=0.8,
513                                             shadow=0,
514                                             flatness=1.0)
515
516        self._power_ranking_rank_text = ba.textwidget(parent=w_parent,
517                                                      position=(473,
518                                                                v - 210 - 70),
519                                                      size=(0, 0),
520                                                      big=False,
521                                                      text='-',
522                                                      h_align='center',
523                                                      v_align='center',
524                                                      scale=1.0)
525
526        self._season_ends_text = ba.textwidget(parent=w_parent,
527                                               position=(470, v - 380),
528                                               size=(0, 0),
529                                               color=(0.6, 0.6, 0.6),
530                                               maxwidth=230,
531                                               text='',
532                                               h_align='center',
533                                               v_align='center',
534                                               scale=0.9,
535                                               shadow=0,
536                                               flatness=1.0)
537        self._trophy_counts_reset_text = ba.textwidget(
538            parent=w_parent,
539            position=(470, v - 410),
540            size=(0, 0),
541            color=(0.5, 0.5, 0.5),
542            maxwidth=230,
543            text='Trophy counts will reset next season.',
544            h_align='center',
545            v_align='center',
546            scale=0.8,
547            shadow=0,
548            flatness=1.0)
549
550        self._power_ranking_score_widgets = []
551
552        self._power_ranking_score_v = v - 56
553
554        h = 707
555        v -= 451
556
557        self._see_more_button = ba.buttonwidget(parent=w_parent,
558                                                label=self._rdict.seeMoreText,
559                                                position=(h, v),
560                                                color=(0.5, 0.5, 0.6),
561                                                textcolor=(0.7, 0.7, 0.8),
562                                                size=(230, 60),
563                                                autoselect=True,
564                                                on_activate_call=ba.WeakCall(
565                                                    self._on_more_press))
566
567    def _on_more_press(self) -> None:
568        our_login_id = _ba.get_public_login_id()
569        # our_login_id = _bs.get_account_misc_read_val_2(
570        #     'resolvedAccountID', None)
571        if not self._can_do_more_button or our_login_id is None:
572            ba.playsound(ba.getsound('error'))
573            ba.screenmessage(ba.Lstr(resource='unavailableText'),
574                             color=(1, 0, 0))
575            return
576        if self._season is None:
577            season_str = ''
578        else:
579            season_str = (
580                '&season=' +
581                ('all_time' if self._season == 'a' else self._season))
582        if self._league_url_arg != '':
583            league_str = '&league=' + self._league_url_arg
584        else:
585            league_str = ''
586        ba.open_url(_ba.get_master_server_address() +
587                    '/highscores?list=powerRankings&v=2' + league_str +
588                    season_str + '&player=' + our_login_id)
589
590    def _update_for_league_rank_data(self,
591                                     data: dict[str, Any] | None) -> None:
592        # pylint: disable=too-many-statements
593        # pylint: disable=too-many-branches
594        # pylint: disable=too-many-locals
595        if not self._root_widget:
596            return
597        accounts = ba.app.accounts_v1
598        in_top = (data is not None and data['rank'] is not None)
599        eq_text = self._rdict.powerRankingPointsEqualsText
600        pts_txt = self._rdict.powerRankingPointsText
601        num_text = ba.Lstr(resource='numberText').evaluate()
602        do_percent = False
603        finished_season_unranked = False
604        self._can_do_more_button = True
605        extra_text = ''
606        if _ba.get_v1_account_state() != 'signed_in':
607            status_text = '(' + ba.Lstr(
608                resource='notSignedInText').evaluate() + ')'
609        elif in_top:
610            assert data is not None
611            status_text = num_text.replace('${NUMBER}', str(data['rank']))
612        elif data is not None:
613            try:
614                # handle old seasons where we didn't wind up ranked
615                # at the end..
616                if not data['scores']:
617                    status_text = (
618                        self._rdict.powerRankingFinishedSeasonUnrankedText)
619                    extra_text = ''
620                    finished_season_unranked = True
621                    self._can_do_more_button = False
622                else:
623                    our_points = accounts.get_league_rank_points(data)
624                    progress = float(our_points) / max(1,
625                                                       data['scores'][-1][1])
626                    status_text = str(int(progress * 100.0)) + '%'
627                    extra_text = (
628                        '\n' +
629                        self._rdict.powerRankingPointsToRankedText.replace(
630                            '${CURRENT}', str(our_points)).replace(
631                                '${REMAINING}', str(data['scores'][-1][1])))
632                    do_percent = True
633            except Exception:
634                ba.print_exception('Error updating power ranking.')
635                status_text = self._rdict.powerRankingNotInTopText.replace(
636                    '${NUMBER}', str(data['listSize']))
637                extra_text = ''
638        else:
639            status_text = '-'
640
641        self._season = data['s'] if data is not None else None
642
643        v = self._subcontainerheight - 20
644        popup_was_selected = False
645        if self._season_popup_menu is not None:
646            btn = self._season_popup_menu.get_button()
647            assert self._subcontainer
648            if self._subcontainer.get_selected_child() == btn:
649                popup_was_selected = True
650            btn.delete()
651        season_choices = []
652        season_choices_display = []
653        did_first = False
654        self._is_current_season = False
655        if data is not None:
656            # build our list of seasons we have available
657            for ssn in data['sl']:
658                season_choices.append(ssn)
659                if ssn != 'a' and not did_first:
660                    season_choices_display.append(
661                        ba.Lstr(resource='league.currentSeasonText',
662                                subs=[('${NUMBER}', ssn)]))
663                    did_first = True
664                    # if we either did not specify a season or specified the
665                    # first, we're looking at the current..
666                    if self._season in [ssn, None]:
667                        self._is_current_season = True
668                elif ssn == 'a':
669                    season_choices_display.append(
670                        ba.Lstr(resource='league.allTimeText'))
671                else:
672                    season_choices_display.append(
673                        ba.Lstr(resource='league.seasonText',
674                                subs=[('${NUMBER}', ssn)]))
675            assert self._subcontainer
676            self._season_popup_menu = popup_ui.PopupMenu(
677                parent=self._subcontainer,
678                position=(390, v - 45),
679                width=150,
680                button_size=(200, 50),
681                choices=season_choices,
682                on_value_change_call=ba.WeakCall(self._on_season_change),
683                choices_display=season_choices_display,
684                current_choice=self._season)
685            if popup_was_selected:
686                ba.containerwidget(
687                    edit=self._subcontainer,
688                    selected_child=self._season_popup_menu.get_button())
689            ba.widget(edit=self._see_more_button, show_buffer_bottom=100)
690            ba.widget(edit=self._season_popup_menu.get_button(),
691                      up_widget=self._back_button)
692            ba.widget(edit=self._back_button,
693                      down_widget=self._power_ranking_achievements_button,
694                      right_widget=self._season_popup_menu.get_button())
695
696        ba.textwidget(edit=self._league_title_text,
697                      text='' if self._season == 'a' else ba.Lstr(
698                          resource='league.leagueText'))
699
700        if data is None:
701            lname = ''
702            lnum = ''
703            lcolor = (1, 1, 1)
704            self._league_url_arg = ''
705        elif self._season == 'a':
706            lname = ba.Lstr(resource='league.allTimeText').evaluate()
707            lnum = ''
708            lcolor = (1, 1, 1)
709            self._league_url_arg = ''
710        else:
711            lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else ''
712            lname = ba.Lstr(translate=('leagueNames',
713                                       data['l']['n'])).evaluate()
714            lcolor = data['l']['c']
715            self._league_url_arg = (data['l']['n'] + '_' +
716                                    str(data['l']['i'])).lower()
717
718        to_end_string: ba.Lstr | str
719        if data is None or self._season == 'a' or data['se'] is None:
720            to_end_string = ''
721            show_season_end = False
722        else:
723            show_season_end = True
724            days_to_end = data['se'][0]
725            minutes_to_end = data['se'][1]
726            if days_to_end > 0:
727                to_end_string = ba.Lstr(resource='league.seasonEndsDaysText',
728                                        subs=[('${NUMBER}', str(days_to_end))])
729            elif days_to_end == 0 and minutes_to_end >= 60:
730                to_end_string = ba.Lstr(resource='league.seasonEndsHoursText',
731                                        subs=[('${NUMBER}',
732                                               str(minutes_to_end // 60))])
733            elif days_to_end == 0 and minutes_to_end >= 0:
734                to_end_string = ba.Lstr(
735                    resource='league.seasonEndsMinutesText',
736                    subs=[('${NUMBER}', str(minutes_to_end))])
737            else:
738                to_end_string = ba.Lstr(
739                    resource='league.seasonEndedDaysAgoText',
740                    subs=[('${NUMBER}', str(-(days_to_end + 1)))])
741
742        ba.textwidget(edit=self._season_ends_text, text=to_end_string)
743        ba.textwidget(edit=self._trophy_counts_reset_text,
744                      text=ba.Lstr(resource='league.trophyCountsResetText')
745                      if self._is_current_season and show_season_end else '')
746
747        ba.textwidget(edit=self._league_text, text=lname, color=lcolor)
748        l_text_width = min(
749            self._league_text_maxwidth,
750            _ba.get_string_width(lname, suppress_warning=True) *
751            self._league_text_scale)
752        ba.textwidget(
753            edit=self._league_number_text,
754            text=lnum,
755            color=lcolor,
756            position=(self._league_number_base_pos[0] + l_text_width * 0.5 + 8,
757                      self._league_number_base_pos[1] + 10))
758        ba.textwidget(
759            edit=self._to_ranked_text,
760            text=ba.Lstr(resource='coopSelectWindow.toRankedText').evaluate() +
761            '' + extra_text if do_percent else '')
762
763        ba.textwidget(
764            edit=self._your_power_ranking_text,
765            text=ba.Lstr(
766                resource='rankText',
767                fallback_resource='coopSelectWindow.yourPowerRankingText') if
768            (not do_percent) else '')
769
770        ba.textwidget(edit=self._power_ranking_rank_text,
771                      position=(473, v - 70 - (170 if do_percent else 220)),
772                      text=status_text,
773                      big=(in_top or do_percent),
774                      scale=3.0 if (in_top or do_percent) else
775                      0.7 if finished_season_unranked else 1.0)
776
777        if self._activity_mult_button is not None:
778            if data is None or data['act'] is None:
779                ba.buttonwidget(edit=self._activity_mult_button,
780                                textcolor=(0.7, 0.7, 0.8, 0.5),
781                                icon_color=(0.5, 0, 0.5, 0.3))
782                ba.textwidget(edit=self._activity_mult_text, text='     -')
783            else:
784                ba.buttonwidget(edit=self._activity_mult_button,
785                                textcolor=(0.7, 0.7, 0.8, 1.0),
786                                icon_color=(0.5, 0, 0.5, 1.0))
787                # pylint: disable=consider-using-f-string
788                ba.textwidget(edit=self._activity_mult_text,
789                              text='x ' + ('%.2f' % data['act']))
790
791        have_pro = False if data is None else data['p']
792        pro_mult = 1.0 + float(
793            _ba.get_v1_account_misc_read_val('proPowerRankingBoost',
794                                             0.0)) * 0.01
795        # pylint: disable=consider-using-f-string
796        ba.textwidget(edit=self._pro_mult_text,
797                      text='     -' if
798                      (data is None or not have_pro) else 'x ' +
799                      ('%.2f' % pro_mult))
800        ba.buttonwidget(edit=self._pro_mult_button,
801                        textcolor=(0.7, 0.7, 0.8, (1.0 if have_pro else 0.5)),
802                        icon_color=(0.5, 0, 0.5) if have_pro else
803                        (0.5, 0, 0.5, 0.2))
804        ba.buttonwidget(edit=self._power_ranking_achievements_button,
805                        label=('' if data is None else
806                               (str(data['a']) + ' ')) +
807                        ba.Lstr(resource='achievementsText').evaluate())
808
809        # for the achievement value, use the number they gave us for
810        # non-current seasons; otherwise calc our own
811        total_ach_value = 0
812        for ach in ba.app.ach.achievements:
813            if ach.complete:
814                total_ach_value += ach.power_ranking_value
815        if self._season != 'a' and not self._is_current_season:
816            if data is not None and 'at' in data:
817                total_ach_value = data['at']
818
819        ba.textwidget(edit=self._power_ranking_achievement_total_text,
820                      text='-' if data is None else
821                      ('+ ' +
822                       pts_txt.replace('${NUMBER}', str(total_ach_value))))
823
824        total_trophies_count = (accounts.get_league_rank_points(
825            data, 'trophyCount'))
826        total_trophies_value = (accounts.get_league_rank_points(
827            data, 'trophies'))
828        ba.buttonwidget(edit=self._power_ranking_trophies_button,
829                        label=('' if data is None else
830                               (str(total_trophies_count) + ' ')) +
831                        ba.Lstr(resource='trophiesText').evaluate())
832        ba.textwidget(
833            edit=self._power_ranking_trophies_total_text,
834            text='-' if data is None else
835            ('+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value))))
836
837        ba.textwidget(
838            edit=self._power_ranking_total_text,
839            text='-' if data is None else eq_text.replace(
840                '${NUMBER}', str(accounts.get_league_rank_points(data))))
841        for widget in self._power_ranking_score_widgets:
842            widget.delete()
843        self._power_ranking_score_widgets = []
844
845        scores = data['scores'] if data is not None else []
846        tally_color = (0.5, 0.6, 0.8)
847        w_parent = self._subcontainer
848        v2 = self._power_ranking_score_v
849
850        for score in scores:
851            h2 = 680
852            is_us = score[3]
853            self._power_ranking_score_widgets.append(
854                ba.textwidget(parent=w_parent,
855                              position=(h2 - 20, v2),
856                              size=(0, 0),
857                              color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
858                              maxwidth=40,
859                              flatness=1.0,
860                              shadow=0.0,
861                              text=num_text.replace('${NUMBER}',
862                                                    str(score[0])),
863                              h_align='right',
864                              v_align='center',
865                              scale=0.5))
866            self._power_ranking_score_widgets.append(
867                ba.textwidget(parent=w_parent,
868                              position=(h2 + 20, v2),
869                              size=(0, 0),
870                              color=(1, 1, 1) if is_us else tally_color,
871                              maxwidth=60,
872                              text=str(score[1]),
873                              flatness=1.0,
874                              shadow=0.0,
875                              h_align='center',
876                              v_align='center',
877                              scale=0.7))
878            txt = ba.textwidget(parent=w_parent,
879                                position=(h2 + 60, v2 - (28 * 0.5) / 0.9),
880                                size=(210 / 0.9, 28),
881                                color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
882                                maxwidth=210,
883                                flatness=1.0,
884                                shadow=0.0,
885                                autoselect=True,
886                                selectable=True,
887                                click_activate=True,
888                                text=score[2],
889                                h_align='left',
890                                v_align='center',
891                                scale=0.9)
892            self._power_ranking_score_widgets.append(txt)
893            ba.textwidget(edit=txt,
894                          on_activate_call=ba.Call(self._show_account_info,
895                                                   score[4], txt))
896            assert self._season_popup_menu is not None
897            ba.widget(edit=txt,
898                      left_widget=self._season_popup_menu.get_button())
899            v2 -= 28
900
901    def _show_account_info(self, account_id: str,
902                           textwidget: ba.Widget) -> None:
903        from bastd.ui.account import viewer
904        ba.playsound(ba.getsound('swish'))
905        viewer.AccountViewerWindow(
906            account_id=account_id,
907            position=textwidget.get_screen_space_center())
908
909    def _on_season_change(self, value: str) -> None:
910        self._requested_season = value
911        self._last_power_ranking_query_time = None  # make sure we update asap
912        self._update(show=True)
913
914    def _save_state(self) -> None:
915        pass
916
917    def _back(self) -> None:
918        from bastd.ui.coop.browser import CoopBrowserWindow
919        self._save_state()
920        ba.containerwidget(edit=self._root_widget,
921                           transition=self._transition_out)
922        if not self._modal:
923            ba.app.ui.set_main_menu_window(
924                CoopBrowserWindow(transition='in_left').get_root_widget())

Window for showing league rank.

LeagueRankWindow( transition: str = 'in_right', modal: bool = False, origin_widget: _ba.Widget | None = None)
 22    def __init__(self,
 23                 transition: str = 'in_right',
 24                 modal: bool = False,
 25                 origin_widget: ba.Widget | None = None):
 26        ba.set_analytics_screen('League Rank Window')
 27
 28        self._league_rank_data: dict[str, Any] | None = None
 29        self._modal = modal
 30
 31        # If they provided an origin-widget, scale up from that.
 32        scale_origin: tuple[float, float] | None
 33        if origin_widget is not None:
 34            self._transition_out = 'out_scale'
 35            scale_origin = origin_widget.get_screen_space_center()
 36            transition = 'in_scale'
 37        else:
 38            self._transition_out = 'out_right'
 39            scale_origin = None
 40
 41        uiscale = ba.app.ui.uiscale
 42        self._width = 1320 if uiscale is ba.UIScale.SMALL else 1120
 43        x_inset = 100 if uiscale is ba.UIScale.SMALL else 0
 44        self._height = (657 if uiscale is ba.UIScale.SMALL else
 45                        710 if uiscale is ba.UIScale.MEDIUM else 800)
 46        self._r = 'coopSelectWindow'
 47        self._rdict = ba.app.lang.get_resource(self._r)
 48        top_extra = 20 if uiscale is ba.UIScale.SMALL else 0
 49
 50        self._league_url_arg = ''
 51
 52        self._is_current_season = False
 53        self._can_do_more_button = True
 54
 55        super().__init__(root_widget=ba.containerwidget(
 56            size=(self._width, self._height + top_extra),
 57            stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (
 58                0, 10) if uiscale is ba.UIScale.MEDIUM else (0, 0),
 59            transition=transition,
 60            scale_origin_stack_offset=scale_origin,
 61            scale=(1.2 if uiscale is ba.UIScale.SMALL else
 62                   0.93 if uiscale is ba.UIScale.MEDIUM else 0.8)))
 63
 64        self._back_button = btn = ba.buttonwidget(
 65            parent=self._root_widget,
 66            position=(75 + x_inset, self._height - 87 -
 67                      (4 if uiscale is ba.UIScale.SMALL else 0)),
 68            size=(120, 60),
 69            scale=1.2,
 70            autoselect=True,
 71            label=ba.Lstr(resource='doneText' if self._modal else 'backText'),
 72            button_type=None if self._modal else 'back',
 73            on_activate_call=self._back)
 74
 75        self._title_text = ba.textwidget(
 76            parent=self._root_widget,
 77            position=(self._width * 0.5, self._height - 56),
 78            size=(0, 0),
 79            text=ba.Lstr(
 80                resource='league.leagueRankText',
 81                fallback_resource='coopSelectWindow.powerRankingText'),
 82            h_align='center',
 83            color=ba.app.ui.title_color,
 84            scale=1.4,
 85            maxwidth=600,
 86            v_align='center')
 87
 88        ba.buttonwidget(edit=btn,
 89                        button_type='backSmall',
 90                        position=(75 + x_inset, self._height - 87 -
 91                                  (2 if uiscale is ba.UIScale.SMALL else 0)),
 92                        size=(60, 55),
 93                        label=ba.charstr(ba.SpecialChar.BACK))
 94
 95        self._scroll_width = self._width - (130 + 2 * x_inset)
 96        self._scroll_height = self._height - 160
 97        self._scrollwidget = ba.scrollwidget(parent=self._root_widget,
 98                                             highlight=False,
 99                                             position=(65 + x_inset, 70),
100                                             size=(self._scroll_width,
101                                                   self._scroll_height),
102                                             center_small_content=True)
103        ba.widget(edit=self._scrollwidget, autoselect=True)
104        ba.containerwidget(edit=self._scrollwidget, claims_left_right=True)
105        ba.containerwidget(edit=self._root_widget,
106                           cancel_button=self._back_button,
107                           selected_child=self._back_button)
108
109        self._last_power_ranking_query_time: float | None = None
110        self._doing_power_ranking_query = False
111
112        self._subcontainer: ba.Widget | None = None
113        self._subcontainerwidth = 800
114        self._subcontainerheight = 483
115        self._power_ranking_score_widgets: list[ba.Widget] = []
116
117        self._season_popup_menu: popup_ui.PopupMenu | None = None
118        self._requested_season: str | None = None
119        self._season: str | None = None
120
121        # take note of our account state; we'll refresh later if this changes
122        self._account_state = _ba.get_v1_account_state()
123
124        self._refresh()
125        self._restore_state()
126
127        # if we've got cached power-ranking data already, display it
128        info = ba.app.accounts_v1.get_cached_league_rank_data()
129        if info is not None:
130            self._update_for_league_rank_data(info)
131
132        self._update_timer = ba.Timer(1.0,
133                                      ba.WeakCall(self._update),
134                                      timetype=ba.TimeType.REAL,
135                                      repeat=True)
136        self._update(show=(info is None))
Inherited Members
ba.ui.Window
get_root_widget