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