def have_pro() -> bool: """Return whether pro is currently unlocked.""" # Check our tickets-based pro upgrade and our two real-IAP based upgrades. return bool( _ba.get_purchased('upgrades.pro') or _ba.get_purchased('static.pro') or _ba.get_purchased('static.pro_sale'))
def have_pro() -> bool: """Return whether pro is currently unlocked.""" # Check our tickets-based pro upgrade and our two real-IAP based upgrades. # Also unlock this stuff in ballistica-core builds. return bool( _ba.get_purchased('upgrades.pro') or _ba.get_purchased('static.pro') or _ba.get_purchased('static.pro_sale') or 'ballistica' + 'core' == 'ballisticacore')
def _on_ad_complete(self, actually_showed: bool) -> None: # Make sure any transactions the ad added got locally applied # (rewards added, etc.). _ba.run_transactions() # If we're already entering the tourney, ignore. if self._entering: return if not actually_showed: return # This should have awarded us the tournament_entry_ad purchase; # make sure that's present. # (otherwise the server will ignore our tournament entry anyway) if not _ba.get_purchased('tournament_entry_ad'): print('no tournament_entry_ad purchase present in _on_ad_complete') ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return self._entering = True _ba.add_transaction({ 'type': 'ENTER_TOURNAMENT', 'fee': 'ad', 'tournamentID': self._tournament_id }) _ba.run_transactions() self._launch()
def _calc_count_for_tab(tabval: List[Dict[str, Any]], our_tickets: int, count: int) -> int: for section in tabval: for item in section['items']: ticket_cost = _ba.get_account_misc_read_val('price.' + item, None) if ticket_cost is not None: if (our_tickets >= ticket_cost and not _ba.get_purchased(item)): count += 1 return count
def _on_cancel(self) -> None: # Don't allow canceling for several seconds after poking an enter # button if it looks like we're waiting on a purchase or entering # the tournament. if ((ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - self._last_ticket_press_time < 6000) and (_ba.have_outstanding_transactions() or _ba.get_purchased(self._purchase_name) or self._entering)): ba.playsound(ba.getsound('error')) return self._transition_out()
def get_purchased_icons() -> List[str]: """(internal)""" # pylint: disable=cyclic-import from ba import _store if _ba.get_account_state() != 'signed_in': return [] icons = [] store_items = _store.get_store_items() for item_name, item in list(store_items.items()): if item_name.startswith('icons.') and _ba.get_purchased(item_name): icons.append(item['icon']) return icons
def _update(self) -> None: can_die = False # We go away if we see that our target item is owned. if self._items == ['pro']: if ba.app.accounts.have_pro(): can_die = True else: if _ba.get_purchased(self._items[0]): can_die = True if can_die: ba.containerwidget(edit=self._root_widget, transition='out_left')
def get_unowned_maps() -> List[str]: """Return the list of local maps not owned by the current account. Category: Asset Functions """ from ba import _store unowned_maps: Set[str] = set() if not _ba.app.headless_build: for map_section in _store.get_store_layout()['maps']: for mapitem in map_section['items']: if not _ba.get_purchased(mapitem): m_info = _store.get_store_item(mapitem) unowned_maps.add(m_info['map_type'].name) return sorted(unowned_maps)
def get_unowned_game_types() -> Set[Type[ba.GameActivity]]: """Return present game types not owned by the current account.""" try: from ba import _store unowned_games: Set[Type[ba.GameActivity]] = set() if not _ba.app.headless_build: for section in _store.get_store_layout()['minigames']: for mname in section['items']: if not _ba.get_purchased(mname): m_info = _store.get_store_item(mname) unowned_games.add(m_info['gametype']) return unowned_games except Exception: from ba import _error _error.print_exception('error calcing un-owned games') return set()
def _update(self) -> None: # If we've got seconds left on our countdown, update it. if self._cancel_delay > 0: self._cancel_delay = max(0, self._cancel_delay - 1) self._update_cancel_button_graphics() can_die = False # We go away if we see that our target item is owned. if self._offer_item == 'pro': if _ba.app.accounts.have_pro(): can_die = True else: if _ba.get_purchased(self._offer_item): can_die = True if can_die: self._transition_out('out_left')
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 _update(self) -> None: # pylint: disable=too-many-branches from ba import SpecialChar, TimeFormat from ba.internal import (get_available_sale_time, get_available_purchase_count) if not self._button: return # Our instance may outlive our UI objects. if self._ticket_text is not None: if _ba.get_account_state() == 'signed_in': sval = ba.charstr(SpecialChar.TICKET) + str( _ba.get_account_ticket_count()) else: sval = '-' ba.textwidget(edit=self._ticket_text, text=sval) available_purchases = get_available_purchase_count() # Old pro sale stuff.. sale_time = get_available_sale_time('extras') # ..also look for new style sales. if sale_time is None: import datetime sales_raw = _ba.get_account_misc_read_val('sales', {}) sale_times = [] try: # Look at the current set of sales; filter any with time # remaining that we don't own. for sale_item, sale_info in list(sales_raw.items()): if not _ba.get_purchased(sale_item): to_end = (datetime.datetime.utcfromtimestamp( sale_info['e']) - datetime.datetime.utcnow()).total_seconds() if to_end > 0: sale_times.append(to_end) except Exception: ba.print_exception('Error parsing sales') if sale_times: sale_time = int(min(sale_times) * 1000) if sale_time is not None: ba.textwidget(edit=self._sale_title_text, text=ba.Lstr(resource='store.saleText')) ba.textwidget(edit=self._sale_time_text, text=ba.timestring( sale_time, centi=False, timeformat=TimeFormat.MILLISECONDS)) ba.imagewidget(edit=self._sale_backing, opacity=1.0) ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0) ba.textwidget(edit=self._available_purchase_text, text='') ba.imagewidget(edit=self._available_purchase_backing, opacity=0.0) else: ba.imagewidget(edit=self._sale_backing, opacity=0.0) ba.textwidget(edit=self._sale_time_text, text='') ba.textwidget(edit=self._sale_title_text, text='') if available_purchases > 0: ba.textwidget(edit=self._available_purchase_text, text=str(available_purchases)) ba.imagewidget(edit=self._available_purchase_backing, opacity=1.0) else: ba.textwidget(edit=self._available_purchase_text, text='') ba.imagewidget(edit=self._available_purchase_backing, opacity=0.0)
def get_available_sale_time(tab: str) -> Optional[int]: """(internal)""" # pylint: disable=too-many-branches # pylint: disable=too-many-nested-blocks # pylint: disable=too-many-locals try: import datetime from ba._generated.enums import TimeType, TimeFormat app = _ba.app sale_times: List[Optional[int]] = [] # Calc time for our pro sale (old special case). if tab == 'extras': config = app.config if app.accounts.have_pro(): return None # If we haven't calced/loaded start times yet. if app.pro_sale_start_time is None: # If we've got a time-remaining in our config, start there. if 'PSTR' in config: app.pro_sale_start_time = int( _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)) app.pro_sale_start_val = config['PSTR'] else: # We start the timer once we get the duration from # the server. start_duration = _ba.get_account_misc_read_val( 'proSaleDurationMinutes', None) if start_duration is not None: app.pro_sale_start_time = int( _ba.time(TimeType.REAL, TimeFormat.MILLISECONDS)) app.pro_sale_start_val = (60000 * start_duration) # If we haven't heard from the server yet, no sale.. else: return None assert app.pro_sale_start_val is not None val: Optional[int] = max( 0, app.pro_sale_start_val - (_ba.time(TimeType.REAL, TimeFormat.MILLISECONDS) - app.pro_sale_start_time)) # Keep the value in the config up to date. I suppose we should # write the config occasionally but it should happen often enough # for other reasons. config['PSTR'] = val if val == 0: val = None sale_times.append(val) # Now look for sales in this tab. sales_raw = _ba.get_account_misc_read_val('sales', {}) store_layout = get_store_layout() for section in store_layout[tab]: for item in section['items']: if item in sales_raw: if not _ba.get_purchased(item): to_end = ((datetime.datetime.utcfromtimestamp( sales_raw[item]['e']) - datetime.datetime.utcnow()).total_seconds()) if to_end > 0: sale_times.append(int(to_end * 1000)) # Return the smallest time I guess? sale_times_int = [t for t in sale_times if isinstance(t, int)] return min(sale_times_int) if sale_times_int else None except Exception: from ba import _error _error.print_exception('error calcing sale time') return None
def get_appearances(include_locked: bool = False) -> List[str]: """Get the list of available spaz appearances.""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches disallowed = [] if not include_locked: # hmm yeah this'll be tough to hack... if not _ba.get_purchased('characters.santa'): disallowed.append('Santa Claus') if not _ba.get_purchased('characters.frosty'): disallowed.append('Frosty') if not _ba.get_purchased('characters.bones'): disallowed.append('Bones') if not _ba.get_purchased('characters.bernard'): disallowed.append('Bernard') if not _ba.get_purchased('characters.pixie'): disallowed.append('Pixel') if not _ba.get_purchased('characters.pascal'): disallowed.append('Pascal') if not _ba.get_purchased('characters.actionhero'): disallowed.append('Todd McBurton') if not _ba.get_purchased('characters.taobaomascot'): disallowed.append('Taobao Mascot') if not _ba.get_purchased('characters.agent'): disallowed.append('Agent Johnson') if not _ba.get_purchased('characters.jumpsuit'): disallowed.append('Lee') if not _ba.get_purchased('characters.assassin'): disallowed.append('Zola') if not _ba.get_purchased('characters.wizard'): disallowed.append('Grumbledorf') if not _ba.get_purchased('characters.cowboy'): disallowed.append('Butch') if not _ba.get_purchased('characters.witch'): disallowed.append('Witch') if not _ba.get_purchased('characters.warrior'): disallowed.append('Warrior') if not _ba.get_purchased('characters.superhero'): disallowed.append('Middle-Man') if not _ba.get_purchased('characters.alien'): disallowed.append('Alien') if not _ba.get_purchased('characters.oldlady'): disallowed.append('OldLady') if not _ba.get_purchased('characters.gladiator'): disallowed.append('Gladiator') if not _ba.get_purchased('characters.wrestler'): disallowed.append('Wrestler') if not _ba.get_purchased('characters.operasinger'): disallowed.append('Gretel') if not _ba.get_purchased('characters.robot'): disallowed.append('Robot') if not _ba.get_purchased('characters.cyborg'): disallowed.append('B-9000') if not _ba.get_purchased('characters.bunny'): disallowed.append('Easter Bunny') if not _ba.get_purchased('characters.kronk'): disallowed.append('Kronk') if not _ba.get_purchased('characters.zoe'): disallowed.append('Zoe') if not _ba.get_purchased('characters.jackmorgan'): disallowed.append('Jack Morgan') if not _ba.get_purchased('characters.mel'): disallowed.append('Mel') if not _ba.get_purchased('characters.snakeshadow'): disallowed.append('Snake Shadow') return [ s for s in list(ba.app.spaz_appearances.keys()) if s not in disallowed ]
def _update(self) -> None: # pylint: disable=too-many-boolean-expressions from ba.internal import have_pro, getcampaign game = self._game campaignname, levelname = game.split(':') # Hack - The Last Stand doesn't actually exist in the # easy tourney; we just want it for display purposes. Map it to # the hard-mode version. if game == 'Easy:The Last Stand': campaignname = 'Default' campaign = getcampaign(campaignname) # If this campaign is sequential, make sure we've unlocked # everything up to here. unlocked = True if campaign.sequential: for level in campaign.levels: if level.name == levelname: break if not level.complete: unlocked = False break # We never actually allow playing last-stand on easy mode. if game == 'Easy:The Last Stand': unlocked = False # Hard-code games we haven't unlocked. if ((game in ('Challenges:Infinite Runaround', 'Challenges:Infinite Onslaught') and not have_pro()) or (game in ('Challenges:Meteor Shower', ) and not _ba.get_purchased('games.meteor_shower')) or (game in ('Challenges:Target Practice', 'Challenges:Target Practice B') and not _ba.get_purchased('games.target_practice')) or (game in ('Challenges:Ninja Fight', ) and not _ba.get_purchased('games.ninja_fight')) or (game in ('Challenges:Pro Ninja Fight', ) and not _ba.get_purchased('games.ninja_fight')) or (game in ('Challenges:Easter Egg Hunt', 'Challenges:Pro Easter Egg Hunt') and not _ba.get_purchased('games.easter_egg_hunt'))): unlocked = False # Let's tint levels a slightly different color when easy mode # is selected. unlocked_color = (0.85, 0.95, 0.5) if game.startswith('Easy:') else (0.5, 0.7, 0.2) ba.buttonwidget(edit=self._button, color=unlocked_color if unlocked else (0.5, 0.5, 0.5)) ba.imagewidget(edit=self._lock_widget, opacity=0.0 if unlocked else 1.0) ba.imagewidget(edit=self._preview_widget, opacity=1.0 if unlocked else 0.3) ba.textwidget(edit=self._name_widget, color=(0.8, 1.0, 0.8, 1.0) if unlocked else (0.7, 0.7, 0.7, 0.7)) for widget in self._star_widgets: ba.imagewidget(edit=widget, opacity=1.0 if unlocked else 0.3, color=(2.2, 1.2, 0.3) if unlocked else (1, 1, 1)) for i, ach in enumerate(self._achievements): a_complete = ach.complete ba.imagewidget(edit=self._achievement_widgets[i][0], opacity=1.0 if (a_complete and unlocked) else 0.3) ba.imagewidget(edit=self._achievement_widgets[i][1], opacity=(1.0 if (a_complete and unlocked) else 0.2 if a_complete else 0.0))