def get_settings_display_string(cls, config: Dict[str, Any]) -> ba.Lstr: """Given a game config dict, return a short description for it. This is used when viewing game-lists or showing what game is up next in a series. """ name = cls.get_display_string(config['settings']) # In newer configs, map is in settings; it used to be in the # config root. if 'map' in config['settings']: sval = Lstr(value='${NAME} @ ${MAP}', subs=[('${NAME}', name), ('${MAP}', _map.get_map_display_string( _map.get_filtered_map_name( config['settings']['map'])))]) elif 'map' in config: sval = Lstr(value='${NAME} @ ${MAP}', subs=[('${NAME}', name), ('${MAP}', _map.get_map_display_string( _map.get_filtered_map_name(config['map']))) ]) else: print('invalid game config - expected map entry under settings') sval = Lstr(value='???') return sval
def _update_text(self) -> None: assert self._text_node is not None if self._ready: # Once we're ready, we've saved the name, so lets ask the system # for it so we get appended numbers and stuff. text = Lstr(value=self._sessionplayer.getname(full=True)) text = Lstr(value='${A} (${B})', subs=[('${A}', text), ('${B}', Lstr(resource='readyText'))]) else: text = Lstr(value=self._getname(full=True)) can_switch_teams = len(self.lobby.sessionteams) > 1 # Flash as we're coming in. fin_color = _ba.safecolor(self.get_color()) + (1, ) if not self._inited: animate_array(self._text_node, 'color', 4, { 0.15: fin_color, 0.25: (2, 2, 2, 1), 0.35: fin_color }) else: # Blend if we're in teams mode; switch instantly otherwise. if can_switch_teams: animate_array(self._text_node, 'color', 4, { 0: self._text_node.color, 0.1: fin_color }) else: self._text_node.color = fin_color self._text_node.text = text
def get_team_score_str(self, team: ba.Team) -> ba.Lstr: """Return the score for the given ba.Team as an Lstr. (properly formatted for the score type.) """ from ba._gameutils import timestring from ba._lang import Lstr from ba._enums import TimeFormat from ba._score import ScoreType if not self._game_set: raise RuntimeError("Can't get team-score-str until game is set.") for score in list(self._scores.values()): if score[0]() is team.sessionteam: if score[1] is None: return Lstr(value='-') if self._score_type is ScoreType.SECONDS: return timestring(score[1] * 1000, centi=False, timeformat=TimeFormat.MILLISECONDS) if self._score_type is ScoreType.MILLISECONDS: return timestring(score[1], centi=True, timeformat=TimeFormat.MILLISECONDS) return Lstr(value=str(score[1])) return Lstr(value='-')
def handle_scan_results(results: ScanResults) -> None: """Called in the game thread with results of a completed scan.""" from ba._lang import Lstr from ba._plugin import PotentialPlugin # Warnings generally only get printed locally for users' benefit # (things like out-of-date scripts being ignored, etc.) # Errors are more serious and will get included in the regular log # warnings = results.get('warnings', '') # errors = results.get('errors', '') if results.warnings != '' or results.errors != '': import textwrap _ba.screenmessage(Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) if results.warnings != '': _ba.log(textwrap.indent(results.warnings, 'Warning (meta-scan): '), to_server=False) if results.errors != '': _ba.log(textwrap.indent(results.errors, 'Error (meta-scan): ')) # Handle plugins. config_changed = False found_new = False plugstates: Dict[str, Dict] = _ba.app.config.setdefault('Plugins', {}) assert isinstance(plugstates, dict) # Create a potential-plugin for each class we found in the scan. for class_path in results.plugins: _ba.app.potential_plugins.append( PotentialPlugin(display_name=Lstr(value=class_path), class_path=class_path, available=True)) if class_path not in plugstates: plugstates[class_path] = {'enabled': False} config_changed = True found_new = True # Also add a special one for any plugins set to load but *not* found # in the scan (this way they will show up in the UI so we can disable them) for class_path, plugstate in plugstates.items(): enabled = plugstate.get('enabled', False) assert isinstance(enabled, bool) if enabled and class_path not in results.plugins: _ba.app.potential_plugins.append( PotentialPlugin(display_name=Lstr(value=class_path), class_path=class_path, available=False)) _ba.app.potential_plugins.sort(key=lambda p: p.class_path) if found_new: _ba.screenmessage(Lstr(resource='pluginsDetectedText'), color=(0, 1, 0)) _ba.playsound(_ba.getsound('ding')) if config_changed: _ba.app.config.commit()
def description_complete(self) -> ba.Lstr: """Get a ba.Lstr for the Achievement's description when completed.""" from ba._lang import Lstr, get_resource if 'descriptionComplete' in get_resource('achievements')[self._name]: return Lstr(resource='achievements.' + self._name + '.descriptionComplete') return Lstr(resource='achievements.' + self._name + '.descriptionFullComplete')
def description_full_complete(self) -> ba.Lstr: """Get a ba.Lstr for the Achievement's full desc. when completed.""" from ba._lang import Lstr return Lstr( resource='achievements.' + self._name + '.descriptionFullComplete', subs=[('${LEVEL}', Lstr(translate=('coopLevelNames', ACH_LEVEL_NAMES.get(self._name, '?'))))])
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 # their pos. if self._player is not None and self._player.node: our_pos = self._player.node.position else: 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))
def on_player_request(self, player: ba.Player) -> bool: """Called when a new ba.Player wants to join the Session. This should return True or False to accept/reject. """ from ba._lang import Lstr # Limit player counts *unless* we're in a stress test. if _ba.app.stress_test_reset_timer is None: if len(self.players) >= self.max_players: # Print a rejection message *only* to the client trying to # join (prevents spamming everyone else in the game). _ba.playsound(_ba.getsound('error')) _ba.screenmessage( Lstr(resource='playerLimitReachedText', subs=[('${COUNT}', str(self.max_players))]), color=(0.8, 0.0, 0.0), clients=[player.get_input_device().client_id], transient=True) return False _ba.playsound(_ba.getsound('dripity')) return True
def _on_player_ready(self, chooser: ba.Chooser) -> None: """Called when a ba.Player has checked themself ready.""" from ba._lang import Lstr lobby = chooser.lobby activity = self._activity_weak() # In joining activities, we wait till all choosers are ready # and then create all players at once. if activity is not None and activity.is_joining_activity: if lobby.check_all_ready(): choosers = lobby.get_choosers() min_players = self.min_players if len(choosers) >= min_players: for lch in lobby.get_choosers(): self._add_chosen_player(lch) lobby.remove_all_choosers() # Get our next activity going. self._complete_end_activity(activity, {}) else: _ba.screenmessage(Lstr(resource='notEnoughPlayersText', subs=[('${COUNT}', str(min_players)) ]), color=(1, 1, 0)) _ba.playsound(_ba.getsound('error')) else: return # Otherwise just add players on the fly. else: self._add_chosen_player(chooser) lobby.remove_chooser(chooser.getplayer())
def _standard_time_limit_tick(self) -> None: from ba._gameutils import animate assert self._standard_time_limit_time is not None self._standard_time_limit_time -= 1 if self._standard_time_limit_time <= 10: if self._standard_time_limit_time == 10: assert self._standard_time_limit_text is not None assert self._standard_time_limit_text.node self._standard_time_limit_text.node.scale = 1.3 self._standard_time_limit_text.node.position = (-30, -45) cnode = _ba.newnode('combine', owner=self._standard_time_limit_text.node, attrs={'size': 4}) cnode.connectattr('output', self._standard_time_limit_text.node, 'color') animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) cnode.input3 = 1.0 _ba.playsound(_ba.getsound('tick')) if self._standard_time_limit_time <= 0: self._standard_time_limit_timer = None self.end_game() node = _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 0.7, 0, 1), 'position': (0, -90), 'scale': 1.2, 'text': Lstr(resource='timeExpiredText') }) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0.0: 0.0, 0.1: 1.4, 0.15: 1.2})
def player_was_killed(self, player: ba.Player, killed: bool = False, killer: ba.Player = None) -> None: """Should be called when a player is killed.""" from ba._lang import Lstr name = player.get_name() prec = self._player_records[name] prec.streak = 0 if killed: prec.accum_killed_count += 1 prec.killed_count += 1 try: if killed and _ba.getactivity().announce_player_deaths: if killer == player: _ba.screenmessage(Lstr(resource='nameSuicideText', subs=[('${NAME}', name)]), top=True, color=player.color, image=player.get_icon()) elif killer is not None: if killer.team == player.team: _ba.screenmessage(Lstr(resource='nameBetrayedText', subs=[('${NAME}', killer.get_name()), ('${VICTIM}', name)]), top=True, color=killer.color, image=killer.get_icon()) else: _ba.screenmessage(Lstr(resource='nameKilledText', subs=[('${NAME}', killer.get_name()), ('${VICTIM}', name)]), top=True, color=killer.color, image=killer.get_icon()) else: _ba.screenmessage(Lstr(resource='nameDiedText', subs=[('${NAME}', name)]), top=True, color=player.color, image=player.get_icon()) except Exception: from ba import _error _error.print_exception('error announcing kill')
def get_description_display_string( cls, sessiontype: Type[ba.Session]) -> ba.Lstr: """Return a translated version of get_description(). Sub-classes should override get_description(); not this. """ description = cls.get_description(sessiontype) return Lstr(translate=('gameDescriptions', description))
def ui_remote_press() -> None: """Handle a press by a remote device that is only usable for nav.""" from ba._lang import Lstr # Can be called without a context; need a context for getsound. with _ba.Context('ui'): _ba.screenmessage(Lstr(resource='internal.controllerForMenusOnlyText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error'))
def _execute_shutdown(self) -> None: from ba._lang import Lstr if self._executing_shutdown: return self._executing_shutdown = True timestrval = time.strftime('%c') if self._shutdown_reason is ShutdownReason.RESTARTING: _ba.screenmessage(Lstr(resource='internal.serverRestartingText'), color=(1, 0.5, 0.0)) print(f'{Clr.SBLU}Exiting for server-restart' f' at {timestrval}{Clr.RST}') else: _ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'), color=(1, 0.5, 0.0)) print(f'{Clr.SBLU}Exiting for server-shutdown' f' at {timestrval}{Clr.RST}') with _ba.Context('ui'): _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
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 _ba.app.pending_promo_codes: _ba.screenmessage( Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error'))
def get_display_string(cls, settings: Optional[Dict] = None) -> ba.Lstr: """Return a descriptive name for this game/settings combo. Subclasses should override getname(); not this. """ name = Lstr(translate=('gameNames', cls.getname())) # A few substitutions for 'Epic', 'Solo' etc. modes. # FIXME: Should provide a way for game types to define filters of # their own and should not rely on hard-coded settings names. if settings is not None: if 'Solo Mode' in settings and settings['Solo Mode']: name = Lstr(resource='soloNameFilterText', subs=[('${NAME}', name)]) if 'Epic Mode' in settings and settings['Epic Mode']: name = Lstr(resource='epicNameFilterText', subs=[('${NAME}', name)]) return name
def handle_account_gained_tickets(count: int) -> None: """Called when the current account has been awarded tickets. (internal) """ from ba._lang import Lstr _ba.screenmessage(Lstr(resource='getTicketsWindow.receivedTicketsText', subs=[('${COUNT}', str(count))]), color=(0, 1, 0)) _ba.playsound(_ba.getsound('cashRegister'))
def do_remove_in_game_ads_message(self) -> None: """(internal)""" from ba._lang import Lstr from ba._enums import TimeType # Print this message once every 10 minutes at most. tval = _ba.time(TimeType.REAL) if (self.last_in_game_ad_remove_message_show_time is None or (tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)): self.last_in_game_ad_remove_message_show_time = tval with _ba.Context('ui'): _ba.timer( 1.0, lambda: _ba.screenmessage(Lstr( resource='removeInGameAdsText', subs=[('${PRO}', Lstr(resource='store.bombSquadProNameText')), ('${APP_NAME}', Lstr(resource='titleText'))]), color=(1, 1, 0)), timetype=TimeType.REAL)
def _request_storage_permission() -> bool: """If needed, requests storage permission from the user (& return true).""" from ba._lang import Lstr from ba._enums import Permission if not _ba.have_permission(Permission.STORAGE): _ba.playsound(_ba.getsound('error')) _ba.screenmessage(Lstr(resource='storagePermissionAccessText'), color=(1, 0, 0)) _ba.timer(1.0, lambda: _ba.request_permission(Permission.STORAGE)) return True return False
def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None: """Called when a previously-accepted ba.SessionPlayer leaves.""" if sessionplayer not in self.players: print('ERROR: Session.on_player_leave called' ' for player not in our list.') return _ba.playsound(_ba.getsound('playerLeft')) activity = self._activity_weak() if not sessionplayer.in_game: # Ok, the player is still in the lobby; simply remove them. with _ba.Context(self): try: self.lobby.remove_chooser(sessionplayer) except Exception: print_exception('Error in Lobby.remove_chooser().') else: # Ok, they've already entered the game. Remove them from # teams/activities/etc. sessionteam = sessionplayer.sessionteam assert sessionteam is not None _ba.screenmessage( Lstr(resource='playerLeftText', subs=[('${PLAYER}', sessionplayer.getname(full=True))])) # Remove them from their SessionTeam. if sessionplayer in sessionteam.players: sessionteam.players.remove(sessionplayer) else: print('SessionPlayer not found in SessionTeam' ' in on_player_leave.') # Grab their activity-specific player instance. player = sessionplayer.activityplayer assert isinstance(player, (Player, type(None))) # Remove them from any current Activity. if activity is not None: if player in activity.players: activity.remove_player(sessionplayer) else: print('Player not found in Activity in on_player_leave.') # If we're a non-team session, remove their team too. if not self.use_teams: self._remove_player_team(sessionteam, activity) # Now remove them from the session list. self.players.remove(sessionplayer)
def _tournament_time_limit_tick(self) -> None: from ba._gameutils import animate assert self._tournament_time_limit is not None self._tournament_time_limit -= 1 if self._tournament_time_limit <= 10: if self._tournament_time_limit == 10: assert self._tournament_time_limit_title_text is not None assert self._tournament_time_limit_title_text.node assert self._tournament_time_limit_text is not None assert self._tournament_time_limit_text.node self._tournament_time_limit_title_text.node.scale = 1.0 self._tournament_time_limit_text.node.scale = 1.3 self._tournament_time_limit_title_text.node.position = (80, 85) self._tournament_time_limit_text.node.position = (80, 60) cnode = _ba.newnode( 'combine', owner=self._tournament_time_limit_text.node, attrs={'size': 4}) cnode.connectattr('output', self._tournament_time_limit_title_text.node, 'color') cnode.connectattr('output', self._tournament_time_limit_text.node, 'color') animate(cnode, 'input0', {0: 1, 0.15: 1}, loop=True) animate(cnode, 'input1', {0: 1, 0.15: 0.5}, loop=True) animate(cnode, 'input2', {0: 0.1, 0.15: 0.0}, loop=True) cnode.input3 = 1.0 _ba.playsound(_ba.getsound('tick')) if self._tournament_time_limit <= 0: self._tournament_time_limit_timer = None self.end_game() tval = Lstr(resource='tournamentTimeExpiredText', fallback_resource='timeExpiredText') node = _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'center', 'color': (1, 0.7, 0, 1), 'position': (0, -200), 'scale': 1.6, 'text': tval }) _ba.playsound(_ba.getsound('refWhistle')) animate(node, 'scale', {0: 0.0, 0.1: 1.4, 0.15: 1.2}) # Normally we just connect this to time, but since this is a bit of a # funky setup we just update it manually once per second. assert self._tournament_time_limit_text_input is not None assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.time2 = ( self._tournament_time_limit * 1000)
def _execute_shutdown(self) -> None: from ba._lang import Lstr if self._executing_shutdown: return self._executing_shutdown = True timestrval = time.strftime('%c') if self._shutdown_reason is ShutdownReason.RESTARTING: # FIXME: Should add a server-screen-message call. # (so we could send this an an Lstr) _ba.chat_message( Lstr(resource='internal.serverRestartingText').evaluate()) print(f'{Clr.SBLU}Exiting for server-restart' f' at {timestrval}{Clr.RST}') else: # FIXME: Should add a server-screen-message call. # (so we could send this an an Lstr) print(f'{Clr.SBLU}Exiting for server-shutdown' f' at {timestrval}{Clr.RST}') _ba.chat_message( Lstr(resource='internal.serverShuttingDownText').evaluate()) with _ba.Context('ui'): _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
def handle_deep_link(self, url: str) -> None: """Handle a deep link URL.""" from ba._lang import Lstr from ba._enums import TimeType appname = _ba.appname() if url.startswith(f'{appname}://code/'): code = url.replace(f'{appname}://code/', '') # 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 _ba.app.pending_promo_codes: _ba.screenmessage( Lstr(resource='signInForPromoCodeText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) _ba.app.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() else: _ba.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error'))
def show_post_purchase_message() -> None: """(internal)""" from ba._lang import Lstr from ba._enums import TimeType app = _ba.app cur_time = _ba.time(TimeType.REAL) if (app.last_post_purchase_message_time is None or cur_time - app.last_post_purchase_message_time > 3.0): app.last_post_purchase_message_time = cur_time with _ba.Context('ui'): _ba.screenmessage(Lstr(resource='updatingAccountText', fallback_resource='purchasingText'), color=(0, 1, 0)) _ba.playsound(_ba.getsound('click01'))
def _calc_map_name(self, settings: dict) -> str: map_name: str if 'map' in settings: map_name = settings['map'] else: # If settings doesn't specify a map, pick a random one from the # list of supported ones. unowned_maps = _map.get_unowned_maps() valid_maps: List[str] = [ m for m in self.get_supported_maps(type(self.session)) if m not in unowned_maps ] if not valid_maps: _ba.screenmessage(Lstr(resource='noValidMapsErrorText')) raise Exception('No valid maps') map_name = valid_maps[random.randrange(len(valid_maps))] return map_name
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._lang 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 _getname(self, full: bool = False) -> str: name_raw = name = self._profilenames[self._profileindex] clamp = False if name == '_random': try: name = ( self._sessionplayer.inputdevice.get_default_player_name()) except Exception: print_exception('Error getting _random chooser name.') name = 'Invalid' clamp = not full elif name == '__account__': try: name = self._sessionplayer.inputdevice.get_account_name(full) except Exception: print_exception('Error getting account name for chooser.') name = 'Invalid' clamp = not full elif name == '_edit': # Explicitly flattening this to a str; it's only relevant on # the host so that's ok. name = (Lstr( resource='createEditPlayerText', fallback_resource='editProfileWindow.titleNewText').evaluate()) else: # If we have a regular profile marked as global with an icon, # use it (for full only). if full: try: if self._profiles[name_raw].get('global', False): icon = (self._profiles[name_raw]['icon'] if 'icon' in self._profiles[name_raw] else _ba.charstr(SpecialChar.LOGO)) name = icon + name except Exception: print_exception('Error applying global icon.') else: # We now clamp non-full versions of names so there's at # least some hope of reading them in-game. clamp = True if clamp: if len(name) > 10: name = name[:10] + '...' return name
def __init__(self, lobby: ba.Lobby): from ba._nodeactor import NodeActor from ba._general import WeakCall self._state = 0 self._press_to_punch: Union[str, ba.Lstr] = _ba.charstr( SpecialChar.LEFT_BUTTON) self._press_to_bomb: Union[str, ba.Lstr] = _ba.charstr( SpecialChar.RIGHT_BUTTON) self._joinmsg = Lstr(resource='pressAnyButtonToJoinText') can_switch_teams = (len(lobby.sessionteams) > 1) # If we have a keyboard, grab keys for punch and pickup. # FIXME: This of course is only correct on the local device; # Should change this for net games. keyboard = _ba.getinputdevice('Keyboard', '#1', doraise=False) if keyboard is not None: self._update_for_keyboard(keyboard) flatness = 1.0 if _ba.app.vr_mode else 0.0 self._text = NodeActor( _ba.newnode('text', attrs={ 'position': (0, -40), 'h_attach': 'center', 'v_attach': 'top', 'h_align': 'center', 'color': (0.7, 0.7, 0.95, 1.0), 'flatness': flatness, 'text': self._joinmsg })) if _ba.app.kiosk_mode: self._messages = [self._joinmsg] else: msg1 = Lstr(resource='pressToSelectProfileText', subs=[ ('${BUTTONS}', _ba.charstr(SpecialChar.UP_ARROW) + ' ' + _ba.charstr(SpecialChar.DOWN_ARROW)) ]) msg2 = Lstr(resource='pressToOverrideCharacterText', subs=[('${BUTTONS}', Lstr(resource='bombBoldText'))]) msg3 = Lstr(value='${A} < ${B} >', subs=[('${A}', msg2), ('${B}', self._press_to_bomb)]) self._messages = (([ Lstr( resource='pressToSelectTeamText', subs=[('${BUTTONS}', _ba.charstr(SpecialChar.LEFT_ARROW) + ' ' + _ba.charstr(SpecialChar.RIGHT_ARROW))], ) ] if can_switch_teams else []) + [msg1] + [msg3] + [self._joinmsg]) self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)
def display_name(self) -> ba.Lstr: """Return a ba.Lstr for this Achievement's name.""" from ba._lang import Lstr name: Union[ba.Lstr, str] try: if self._level_name != '': from ba._campaign import getcampaign campaignname, campaign_level = self._level_name.split(':') name = getcampaign(campaignname).getlevel( campaign_level).displayname else: name = '' except Exception: from ba import _error name = '' _error.print_exception() return Lstr(resource='achievements.' + self._name + '.name', subs=[('${LEVEL}', name)])
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