def __init__(self, items: List[str], transition: str = 'in_right', header_text: ba.Lstr = None): from ba.internal import get_store_item_display_size from bastd.ui.store import item as storeitemui if header_text is None: header_text = ba.Lstr(resource='unlockThisText', fallback_resource='unlockThisInTheStoreText') if len(items) != 1: raise ValueError('expected exactly 1 item') self._items = list(items) self._width = 580 self._height = 520 uiscale = ba.app.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, toolbar_visibility='menu_currency', scale=(1.2 if uiscale is ba.UIScale.SMALL else 1.1 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0))) self._is_double = False self._title_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 30), size=(0, 0), text=header_text, h_align='center', v_align='center', maxwidth=self._width * 0.9 - 120, scale=1.2, color=(1, 0.8, 0.3, 1)) size = get_store_item_display_size(items[0]) display: Dict[str, Any] = {} storeitemui.instantiate_store_item_display( items[0], display, parent_widget=self._root_widget, b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 - ((size[0] * 0.5 + 30) if self._is_double else 0), self._height * 0.5 - size[1] * 0.5 + 30 + (20 if self._is_double else 0)), b_width=size[0], b_height=size[1], button=False) # Wire up the parts we need. if self._is_double: pass # not working else: if self._items == ['pro']: price_str = _ba.get_price(self._items[0]) pyoffs = -15 else: pyoffs = 0 price = self._price = _ba.get_account_misc_read_val( 'price.' + str(items[0]), -1) price_str = ba.charstr(ba.SpecialChar.TICKET) + str(price) self._price_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, 150 + pyoffs), size=(0, 0), text=price_str, h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=1.4, color=(0.2, 1, 0.2)) self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(50, 40), size=(150, 60), scale=1.0, on_activate_call=self._cancel, autoselect=True, label=ba.Lstr(resource='cancelText')) self._purchase_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 200, 40), size=(150, 60), scale=1.0, on_activate_call=self._purchase, autoselect=True, label=ba.Lstr(resource='store.purchaseText')) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button, start_button=self._purchase_button, selected_child=self._purchase_button)
def __init__(self, transition: str = 'in_right', from_modal_store: bool = False, modal: bool = False, origin_widget: ba.Widget = None, store_back_location: str = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals ba.set_analytics_screen('Get Tickets Window') self._transitioning_out = False self._store_back_location = store_back_location # ew. self._ad_button_greyed = False self._smooth_update_timer: Optional[ba.Timer] = None self._ad_button = None self._ad_label = None self._ad_image = None self._ad_time_text = None # If they provided an origin-widget, scale up from that. scale_origin: Optional[Tuple[float, float]] if origin_widget is not None: self._transition_out = 'out_scale' scale_origin = origin_widget.get_screen_space_center() transition = 'in_scale' else: self._transition_out = 'out_right' scale_origin = None uiscale = ba.app.uiscale self._width = 1000.0 if uiscale is ba.UIScale.SMALL else 800.0 x_inset = 100.0 if uiscale is ba.UIScale.SMALL else 0.0 self._height = 480.0 self._modal = modal self._from_modal_store = from_modal_store self._r = 'getTicketsWindow' top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height + top_extra), transition=transition, scale_origin_stack_offset=scale_origin, color=(0.4, 0.37, 0.55), scale=(1.63 if uiscale is ba.UIScale.SMALL else 1.2 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -3) if uiscale is ba.UIScale.SMALL else (0, 0))) btn = ba.buttonwidget( parent=self._root_widget, position=(55 + x_inset, self._height - 79), size=(140, 60), scale=1.0, autoselect=True, label=ba.Lstr(resource='doneText' if modal else 'backText'), button_type='regular' if modal else 'back', on_activate_call=self._back) ba.containerwidget(edit=self._root_widget, cancel_button=btn) ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height - 55), size=(0, 0), color=ba.app.ui.title_color, scale=1.2, h_align='center', v_align='center', text=ba.Lstr(resource=self._r + '.titleText'), maxwidth=290) if not modal: ba.buttonwidget(edit=btn, button_type='backSmall', size=(60, 60), label=ba.charstr(ba.SpecialChar.BACK)) b_size = (220.0, 180.0) v = self._height - b_size[1] - 80 spacing = 1 self._ad_button = None def _add_button(item: str, position: Tuple[float, float], size: Tuple[float, float], label: ba.Lstr, price: str = None, tex_name: str = None, tex_opacity: float = 1.0, tex_scale: float = 1.0, enabled: bool = True, text_scale: float = 1.0) -> ba.Widget: btn2 = ba.buttonwidget( parent=self._root_widget, position=position, button_type='square', size=size, label='', autoselect=True, color=None if enabled else (0.5, 0.5, 0.5), on_activate_call=(ba.Call(self._purchase, item) if enabled else self._disabled_press)) txt = ba.textwidget(parent=self._root_widget, text=label, position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.3), scale=text_scale, maxwidth=size[0] * 0.75, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2)) if price is not None and enabled: ba.textwidget(parent=self._root_widget, text=price, position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.17), scale=0.7, maxwidth=size[0] * 0.75, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.4, 0.9, 0.4, 1.0)) i = None if tex_name is not None: tex_size = 90.0 * tex_scale i = ba.imagewidget( parent=self._root_widget, texture=ba.gettexture(tex_name), position=(position[0] + size[0] * 0.5 - tex_size * 0.5, position[1] + size[1] * 0.66 - tex_size * 0.5), size=(tex_size, tex_size), draw_controller=btn2, opacity=tex_opacity * (1.0 if enabled else 0.25)) if item == 'ad': self._ad_button = btn2 self._ad_label = txt assert i is not None self._ad_image = i self._ad_time_text = ba.textwidget( parent=self._root_widget, text='1m 10s', position=(position[0] + size[0] * 0.5, position[1] + size[1] * 0.5), scale=text_scale * 1.2, maxwidth=size[0] * 0.85, size=(0, 0), h_align='center', v_align='center', draw_controller=btn2, color=(0.4, 0.9, 0.4, 1.0)) return btn2 rsrc = self._r + '.ticketsText' c2txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets2Amount', 500)))]) c3txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets3Amount', 1500)))]) c4txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets4Amount', 5000)))]) c5txt = ba.Lstr( resource=rsrc, subs=[('${COUNT}', str(_ba.get_account_misc_read_val('tickets5Amount', 15000)))]) h = 110.0 # enable buttons if we have prices.. tickets2_price = _ba.get_price('tickets2') tickets3_price = _ba.get_price('tickets3') tickets4_price = _ba.get_price('tickets4') tickets5_price = _ba.get_price('tickets5') # TEMP # tickets1_price = '$0.99' # tickets2_price = '$4.99' # tickets3_price = '$9.99' # tickets4_price = '$19.99' # tickets5_price = '$49.99' _add_button('tickets2', enabled=(tickets2_price is not None), position=(self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, v), size=b_size, label=c2txt, price=tickets2_price, tex_name='ticketsMore') # 0.99-ish _add_button('tickets3', enabled=(tickets3_price is not None), position=(self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, v), size=b_size, label=c3txt, price=tickets3_price, tex_name='ticketRoll') # 4.99-ish v -= b_size[1] - 5 _add_button('tickets4', enabled=(tickets4_price is not None), position=(self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h, v), size=b_size, label=c4txt, price=tickets4_price, tex_name='ticketRollBig', tex_scale=1.2) # 9.99-ish _add_button('tickets5', enabled=(tickets5_price is not None), position=(self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h, v), size=b_size, label=c5txt, price=tickets5_price, tex_name='ticketRolls', tex_scale=1.2) # 19.99-ish self._enable_ad_button = _ba.has_video_ads() h = self._width * 0.5 + 110.0 v = self._height - b_size[1] - 115.0 if self._enable_ad_button: h_offs = 35 b_size_3 = (150, 120) cdb = _add_button( 'ad', position=(h + h_offs, v), size=b_size_3, label=ba.Lstr(resource=self._r + '.ticketsFromASponsorText', subs=[('${COUNT}', str( _ba.get_account_misc_read_val( 'sponsorTickets', 5)))]), tex_name='ticketsMore', enabled=self._enable_ad_button, tex_opacity=0.6, tex_scale=0.7, text_scale=0.7) ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7) if self._enable_ad_button else (0.5, 0.5, 0.5)) self._ad_free_text = ba.textwidget( parent=self._root_widget, text=ba.Lstr(resource=self._r + '.freeText'), position=(h + h_offs + b_size_3[0] * 0.5, v + b_size_3[1] * 0.5 + 25), size=(0, 0), color=(1, 1, 0, 1.0) if self._enable_ad_button else (1, 1, 1, 0.2), draw_controller=cdb, rotate=15, shadow=1.0, maxwidth=150, h_align='center', v_align='center', scale=1.0) v -= 125 else: v -= 20 if True: # pylint: disable=using-constant-test h_offs = 35 b_size_3 = (150, 120) cdb = _add_button( 'app_invite', position=(h + h_offs, v), size=b_size_3, label=ba.Lstr( resource='gatherWindow.earnTicketsForRecommendingText', subs=[ ('${COUNT}', str(_ba.get_account_misc_read_val( 'sponsorTickets', 5))) ]), tex_name='ticketsMore', enabled=True, tex_opacity=0.6, tex_scale=0.7, text_scale=0.7) ba.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7)) ba.textwidget(parent=self._root_widget, text=ba.Lstr(resource=self._r + '.freeText'), position=(h + h_offs + b_size_3[0] * 0.5, v + b_size_3[1] * 0.5 + 25), size=(0, 0), color=(1, 1, 0, 1.0), draw_controller=cdb, rotate=15, shadow=1.0, maxwidth=150, h_align='center', v_align='center', scale=1.0) tc_y_offs = 0 h = self._width - (185 + x_inset) v = self._height - 95 + tc_y_offs txt1 = (ba.Lstr( resource=self._r + '.youHaveText').evaluate().split('${COUNT}')[0].strip()) txt2 = (ba.Lstr( resource=self._r + '.youHaveText').evaluate().split('${COUNT}')[-1].strip()) ba.textwidget(parent=self._root_widget, text=txt1, position=(h, v), size=(0, 0), color=(0.5, 0.5, 0.6), maxwidth=200, h_align='center', v_align='center', scale=0.8) v -= 30 self._ticket_count_text = ba.textwidget(parent=self._root_widget, position=(h, v), size=(0, 0), color=(0.2, 1.0, 0.2), maxwidth=200, h_align='center', v_align='center', scale=1.6) v -= 30 ba.textwidget(parent=self._root_widget, text=txt2, position=(h, v), size=(0, 0), color=(0.5, 0.5, 0.6), maxwidth=200, h_align='center', v_align='center', scale=0.8) # update count now and once per second going forward.. self._ticking_node: Optional[ba.Node] = None self._smooth_ticket_count: Optional[float] = None self._ticket_count = 0 self._update() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._smooth_increase_speed = 1.0
def update_buttons(self) -> None: """Update our buttons.""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import have_pro, get_available_sale_time from ba import SpecialChar if not self._root_widget: return import datetime sales_raw = _ba.get_account_misc_read_val('sales', {}) sales = {} try: # Look at the current set of sales; filter any with time remaining. for sale_item, sale_info in list(sales_raw.items()): to_end = (datetime.datetime.utcfromtimestamp(sale_info['e']) - datetime.datetime.utcnow()).total_seconds() if to_end > 0: sales[sale_item] = { 'to_end': to_end, 'original_price': sale_info['op'] } except Exception: ba.print_exception('Error parsing sales.') assert self.button_infos is not None for b_type, b_info in self.button_infos.items(): if b_type in ['upgrades.pro', 'pro']: purchased = have_pro() else: purchased = _ba.get_purchased(b_type) sale_opacity = 0.0 sale_title_text: Union[str, ba.Lstr] = '' sale_time_text: Union[str, ba.Lstr] = '' if purchased: title_color = (0.8, 0.7, 0.9, 1.0) color = (0.63, 0.55, 0.78) extra_image_opacity = 0.5 call = ba.WeakCall(self._print_already_own, b_info['name']) price_text = '' price_text_left = '' price_text_right = '' show_purchase_check = True description_color: Sequence[float] = (0.4, 1.0, 0.4, 0.4) description_color2: Sequence[float] = (0.0, 0.0, 0.0, 0.0) price_color = (0.5, 1, 0.5, 0.3) else: title_color = (0.7, 0.9, 0.7, 1.0) color = (0.4, 0.8, 0.1) extra_image_opacity = 1.0 call = b_info['call'] if 'call' in b_info else None if b_type in ['upgrades.pro', 'pro']: sale_time = get_available_sale_time('extras') if sale_time is not None: priceraw = _ba.get_price('pro') price_text_left = (priceraw if priceraw is not None else '?') priceraw = _ba.get_price('pro_sale') price_text_right = (priceraw if priceraw is not None else '?') sale_opacity = 1.0 price_text = '' sale_title_text = ba.Lstr(resource='store.saleText') sale_time_text = ba.timestring( sale_time, centi=False, timeformat=ba.TimeFormat.MILLISECONDS) else: priceraw = _ba.get_price('pro') price_text = priceraw if priceraw is not None else '?' price_text_left = '' price_text_right = '' else: price = _ba.get_account_misc_read_val('price.' + b_type, 0) # Color the button differently if we cant afford this. if _ba.get_account_state() == 'signed_in': if _ba.get_account_ticket_count() < price: color = (0.6, 0.61, 0.6) price_text = ba.charstr(ba.SpecialChar.TICKET) + str( _ba.get_account_misc_read_val('price.' + b_type, '?')) price_text_left = '' price_text_right = '' # TESTING: if b_type in sales: sale_opacity = 1.0 price_text_left = ba.charstr(SpecialChar.TICKET) + str( sales[b_type]['original_price']) price_text_right = price_text price_text = '' sale_title_text = ba.Lstr(resource='store.saleText') sale_time_text = ba.timestring( int(sales[b_type]['to_end'] * 1000), centi=False, timeformat=ba.TimeFormat.MILLISECONDS) description_color = (0.5, 1.0, 0.5) description_color2 = (0.3, 1.0, 1.0) price_color = (0.2, 1, 0.2, 1.0) show_purchase_check = False if 'title_text' in b_info: ba.textwidget(edit=b_info['title_text'], color=title_color) if 'purchase_check' in b_info: ba.imagewidget(edit=b_info['purchase_check'], opacity=1.0 if show_purchase_check else 0.0) if 'price_widget' in b_info: ba.textwidget(edit=b_info['price_widget'], text=price_text, color=price_color) if 'price_widget_left' in b_info: ba.textwidget(edit=b_info['price_widget_left'], text=price_text_left) if 'price_widget_right' in b_info: ba.textwidget(edit=b_info['price_widget_right'], text=price_text_right) if 'price_slash_widget' in b_info: ba.imagewidget(edit=b_info['price_slash_widget'], opacity=sale_opacity) if 'sale_bg_widget' in b_info: ba.imagewidget(edit=b_info['sale_bg_widget'], opacity=sale_opacity) if 'sale_title_widget' in b_info: ba.textwidget(edit=b_info['sale_title_widget'], text=sale_title_text) if 'sale_time_widget' in b_info: ba.textwidget(edit=b_info['sale_time_widget'], text=sale_time_text) if 'button' in b_info: ba.buttonwidget(edit=b_info['button'], color=color, on_activate_call=call) if 'extra_backings' in b_info: for bck in b_info['extra_backings']: ba.imagewidget(edit=bck, color=color, opacity=extra_image_opacity) if 'extra_images' in b_info: for img in b_info['extra_images']: ba.imagewidget(edit=img, opacity=extra_image_opacity) if 'extra_texts' in b_info: for etxt in b_info['extra_texts']: ba.textwidget(edit=etxt, color=description_color) if 'extra_texts_2' in b_info: for etxt in b_info['extra_texts_2']: ba.textwidget(edit=etxt, color=description_color2) if 'descriptionText' in b_info: ba.textwidget(edit=b_info['descriptionText'], color=description_color)
def instantiate_store_item_display(item_name: str, item: Dict[str, Any], parent_widget: ba.Widget, b_pos: Tuple[float, float], b_width: float, b_height: float, boffs_h: float = 0.0, boffs_h2: float = 0.0, boffs_v2: float = 0, delay: float = 0.0, button: bool = True) -> None: """(internal)""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import (get_store_item, get_store_item_name_translated, get_clean_price) del boffs_h # unused arg del boffs_h2 # unused arg del boffs_v2 # unused arg item_info = get_store_item(item_name) title_v = 0.24 price_v = 0.145 base_text_scale = 1.0 item['name'] = title = get_store_item_name_translated(item_name) btn: Optional[ba.Widget] if button: item['button'] = btn = ba.buttonwidget(parent=parent_widget, position=b_pos, transition_delay=delay, show_buffer_top=76.0, enable_sound=False, button_type='square', size=(b_width, b_height), autoselect=True, label='') ba.widget(edit=btn, show_buffer_bottom=76.0) else: btn = None b_offs_x = -0.015 * b_width check_pos = 0.76 icon_tex = None tint_tex = None tint_color = None tint2_color = None tex_name: Optional[str] = None desc: Optional[str] = None modes: Optional[ba.Lstr] = None if item_name.startswith('characters.'): character = ba.app.spaz_appearances[item_info['character']] tint_color = ( item_info['color'] if 'color' in item_info else character.default_color if character.default_color is not None else (1, 1, 1)) tint2_color = (item_info['highlight'] if 'highlight' in item_info else character.default_highlight if character.default_highlight is not None else (1, 1, 1)) icon_tex = character.icon_texture tint_tex = character.icon_mask_texture title_v = 0.255 price_v = 0.145 elif item_name in ['upgrades.pro', 'pro']: base_text_scale = 0.6 title_v = 0.85 price_v = 0.15 elif item_name.startswith('maps.'): map_type = item_info['map_type'] tex_name = map_type.get_preview_texture_name() title_v = 0.312 price_v = 0.17 elif item_name.startswith('games.'): gametype = item_info['gametype'] modes_l = [] if gametype.supports_session_type(ba.CoopSession): modes_l.append(ba.Lstr(resource='playModes.coopText')) if gametype.supports_session_type(ba.TeamsSession): modes_l.append(ba.Lstr(resource='playModes.teamsText')) if gametype.supports_session_type(ba.FreeForAllSession): modes_l.append(ba.Lstr(resource='playModes.freeForAllText')) if len(modes_l) == 3: modes = ba.Lstr(value='${A}, ${B}, ${C}', subs=[('${A}', modes_l[0]), ('${B}', modes_l[1]), ('${C}', modes_l[2])]) elif len(modes_l) == 2: modes = ba.Lstr(value='${A}, ${B}', subs=[('${A}', modes_l[0]), ('${B}', modes_l[1])]) elif len(modes_l) == 1: modes = modes_l[0] else: raise Exception() desc = gametype.get_description_display_string(ba.CoopSession) tex_name = item_info['previewTex'] base_text_scale = 0.8 title_v = 0.48 price_v = 0.17 elif item_name.startswith('icons.'): base_text_scale = 1.5 price_v = 0.2 check_pos = 0.6 if item_name.startswith('characters.'): frame_size = b_width * 0.7 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.57 - im_dim * 0.5) mask_texture = ba.gettexture('characterIconMask') assert icon_tex is not None assert tint_tex is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim), color=(1, 1, 1), transition_delay=delay, mask_texture=mask_texture, draw_controller=btn, texture=ba.gettexture(icon_tex), tint_texture=ba.gettexture(tint_tex), tint_color=tint_color, tint2_color=tint2_color) if item_name in ['pro', 'upgrades.pro']: frame_size = b_width * 0.5 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.5 - im_dim * 0.5) ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim), transition_delay=delay, draw_controller=btn, color=(0.3, 0.0, 0.3), opacity=0.3, texture=ba.gettexture('logo')) txt = ba.Lstr(resource='store.bombSquadProNewDescriptionText') # t = 'foo\nfoo\nfoo\nfoo\nfoo\nfoo' item['descriptionText'] = ba.textwidget( parent=parent_widget, text=txt, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.69), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.75, maxwidth=b_width * 0.75, max_height=b_height * 0.2, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.3, 1, 0.3)) extra_backings = item['extra_backings'] = [] extra_images = item['extra_images'] = [] extra_texts = item['extra_texts'] = [] extra_texts_2 = item['extra_texts_2'] = [] backing_color = (0.5, 0.8, 0.3) if button else (0.6, 0.5, 0.65) b_square_texture = ba.gettexture('buttonSquare') char_mask_texture = ba.gettexture('characterIconMask') pos = (0.17, 0.43) tile_size = (b_width * 0.16 * 1.2, b_width * 0.2 * 1.2) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) im_size = tile_size[0] * 0.8 extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.5, tile_pos[1] - im_size * 0.4), size=(im_size, im_size), transition_delay=delay, draw_controller=btn, color=(1, 1, 1), texture=ba.gettexture('ticketsMore'))) bonus_tickets = str( _ba.get_account_misc_read_val('proBonusTickets', 100)) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - tile_size[0] * 0.03, tile_pos[1] - tile_size[1] * 0.25), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(resource='getTicketsWindow.ticketsText', subs=[('${COUNT}', bonus_tickets)]), flatness=1.0, shadow=0.0)) for charname, pos in [('Kronk', (0.32, 0.45)), ('Zoe', (0.425, 0.4)), ('Jack Morgan', (0.555, 0.45)), ('Mel', (0.645, 0.4))]: tile_size = (b_width * 0.16 * 0.9, b_width * 0.2 * 0.9) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) character = ba.app.spaz_appearances[charname] extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) im_size = tile_size[0] * 0.7 extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.53, tile_pos[1] - im_size * 0.35), size=(im_size, im_size), transition_delay=delay, draw_controller=btn, color=(1, 1, 1), texture=ba.gettexture(character.icon_texture), tint_texture=ba.gettexture( character.icon_mask_texture), tint_color=character.default_color, tint2_color=character.default_highlight, mask_texture=char_mask_texture)) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - im_size * 0.03, tile_pos[1] - im_size * 0.51), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(translate=('characterNames', charname)), flatness=1.0, shadow=0.0)) # If we have a 'total-worth' item-id for this id, show that price so # the user knows how much this is worth. total_worth_item = _ba.get_account_misc_read_val('twrths', {}).get(item_name) total_worth_price: Optional[str] if total_worth_item is not None: price = _ba.get_price(total_worth_item) assert price is not None total_worth_price = get_clean_price(price) else: total_worth_price = None if total_worth_price is not None: total_worth_text = ba.Lstr(resource='store.totalWorthText', subs=[('${TOTAL_WORTH}', total_worth_price)]) extra_texts_2.append( ba.textwidget(parent=parent_widget, text=total_worth_text, position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * 0.25), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.45, maxwidth=b_width * 0.5, size=(0, 0), h_align='center', v_align='center', shadow=1.0, flatness=1.0, draw_controller=btn, color=(0.3, 1, 1))) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') for levelname, preview_tex_name, pos in [ ('Infinite Onslaught', 'doomShroomPreview', (0.80, 0.48)), ('Infinite Runaround', 'towerDPreview', (0.80, 0.32)) ]: tile_size = (b_width * 0.2, b_width * 0.13) tile_pos = (b_pos[0] + b_width * pos[0], b_pos[1] + b_height * pos[1]) im_size = tile_size[0] * 0.8 extra_backings.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - tile_size[0] * 0.5, tile_pos[1] - tile_size[1] * 0.5), size=tile_size, transition_delay=delay, draw_controller=btn, color=backing_color, texture=b_square_texture)) # hack - gotta draw two transparent versions to avoid z issues for mod in model_opaque, model_transparent: extra_images.append( ba.imagewidget(parent=parent_widget, position=(tile_pos[0] - im_size * 0.52, tile_pos[1] - im_size * 0.2), size=(im_size, im_size * 0.5), transition_delay=delay, model_transparent=mod, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(preview_tex_name))) extra_texts.append( ba.textwidget(parent=parent_widget, draw_controller=btn, position=(tile_pos[0] - im_size * 0.03, tile_pos[1] - im_size * 0.2), size=(0, 0), color=(0.6, 1, 0.6), transition_delay=delay, h_align='center', v_align='center', maxwidth=tile_size[0] * 0.7, scale=0.55, text=ba.Lstr(translate=('coopLevelNames', levelname)), flatness=1.0, shadow=0.0)) if item_name.startswith('icons.'): item['icon_text'] = ba.textwidget( parent=parent_widget, text=item_info['icon'], position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.5), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 2.0, maxwidth=b_width * 0.9, max_height=b_height * 0.9, size=(0, 0), h_align='center', v_align='center', draw_controller=btn) if item_name.startswith('maps.'): frame_size = b_width * 0.9 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.62 - im_dim * 0.25) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim * 0.5), transition_delay=delay, model_opaque=model_opaque, model_transparent=model_transparent, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(tex_name)) if item_name.startswith('games.'): frame_size = b_width * 0.8 im_dim = frame_size * (100.0 / 113.0) im_pos = (b_pos[0] + b_width * 0.5 - im_dim * 0.5 + b_offs_x, b_pos[1] + b_height * 0.72 - im_dim * 0.25) model_opaque = ba.getmodel('level_select_button_opaque') model_transparent = ba.getmodel('level_select_button_transparent') mask_tex = ba.gettexture('mapPreviewMask') assert tex_name is not None ba.imagewidget(parent=parent_widget, position=im_pos, size=(im_dim, im_dim * 0.5), transition_delay=delay, model_opaque=model_opaque, model_transparent=model_transparent, mask_texture=mask_tex, draw_controller=btn, texture=ba.gettexture(tex_name)) item['descriptionText'] = ba.textwidget( parent=parent_widget, text=desc, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.36), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.78, maxwidth=b_width * 0.8, max_height=b_height * 0.14, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, flatness=1.0, shadow=0.0, color=(0.6, 1, 0.6)) item['gameModesText'] = ba.textwidget( parent=parent_widget, text=modes, position=(b_pos[0] + b_width * 0.5, b_pos[1] + b_height * 0.26), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale * 0.65, maxwidth=b_width * 0.8, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0, flatness=1.0, color=(0.6, 0.8, 0.6)) if not item_name.startswith('icons.'): item['title_text'] = ba.textwidget( parent=parent_widget, text=title, position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * title_v), transition_delay=delay, scale=b_width * (1.0 / 230.0) * base_text_scale, maxwidth=b_width * 0.8, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.7, 0.9, 0.7, 1.0)) item['purchase_check'] = ba.imagewidget( parent=parent_widget, position=(b_pos[0] + b_width * check_pos, b_pos[1] + b_height * 0.05), transition_delay=delay, model_transparent=ba.getmodel('checkTransparent'), opacity=0.0, size=(60, 60), color=(0.6, 0.5, 0.8), draw_controller=btn, texture=ba.gettexture('uiAtlas')) item['price_widget'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.5 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.9, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 1.0)) item['price_widget_left'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.33 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 0.5)) item['price_widget_right'] = ba.textwidget( parent=parent_widget, text='', position=(b_pos[0] + b_width * 0.66 + b_offs_x, b_pos[1] + b_height * price_v), transition_delay=delay, scale=1.1 * b_width * (1.0 / 300.0) * base_text_scale, maxwidth=b_width * 0.3, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, color=(0.2, 1, 0.2, 1.0)) item['price_slash_widget'] = ba.imagewidget( parent=parent_widget, position=(b_pos[0] + b_width * 0.33 + b_offs_x - 36, b_pos[1] + b_height * price_v - 35), transition_delay=delay, texture=ba.gettexture('slash'), opacity=0.0, size=(70, 70), draw_controller=btn, color=(1, 0, 0)) badge_rad = 44 badge_center = (b_pos[0] + b_width * 0.1 + b_offs_x, b_pos[1] + b_height * 0.87) item['sale_bg_widget'] = ba.imagewidget( parent=parent_widget, position=(badge_center[0] - badge_rad, badge_center[1] - badge_rad), opacity=0.0, transition_delay=delay, texture=ba.gettexture('circleZigZag'), draw_controller=btn, size=(badge_rad * 2, badge_rad * 2), color=(0.5, 0, 1)) item['sale_title_widget'] = ba.textwidget(parent=parent_widget, position=(badge_center[0], badge_center[1] + 12), transition_delay=delay, scale=1.0, maxwidth=badge_rad * 1.6, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0.0, flatness=1.0, color=(0, 1, 0)) item['sale_time_widget'] = ba.textwidget(parent=parent_widget, position=(badge_center[0], badge_center[1] - 12), transition_delay=delay, scale=0.7, maxwidth=badge_rad * 1.6, size=(0, 0), h_align='center', v_align='center', draw_controller=btn, shadow=0.0, flatness=1.0, color=(0.0, 1, 0.0, 1))
def __init__(self, offer: Dict[str, Any], transition: str = 'in_right'): # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba.internal import (get_store_item_display_size, get_clean_price) from ba import SpecialChar from bastd.ui.store import item as storeitemui self._cancel_delay = offer.get('cancelDelay', 0) # First thing: if we're offering pro or an IAP, see if we have a # price for it. # If not, abort and go into zombie mode (the user should never see # us that way). real_price: Optional[str] # Misnomer: 'pro' actually means offer 'pro_sale'. if offer['item'] in ['pro', 'pro_fullprice']: real_price = _ba.get_price('pro' if offer['item'] == 'pro_fullprice' else 'pro_sale') if real_price is None and ba.app.debug_build: print('NOTE: Faking prices for debug build.') real_price = '$1.23' zombie = real_price is None elif isinstance(offer['price'], str): # (a string price implies IAP id) real_price = _ba.get_price(offer['price']) if real_price is None and ba.app.debug_build: print('NOTE: Faking price for debug build.') real_price = '$1.23' zombie = real_price is None else: real_price = None zombie = False if real_price is None: real_price = '?' if offer['item'] in ['pro', 'pro_fullprice']: self._offer_item = 'pro' else: self._offer_item = offer['item'] # If we wanted a real price but didn't find one, go zombie. if zombie: return # This can pop up suddenly, so lets block input for 1 second. _ba.lock_all_input() ba.timer(1.0, _ba.unlock_all_input, timetype=ba.TimeType.REAL) ba.playsound(ba.getsound('ding')) ba.timer(0.3, lambda: ba.playsound(ba.getsound('ooh')), timetype=ba.TimeType.REAL) self._offer = copy.deepcopy(offer) self._width = 580 self._height = 590 uiscale = ba.app.ui.uiscale super().__init__(root_widget=ba.containerwidget( size=(self._width, self._height), transition=transition, scale=(1.2 if uiscale is ba.UIScale.SMALL else 1.15 if uiscale is ba.UIScale.MEDIUM else 1.0), stack_offset=(0, -15) if uiscale is ba.UIScale.SMALL else (0, 0))) self._is_bundle_sale = False try: if offer['item'] in ['pro', 'pro_fullprice']: original_price_str = _ba.get_price('pro') if original_price_str is None: original_price_str = '?' new_price_str = _ba.get_price('pro_sale') if new_price_str is None: new_price_str = '?' percent_off_text = '' else: # If the offer includes bonus tickets, it's a bundle-sale. if ('bonusTickets' in offer and offer['bonusTickets'] is not None): self._is_bundle_sale = True original_price = _ba.get_account_misc_read_val( 'price.' + self._offer_item, 9999) # For pure ticket prices we can show a percent-off. if isinstance(offer['price'], int): new_price = offer['price'] tchar = ba.charstr(SpecialChar.TICKET) original_price_str = tchar + str(original_price) new_price_str = tchar + str(new_price) percent_off = int( round(100.0 - (float(new_price) / original_price) * 100.0)) percent_off_text = ' ' + ba.Lstr( resource='store.salePercentText').evaluate().replace( '${PERCENT}', str(percent_off)) else: original_price_str = new_price_str = '?' percent_off_text = '' except Exception: print(f'Offer: {offer}') ba.print_exception('Error setting up special-offer') original_price_str = new_price_str = '?' percent_off_text = '' # If its a bundle sale, change the title. if self._is_bundle_sale: sale_text = ba.Lstr(resource='store.saleBundleText', fallback_resource='store.saleText').evaluate() else: # For full pro we say 'Upgrade?' since its not really a sale. if offer['item'] == 'pro_fullprice': sale_text = ba.Lstr( resource='store.upgradeQuestionText', fallback_resource='store.saleExclaimText').evaluate() else: sale_text = ba.Lstr( resource='store.saleExclaimText', fallback_resource='store.saleText').evaluate() self._title_text = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5, self._height - 40), size=(0, 0), text=sale_text + ((' ' + ba.Lstr(resource='store.oneTimeOnlyText').evaluate()) if self._offer['oneTimeOnly'] else '') + percent_off_text, h_align='center', v_align='center', maxwidth=self._width * 0.9 - 220, scale=1.4, color=(0.3, 1, 0.3)) self._flash_on = False self._flashing_timer: Optional[ba.Timer] = ba.Timer( 0.05, ba.WeakCall(self._flash_cycle), repeat=True, timetype=ba.TimeType.REAL) ba.timer(0.6, ba.WeakCall(self._stop_flashing), timetype=ba.TimeType.REAL) size = get_store_item_display_size(self._offer_item) display: Dict[str, Any] = {} storeitemui.instantiate_store_item_display( self._offer_item, display, parent_widget=self._root_widget, b_pos=(self._width * 0.5 - size[0] * 0.5 + 10 - ((size[0] * 0.5 + 30) if self._is_bundle_sale else 0), self._height * 0.5 - size[1] * 0.5 + 20 + (20 if self._is_bundle_sale else 0)), b_width=size[0], b_height=size[1], button=not self._is_bundle_sale) # Wire up the parts we need. if self._is_bundle_sale: self._plus_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, self._height * 0.5 + 50), size=(0, 0), text='+', h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=1.4, color=(0.5, 0.5, 0.5)) self._plus_tickets = ba.textwidget( parent=self._root_widget, position=(self._width * 0.5 + 120, self._height * 0.5 + 50), size=(0, 0), text=ba.charstr(SpecialChar.TICKET_BACKING) + str(offer['bonusTickets']), h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=2.5, color=(0.2, 1, 0.2)) self._price_text = ba.textwidget(parent=self._root_widget, position=(self._width * 0.5, 150), size=(0, 0), text=real_price, h_align='center', v_align='center', maxwidth=self._width * 0.9, scale=1.4, color=(0.2, 1, 0.2)) # Total-value if they supplied it. total_worth_item = offer.get('valueItem', None) if total_worth_item is not None: price = _ba.get_price(total_worth_item) total_worth_price = (get_clean_price(price) if price is not None else None) if total_worth_price is not None: total_worth_text = ba.Lstr(resource='store.totalWorthText', subs=[('${TOTAL_WORTH}', total_worth_price)]) self._total_worth_text = ba.textwidget( parent=self._root_widget, text=total_worth_text, position=(self._width * 0.5, 210), scale=0.9, maxwidth=self._width * 0.7, size=(0, 0), h_align='center', v_align='center', shadow=1.0, flatness=1.0, color=(0.3, 1, 1)) elif offer['item'] == 'pro_fullprice': # for full-price pro we simply show full price ba.textwidget(edit=display['price_widget'], text=real_price) ba.buttonwidget(edit=display['button'], on_activate_call=self._purchase) else: # Show old/new prices otherwise (for pro sale). ba.buttonwidget(edit=display['button'], on_activate_call=self._purchase) ba.imagewidget(edit=display['price_slash_widget'], opacity=1.0) ba.textwidget(edit=display['price_widget_left'], text=original_price_str) ba.textwidget(edit=display['price_widget_right'], text=new_price_str) # Add ticket button only if this is ticket-purchasable. if isinstance(offer.get('price'), int): self._get_tickets_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 125, self._height - 68), size=(90, 55), scale=1.0, button_type='square', color=(0.7, 0.5, 0.85), textcolor=(0.2, 1, 0.2), autoselect=True, label=ba.Lstr(resource='getTicketsWindow.titleText'), on_activate_call=self._on_get_more_tickets_press) self._ticket_text_update_timer = ba.Timer( 1.0, ba.WeakCall(self._update_tickets_text), timetype=ba.TimeType.REAL, repeat=True) self._update_tickets_text() self._update_timer = ba.Timer(1.0, ba.WeakCall(self._update), timetype=ba.TimeType.REAL, repeat=True) self._cancel_button = ba.buttonwidget( parent=self._root_widget, position=(50, 40) if self._is_bundle_sale else (self._width * 0.5 - 75, 40), size=(150, 60), scale=1.0, on_activate_call=self._cancel, autoselect=True, label=ba.Lstr(resource='noThanksText')) self._cancel_countdown_text = ba.textwidget( parent=self._root_widget, text='', position=(50 + 150 + 20, 40 + 27) if self._is_bundle_sale else (self._width * 0.5 - 75 + 150 + 20, 40 + 27), scale=1.1, size=(0, 0), h_align='left', v_align='center', shadow=1.0, flatness=1.0, color=(0.6, 0.5, 0.5)) self._update_cancel_button_graphics() if self._is_bundle_sale: self._purchase_button = ba.buttonwidget( parent=self._root_widget, position=(self._width - 200, 40), size=(150, 60), scale=1.0, on_activate_call=self._purchase, autoselect=True, label=ba.Lstr(resource='store.purchaseText')) ba.containerwidget(edit=self._root_widget, cancel_button=self._cancel_button, start_button=self._purchase_button if self._is_bundle_sale else None, selected_child=self._purchase_button if self._is_bundle_sale else display['button'])