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

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.