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 _invite_to_try_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.appinvite import handle_app_invites_press if _ba.get_account_state() != 'signed_in': show_sign_in_prompt() return handle_app_invites_press()
def _on_friend_promo_code_press(self) -> None: from bastd.ui import appinvite from bastd.ui import account if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return appinvite.handle_app_invites_press()
def _on_start_advertizing_press(self) -> None: from bastd.ui.account import show_sign_in_prompt if _ba.get_account_state() != 'signed_in': show_sign_in_prompt() return name = cast(str, ba.textwidget(query=self._host_name_text)) if name == '': ba.screenmessage(ba.Lstr(resource='internal.invalidNameErrorText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return _ba.set_public_party_name(name) cfg = ba.app.config cfg['Public Party Name'] = name cfg.commit() ba.playsound(ba.getsound('shieldUp')) _ba.set_public_party_enabled(True) # In GUI builds we want to authenticate clients only when hosting # public parties. _ba.set_authenticate_clients(True) self._do_status_check() ba.buttonwidget( edit=self._host_toggle_button, label=ba.Lstr( resource='gatherWindow.makePartyPrivateText', fallback_resource='gatherWindow.stopAdvertisingText'), on_activate_call=self._on_stop_advertising_press)
def _on_google_play_show_invites_press(self) -> None: from bastd.ui import account if (_ba.get_account_state() != 'signed_in' or _ba.get_account_type() != 'Google Play'): account.show_sign_in_prompt('Google Play') else: _ba.show_invites_ui()
def _email(self) -> None: import urllib.parse # If somehow we got signed out. if _ba.get_account_state() != 'signed_in': ba.screenmessage(ba.Lstr(resource='notSignedInText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return ba.set_analytics_screen('Email Friend Code') subject = (ba.Lstr(resource='gatherWindow.friendHasSentPromoCodeText'). evaluate().replace( '${NAME}', _ba.get_account_name()).replace( '${APP_NAME}', ba.Lstr(resource='titleText').evaluate()).replace( '${COUNT}', str(self._data['tickets']))) body = (ba.Lstr(resource='gatherWindow.youHaveBeenSentAPromoCodeText'). evaluate().replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + '\n\n' + str(self._data['code']) + '\n\n') body += ( (ba.Lstr(resource='gatherWindow.friendPromoCodeRedeemShortText'). evaluate().replace('${COUNT}', str(self._data['tickets']))) + '\n\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeInstructionsText'). evaluate().replace('${APP_NAME}', ba.Lstr(resource='titleText').evaluate()) + '\n' + ba.Lstr(resource='gatherWindow.friendPromoCodeExpireText'). evaluate().replace('${EXPIRE_HOURS}', str( self._data['expireHours'])) + '\n' + ba.Lstr(resource='enjoyText').evaluate()) ba.open_url('mailto:?subject=' + urllib.parse.quote(subject) + '&body=' + urllib.parse.quote(body))
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 _on_get_more_tickets_press(self) -> None: from bastd.ui import account from bastd.ui import getcurrency if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return getcurrency.GetCurrencyWindow(modal=True).get_root_widget()
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_continue_press(self) -> None: from bastd.ui import getcurrency # Disallow for first second. if self._start_count - self._count < 2: ba.playsound(ba.getsound('error')) else: # If somehow we got signed out... if _ba.get_account_state() != 'signed_in': ba.screenmessage(ba.Lstr(resource='notSignedInText'), color=(1, 0, 0)) ba.playsound(ba.getsound('error')) return # If it appears we don't have enough tickets, offer to buy more. tickets = _ba.get_account_ticket_count() if tickets < self._cost: # FIXME: Should we start the timer back up again after? self._counting_down = False ba.textwidget(edit=self._counter_text, text='') ba.playsound(ba.getsound('error')) getcurrency.show_get_tickets_prompt() return if not self._transitioning_out: ba.playsound(ba.getsound('swish')) self._transitioning_out = True ba.containerwidget(edit=self._root_widget, transition='out_scale') self._continue_call()
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 _default_on_activate_call(self) -> None: from bastd.ui import account from bastd.ui.store import browser if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return browser.StoreBrowserWindow(modal=True, origin_widget=self._button)
def _tick(self) -> None: # if our target activity is gone or has ended, go away activity = self._activity() if activity is None or activity.has_ended(): self._on_cancel() return if _ba.get_account_state() == 'signed_in': sval = (ba.charstr(ba.SpecialChar.TICKET) + str(_ba.get_account_ticket_count())) else: sval = '?' if self._tickets_text is not None: assert self._tickets_text_base is not None ba.textwidget(edit=self._tickets_text, text=self._tickets_text_base.replace( '${COUNT}', sval)) if self._counting_down: self._count -= 1 ba.playsound(ba.getsound('tick')) if self._count <= 0: self._on_cancel() else: ba.textwidget(edit=self._counter_text, text=str(self._count))
def _update(self) -> None: # If they want us to close once we're signed in, do so. if self._close_once_signed_in and self._signed_in: self._back() return # Hmm should update this to use get_account_state_num. # Theoretically if we switch from one signed-in account to another # in the background this would break. account_state_num = _ba.get_account_state_num() account_state = _ba.get_account_state() show_linked = (self._signed_in and _ba.get_account_misc_read_val( 'allowAccountLinking2', False)) if (account_state_num != self._account_state_num or self._show_linked != show_linked or self._needs_refresh): self._show_linked = show_linked self._account_state_num = account_state_num self._signed_in = (account_state == 'signed_in') self._refresh() # Go ahead and refresh some individual things # that may change under us. self._update_linked_accounts_text() self._update_unlink_accounts_button() self._refresh_campaign_progress_text() self._refresh_achievements() self._refresh_tickets_text() self._refresh_account_name_text()
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_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 announce_completion(self, sound: bool = True) -> None: """Kick off an announcement for this achievement's completion.""" from ba._enums import TimeType app = _ba.app # Even though there are technically achievements when we're not # signed in, lets not show them (otherwise we tend to get # confusing 'controller connected' achievements popping up while # waiting to log in which can be confusing). if _ba.get_account_state() != 'signed_in': return # If we're being freshly complete, display/report it and whatnot. if (self, sound) not in app.achievements_to_display: app.achievements_to_display.append((self, sound)) # If there's no achievement display timer going, kick one off # (if one's already running it will pick this up before it dies). # Need to check last time too; its possible our timer wasn't able to # clear itself if an activity died and took it down with it. if ((app.achievement_display_timer is None or _ba.time(TimeType.REAL) - app.last_achievement_display_time > 2.0) and _ba.getactivity(doraise=False) is not None): app.achievement_display_timer = _ba.Timer( 1.0, _display_next_achievement, repeat=True, timetype=TimeType.BASE) # Show the first immediately. _display_next_achievement()
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 _launch_server_session(self) -> None: """Kick off a host-session based on the current server config.""" app = _ba.app appcfg = app.config sessiontype = self._get_session_type() if _ba.get_account_state() != 'signed_in': print('WARNING: launch_server_session() expects to run ' 'with a signed in server account') if self._first_run: curtimestr = time.strftime('%c') _ba.log( f'{Clr.BLD}{Clr.BLU}{_ba.appnameupper()} {app.version}' f' ({app.build_number})' f' entering server-mode {curtimestr}{Clr.RST}', to_server=False) if sessiontype is FreeForAllSession: appcfg['Free-for-All Playlist Selection'] = self._playlist_name appcfg['Free-for-All Playlist Randomize'] = ( self._config.playlist_shuffle) elif sessiontype is DualTeamSession: appcfg['Team Tournament Playlist Selection'] = self._playlist_name appcfg['Team Tournament Playlist Randomize'] = ( self._config.playlist_shuffle) else: raise RuntimeError(f'Unknown session type {sessiontype}') app.teams_series_length = self._config.teams_series_length app.ffa_series_length = self._config.ffa_series_length _ba.set_authenticate_clients(self._config.authenticate_clients) _ba.set_enable_default_kick_voting( self._config.enable_default_kick_voting) _ba.set_admins(self._config.admins) # Call set-enabled last (will push state to the cloud). _ba.set_public_party_max_size(self._config.max_party_size) _ba.set_public_party_name(self._config.party_name) _ba.set_public_party_stats_url(self._config.stats_url) _ba.set_public_party_enabled(self._config.party_is_public) # And here.. we.. go. if self._config.stress_test_players is not None: # Special case: run a stress test. from ba.internal import run_stress_test run_stress_test(playlist_type='Random', playlist_name='__default__', player_count=self._config.stress_test_players, round_duration=30) else: _ba.new_host_session(sessiontype) # Run an access check if we're trying to make a public party. if not self._ran_access_check and self._config.party_is_public: self._run_access_check() self._ran_access_check = True
def _default_on_activate_call(self) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': show_sign_in_prompt() return StoreBrowserWindow(modal=True, origin_widget=self._button)
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 __init__(self, transition: Optional[str] = 'in_right'): # pylint: disable=cyclic-import import threading from bastd.mainmenu import MainMenuSession self._in_game = not isinstance(_ba.get_foreground_host_session(), MainMenuSession) # Preload some modules we use in a background thread so we won't # have a visual hitch when the user taps them. threading.Thread(target=self._preload_modules).start() if not self._in_game: ba.set_analytics_screen('Main Menu') self._show_remote_app_info_on_first_launch() # Make a vanilla container; we'll modify it to our needs in refresh. super().__init__(root_widget=ba.containerwidget( transition=transition, toolbar_visibility='menu_minimal_no_back' if self. _in_game else 'menu_minimal_no_back')) # Grab this stuff in case it changes. self._is_demo = ba.app.demo_mode self._is_arcade = ba.app.arcade_mode self._is_iircade = ba.app.iircade_mode self._tdelay = 0.0 self._t_delay_inc = 0.02 self._t_delay_play = 1.7 self._p_index = 0 self._use_autoselect = True self._button_width = 200.0 self._button_height = 45.0 self._width = 100.0 self._height = 100.0 self._demo_menu_button: Optional[ba.Widget] = None self._gather_button: Optional[ba.Widget] = None self._start_button: Optional[ba.Widget] = None self._watch_button: Optional[ba.Widget] = None self._gc_button: Optional[ba.Widget] = None self._how_to_play_button: Optional[ba.Widget] = None self._credits_button: Optional[ba.Widget] = None self._settings_button: Optional[ba.Widget] = None self._store_char_tex = self._get_store_char_tex() self._refresh() self._restore_state() # Keep an eye on a few things and refresh if they change. self._account_state = _ba.get_account_state() self._account_state_num = _ba.get_account_state_num() self._account_type = (_ba.get_account_type() if self._account_state == 'signed_in' else None) self._refresh_timer = ba.Timer(1.0, ba.WeakCall(self._check_refresh), repeat=True, timetype=ba.TimeType.REAL)
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 upgrade_profile(self) -> None: """Attempt to ugrade the profile to global.""" from bastd.ui import account from bastd.ui.profile import upgrade as pupgrade if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return pupgrade.ProfileUpgradeWindow(self)
def _on_get_more_games_press(self) -> None: from bastd.ui import account from bastd.ui.store import browser if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return browser.StoreBrowserWindow(modal=True, show_tab='minigames', on_close_call=self._on_store_close, origin_widget=self._get_more_games_button)
def _on_store_press(self) -> None: from bastd.ui.account import show_sign_in_prompt from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': show_sign_in_prompt() return self._transition_out() StoreBrowserWindow(modal=True, show_tab=StoreBrowserWindow.TabID.ICONS, origin_widget=self._get_more_icons_button)
def _on_store_press(self) -> None: from bastd.ui import account from bastd.ui.store import browser if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return self._transition_out() browser.StoreBrowserWindow(modal=True, show_tab='icons', origin_widget=self._get_more_icons_button)
def _on_store_press(self) -> None: from bastd.ui import account from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return StoreBrowserWindow(modal=True, show_tab=StoreBrowserWindow.TabID.MAPS, on_close_call=self._on_store_close, origin_widget=self._get_more_maps_button)
def _update_tickets_text(self) -> None: from ba import SpecialChar if not self._root_widget: return sval: Union[str, ba.Lstr] if _ba.get_account_state() == 'signed_in': sval = ba.charstr(SpecialChar.TICKET) + str( _ba.get_account_ticket_count()) else: sval = ba.Lstr(resource='getTicketsWindow.titleText') ba.buttonwidget(edit=self._get_tickets_button, label=sval)
def _update(self) -> None: # Kiosk-mode is designed to be used signed-out... try for force # the issue. if _ba.get_account_state() == 'signed_in': # _bs.sign_out() # FIXME: Try to delete player profiles here too. pass else: # Also make sure there's no player profiles. bs_config = ba.app.config bs_config['Player Profiles'] = {}
def _on_store_pressed(self) -> None: # pylint: disable=cyclic-import from bastd.ui.store import browser from bastd.ui import account if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return self._save_state() ba.containerwidget(edit=self._root_widget, transition='out_left') ba.app.main_menu_window = (browser.StoreBrowserWindow( origin_widget=self._store_button).get_root_widget())