bastd.ui.getcurrency
UI functionality for purchasing/acquiring currency.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality for purchasing/acquiring currency.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING 8 9import ba 10import ba.internal 11 12if TYPE_CHECKING: 13 from typing import Any 14 15 16class GetCurrencyWindow(ba.Window): 17 """Window for purchasing/acquiring currency.""" 18 19 def __init__( 20 self, 21 transition: str = 'in_right', 22 from_modal_store: bool = False, 23 modal: bool = False, 24 origin_widget: ba.Widget | None = None, 25 store_back_location: str | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 # pylint: disable=too-many-locals 29 30 ba.set_analytics_screen('Get Tickets Window') 31 32 self._transitioning_out = False 33 self._store_back_location = store_back_location # ew. 34 35 self._ad_button_greyed = False 36 self._smooth_update_timer: ba.Timer | None = None 37 self._ad_button = None 38 self._ad_label = None 39 self._ad_image = None 40 self._ad_time_text = None 41 42 # If they provided an origin-widget, scale up from that. 43 scale_origin: tuple[float, float] | None 44 if origin_widget is not None: 45 self._transition_out = 'out_scale' 46 scale_origin = origin_widget.get_screen_space_center() 47 transition = 'in_scale' 48 else: 49 self._transition_out = 'out_right' 50 scale_origin = None 51 52 uiscale = ba.app.ui.uiscale 53 self._width = 1000.0 if uiscale is ba.UIScale.SMALL else 800.0 54 x_inset = 100.0 if uiscale is ba.UIScale.SMALL else 0.0 55 self._height = 480.0 56 57 self._modal = modal 58 self._from_modal_store = from_modal_store 59 self._r = 'getTicketsWindow' 60 61 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 62 63 super().__init__( 64 root_widget=ba.containerwidget( 65 size=(self._width, self._height + top_extra), 66 transition=transition, 67 scale_origin_stack_offset=scale_origin, 68 color=(0.4, 0.37, 0.55), 69 scale=( 70 1.63 71 if uiscale is ba.UIScale.SMALL 72 else 1.2 73 if uiscale is ba.UIScale.MEDIUM 74 else 1.0 75 ), 76 stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0), 77 ) 78 ) 79 80 btn = ba.buttonwidget( 81 parent=self._root_widget, 82 position=(55 + x_inset, self._height - 79), 83 size=(140, 60), 84 scale=1.0, 85 autoselect=True, 86 label=ba.Lstr(resource='doneText' if modal else 'backText'), 87 button_type='regular' if modal else 'back', 88 on_activate_call=self._back, 89 ) 90 91 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 92 93 ba.textwidget( 94 parent=self._root_widget, 95 position=(self._width * 0.5, self._height - 55), 96 size=(0, 0), 97 color=ba.app.ui.title_color, 98 scale=1.2, 99 h_align='center', 100 v_align='center', 101 text=ba.Lstr(resource=self._r + '.titleText'), 102 maxwidth=290, 103 ) 104 105 if not modal: 106 ba.buttonwidget( 107 edit=btn, 108 button_type='backSmall', 109 size=(60, 60), 110 label=ba.charstr(ba.SpecialChar.BACK), 111 ) 112 113 b_size = (220.0, 180.0) 114 v = self._height - b_size[1] - 80 115 spacing = 1 116 117 self._ad_button = None 118 119 def _add_button( 120 item: str, 121 position: tuple[float, float], 122 size: tuple[float, float], 123 label: ba.Lstr, 124 price: str | None = None, 125 tex_name: str | None = None, 126 tex_opacity: float = 1.0, 127 tex_scale: float = 1.0, 128 enabled: bool = True, 129 text_scale: float = 1.0, 130 ) -> ba.Widget: 131 btn2 = ba.buttonwidget( 132 parent=self._root_widget, 133 position=position, 134 button_type='square', 135 size=size, 136 label='', 137 autoselect=True, 138 color=None if enabled else (0.5, 0.5, 0.5), 139 on_activate_call=( 140 ba.Call(self._purchase, item) 141 if enabled 142 else self._disabled_press 143 ), 144 ) 145 txt = ba.textwidget( 146 parent=self._root_widget, 147 text=label, 148 position=( 149 position[0] + size[0] * 0.5, 150 position[1] + size[1] * 0.3, 151 ), 152 scale=text_scale, 153 maxwidth=size[0] * 0.75, 154 size=(0, 0), 155 h_align='center', 156 v_align='center', 157 draw_controller=btn2, 158 color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2), 159 ) 160 if price is not None and enabled: 161 ba.textwidget( 162 parent=self._root_widget, 163 text=price, 164 position=( 165 position[0] + size[0] * 0.5, 166 position[1] + size[1] * 0.17, 167 ), 168 scale=0.7, 169 maxwidth=size[0] * 0.75, 170 size=(0, 0), 171 h_align='center', 172 v_align='center', 173 draw_controller=btn2, 174 color=(0.4, 0.9, 0.4, 1.0), 175 ) 176 i = None 177 if tex_name is not None: 178 tex_size = 90.0 * tex_scale 179 i = ba.imagewidget( 180 parent=self._root_widget, 181 texture=ba.gettexture(tex_name), 182 position=( 183 position[0] + size[0] * 0.5 - tex_size * 0.5, 184 position[1] + size[1] * 0.66 - tex_size * 0.5, 185 ), 186 size=(tex_size, tex_size), 187 draw_controller=btn2, 188 opacity=tex_opacity * (1.0 if enabled else 0.25), 189 ) 190 if item == 'ad': 191 self._ad_button = btn2 192 self._ad_label = txt 193 assert i is not None 194 self._ad_image = i 195 self._ad_time_text = ba.textwidget( 196 parent=self._root_widget, 197 text='1m 10s', 198 position=( 199 position[0] + size[0] * 0.5, 200 position[1] + size[1] * 0.5, 201 ), 202 scale=text_scale * 1.2, 203 maxwidth=size[0] * 0.85, 204 size=(0, 0), 205 h_align='center', 206 v_align='center', 207 draw_controller=btn2, 208 color=(0.4, 0.9, 0.4, 1.0), 209 ) 210 return btn2 211 212 rsrc = self._r + '.ticketsText' 213 214 c2txt = ba.Lstr( 215 resource=rsrc, 216 subs=[ 217 ( 218 '${COUNT}', 219 str( 220 ba.internal.get_v1_account_misc_read_val( 221 'tickets2Amount', 500 222 ) 223 ), 224 ) 225 ], 226 ) 227 c3txt = ba.Lstr( 228 resource=rsrc, 229 subs=[ 230 ( 231 '${COUNT}', 232 str( 233 ba.internal.get_v1_account_misc_read_val( 234 'tickets3Amount', 1500 235 ) 236 ), 237 ) 238 ], 239 ) 240 c4txt = ba.Lstr( 241 resource=rsrc, 242 subs=[ 243 ( 244 '${COUNT}', 245 str( 246 ba.internal.get_v1_account_misc_read_val( 247 'tickets4Amount', 5000 248 ) 249 ), 250 ) 251 ], 252 ) 253 c5txt = ba.Lstr( 254 resource=rsrc, 255 subs=[ 256 ( 257 '${COUNT}', 258 str( 259 ba.internal.get_v1_account_misc_read_val( 260 'tickets5Amount', 15000 261 ) 262 ), 263 ) 264 ], 265 ) 266 267 h = 110.0 268 269 # enable buttons if we have prices.. 270 tickets2_price = ba.internal.get_price('tickets2') 271 tickets3_price = ba.internal.get_price('tickets3') 272 tickets4_price = ba.internal.get_price('tickets4') 273 tickets5_price = ba.internal.get_price('tickets5') 274 275 # TEMP 276 # tickets1_price = '$0.99' 277 # tickets2_price = '$4.99' 278 # tickets3_price = '$9.99' 279 # tickets4_price = '$19.99' 280 # tickets5_price = '$49.99' 281 282 _add_button( 283 'tickets2', 284 enabled=(tickets2_price is not None), 285 position=( 286 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 287 v, 288 ), 289 size=b_size, 290 label=c2txt, 291 price=tickets2_price, 292 tex_name='ticketsMore', 293 ) # 0.99-ish 294 _add_button( 295 'tickets3', 296 enabled=(tickets3_price is not None), 297 position=( 298 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 299 v, 300 ), 301 size=b_size, 302 label=c3txt, 303 price=tickets3_price, 304 tex_name='ticketRoll', 305 ) # 4.99-ish 306 v -= b_size[1] - 5 307 _add_button( 308 'tickets4', 309 enabled=(tickets4_price is not None), 310 position=( 311 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 312 v, 313 ), 314 size=b_size, 315 label=c4txt, 316 price=tickets4_price, 317 tex_name='ticketRollBig', 318 tex_scale=1.2, 319 ) # 9.99-ish 320 _add_button( 321 'tickets5', 322 enabled=(tickets5_price is not None), 323 position=( 324 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 325 v, 326 ), 327 size=b_size, 328 label=c5txt, 329 price=tickets5_price, 330 tex_name='ticketRolls', 331 tex_scale=1.2, 332 ) # 19.99-ish 333 334 self._enable_ad_button = ba.internal.has_video_ads() 335 h = self._width * 0.5 + 110.0 336 v = self._height - b_size[1] - 115.0 337 338 if self._enable_ad_button: 339 h_offs = 35 340 b_size_3 = (150, 120) 341 cdb = _add_button( 342 'ad', 343 position=(h + h_offs, v), 344 size=b_size_3, 345 label=ba.Lstr( 346 resource=self._r + '.ticketsFromASponsorText', 347 subs=[ 348 ( 349 '${COUNT}', 350 str( 351 ba.internal.get_v1_account_misc_read_val( 352 'sponsorTickets', 5 353 ) 354 ), 355 ) 356 ], 357 ), 358 tex_name='ticketsMore', 359 enabled=self._enable_ad_button, 360 tex_opacity=0.6, 361 tex_scale=0.7, 362 text_scale=0.7, 363 ) 364 ba.buttonwidget( 365 edit=cdb, 366 color=(0.65, 0.5, 0.7) 367 if self._enable_ad_button 368 else (0.5, 0.5, 0.5), 369 ) 370 371 self._ad_free_text = ba.textwidget( 372 parent=self._root_widget, 373 text=ba.Lstr(resource=self._r + '.freeText'), 374 position=( 375 h + h_offs + b_size_3[0] * 0.5, 376 v + b_size_3[1] * 0.5 + 25, 377 ), 378 size=(0, 0), 379 color=(1, 1, 0, 1.0) 380 if self._enable_ad_button 381 else (1, 1, 1, 0.2), 382 draw_controller=cdb, 383 rotate=15, 384 shadow=1.0, 385 maxwidth=150, 386 h_align='center', 387 v_align='center', 388 scale=1.0, 389 ) 390 v -= 125 391 else: 392 v -= 20 393 394 if True: # pylint: disable=using-constant-test 395 h_offs = 35 396 b_size_3 = (150, 120) 397 cdb = _add_button( 398 'app_invite', 399 position=(h + h_offs, v), 400 size=b_size_3, 401 label=ba.Lstr( 402 resource='gatherWindow.earnTicketsForRecommendingText', 403 subs=[ 404 ( 405 '${COUNT}', 406 str( 407 ba.internal.get_v1_account_misc_read_val( 408 'sponsorTickets', 5 409 ) 410 ), 411 ) 412 ], 413 ), 414 tex_name='ticketsMore', 415 enabled=True, 416 tex_opacity=0.6, 417 tex_scale=0.7, 418 text_scale=0.7, 419 ) 420 ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) 421 422 ba.textwidget( 423 parent=self._root_widget, 424 text=ba.Lstr(resource=self._r + '.freeText'), 425 position=( 426 h + h_offs + b_size_3[0] * 0.5, 427 v + b_size_3[1] * 0.5 + 25, 428 ), 429 size=(0, 0), 430 color=(1, 1, 0, 1.0), 431 draw_controller=cdb, 432 rotate=15, 433 shadow=1.0, 434 maxwidth=150, 435 h_align='center', 436 v_align='center', 437 scale=1.0, 438 ) 439 tc_y_offs = 0 440 441 h = self._width - (185 + x_inset) 442 v = self._height - 95 + tc_y_offs 443 444 txt1 = ( 445 ba.Lstr(resource=self._r + '.youHaveText') 446 .evaluate() 447 .partition('${COUNT}')[0] 448 .strip() 449 ) 450 txt2 = ( 451 ba.Lstr(resource=self._r + '.youHaveText') 452 .evaluate() 453 .rpartition('${COUNT}')[-1] 454 .strip() 455 ) 456 457 ba.textwidget( 458 parent=self._root_widget, 459 text=txt1, 460 position=(h, v), 461 size=(0, 0), 462 color=(0.5, 0.5, 0.6), 463 maxwidth=200, 464 h_align='center', 465 v_align='center', 466 scale=0.8, 467 ) 468 v -= 30 469 self._ticket_count_text = ba.textwidget( 470 parent=self._root_widget, 471 position=(h, v), 472 size=(0, 0), 473 color=(0.2, 1.0, 0.2), 474 maxwidth=200, 475 h_align='center', 476 v_align='center', 477 scale=1.6, 478 ) 479 v -= 30 480 ba.textwidget( 481 parent=self._root_widget, 482 text=txt2, 483 position=(h, v), 484 size=(0, 0), 485 color=(0.5, 0.5, 0.6), 486 maxwidth=200, 487 h_align='center', 488 v_align='center', 489 scale=0.8, 490 ) 491 492 # update count now and once per second going forward.. 493 self._ticking_node: ba.Node | None = None 494 self._smooth_ticket_count: float | None = None 495 self._ticket_count = 0 496 self._update() 497 self._update_timer = ba.Timer( 498 1.0, 499 ba.WeakCall(self._update), 500 timetype=ba.TimeType.REAL, 501 repeat=True, 502 ) 503 self._smooth_increase_speed = 1.0 504 505 def __del__(self) -> None: 506 if self._ticking_node is not None: 507 self._ticking_node.delete() 508 self._ticking_node = None 509 510 def _smooth_update(self) -> None: 511 if not self._ticket_count_text: 512 self._smooth_update_timer = None 513 return 514 515 finished = False 516 517 # if we're going down, do it immediately 518 assert self._smooth_ticket_count is not None 519 if int(self._smooth_ticket_count) >= self._ticket_count: 520 self._smooth_ticket_count = float(self._ticket_count) 521 finished = True 522 else: 523 # we're going up; start a sound if need be 524 self._smooth_ticket_count = min( 525 self._smooth_ticket_count + 1.0 * self._smooth_increase_speed, 526 self._ticket_count, 527 ) 528 if int(self._smooth_ticket_count) >= self._ticket_count: 529 finished = True 530 self._smooth_ticket_count = float(self._ticket_count) 531 elif self._ticking_node is None: 532 with ba.Context('ui'): 533 self._ticking_node = ba.newnode( 534 'sound', 535 attrs={ 536 'sound': ba.getsound('scoreIncrease'), 537 'positional': False, 538 }, 539 ) 540 541 ba.textwidget( 542 edit=self._ticket_count_text, 543 text=str(int(self._smooth_ticket_count)), 544 ) 545 546 # if we've reached the target, kill the timer/sound/etc 547 if finished: 548 self._smooth_update_timer = None 549 if self._ticking_node is not None: 550 self._ticking_node.delete() 551 self._ticking_node = None 552 ba.playsound(ba.getsound('cashRegister2')) 553 554 def _update(self) -> None: 555 import datetime 556 557 # if we somehow get signed out, just die.. 558 if ba.internal.get_v1_account_state() != 'signed_in': 559 self._back() 560 return 561 562 self._ticket_count = ba.internal.get_v1_account_ticket_count() 563 564 # update our incentivized ad button depending on whether ads are 565 # available 566 if self._ad_button is not None: 567 next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( 568 'nextRewardAdTime', None 569 ) 570 if next_reward_ad_time is not None: 571 next_reward_ad_time = datetime.datetime.utcfromtimestamp( 572 next_reward_ad_time 573 ) 574 now = datetime.datetime.utcnow() 575 if ba.internal.have_incentivized_ad() and ( 576 next_reward_ad_time is None or next_reward_ad_time <= now 577 ): 578 self._ad_button_greyed = False 579 ba.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7)) 580 ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0)) 581 ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1)) 582 ba.imagewidget(edit=self._ad_image, opacity=0.6) 583 ba.textwidget(edit=self._ad_time_text, text='') 584 else: 585 self._ad_button_greyed = True 586 ba.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5)) 587 ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2)) 588 ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2)) 589 ba.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25) 590 sval: str | ba.Lstr 591 if ( 592 next_reward_ad_time is not None 593 and next_reward_ad_time > now 594 ): 595 sval = ba.timestring( 596 (next_reward_ad_time - now).total_seconds() * 1000.0, 597 centi=False, 598 timeformat=ba.TimeFormat.MILLISECONDS, 599 ) 600 else: 601 sval = '' 602 ba.textwidget(edit=self._ad_time_text, text=sval) 603 604 # if this is our first update, assign immediately; otherwise kick 605 # off a smooth transition if the value has changed 606 if self._smooth_ticket_count is None: 607 self._smooth_ticket_count = float(self._ticket_count) 608 self._smooth_update() # will set the text widget 609 610 elif ( 611 self._ticket_count != int(self._smooth_ticket_count) 612 and self._smooth_update_timer is None 613 ): 614 self._smooth_update_timer = ba.Timer( 615 0.05, 616 ba.WeakCall(self._smooth_update), 617 repeat=True, 618 timetype=ba.TimeType.REAL, 619 ) 620 diff = abs(float(self._ticket_count) - self._smooth_ticket_count) 621 self._smooth_increase_speed = ( 622 diff / 100.0 623 if diff >= 5000 624 else diff / 50.0 625 if diff >= 1500 626 else diff / 30.0 627 if diff >= 500 628 else diff / 15.0 629 ) 630 631 def _disabled_press(self) -> None: 632 633 # if we're on a platform without purchases, inform the user they 634 # can link their accounts and buy stuff elsewhere 635 app = ba.app 636 if ( 637 app.test_build 638 or ( 639 app.platform == 'android' 640 and app.subplatform in ['oculus', 'cardboard'] 641 ) 642 ) and ba.internal.get_v1_account_misc_read_val( 643 'allowAccountLinking2', False 644 ): 645 ba.screenmessage( 646 ba.Lstr(resource=self._r + '.unavailableLinkAccountText'), 647 color=(1, 0.5, 0), 648 ) 649 else: 650 ba.screenmessage( 651 ba.Lstr(resource=self._r + '.unavailableText'), 652 color=(1, 0.5, 0), 653 ) 654 ba.playsound(ba.getsound('error')) 655 656 def _purchase(self, item: str) -> None: 657 from bastd.ui import account 658 from bastd.ui import appinvite 659 from ba.internal import master_server_get 660 661 if item == 'app_invite': 662 if ba.internal.get_v1_account_state() != 'signed_in': 663 account.show_sign_in_prompt() 664 return 665 appinvite.handle_app_invites_press() 666 return 667 # here we ping the server to ask if it's valid for us to 668 # purchase this.. (better to fail now than after we've paid locally) 669 app = ba.app 670 master_server_get( 671 'bsAccountPurchaseCheck', 672 { 673 'item': item, 674 'platform': app.platform, 675 'subplatform': app.subplatform, 676 'version': app.version, 677 'buildNumber': app.build_number, 678 }, 679 callback=ba.WeakCall(self._purchase_check_result, item), 680 ) 681 682 def _purchase_check_result( 683 self, item: str, result: dict[str, Any] | None 684 ) -> None: 685 if result is None: 686 ba.playsound(ba.getsound('error')) 687 ba.screenmessage( 688 ba.Lstr(resource='internal.unavailableNoConnectionText'), 689 color=(1, 0, 0), 690 ) 691 else: 692 if result['allow']: 693 self._do_purchase(item) 694 else: 695 if result['reason'] == 'versionTooOld': 696 ba.playsound(ba.getsound('error')) 697 ba.screenmessage( 698 ba.Lstr(resource='getTicketsWindow.versionTooOldText'), 699 color=(1, 0, 0), 700 ) 701 else: 702 ba.playsound(ba.getsound('error')) 703 ba.screenmessage( 704 ba.Lstr(resource='getTicketsWindow.unavailableText'), 705 color=(1, 0, 0), 706 ) 707 708 # actually start the purchase locally.. 709 def _do_purchase(self, item: str) -> None: 710 if item == 'ad': 711 import datetime 712 713 # if ads are disabled until some time, error.. 714 next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( 715 'nextRewardAdTime', None 716 ) 717 if next_reward_ad_time is not None: 718 next_reward_ad_time = datetime.datetime.utcfromtimestamp( 719 next_reward_ad_time 720 ) 721 now = datetime.datetime.utcnow() 722 if ( 723 next_reward_ad_time is not None and next_reward_ad_time > now 724 ) or self._ad_button_greyed: 725 ba.playsound(ba.getsound('error')) 726 ba.screenmessage( 727 ba.Lstr( 728 resource='getTicketsWindow.unavailableTemporarilyText' 729 ), 730 color=(1, 0, 0), 731 ) 732 elif self._enable_ad_button: 733 ba.app.ads.show_ad('tickets') 734 else: 735 ba.internal.purchase(item) 736 737 def _back(self) -> None: 738 from bastd.ui.store import browser 739 740 if self._transitioning_out: 741 return 742 ba.containerwidget( 743 edit=self._root_widget, transition=self._transition_out 744 ) 745 if not self._modal: 746 window = browser.StoreBrowserWindow( 747 transition='in_left', 748 modal=self._from_modal_store, 749 back_location=self._store_back_location, 750 ).get_root_widget() 751 if not self._from_modal_store: 752 ba.app.ui.set_main_menu_window(window) 753 self._transitioning_out = True 754 755 756def show_get_tickets_prompt() -> None: 757 """Show a 'not enough tickets' prompt with an option to purchase more. 758 759 Note that the purchase option may not always be available 760 depending on the build of the game. 761 """ 762 from bastd.ui.confirm import ConfirmWindow 763 764 if ba.app.allow_ticket_purchases: 765 ConfirmWindow( 766 ba.Lstr( 767 translate=( 768 'serverResponses', 769 'You don\'t have enough tickets for this!', 770 ) 771 ), 772 lambda: GetCurrencyWindow(modal=True), 773 ok_text=ba.Lstr(resource='getTicketsWindow.titleText'), 774 width=460, 775 height=130, 776 ) 777 else: 778 ConfirmWindow( 779 ba.Lstr( 780 translate=( 781 'serverResponses', 782 'You don\'t have enough tickets for this!', 783 ) 784 ), 785 cancel_button=False, 786 width=460, 787 height=130, 788 )
class
GetCurrencyWindow(ba.ui.Window):
17class GetCurrencyWindow(ba.Window): 18 """Window for purchasing/acquiring currency.""" 19 20 def __init__( 21 self, 22 transition: str = 'in_right', 23 from_modal_store: bool = False, 24 modal: bool = False, 25 origin_widget: ba.Widget | None = None, 26 store_back_location: str | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 # pylint: disable=too-many-locals 30 31 ba.set_analytics_screen('Get Tickets Window') 32 33 self._transitioning_out = False 34 self._store_back_location = store_back_location # ew. 35 36 self._ad_button_greyed = False 37 self._smooth_update_timer: ba.Timer | None = None 38 self._ad_button = None 39 self._ad_label = None 40 self._ad_image = None 41 self._ad_time_text = None 42 43 # If they provided an origin-widget, scale up from that. 44 scale_origin: tuple[float, float] | None 45 if origin_widget is not None: 46 self._transition_out = 'out_scale' 47 scale_origin = origin_widget.get_screen_space_center() 48 transition = 'in_scale' 49 else: 50 self._transition_out = 'out_right' 51 scale_origin = None 52 53 uiscale = ba.app.ui.uiscale 54 self._width = 1000.0 if uiscale is ba.UIScale.SMALL else 800.0 55 x_inset = 100.0 if uiscale is ba.UIScale.SMALL else 0.0 56 self._height = 480.0 57 58 self._modal = modal 59 self._from_modal_store = from_modal_store 60 self._r = 'getTicketsWindow' 61 62 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 63 64 super().__init__( 65 root_widget=ba.containerwidget( 66 size=(self._width, self._height + top_extra), 67 transition=transition, 68 scale_origin_stack_offset=scale_origin, 69 color=(0.4, 0.37, 0.55), 70 scale=( 71 1.63 72 if uiscale is ba.UIScale.SMALL 73 else 1.2 74 if uiscale is ba.UIScale.MEDIUM 75 else 1.0 76 ), 77 stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0), 78 ) 79 ) 80 81 btn = ba.buttonwidget( 82 parent=self._root_widget, 83 position=(55 + x_inset, self._height - 79), 84 size=(140, 60), 85 scale=1.0, 86 autoselect=True, 87 label=ba.Lstr(resource='doneText' if modal else 'backText'), 88 button_type='regular' if modal else 'back', 89 on_activate_call=self._back, 90 ) 91 92 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 93 94 ba.textwidget( 95 parent=self._root_widget, 96 position=(self._width * 0.5, self._height - 55), 97 size=(0, 0), 98 color=ba.app.ui.title_color, 99 scale=1.2, 100 h_align='center', 101 v_align='center', 102 text=ba.Lstr(resource=self._r + '.titleText'), 103 maxwidth=290, 104 ) 105 106 if not modal: 107 ba.buttonwidget( 108 edit=btn, 109 button_type='backSmall', 110 size=(60, 60), 111 label=ba.charstr(ba.SpecialChar.BACK), 112 ) 113 114 b_size = (220.0, 180.0) 115 v = self._height - b_size[1] - 80 116 spacing = 1 117 118 self._ad_button = None 119 120 def _add_button( 121 item: str, 122 position: tuple[float, float], 123 size: tuple[float, float], 124 label: ba.Lstr, 125 price: str | None = None, 126 tex_name: str | None = None, 127 tex_opacity: float = 1.0, 128 tex_scale: float = 1.0, 129 enabled: bool = True, 130 text_scale: float = 1.0, 131 ) -> ba.Widget: 132 btn2 = ba.buttonwidget( 133 parent=self._root_widget, 134 position=position, 135 button_type='square', 136 size=size, 137 label='', 138 autoselect=True, 139 color=None if enabled else (0.5, 0.5, 0.5), 140 on_activate_call=( 141 ba.Call(self._purchase, item) 142 if enabled 143 else self._disabled_press 144 ), 145 ) 146 txt = ba.textwidget( 147 parent=self._root_widget, 148 text=label, 149 position=( 150 position[0] + size[0] * 0.5, 151 position[1] + size[1] * 0.3, 152 ), 153 scale=text_scale, 154 maxwidth=size[0] * 0.75, 155 size=(0, 0), 156 h_align='center', 157 v_align='center', 158 draw_controller=btn2, 159 color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2), 160 ) 161 if price is not None and enabled: 162 ba.textwidget( 163 parent=self._root_widget, 164 text=price, 165 position=( 166 position[0] + size[0] * 0.5, 167 position[1] + size[1] * 0.17, 168 ), 169 scale=0.7, 170 maxwidth=size[0] * 0.75, 171 size=(0, 0), 172 h_align='center', 173 v_align='center', 174 draw_controller=btn2, 175 color=(0.4, 0.9, 0.4, 1.0), 176 ) 177 i = None 178 if tex_name is not None: 179 tex_size = 90.0 * tex_scale 180 i = ba.imagewidget( 181 parent=self._root_widget, 182 texture=ba.gettexture(tex_name), 183 position=( 184 position[0] + size[0] * 0.5 - tex_size * 0.5, 185 position[1] + size[1] * 0.66 - tex_size * 0.5, 186 ), 187 size=(tex_size, tex_size), 188 draw_controller=btn2, 189 opacity=tex_opacity * (1.0 if enabled else 0.25), 190 ) 191 if item == 'ad': 192 self._ad_button = btn2 193 self._ad_label = txt 194 assert i is not None 195 self._ad_image = i 196 self._ad_time_text = ba.textwidget( 197 parent=self._root_widget, 198 text='1m 10s', 199 position=( 200 position[0] + size[0] * 0.5, 201 position[1] + size[1] * 0.5, 202 ), 203 scale=text_scale * 1.2, 204 maxwidth=size[0] * 0.85, 205 size=(0, 0), 206 h_align='center', 207 v_align='center', 208 draw_controller=btn2, 209 color=(0.4, 0.9, 0.4, 1.0), 210 ) 211 return btn2 212 213 rsrc = self._r + '.ticketsText' 214 215 c2txt = ba.Lstr( 216 resource=rsrc, 217 subs=[ 218 ( 219 '${COUNT}', 220 str( 221 ba.internal.get_v1_account_misc_read_val( 222 'tickets2Amount', 500 223 ) 224 ), 225 ) 226 ], 227 ) 228 c3txt = ba.Lstr( 229 resource=rsrc, 230 subs=[ 231 ( 232 '${COUNT}', 233 str( 234 ba.internal.get_v1_account_misc_read_val( 235 'tickets3Amount', 1500 236 ) 237 ), 238 ) 239 ], 240 ) 241 c4txt = ba.Lstr( 242 resource=rsrc, 243 subs=[ 244 ( 245 '${COUNT}', 246 str( 247 ba.internal.get_v1_account_misc_read_val( 248 'tickets4Amount', 5000 249 ) 250 ), 251 ) 252 ], 253 ) 254 c5txt = ba.Lstr( 255 resource=rsrc, 256 subs=[ 257 ( 258 '${COUNT}', 259 str( 260 ba.internal.get_v1_account_misc_read_val( 261 'tickets5Amount', 15000 262 ) 263 ), 264 ) 265 ], 266 ) 267 268 h = 110.0 269 270 # enable buttons if we have prices.. 271 tickets2_price = ba.internal.get_price('tickets2') 272 tickets3_price = ba.internal.get_price('tickets3') 273 tickets4_price = ba.internal.get_price('tickets4') 274 tickets5_price = ba.internal.get_price('tickets5') 275 276 # TEMP 277 # tickets1_price = '$0.99' 278 # tickets2_price = '$4.99' 279 # tickets3_price = '$9.99' 280 # tickets4_price = '$19.99' 281 # tickets5_price = '$49.99' 282 283 _add_button( 284 'tickets2', 285 enabled=(tickets2_price is not None), 286 position=( 287 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 288 v, 289 ), 290 size=b_size, 291 label=c2txt, 292 price=tickets2_price, 293 tex_name='ticketsMore', 294 ) # 0.99-ish 295 _add_button( 296 'tickets3', 297 enabled=(tickets3_price is not None), 298 position=( 299 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 300 v, 301 ), 302 size=b_size, 303 label=c3txt, 304 price=tickets3_price, 305 tex_name='ticketRoll', 306 ) # 4.99-ish 307 v -= b_size[1] - 5 308 _add_button( 309 'tickets4', 310 enabled=(tickets4_price is not None), 311 position=( 312 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 313 v, 314 ), 315 size=b_size, 316 label=c4txt, 317 price=tickets4_price, 318 tex_name='ticketRollBig', 319 tex_scale=1.2, 320 ) # 9.99-ish 321 _add_button( 322 'tickets5', 323 enabled=(tickets5_price is not None), 324 position=( 325 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 326 v, 327 ), 328 size=b_size, 329 label=c5txt, 330 price=tickets5_price, 331 tex_name='ticketRolls', 332 tex_scale=1.2, 333 ) # 19.99-ish 334 335 self._enable_ad_button = ba.internal.has_video_ads() 336 h = self._width * 0.5 + 110.0 337 v = self._height - b_size[1] - 115.0 338 339 if self._enable_ad_button: 340 h_offs = 35 341 b_size_3 = (150, 120) 342 cdb = _add_button( 343 'ad', 344 position=(h + h_offs, v), 345 size=b_size_3, 346 label=ba.Lstr( 347 resource=self._r + '.ticketsFromASponsorText', 348 subs=[ 349 ( 350 '${COUNT}', 351 str( 352 ba.internal.get_v1_account_misc_read_val( 353 'sponsorTickets', 5 354 ) 355 ), 356 ) 357 ], 358 ), 359 tex_name='ticketsMore', 360 enabled=self._enable_ad_button, 361 tex_opacity=0.6, 362 tex_scale=0.7, 363 text_scale=0.7, 364 ) 365 ba.buttonwidget( 366 edit=cdb, 367 color=(0.65, 0.5, 0.7) 368 if self._enable_ad_button 369 else (0.5, 0.5, 0.5), 370 ) 371 372 self._ad_free_text = ba.textwidget( 373 parent=self._root_widget, 374 text=ba.Lstr(resource=self._r + '.freeText'), 375 position=( 376 h + h_offs + b_size_3[0] * 0.5, 377 v + b_size_3[1] * 0.5 + 25, 378 ), 379 size=(0, 0), 380 color=(1, 1, 0, 1.0) 381 if self._enable_ad_button 382 else (1, 1, 1, 0.2), 383 draw_controller=cdb, 384 rotate=15, 385 shadow=1.0, 386 maxwidth=150, 387 h_align='center', 388 v_align='center', 389 scale=1.0, 390 ) 391 v -= 125 392 else: 393 v -= 20 394 395 if True: # pylint: disable=using-constant-test 396 h_offs = 35 397 b_size_3 = (150, 120) 398 cdb = _add_button( 399 'app_invite', 400 position=(h + h_offs, v), 401 size=b_size_3, 402 label=ba.Lstr( 403 resource='gatherWindow.earnTicketsForRecommendingText', 404 subs=[ 405 ( 406 '${COUNT}', 407 str( 408 ba.internal.get_v1_account_misc_read_val( 409 'sponsorTickets', 5 410 ) 411 ), 412 ) 413 ], 414 ), 415 tex_name='ticketsMore', 416 enabled=True, 417 tex_opacity=0.6, 418 tex_scale=0.7, 419 text_scale=0.7, 420 ) 421 ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) 422 423 ba.textwidget( 424 parent=self._root_widget, 425 text=ba.Lstr(resource=self._r + '.freeText'), 426 position=( 427 h + h_offs + b_size_3[0] * 0.5, 428 v + b_size_3[1] * 0.5 + 25, 429 ), 430 size=(0, 0), 431 color=(1, 1, 0, 1.0), 432 draw_controller=cdb, 433 rotate=15, 434 shadow=1.0, 435 maxwidth=150, 436 h_align='center', 437 v_align='center', 438 scale=1.0, 439 ) 440 tc_y_offs = 0 441 442 h = self._width - (185 + x_inset) 443 v = self._height - 95 + tc_y_offs 444 445 txt1 = ( 446 ba.Lstr(resource=self._r + '.youHaveText') 447 .evaluate() 448 .partition('${COUNT}')[0] 449 .strip() 450 ) 451 txt2 = ( 452 ba.Lstr(resource=self._r + '.youHaveText') 453 .evaluate() 454 .rpartition('${COUNT}')[-1] 455 .strip() 456 ) 457 458 ba.textwidget( 459 parent=self._root_widget, 460 text=txt1, 461 position=(h, v), 462 size=(0, 0), 463 color=(0.5, 0.5, 0.6), 464 maxwidth=200, 465 h_align='center', 466 v_align='center', 467 scale=0.8, 468 ) 469 v -= 30 470 self._ticket_count_text = ba.textwidget( 471 parent=self._root_widget, 472 position=(h, v), 473 size=(0, 0), 474 color=(0.2, 1.0, 0.2), 475 maxwidth=200, 476 h_align='center', 477 v_align='center', 478 scale=1.6, 479 ) 480 v -= 30 481 ba.textwidget( 482 parent=self._root_widget, 483 text=txt2, 484 position=(h, v), 485 size=(0, 0), 486 color=(0.5, 0.5, 0.6), 487 maxwidth=200, 488 h_align='center', 489 v_align='center', 490 scale=0.8, 491 ) 492 493 # update count now and once per second going forward.. 494 self._ticking_node: ba.Node | None = None 495 self._smooth_ticket_count: float | None = None 496 self._ticket_count = 0 497 self._update() 498 self._update_timer = ba.Timer( 499 1.0, 500 ba.WeakCall(self._update), 501 timetype=ba.TimeType.REAL, 502 repeat=True, 503 ) 504 self._smooth_increase_speed = 1.0 505 506 def __del__(self) -> None: 507 if self._ticking_node is not None: 508 self._ticking_node.delete() 509 self._ticking_node = None 510 511 def _smooth_update(self) -> None: 512 if not self._ticket_count_text: 513 self._smooth_update_timer = None 514 return 515 516 finished = False 517 518 # if we're going down, do it immediately 519 assert self._smooth_ticket_count is not None 520 if int(self._smooth_ticket_count) >= self._ticket_count: 521 self._smooth_ticket_count = float(self._ticket_count) 522 finished = True 523 else: 524 # we're going up; start a sound if need be 525 self._smooth_ticket_count = min( 526 self._smooth_ticket_count + 1.0 * self._smooth_increase_speed, 527 self._ticket_count, 528 ) 529 if int(self._smooth_ticket_count) >= self._ticket_count: 530 finished = True 531 self._smooth_ticket_count = float(self._ticket_count) 532 elif self._ticking_node is None: 533 with ba.Context('ui'): 534 self._ticking_node = ba.newnode( 535 'sound', 536 attrs={ 537 'sound': ba.getsound('scoreIncrease'), 538 'positional': False, 539 }, 540 ) 541 542 ba.textwidget( 543 edit=self._ticket_count_text, 544 text=str(int(self._smooth_ticket_count)), 545 ) 546 547 # if we've reached the target, kill the timer/sound/etc 548 if finished: 549 self._smooth_update_timer = None 550 if self._ticking_node is not None: 551 self._ticking_node.delete() 552 self._ticking_node = None 553 ba.playsound(ba.getsound('cashRegister2')) 554 555 def _update(self) -> None: 556 import datetime 557 558 # if we somehow get signed out, just die.. 559 if ba.internal.get_v1_account_state() != 'signed_in': 560 self._back() 561 return 562 563 self._ticket_count = ba.internal.get_v1_account_ticket_count() 564 565 # update our incentivized ad button depending on whether ads are 566 # available 567 if self._ad_button is not None: 568 next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( 569 'nextRewardAdTime', None 570 ) 571 if next_reward_ad_time is not None: 572 next_reward_ad_time = datetime.datetime.utcfromtimestamp( 573 next_reward_ad_time 574 ) 575 now = datetime.datetime.utcnow() 576 if ba.internal.have_incentivized_ad() and ( 577 next_reward_ad_time is None or next_reward_ad_time <= now 578 ): 579 self._ad_button_greyed = False 580 ba.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7)) 581 ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0)) 582 ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1)) 583 ba.imagewidget(edit=self._ad_image, opacity=0.6) 584 ba.textwidget(edit=self._ad_time_text, text='') 585 else: 586 self._ad_button_greyed = True 587 ba.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5)) 588 ba.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2)) 589 ba.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2)) 590 ba.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25) 591 sval: str | ba.Lstr 592 if ( 593 next_reward_ad_time is not None 594 and next_reward_ad_time > now 595 ): 596 sval = ba.timestring( 597 (next_reward_ad_time - now).total_seconds() * 1000.0, 598 centi=False, 599 timeformat=ba.TimeFormat.MILLISECONDS, 600 ) 601 else: 602 sval = '' 603 ba.textwidget(edit=self._ad_time_text, text=sval) 604 605 # if this is our first update, assign immediately; otherwise kick 606 # off a smooth transition if the value has changed 607 if self._smooth_ticket_count is None: 608 self._smooth_ticket_count = float(self._ticket_count) 609 self._smooth_update() # will set the text widget 610 611 elif ( 612 self._ticket_count != int(self._smooth_ticket_count) 613 and self._smooth_update_timer is None 614 ): 615 self._smooth_update_timer = ba.Timer( 616 0.05, 617 ba.WeakCall(self._smooth_update), 618 repeat=True, 619 timetype=ba.TimeType.REAL, 620 ) 621 diff = abs(float(self._ticket_count) - self._smooth_ticket_count) 622 self._smooth_increase_speed = ( 623 diff / 100.0 624 if diff >= 5000 625 else diff / 50.0 626 if diff >= 1500 627 else diff / 30.0 628 if diff >= 500 629 else diff / 15.0 630 ) 631 632 def _disabled_press(self) -> None: 633 634 # if we're on a platform without purchases, inform the user they 635 # can link their accounts and buy stuff elsewhere 636 app = ba.app 637 if ( 638 app.test_build 639 or ( 640 app.platform == 'android' 641 and app.subplatform in ['oculus', 'cardboard'] 642 ) 643 ) and ba.internal.get_v1_account_misc_read_val( 644 'allowAccountLinking2', False 645 ): 646 ba.screenmessage( 647 ba.Lstr(resource=self._r + '.unavailableLinkAccountText'), 648 color=(1, 0.5, 0), 649 ) 650 else: 651 ba.screenmessage( 652 ba.Lstr(resource=self._r + '.unavailableText'), 653 color=(1, 0.5, 0), 654 ) 655 ba.playsound(ba.getsound('error')) 656 657 def _purchase(self, item: str) -> None: 658 from bastd.ui import account 659 from bastd.ui import appinvite 660 from ba.internal import master_server_get 661 662 if item == 'app_invite': 663 if ba.internal.get_v1_account_state() != 'signed_in': 664 account.show_sign_in_prompt() 665 return 666 appinvite.handle_app_invites_press() 667 return 668 # here we ping the server to ask if it's valid for us to 669 # purchase this.. (better to fail now than after we've paid locally) 670 app = ba.app 671 master_server_get( 672 'bsAccountPurchaseCheck', 673 { 674 'item': item, 675 'platform': app.platform, 676 'subplatform': app.subplatform, 677 'version': app.version, 678 'buildNumber': app.build_number, 679 }, 680 callback=ba.WeakCall(self._purchase_check_result, item), 681 ) 682 683 def _purchase_check_result( 684 self, item: str, result: dict[str, Any] | None 685 ) -> None: 686 if result is None: 687 ba.playsound(ba.getsound('error')) 688 ba.screenmessage( 689 ba.Lstr(resource='internal.unavailableNoConnectionText'), 690 color=(1, 0, 0), 691 ) 692 else: 693 if result['allow']: 694 self._do_purchase(item) 695 else: 696 if result['reason'] == 'versionTooOld': 697 ba.playsound(ba.getsound('error')) 698 ba.screenmessage( 699 ba.Lstr(resource='getTicketsWindow.versionTooOldText'), 700 color=(1, 0, 0), 701 ) 702 else: 703 ba.playsound(ba.getsound('error')) 704 ba.screenmessage( 705 ba.Lstr(resource='getTicketsWindow.unavailableText'), 706 color=(1, 0, 0), 707 ) 708 709 # actually start the purchase locally.. 710 def _do_purchase(self, item: str) -> None: 711 if item == 'ad': 712 import datetime 713 714 # if ads are disabled until some time, error.. 715 next_reward_ad_time = ba.internal.get_v1_account_misc_read_val_2( 716 'nextRewardAdTime', None 717 ) 718 if next_reward_ad_time is not None: 719 next_reward_ad_time = datetime.datetime.utcfromtimestamp( 720 next_reward_ad_time 721 ) 722 now = datetime.datetime.utcnow() 723 if ( 724 next_reward_ad_time is not None and next_reward_ad_time > now 725 ) or self._ad_button_greyed: 726 ba.playsound(ba.getsound('error')) 727 ba.screenmessage( 728 ba.Lstr( 729 resource='getTicketsWindow.unavailableTemporarilyText' 730 ), 731 color=(1, 0, 0), 732 ) 733 elif self._enable_ad_button: 734 ba.app.ads.show_ad('tickets') 735 else: 736 ba.internal.purchase(item) 737 738 def _back(self) -> None: 739 from bastd.ui.store import browser 740 741 if self._transitioning_out: 742 return 743 ba.containerwidget( 744 edit=self._root_widget, transition=self._transition_out 745 ) 746 if not self._modal: 747 window = browser.StoreBrowserWindow( 748 transition='in_left', 749 modal=self._from_modal_store, 750 back_location=self._store_back_location, 751 ).get_root_widget() 752 if not self._from_modal_store: 753 ba.app.ui.set_main_menu_window(window) 754 self._transitioning_out = True
Window for purchasing/acquiring currency.
GetCurrencyWindow( transition: str = 'in_right', from_modal_store: bool = False, modal: bool = False, origin_widget: _ba.Widget | None = None, store_back_location: str | None = None)
20 def __init__( 21 self, 22 transition: str = 'in_right', 23 from_modal_store: bool = False, 24 modal: bool = False, 25 origin_widget: ba.Widget | None = None, 26 store_back_location: str | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 # pylint: disable=too-many-locals 30 31 ba.set_analytics_screen('Get Tickets Window') 32 33 self._transitioning_out = False 34 self._store_back_location = store_back_location # ew. 35 36 self._ad_button_greyed = False 37 self._smooth_update_timer: ba.Timer | None = None 38 self._ad_button = None 39 self._ad_label = None 40 self._ad_image = None 41 self._ad_time_text = None 42 43 # If they provided an origin-widget, scale up from that. 44 scale_origin: tuple[float, float] | None 45 if origin_widget is not None: 46 self._transition_out = 'out_scale' 47 scale_origin = origin_widget.get_screen_space_center() 48 transition = 'in_scale' 49 else: 50 self._transition_out = 'out_right' 51 scale_origin = None 52 53 uiscale = ba.app.ui.uiscale 54 self._width = 1000.0 if uiscale is ba.UIScale.SMALL else 800.0 55 x_inset = 100.0 if uiscale is ba.UIScale.SMALL else 0.0 56 self._height = 480.0 57 58 self._modal = modal 59 self._from_modal_store = from_modal_store 60 self._r = 'getTicketsWindow' 61 62 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 63 64 super().__init__( 65 root_widget=ba.containerwidget( 66 size=(self._width, self._height + top_extra), 67 transition=transition, 68 scale_origin_stack_offset=scale_origin, 69 color=(0.4, 0.37, 0.55), 70 scale=( 71 1.63 72 if uiscale is ba.UIScale.SMALL 73 else 1.2 74 if uiscale is ba.UIScale.MEDIUM 75 else 1.0 76 ), 77 stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0), 78 ) 79 ) 80 81 btn = ba.buttonwidget( 82 parent=self._root_widget, 83 position=(55 + x_inset, self._height - 79), 84 size=(140, 60), 85 scale=1.0, 86 autoselect=True, 87 label=ba.Lstr(resource='doneText' if modal else 'backText'), 88 button_type='regular' if modal else 'back', 89 on_activate_call=self._back, 90 ) 91 92 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 93 94 ba.textwidget( 95 parent=self._root_widget, 96 position=(self._width * 0.5, self._height - 55), 97 size=(0, 0), 98 color=ba.app.ui.title_color, 99 scale=1.2, 100 h_align='center', 101 v_align='center', 102 text=ba.Lstr(resource=self._r + '.titleText'), 103 maxwidth=290, 104 ) 105 106 if not modal: 107 ba.buttonwidget( 108 edit=btn, 109 button_type='backSmall', 110 size=(60, 60), 111 label=ba.charstr(ba.SpecialChar.BACK), 112 ) 113 114 b_size = (220.0, 180.0) 115 v = self._height - b_size[1] - 80 116 spacing = 1 117 118 self._ad_button = None 119 120 def _add_button( 121 item: str, 122 position: tuple[float, float], 123 size: tuple[float, float], 124 label: ba.Lstr, 125 price: str | None = None, 126 tex_name: str | None = None, 127 tex_opacity: float = 1.0, 128 tex_scale: float = 1.0, 129 enabled: bool = True, 130 text_scale: float = 1.0, 131 ) -> ba.Widget: 132 btn2 = ba.buttonwidget( 133 parent=self._root_widget, 134 position=position, 135 button_type='square', 136 size=size, 137 label='', 138 autoselect=True, 139 color=None if enabled else (0.5, 0.5, 0.5), 140 on_activate_call=( 141 ba.Call(self._purchase, item) 142 if enabled 143 else self._disabled_press 144 ), 145 ) 146 txt = ba.textwidget( 147 parent=self._root_widget, 148 text=label, 149 position=( 150 position[0] + size[0] * 0.5, 151 position[1] + size[1] * 0.3, 152 ), 153 scale=text_scale, 154 maxwidth=size[0] * 0.75, 155 size=(0, 0), 156 h_align='center', 157 v_align='center', 158 draw_controller=btn2, 159 color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2), 160 ) 161 if price is not None and enabled: 162 ba.textwidget( 163 parent=self._root_widget, 164 text=price, 165 position=( 166 position[0] + size[0] * 0.5, 167 position[1] + size[1] * 0.17, 168 ), 169 scale=0.7, 170 maxwidth=size[0] * 0.75, 171 size=(0, 0), 172 h_align='center', 173 v_align='center', 174 draw_controller=btn2, 175 color=(0.4, 0.9, 0.4, 1.0), 176 ) 177 i = None 178 if tex_name is not None: 179 tex_size = 90.0 * tex_scale 180 i = ba.imagewidget( 181 parent=self._root_widget, 182 texture=ba.gettexture(tex_name), 183 position=( 184 position[0] + size[0] * 0.5 - tex_size * 0.5, 185 position[1] + size[1] * 0.66 - tex_size * 0.5, 186 ), 187 size=(tex_size, tex_size), 188 draw_controller=btn2, 189 opacity=tex_opacity * (1.0 if enabled else 0.25), 190 ) 191 if item == 'ad': 192 self._ad_button = btn2 193 self._ad_label = txt 194 assert i is not None 195 self._ad_image = i 196 self._ad_time_text = ba.textwidget( 197 parent=self._root_widget, 198 text='1m 10s', 199 position=( 200 position[0] + size[0] * 0.5, 201 position[1] + size[1] * 0.5, 202 ), 203 scale=text_scale * 1.2, 204 maxwidth=size[0] * 0.85, 205 size=(0, 0), 206 h_align='center', 207 v_align='center', 208 draw_controller=btn2, 209 color=(0.4, 0.9, 0.4, 1.0), 210 ) 211 return btn2 212 213 rsrc = self._r + '.ticketsText' 214 215 c2txt = ba.Lstr( 216 resource=rsrc, 217 subs=[ 218 ( 219 '${COUNT}', 220 str( 221 ba.internal.get_v1_account_misc_read_val( 222 'tickets2Amount', 500 223 ) 224 ), 225 ) 226 ], 227 ) 228 c3txt = ba.Lstr( 229 resource=rsrc, 230 subs=[ 231 ( 232 '${COUNT}', 233 str( 234 ba.internal.get_v1_account_misc_read_val( 235 'tickets3Amount', 1500 236 ) 237 ), 238 ) 239 ], 240 ) 241 c4txt = ba.Lstr( 242 resource=rsrc, 243 subs=[ 244 ( 245 '${COUNT}', 246 str( 247 ba.internal.get_v1_account_misc_read_val( 248 'tickets4Amount', 5000 249 ) 250 ), 251 ) 252 ], 253 ) 254 c5txt = ba.Lstr( 255 resource=rsrc, 256 subs=[ 257 ( 258 '${COUNT}', 259 str( 260 ba.internal.get_v1_account_misc_read_val( 261 'tickets5Amount', 15000 262 ) 263 ), 264 ) 265 ], 266 ) 267 268 h = 110.0 269 270 # enable buttons if we have prices.. 271 tickets2_price = ba.internal.get_price('tickets2') 272 tickets3_price = ba.internal.get_price('tickets3') 273 tickets4_price = ba.internal.get_price('tickets4') 274 tickets5_price = ba.internal.get_price('tickets5') 275 276 # TEMP 277 # tickets1_price = '$0.99' 278 # tickets2_price = '$4.99' 279 # tickets3_price = '$9.99' 280 # tickets4_price = '$19.99' 281 # tickets5_price = '$49.99' 282 283 _add_button( 284 'tickets2', 285 enabled=(tickets2_price is not None), 286 position=( 287 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 288 v, 289 ), 290 size=b_size, 291 label=c2txt, 292 price=tickets2_price, 293 tex_name='ticketsMore', 294 ) # 0.99-ish 295 _add_button( 296 'tickets3', 297 enabled=(tickets3_price is not None), 298 position=( 299 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 300 v, 301 ), 302 size=b_size, 303 label=c3txt, 304 price=tickets3_price, 305 tex_name='ticketRoll', 306 ) # 4.99-ish 307 v -= b_size[1] - 5 308 _add_button( 309 'tickets4', 310 enabled=(tickets4_price is not None), 311 position=( 312 self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, 313 v, 314 ), 315 size=b_size, 316 label=c4txt, 317 price=tickets4_price, 318 tex_name='ticketRollBig', 319 tex_scale=1.2, 320 ) # 9.99-ish 321 _add_button( 322 'tickets5', 323 enabled=(tickets5_price is not None), 324 position=( 325 self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, 326 v, 327 ), 328 size=b_size, 329 label=c5txt, 330 price=tickets5_price, 331 tex_name='ticketRolls', 332 tex_scale=1.2, 333 ) # 19.99-ish 334 335 self._enable_ad_button = ba.internal.has_video_ads() 336 h = self._width * 0.5 + 110.0 337 v = self._height - b_size[1] - 115.0 338 339 if self._enable_ad_button: 340 h_offs = 35 341 b_size_3 = (150, 120) 342 cdb = _add_button( 343 'ad', 344 position=(h + h_offs, v), 345 size=b_size_3, 346 label=ba.Lstr( 347 resource=self._r + '.ticketsFromASponsorText', 348 subs=[ 349 ( 350 '${COUNT}', 351 str( 352 ba.internal.get_v1_account_misc_read_val( 353 'sponsorTickets', 5 354 ) 355 ), 356 ) 357 ], 358 ), 359 tex_name='ticketsMore', 360 enabled=self._enable_ad_button, 361 tex_opacity=0.6, 362 tex_scale=0.7, 363 text_scale=0.7, 364 ) 365 ba.buttonwidget( 366 edit=cdb, 367 color=(0.65, 0.5, 0.7) 368 if self._enable_ad_button 369 else (0.5, 0.5, 0.5), 370 ) 371 372 self._ad_free_text = ba.textwidget( 373 parent=self._root_widget, 374 text=ba.Lstr(resource=self._r + '.freeText'), 375 position=( 376 h + h_offs + b_size_3[0] * 0.5, 377 v + b_size_3[1] * 0.5 + 25, 378 ), 379 size=(0, 0), 380 color=(1, 1, 0, 1.0) 381 if self._enable_ad_button 382 else (1, 1, 1, 0.2), 383 draw_controller=cdb, 384 rotate=15, 385 shadow=1.0, 386 maxwidth=150, 387 h_align='center', 388 v_align='center', 389 scale=1.0, 390 ) 391 v -= 125 392 else: 393 v -= 20 394 395 if True: # pylint: disable=using-constant-test 396 h_offs = 35 397 b_size_3 = (150, 120) 398 cdb = _add_button( 399 'app_invite', 400 position=(h + h_offs, v), 401 size=b_size_3, 402 label=ba.Lstr( 403 resource='gatherWindow.earnTicketsForRecommendingText', 404 subs=[ 405 ( 406 '${COUNT}', 407 str( 408 ba.internal.get_v1_account_misc_read_val( 409 'sponsorTickets', 5 410 ) 411 ), 412 ) 413 ], 414 ), 415 tex_name='ticketsMore', 416 enabled=True, 417 tex_opacity=0.6, 418 tex_scale=0.7, 419 text_scale=0.7, 420 ) 421 ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) 422 423 ba.textwidget( 424 parent=self._root_widget, 425 text=ba.Lstr(resource=self._r + '.freeText'), 426 position=( 427 h + h_offs + b_size_3[0] * 0.5, 428 v + b_size_3[1] * 0.5 + 25, 429 ), 430 size=(0, 0), 431 color=(1, 1, 0, 1.0), 432 draw_controller=cdb, 433 rotate=15, 434 shadow=1.0, 435 maxwidth=150, 436 h_align='center', 437 v_align='center', 438 scale=1.0, 439 ) 440 tc_y_offs = 0 441 442 h = self._width - (185 + x_inset) 443 v = self._height - 95 + tc_y_offs 444 445 txt1 = ( 446 ba.Lstr(resource=self._r + '.youHaveText') 447 .evaluate() 448 .partition('${COUNT}')[0] 449 .strip() 450 ) 451 txt2 = ( 452 ba.Lstr(resource=self._r + '.youHaveText') 453 .evaluate() 454 .rpartition('${COUNT}')[-1] 455 .strip() 456 ) 457 458 ba.textwidget( 459 parent=self._root_widget, 460 text=txt1, 461 position=(h, v), 462 size=(0, 0), 463 color=(0.5, 0.5, 0.6), 464 maxwidth=200, 465 h_align='center', 466 v_align='center', 467 scale=0.8, 468 ) 469 v -= 30 470 self._ticket_count_text = ba.textwidget( 471 parent=self._root_widget, 472 position=(h, v), 473 size=(0, 0), 474 color=(0.2, 1.0, 0.2), 475 maxwidth=200, 476 h_align='center', 477 v_align='center', 478 scale=1.6, 479 ) 480 v -= 30 481 ba.textwidget( 482 parent=self._root_widget, 483 text=txt2, 484 position=(h, v), 485 size=(0, 0), 486 color=(0.5, 0.5, 0.6), 487 maxwidth=200, 488 h_align='center', 489 v_align='center', 490 scale=0.8, 491 ) 492 493 # update count now and once per second going forward.. 494 self._ticking_node: ba.Node | None = None 495 self._smooth_ticket_count: float | None = None 496 self._ticket_count = 0 497 self._update() 498 self._update_timer = ba.Timer( 499 1.0, 500 ba.WeakCall(self._update), 501 timetype=ba.TimeType.REAL, 502 repeat=True, 503 ) 504 self._smooth_increase_speed = 1.0
Inherited Members
- ba.ui.Window
- get_root_widget
def
show_get_tickets_prompt() -> None:
757def show_get_tickets_prompt() -> None: 758 """Show a 'not enough tickets' prompt with an option to purchase more. 759 760 Note that the purchase option may not always be available 761 depending on the build of the game. 762 """ 763 from bastd.ui.confirm import ConfirmWindow 764 765 if ba.app.allow_ticket_purchases: 766 ConfirmWindow( 767 ba.Lstr( 768 translate=( 769 'serverResponses', 770 'You don\'t have enough tickets for this!', 771 ) 772 ), 773 lambda: GetCurrencyWindow(modal=True), 774 ok_text=ba.Lstr(resource='getTicketsWindow.titleText'), 775 width=460, 776 height=130, 777 ) 778 else: 779 ConfirmWindow( 780 ba.Lstr( 781 translate=( 782 'serverResponses', 783 'You don\'t have enough tickets for this!', 784 ) 785 ), 786 cancel_button=False, 787 width=460, 788 height=130, 789 )
Show a 'not enough tickets' prompt with an option to purchase more.
Note that the purchase option may not always be available depending on the build of the game.