def ensure_have_account_player_profile() -> None: """ Ensure the standard account-named player profile exists; creating if needed. """ # This only applies when we're signed in. if _ba.get_account_state() != 'signed_in': return # If the short version of our account name currently cant be # displayed by the game, cancel. if not _ba.have_chars(_ba.get_account_display_string(full=False)): return config = _ba.app.config if ('Player Profiles' not in config or '__account__' not in config['Player Profiles']): # Create a spaz with a nice default purply color. _ba.add_transaction({ 'type': 'ADD_PLAYER_PROFILE', 'name': '__account__', 'profile': { 'character': 'Spaz', 'color': [0.5, 0.25, 1.0], 'highlight': [0.5, 0.25, 1.0] } }) _ba.run_transactions()
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 on_boost_press(self) -> None: """Boost was pressed.""" from bastd.ui import account from bastd.ui import getcurrency if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return if _ba.get_account_ticket_count() < self._boost_tickets: ba.playsound(ba.getsound('error')) getcurrency.show_get_tickets_prompt() return ba.playsound(ba.getsound('laserReverse')) _ba.add_transaction( { 'type': 'PARTY_QUEUE_BOOST', 't': self._boost_tickets, 'q': self._queue_id }, callback=ba.WeakCall(self.on_update_response)) # lets not run these immediately (since they may be rapid-fire, # just bucket them until the next tick) # the transaction handles the local ticket change, but we apply our # local boost vis manually here.. # (our visualization isn't really wired up to be transaction-based) our_dude = self._dudes_by_id.get(-1) if our_dude is not None: our_dude.boost(self._boost_strength, self._smoothing)
def _update(self) -> None: """Periodic updating.""" now = ba.time(ba.TimeType.REAL) self._update_currency_ui() if self._state.sub_tab is SubTabType.HOST: # If we're not signed in, just refresh to show that. if (_ba.get_account_state() != 'signed_in' and self._showing_not_signed_in_screen): self._refresh_sub_tab() else: # Query an updated state periodically. if (self._last_hosting_state_query_time is None or now - self._last_hosting_state_query_time > 15.0): self._debug_server_comm('querying private party state') if _ba.get_account_state() == 'signed_in': _ba.add_transaction( {'type': 'PRIVATE_PARTY_QUERY'}, callback=ba.WeakCall( self._hosting_state_idle_response), ) _ba.run_transactions() else: self._hosting_state_idle_response(None) self._last_hosting_state_query_time = now
def _share_playlist(self) -> None: # pylint: disable=cyclic-import from ba.internal import have_pro_options from bastd.ui import purchase if not have_pro_options(): purchase.PurchaseWindow(items=['pro']) return # Gotta be signed in for this to work. if _ba.get_account_state() != 'signed_in': ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return if self._selected_playlist_name == '__default__': ba.playsound(ba.getsound('error')) ba.screenmessage(ba.Lstr(resource=self._r + '.cantShareDefaultText'), color=(1, 0, 0)) return if self._selected_playlist_name is None: return _ba.add_transaction( { 'type': 'SHARE_PLAYLIST', 'expire_time': time.time() + 5, 'playlistType': self._pvars.config_name, 'playlistName': self._selected_playlist_name }, callback=ba.WeakCall(self._on_share_playlist_response, self._selected_playlist_name)) _ba.run_transactions() ba.screenmessage(ba.Lstr(resource='sharingText'))
def add_pending_promo_code(self, code: str) -> None: """(internal)""" from ba._language import Lstr from ba._generated.enums import TimeType # If we're not signed in, queue up the code to run the next time we # are and issue a warning if we haven't signed in within the next # few seconds. if _ba.get_v1_account_state() != 'signed_in': def check_pending_codes() -> None: """(internal)""" # If we're still not signed in and have pending codes, # inform the user that they need to sign in to use them. if self.pending_promo_codes: _ba.screenmessage(Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) self.pending_promo_codes.append(code) _ba.timer(6.0, check_pending_codes, timetype=TimeType.REAL) return _ba.screenmessage(Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0)) _ba.add_transaction({ 'type': 'PROMO_CODE', 'expire_time': time.time() + 5, 'code': code }) _ba.run_transactions()
def _on_lang_inform_value_change(self, val: bool) -> None: _ba.add_transaction({ 'type': 'SET_MISC_VAL', 'name': 'langInform', 'value': val }) _ba.run_transactions()
def handle_app_invites_press(force_code: bool = False) -> None: """(internal)""" app = ba.app do_app_invites = (app.platform == 'android' and app.subplatform == 'google' and _ba.get_account_misc_read_val( 'enableAppInvites', False) and not app.on_tv) if force_code: do_app_invites = False # FIXME: Should update this to grab a code before showing the invite UI. if do_app_invites: AppInviteWindow() else: ba.screenmessage( ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), color=(0, 1, 0)) def handle_result(result: Optional[Dict[str, Any]]) -> None: with ba.Context('ui'): if result is None: ba.screenmessage(ba.Lstr(resource='errorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) else: ShowFriendCodeWindow(result) _ba.add_transaction( { 'type': 'FRIEND_PROMO_CODE_REQUEST', 'ali': False, 'expire_time': time.time() + 10 }, callback=handle_result) _ba.run_transactions()
def return_to_main_menu_session_gracefully(self) -> None: """Attempt to cleanly get back to the main menu.""" # pylint: disable=cyclic-import from ba import _benchmark from ba._general import Call from bastd.mainmenu import MainMenuSession _ba.app.main_window = None if isinstance(_ba.get_foreground_host_session(), MainMenuSession): # It may be possible we're on the main menu but the screen is faded # so fade back in. _ba.fade_screen(True) return _benchmark.stop_stress_test() # Stop stress-test if in progress. # If we're in a host-session, tell them to end. # This lets them tear themselves down gracefully. host_session: Optional[ba.Session] = _ba.get_foreground_host_session() if host_session is not None: # Kick off a little transaction so we'll hopefully have all the # latest account state when we get back to the menu. _ba.add_transaction({ 'type': 'END_SESSION', 'sType': str(type(host_session)) }) _ba.run_transactions() host_session.end() # Otherwise just force the issue. else: _ba.pushcall(Call(_ba.new_host_session, MainMenuSession))
def _save_press(self) -> None: from bastd.ui.playlist.customizebrowser import ( PlaylistCustomizeBrowserWindow) new_name = cast(str, ba.textwidget(query=self._text_field)) if (new_name != self._editcontroller.get_existing_playlist_name() and new_name in ba.app.config[self._editcontroller.get_config_name() + ' Playlists']): ba.screenmessage( ba.Lstr(resource=self._r + '.cantSaveAlreadyExistsText')) ba.playsound(ba.getsound('error')) return if not new_name: ba.playsound(ba.getsound('error')) return if not self._editcontroller.get_playlist(): ba.screenmessage( ba.Lstr(resource=self._r + '.cantSaveEmptyListText')) ba.playsound(ba.getsound('error')) return # We couldn't actually replace the default list anyway, but disallow # using its exact name to avoid confusion. if new_name == self._editcontroller.get_default_list_name().evaluate(): ba.screenmessage( ba.Lstr(resource=self._r + '.cantOverwriteDefaultText')) ba.playsound(ba.getsound('error')) return # If we had an old one, delete it. if self._editcontroller.get_existing_playlist_name() is not None: _ba.add_transaction({ 'type': 'REMOVE_PLAYLIST', 'playlistType': self._editcontroller.get_config_name(), 'playlistName': self._editcontroller.get_existing_playlist_name() }) _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': self._editcontroller.get_config_name(), 'playlistName': new_name, 'playlist': self._editcontroller.get_playlist() }) _ba.run_transactions() ba.containerwidget(edit=self._root_widget, transition='out_right') ba.playsound(ba.getsound('gunCocking')) ba.app.ui.set_main_menu_window( PlaylistCustomizeBrowserWindow( transition='in_left', sessiontype=self._editcontroller.get_session_type(), select_playlist=new_name).get_root_widget())
def do_it() -> None: if _ba.get_account_state() == 'signed_in': can_launch = False # If we're trying to fetch a playlist, we do that first. if app.server_playlist_fetch is not None: # Send request if we haven't. if not app.server_playlist_fetch['sent_request']: def on_playlist_fetch_response( result: Optional[Dict[str, Any]]) -> None: if result is None: print('Error fetching playlist; aborting.') sys.exit(-1) # Once we get here we simply modify our # config to use this playlist. type_name = ('teams' if result['playlistType'] == 'Team Tournament' else 'ffa' if result['playlistType'] == 'Free-for-All' else '??') print(('Playlist \'' + result['playlistName'] + '\' (' + type_name + ') downloaded; running...')) assert app.server_playlist_fetch is not None app.server_playlist_fetch['got_response'] = ( True) app.server_config['session_type'] = type_name app.server_config['playlist_name'] = ( result['playlistName']) print(('Requesting shared-playlist ' + str( app.server_playlist_fetch['playlist_code']) + '...')) app.server_playlist_fetch['sent_request'] = True _ba.add_transaction( { 'type': 'IMPORT_PLAYLIST', 'code': app.server_playlist_fetch['playlist_code'], 'overwrite': True }, callback=on_playlist_fetch_response) _ba.run_transactions() # If we got a valid result, forget the fetch ever # existed and move on. if app.server_playlist_fetch['got_response']: app.server_playlist_fetch = None can_launch = True else: can_launch = True if can_launch: app.run_server_wait_timer = None _ba.pushcall(launch_server_session)
def playlist(code): _ba.add_transaction( { 'type': 'IMPORT_PLAYLIST', 'code': str(code), 'overwrite': True }, callback=set_playlist) _ba.run_transactions()
def _start_stop_button_press(self) -> None: if (self._waiting_for_start_stop_response or self._waiting_for_initial_state): return if _ba.get_account_state() != 'signed_in': ba.screenmessage(ba.Lstr(resource='notSignedInErrorText')) ba.playsound(ba.getsound('error')) self._refresh_sub_tab() return if self._hostingstate.unavailable_error is not None: ba.playsound(ba.getsound('error')) return ba.playsound(ba.getsound('click01')) # If we're not hosting, start. if self._hostingstate.party_code is None: # If there's a ticket cost, make sure we have enough tickets. if self._hostingstate.tickets_to_host_now > 0: ticket_count: Optional[int] try: ticket_count = _ba.get_account_ticket_count() except Exception: # FIXME: should add a ba.NotSignedInError we can use here. ticket_count = None ticket_cost = self._hostingstate.tickets_to_host_now if ticket_count is not None and ticket_count < ticket_cost: getcurrency.show_get_tickets_prompt() ba.playsound(ba.getsound('error')) return self._last_action_send_time = time.time() _ba.add_transaction( { 'type': 'PRIVATE_PARTY_START', 'config': dataclass_to_dict(self._hostingconfig), 'region_pings': ba.app.net.region_pings, 'expire_time': time.time() + 20, }, callback=ba.WeakCall(self._hosting_state_response)) _ba.run_transactions() else: self._last_action_send_time = time.time() _ba.add_transaction( { 'type': 'PRIVATE_PARTY_STOP', 'expire_time': time.time() + 20, }, callback=ba.WeakCall(self._hosting_state_response)) _ba.run_transactions() ba.playsound(ba.getsound('click01')) self._waiting_for_start_stop_response = True self._refresh_sub_tab()
def _do_enter(self) -> None: _ba.add_transaction( { 'type': 'IMPORT_PLAYLIST', 'expire_time': time.time() + 5, 'code': ba.textwidget(query=self._text_field) }, callback=ba.WeakCall(self._on_import_response)) _ba.run_transactions() ba.screenmessage(ba.Lstr(resource='importingText'))
def _do_it() -> None: # Fire off a transaction with a callback. _ba.add_transaction( { 'type': 'PRIVATE_PARTY_QUERY', 'expire_time': time.time() + 20, }, callback=_cb, ) _ba.run_transactions()
def __del__(self) -> None: try: ba.app.ui.have_party_queue_window = False _ba.add_transaction({ 'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id }) _ba.run_transactions() except Exception: ba.print_exception('Error removing self from party queue.')
def _ok_press(self) -> None: if ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) - self._starttime < 1000: ba.playsound(ba.getsound('error')) return _ba.add_transaction({ 'type': 'DIALOG_RESPONSE', 'dialogID': self._dialog_id, 'response': 1 }) ba.containerwidget(edit=self._root_widget, transition='out_scale')
def _do_delete_profile(self) -> None: _ba.add_transaction({ 'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile }) _ba.run_transactions() ba.playsound(ba.getsound('shieldDown')) self._refresh() # Select profile list. ba.containerwidget(edit=self._root_widget, selected_child=self._scrollwidget)
def _on_pay_with_tickets_press(self) -> None: from bastd.ui import getcurrency # If we're already entering, ignore. if self._entering: return if not self._have_valid_data: ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # If we don't have a price. if self._purchase_price is None: ba.screenmessage(ba.Lstr(resource='tournamentCheckingStateText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # Deny if it looks like the tourney has ended. if self._seconds_remaining == 0: ba.screenmessage(ba.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # Deny if we don't have enough tickets. ticket_count: Optional[int] try: ticket_count = _ba.get_account_ticket_count() except Exception: # FIXME: should add a ba.NotSignedInError we can use here. ticket_count = None ticket_cost = self._purchase_price if ticket_count is not None and ticket_count < ticket_cost: getcurrency.show_get_tickets_prompt() ba.playsound(ba.getsound('error')) return cur_time = ba.time(ba.TimeType.REAL, ba.TimeFormat.MILLISECONDS) self._last_ticket_press_time = cur_time assert isinstance(ticket_cost, int) _ba.in_game_purchase(self._purchase_name, ticket_cost) self._entering = True _ba.add_transaction({ 'type': 'ENTER_TOURNAMENT', 'fee': self._fee, 'tournamentID': self._tournament_id }) _ba.run_transactions() self._launch()
def _on_entry_selected(self, entry: dict[str, Any]) -> None: ba.screenmessage(ba.Lstr(resource='pleaseWaitText', fallback_resource='requestingText'), color=(0, 1, 0)) _ba.add_transaction({ 'type': 'ACCOUNT_UNLINK_REQUEST', 'accountID': entry['id'], 'expire_time': time.time() + 5 }) _ba.run_transactions() ba.containerwidget(edit=self._root_widget, transition=self._transition_out)
def _on_cheating_press(self) -> None: from urllib import parse _ba.add_transaction({ 'type': 'REPORT_ACCOUNT', 'reason': 'cheating', 'account': self._account_id }) body = ba.Lstr(resource='reportPlayerExplanationText').evaluate() ba.open_url('mailto:[email protected]' f'?subject={_ba.appnameupper()} Player Report: ' + self._account_id + '&body=' + parse.quote(body)) self.close()
def updatepartylist(): self._internet_join_last_refresh_time = now self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 app = ba.app _ba.add_transaction( { 'type': 'PUBLIC_PARTY_QUERY', 'proto': app.protocol_version, 'lang': app.language }, callback=ba.WeakCall(self._on_public_party_query_result)) _ba.run_transactions()
def _generate_press(self) -> None: from bastd.ui import account if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return ba.screenmessage( ba.Lstr(resource='gatherWindow.requestingAPromoCodeText'), color=(0, 1, 0)) _ba.add_transaction({ 'type': 'ACCOUNT_LINK_CODE_REQUEST', 'expire_time': time.time() + 5 }) _ba.run_transactions()
def _do_enter(self) -> None: # pylint: disable=cyclic-import from bastd.ui.settings.advanced import AdvancedSettingsWindow ba.containerwidget(edit=self._root_widget, transition=self._transition_out) if not self._modal: ba.app.ui.set_main_menu_window( AdvancedSettingsWindow(transition='in_left').get_root_widget()) _ba.add_transaction({ 'type': 'PROMO_CODE', 'expire_time': time.time() + 5, 'code': ba.textwidget(query=self._text_field) }) _ba.run_transactions()
def on_account_state_changed(self) -> None: """(internal)""" from ba._language import Lstr # Run any pending promo codes we had queued up while not signed in. if _ba.get_account_state() == 'signed_in' and self.pending_promo_codes: for code in self.pending_promo_codes: _ba.screenmessage(Lstr(resource='submittingPromoCodeText'), color=(0, 1, 0)) _ba.add_transaction({ 'type': 'PROMO_CODE', 'expire_time': time.time() + 5, 'code': code }) _ba.run_transactions() self.pending_promo_codes = []
def _do_delete_playlist(self) -> None: _ba.add_transaction({ 'type': 'REMOVE_PLAYLIST', 'playlistType': self._pvars.config_name, 'playlistName': self._selected_playlist_name }) _ba.run_transactions() ba.playsound(ba.getsound('shieldDown')) # (we don't use len()-1 here because the default list adds one) assert self._selected_playlist_index is not None if self._selected_playlist_index > len( ba.app.config[self._pvars.config_name + ' Playlists']): self._selected_playlist_index = len( ba.app.config[self._pvars.config_name + ' Playlists']) self._refresh()
def update(self) -> None: """Update!""" if not self._root_widget: return # Update boost-price. if self._boost_price is not None: ba.textwidget(edit=self._boost_price, text=ba.charstr(ba.SpecialChar.TICKET) + str(self._boost_tickets)) # Update boost button color based on if we have enough moola. if self._boost_button is not None: can_boost = ( (_ba.get_account_state() == 'signed_in' and _ba.get_account_ticket_count() >= self._boost_tickets)) ba.buttonwidget(edit=self._boost_button, color=(0, 1, 0) if can_boost else (0.7, 0.7, 0.7)) # Update ticket-count. if self._tickets_text is not None: if self._boost_button is not None: if _ba.get_account_state() == 'signed_in': val = ba.charstr(ba.SpecialChar.TICKET) + str( _ba.get_account_ticket_count()) else: val = ba.charstr(ba.SpecialChar.TICKET) + '???' ba.textwidget(edit=self._tickets_text, text=val) else: ba.textwidget(edit=self._tickets_text, text='') current_time = ba.time(ba.TimeType.REAL) if (self._last_transaction_time is None or current_time - self._last_transaction_time > 0.001 * _ba.get_account_misc_read_val('pqInt', 5000)): self._last_transaction_time = current_time _ba.add_transaction( { 'type': 'PARTY_QUEUE_QUERY', 'q': self._queue_id }, callback=ba.WeakCall(self.on_update_response)) _ba.run_transactions() # step our dudes for dude in self._dudes: dude.step(self._smoothing)
def award_local_achievement(self, achname: str) -> None: """For non-game-based achievements such as controller-connection.""" try: ach = self.get_achievement(achname) if not ach.complete: # Report new achievements to the game-service. _ba.report_achievement(achname) # And to our account. _ba.add_transaction({'type': 'ACHIEVEMENT', 'name': achname}) # Now attempt to show a banner. self.display_achievement_banner(achname) except Exception: print_exception()
def save(self, transition_out: bool = True) -> bool: """Save has been selected.""" from bastd.ui.profile.browser import ProfileBrowserWindow new_name = self.getname().strip() if not new_name: ba.screenmessage(ba.Lstr(resource='nameNotEmptyText')) ba.playsound(ba.getsound('error')) return False if transition_out: ba.playsound(ba.getsound('gunCocking')) # Delete old in case we're renaming. if self._existing_profile and self._existing_profile != new_name: _ba.add_transaction({ 'type': 'REMOVE_PLAYER_PROFILE', 'name': self._existing_profile }) # Also lets be aware we're no longer global if we're taking a # new name (will need to re-request it). self._global = False _ba.add_transaction({ 'type': 'ADD_PLAYER_PROFILE', 'name': new_name, 'profile': { 'character': self._spazzes[self._icon_index], 'color': list(self._color), 'global': self._global, 'icon': self._icon, 'highlight': list(self._highlight) } }) if transition_out: _ba.run_transactions() ba.containerwidget(edit=self._root_widget, transition='out_right') ba.app.ui.set_main_menu_window( ProfileBrowserWindow( 'in_left', selected_profile=new_name, in_main_menu=self._in_main_menu).get_root_widget()) return True
def _query_party_list_periodically(self) -> None: now = ba.time(ba.TimeType.REAL) # Fire off a new public-party query periodically. if (self._last_server_list_query_time is None or now - self._last_server_list_query_time > 0.001 * _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): self._last_server_list_query_time = now if DEBUG_SERVER_COMMUNICATION: print('REQUESTING SERVER LIST') _ba.add_transaction( { 'type': 'PUBLIC_PARTY_QUERY', 'proto': ba.app.protocol_version, 'lang': ba.app.lang.language }, callback=ba.WeakCall(self._on_public_party_query_result)) _ba.run_transactions()