def _reset_stress_test(args: Dict[str, Any]) -> None: from ba._general import Call from ba._enums import TimeType _ba.set_stress_testing(False, args['player_count']) _ba.screenmessage('Resetting stress test...') session = _ba.get_foreground_host_session() assert session is not None session.end() _ba.timer(1.0, Call(start_stress_test, args), timetype=TimeType.REAL)
def save_ids(ids, pb_id, display_string): pdata.update_display_string(pb_id, ids) if display_string not in ids: msg = "Spoofed Id detected , Goodbye" _ba.pushcall(Call(kick_by_pb_id, pb_id, msg), from_other_thread=True) serverdata.clients[pb_id]["verified"] = False logger.log(pb_id + "|| kicked , for using spoofed id " + display_string) else: serverdata.clients[pb_id]["verified"] = True
def save_age(age, pb_id, display_string): pdata.add_profile(pb_id, display_string, display_string, age) time.sleep(2) thread2 = FetchThread(target=get_device_accounts, callback=save_ids, pb_id=pb_id, display_string=display_string) thread2.start() if get_account_age(age) < settings["minAgeToJoinInHours"]: msg = "New Accounts not allowed to play here , come back tmrw." logger.log(pb_id + "|| kicked > new account") _ba.pushcall(Call(kick_by_pb_id, pb_id, msg), from_other_thread=True)
def delay_add(start_time: float) -> None: def doit(start_time_2: float) -> None: from ba import _lang _ba.screenmessage( _lang.get_resource('debugWindow.totalReloadTimeText').replace( '${TIME}', str(_ba.time(TimeType.REAL) - start_time_2))) _ba.print_load_info() if _ba.app.config.resolve('Texture Quality') != 'High': _ba.screenmessage(_lang.get_resource( 'debugWindow.reloadBenchmarkBestResultsText'), color=(1, 1, 0)) _ba.add_clean_frame_callback(Call(doit, start_time))
def print_corrupt_file_error() -> None: """Print an error if a corrupt file is found.""" from ba._general import Call from ba._generated.enums import TimeType _ba.timer(2.0, lambda: _ba.screenmessage( _ba.app.lang.get_resource('internal.corruptFileText'). replace('${EMAIL}', '*****@*****.**'), color=(1, 0, 0), ), timetype=TimeType.REAL) _ba.timer(2.0, Call(_ba.playsound, _ba.getsound('error')), timetype=TimeType.REAL)
def __del__(self) -> None: # If the activity has been run then we should have already cleaned # it up, but we still need to run expire calls for un-run activities. if not self._expired: with _ba.Context('empty'): self._expire() # Inform our owner that we officially kicked the bucket. if self._transitioning_out: session = self._session() if session is not None: _ba.pushcall( Call(session.transitioning_out_activity_was_freed, self.can_show_ad_on_death))
def send_message( self, msg: Message, on_response: Callable[[Any], None], ) -> None: """Asynchronously send a message to the cloud from the game thread. The provided on_response call will be run in the logic thread and passed either the response or the error that occurred. """ from ba._general import Call del msg # Unused. _ba.pushcall( Call(on_response, RuntimeError('Cloud functionality is not available.')))
def _play_current_playlist(self) -> None: try: from ba._general import Call assert self._current_playlist is not None if _ba.mac_music_app_play_playlist(self._current_playlist): pass else: _ba.pushcall(Call( _ba.screenmessage, _ba.app.lang.get_resource('playlistNotFoundText') + ': \'' + self._current_playlist + '\'', (1, 0, 0)), from_other_thread=True) except Exception: from ba import _error _error.print_exception( f'error playing playlist {self._current_playlist}')
def _handle_get_playlists_command( self, target: Callable[[List[str]], None]) -> None: from ba._general import Call try: playlists = _ba.mac_music_app_get_playlists() playlists = [ p for p in playlists if p not in [ 'Music', 'Movies', 'TV Shows', 'Podcasts', 'iTunes\xa0U', 'Books', 'Genius', 'iTunes DJ', 'Music Videos', 'Home Videos', 'Voice Memos', 'Audiobooks' ] ] playlists.sort(key=lambda x: x.lower()) except Exception as exc: print('Error getting iTunes playlists:', exc) playlists = [] _ba.pushcall(Call(target, playlists), from_other_thread=True)
def announce_game_results(self, activity: ba.GameActivity, results: ba.GameResults, delay: float, announce_winning_team: bool = True) -> None: """Show basic game result at the end of a game. (before transitioning to a score screen). This will include a zoom-text of 'BLUE WINS' or whatnot, along with a possible audio announcement of the same. """ # pylint: disable=cyclic-import # pylint: disable=too-many-locals from ba._math import normalized_color from ba._general import Call from ba._gameutils import cameraflash from ba._language import Lstr from ba._freeforallsession import FreeForAllSession from ba._messages import CelebrateMessage _ba.timer(delay, Call(_ba.playsound, _ba.getsound('boxingBell'))) if announce_winning_team: winning_sessionteam = results.winning_sessionteam if winning_sessionteam is not None: # Have all players celebrate. celebrate_msg = CelebrateMessage(duration=10.0) assert winning_sessionteam.activityteam is not None for player in winning_sessionteam.activityteam.players: if player.actor: player.actor.handlemessage(celebrate_msg) cameraflash() # Some languages say "FOO WINS" different for teams vs players. if isinstance(self, FreeForAllSession): wins_resource = 'winsPlayerText' else: wins_resource = 'winsTeamText' wins_text = Lstr(resource=wins_resource, subs=[('${NAME}', winning_sessionteam.name)]) activity.show_zoom_message( wins_text, scale=0.85, color=normalized_color(winning_sessionteam.color), )
def run(self) -> None: from ba._general import Call try: scan = DirectoryScan(self._dirs) scan.scan() results = scan.results except Exception as exc: results = ScanResults(errors=f'Scan exception: {exc}') # Push a call to the game thread to print warnings/errors # or otherwise deal with scan results. _ba.pushcall(Call(_ba.app.meta.handle_scan_results, results), from_other_thread=True) # We also, however, immediately make results available. # This is because the game thread may be blocked waiting # for them so we can't push a call or we'd get deadlock. _ba.app.meta.metascan = results
def _handle_server_restarts(self) -> bool: """Handle automatic restarts/shutdowns in server mode. Returns True if an action was taken; otherwise default action should occur (starting next round, etc). """ # pylint: disable=cyclic-import # FIXME: Move server stuff to its own module. if self._allow_server_restart and _ba.app.server_config_dirty: from ba import _server from ba._lang import Lstr from ba._general import Call from ba._enums import TimeType if _ba.app.server_config.get('quit', False): if not self._kicked_off_server_shutdown: if _ba.app.server_config.get( 'quit_reason') == 'restarting': # FIXME: Should add a server-screen-message call # or something. _ba.chat_message( Lstr(resource='internal.serverRestartingText'). evaluate()) print(('Exiting for server-restart at ' + time.strftime('%c'))) else: print(('Exiting for server-shutdown at ' + time.strftime('%c'))) with _ba.Context('ui'): _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL) self._kicked_off_server_shutdown = True return True else: if not self._kicked_off_server_restart: print(('Running updated server config at ' + time.strftime('%c'))) with _ba.Context('ui'): _ba.timer(1.0, Call(_ba.pushcall, _server.launch_server_session), timetype=TimeType.REAL) self._kicked_off_server_restart = True return True return False
def end( # type: ignore self, results: Any = None, announce_winning_team: bool = True, announce_delay: float = 0.1, force: bool = False) -> None: """ End the game and announce the single winning team unless 'announce_winning_team' is False. (for results without a single most-important winner). """ # pylint: disable=arguments-differ from ba._coopsession import CoopSession from ba._multiteamsession import MultiTeamSession from ba._general import Call # Announce win (but only for the first finish() call) # (also don't announce in co-op sessions; we leave that up to them). session = self.session if not isinstance(session, CoopSession): do_announce = not self.has_ended() super().end(results, delay=2.0 + announce_delay, force=force) # Need to do this *after* end end call so that results is valid. assert isinstance(results, TeamGameResults) if do_announce and isinstance(session, MultiTeamSession): session.announce_game_results( self, results, delay=announce_delay, announce_winning_team=announce_winning_team) # For co-op we just pass this up the chain with a delay added # (in most cases). Team games expect a delay for the announce # portion in teams/ffa mode so this keeps it consistent. else: # don't want delay on restarts.. if (isinstance(results, dict) and 'outcome' in results and results['outcome'] == 'restart'): delay = 0.0 else: delay = 2.0 _ba.timer(0.1, Call(_ba.playsound, _ba.getsound('boxingBell'))) super().end(results, delay=delay, force=force)
def return_to_main_menu_session_gracefully(self, reset_ui: bool = True) -> 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 if reset_ui: _ba.app.ui.clear_main_menu_window() 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 run(self): player_data = pdata.get_info(self.pbid) _ba.pushcall(Call(on_player_join_server, self.pbid, player_data), from_other_thread=True)
def _show_info(self) -> None: """Show the game description.""" from ba._gameutils import animate from bastd.actor.zoomtext import ZoomText name = self.get_instance_display_string() ZoomText(name, maxwidth=800, lifespan=2.5, jitter=2.0, position=(0, 180), flash=False, color=(0.93 * 1.25, 0.9 * 1.25, 1.0 * 1.25), trailcolor=(0.15, 0.05, 1.0, 0.0)).autoretain() _ba.timer(0.2, Call(_ba.playsound, _ba.getsound('gong'))) # The description can be either a string or a sequence with args # to swap in post-translation. desc_in = self.get_instance_description() desc_l: Sequence if isinstance(desc_in, str): desc_l = [desc_in] # handle simple string case else: desc_l = desc_in if not isinstance(desc_l[0], str): raise TypeError('Invalid format for instance description') subs = [] for i in range(len(desc_l) - 1): subs.append(('${ARG' + str(i + 1) + '}', str(desc_l[i + 1]))) translation = Lstr(translate=('gameDescriptions', desc_l[0]), subs=subs) # Do some standard filters (epic mode, etc). if self.settings_raw.get('Epic Mode', False): translation = Lstr(resource='epicDescriptionFilterText', subs=[('${DESCRIPTION}', translation)]) vrmode = _ba.app.vr_mode dnode = _ba.newnode('text', attrs={ 'v_attach': 'center', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 1, 1, 1), 'shadow': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5, 'vr_depth': -30, 'position': (0, 80), 'scale': 1.2, 'maxwidth': 700, 'text': translation }) cnode = _ba.newnode('combine', owner=dnode, attrs={ 'input0': 1.0, 'input1': 1.0, 'input2': 1.0, 'size': 4 }) cnode.connectattr('output', dnode, 'color') keys = {0.5: 0, 1.0: 1.0, 2.5: 1.0, 4.0: 0.0} animate(cnode, 'input3', keys) _ba.timer(4.0, dnode.delete)
def submit_kill(self, showpoints: bool = True) -> None: """Submit a kill for this player entry.""" # FIXME Clean this up. # pylint: disable=too-many-statements from ba._lang import Lstr from ba._general import Call self._multi_kill_count += 1 stats = self._stats() assert stats if self._multi_kill_count == 1: score = 0 name = None delay = 0.0 color = (0.0, 0.0, 0.0, 1.0) scale = 1.0 sound = None elif self._multi_kill_count == 2: score = 20 name = Lstr(resource='twoKillText') color = (0.1, 1.0, 0.0, 1) scale = 1.0 delay = 0.0 sound = stats.orchestrahitsound1 elif self._multi_kill_count == 3: score = 40 name = Lstr(resource='threeKillText') color = (1.0, 0.7, 0.0, 1) scale = 1.1 delay = 0.3 sound = stats.orchestrahitsound2 elif self._multi_kill_count == 4: score = 60 name = Lstr(resource='fourKillText') color = (1.0, 1.0, 0.0, 1) scale = 1.2 delay = 0.6 sound = stats.orchestrahitsound3 elif self._multi_kill_count == 5: score = 80 name = Lstr(resource='fiveKillText') color = (1.0, 0.5, 0.0, 1) scale = 1.3 delay = 0.9 sound = stats.orchestrahitsound4 else: score = 100 name = Lstr(resource='multiKillText', subs=[('${COUNT}', str(self._multi_kill_count))]) color = (1.0, 0.5, 0.0, 1) scale = 1.3 delay = 1.0 sound = stats.orchestrahitsound4 def _apply(name2: Lstr, score2: int, showpoints2: bool, color2: Tuple[float, float, float, float], scale2: float, sound2: Optional[ba.Sound]) -> None: from bastd.actor.popuptext import PopupText # Only award this if they're still alive and we can get # a current position for them. our_pos: Optional[Sequence[float]] = None if self._player is not None: if self._player.gameplayer is not None: if self._player.gameplayer.node: our_pos = self._player.gameplayer.node.position if our_pos is None: return # Jitter position a bit since these often come in clusters. our_pos = (our_pos[0] + (random.random() - 0.5) * 2.0, our_pos[1] + (random.random() - 0.5) * 2.0, our_pos[2] + (random.random() - 0.5) * 2.0) activity = self.getactivity() if activity is not None: PopupText(Lstr( value=(('+' + str(score2) + ' ') if showpoints2 else '') + '${N}', subs=[('${N}', name2)]), color=color2, scale=scale2, position=our_pos).autoretain() if sound2: _ba.playsound(sound2) self.score += score2 self.accumscore += score2 # Inform a running game of the score. if score2 != 0 and activity is not None: activity.handlemessage(PlayerScoredMessage(score=score2)) if name is not None: _ba.timer( 0.3 + delay, Call(_apply, name, score, showpoints, color, scale, sound)) # Keep the tally rollin'... # set a timer for a bit in the future. self._multi_kill_timer = _ba.Timer(1.0, self._end_multi_kill)
def withDelay(session, playlist): time.sleep(1) _ba.pushcall(Call(updateSession, session, playlist), from_other_thread=True)
def do_print() -> None: _ba.timer(1.0, Call(_ba.screenmessage, Lstr(resource='usingItunesText'), (0, 1, 0)), timetype=TimeType.REAL)
def _set_ready(self, ready: bool) -> None: # pylint: disable=cyclic-import from bastd.ui.profile import browser as pbrowser from ba._general import Call profilename = self._profilenames[self._profileindex] # Handle '_edit' as a special case. if profilename == '_edit' and ready: with _ba.Context('ui'): pbrowser.ProfileBrowserWindow(in_main_menu=False) # Give their input-device UI ownership too # (prevent someone else from snatching it in crowded games) _ba.set_ui_input_device(self._sessionplayer.inputdevice) return if not ready: self._sessionplayer.assigninput( InputType.LEFT_PRESS, Call(self.handlemessage, ChangeMessage('team', -1))) self._sessionplayer.assigninput( InputType.RIGHT_PRESS, Call(self.handlemessage, ChangeMessage('team', 1))) self._sessionplayer.assigninput( InputType.BOMB_PRESS, Call(self.handlemessage, ChangeMessage('character', 1))) self._sessionplayer.assigninput( InputType.UP_PRESS, Call(self.handlemessage, ChangeMessage('profileindex', -1))) self._sessionplayer.assigninput( InputType.DOWN_PRESS, Call(self.handlemessage, ChangeMessage('profileindex', 1))) self._sessionplayer.assigninput( (InputType.JUMP_PRESS, InputType.PICK_UP_PRESS, InputType.PUNCH_PRESS), Call(self.handlemessage, ChangeMessage('ready', 1))) self._ready = False self._update_text() self._sessionplayer.setname('untitled', real=False) else: self._sessionplayer.assigninput( (InputType.LEFT_PRESS, InputType.RIGHT_PRESS, InputType.UP_PRESS, InputType.DOWN_PRESS, InputType.JUMP_PRESS, InputType.BOMB_PRESS, InputType.PICK_UP_PRESS), self._do_nothing) self._sessionplayer.assigninput( (InputType.JUMP_PRESS, InputType.BOMB_PRESS, InputType.PICK_UP_PRESS, InputType.PUNCH_PRESS), Call(self.handlemessage, ChangeMessage('ready', 0))) # Store the last profile picked by this input for reuse. input_device = self._sessionplayer.inputdevice name = input_device.name unique_id = input_device.unique_identifier device_profiles = _ba.app.config.setdefault( 'Default Player Profiles', {}) # Make an exception if we have no custom profiles and are set # to random; in that case we'll want to start picking up custom # profiles if/when one is made so keep our setting cleared. special = ('_random', '_edit', '__account__') have_custom_profiles = any(p not in special for p in self._profiles) profilekey = name + ' ' + unique_id if profilename == '_random' and not have_custom_profiles: if profilekey in device_profiles: del device_profiles[profilekey] else: device_profiles[profilekey] = profilename _ba.app.config.commit() # Set this player's short and full name. self._sessionplayer.setname(self._getname(), self._getname(full=True), real=True) self._ready = True self._update_text() # Inform the session that this player is ready. _ba.getsession().handlemessage(PlayerReadyMessage(self))