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