def load_plugins(self) -> None: """(internal)""" from ba._general import getclass from ba._plugin import Plugin # Note: the plugins we load is purely based on what's enabled # in the app config. Our meta-scan gives us a list of available # plugins, but that is only used to give the user a list of plugins # that they can enable. (we wouldn't want to look at meta-scan here # anyway because it may not be done yet at this point in the launch) plugstates: Dict[str, Dict] = self.config.get('Plugins', {}) assert isinstance(plugstates, dict) plugkeys: List[str] = sorted(key for key, val in plugstates.items() if val.get('enabled', False)) for plugkey in plugkeys: try: cls = getclass(plugkey, Plugin) except Exception as exc: _ba.log(f"Error loading plugin class '{plugkey}': {exc}", to_server=False) continue try: plugin = cls() assert plugkey not in self.active_plugins self.active_plugins[plugkey] = plugin except Exception: from ba import _error _error.print_exception(f'Error loading plugin: {plugkey}')
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 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 read_config() -> tuple[AppConfig, bool]: """Read the game config.""" import os import json from ba._generated.enums import TimeType config_file_healthy = False # NOTE: it is assumed that this only gets called once and the # config object will not change from here on out config_file_path = _ba.app.config_file_path config_contents = '' try: if os.path.exists(config_file_path): with open(config_file_path, encoding='utf-8') as infile: config_contents = infile.read() config = AppConfig(json.loads(config_contents)) else: config = AppConfig() config_file_healthy = True except Exception as exc: print(('error reading config file at time ' + str(_ba.time(TimeType.REAL)) + ': \'' + config_file_path + '\':\n'), exc) # Whenever this happens lets back up the broken one just in case it # gets overwritten accidentally. print(('backing up current config file to \'' + config_file_path + ".broken\'")) try: import shutil shutil.copyfile(config_file_path, config_file_path + '.broken') except Exception as exc2: print('EXC copying broken config:', exc2) try: _ba.log('broken config contents:\n' + config_contents.replace('\000', '<NULL_BYTE>'), to_stdout=False) except Exception as exc2: print('EXC logging broken config contents:', exc2) config = AppConfig() # Now attempt to read one of our 'prev' backup copies. prev_path = config_file_path + '.prev' try: if os.path.exists(prev_path): with open(prev_path, encoding='utf-8') as infile: config_contents = infile.read() config = AppConfig(json.loads(config_contents)) else: config = AppConfig() config_file_healthy = True print('successfully read backup config.') except Exception as exc2: print('EXC reading prev backup config:', exc2) return config, config_file_healthy
def _shiplog(self) -> None: with self._lock: line = ''.join(self._linebits) if not line: return self._linebits = [] # Log messages aren't expected to have trailing newlines. if line.endswith('\n'): line = line[:-1] _ba.log(line, to_stdout=False)
def handle_scan_results(results: ScanResults) -> None: """Called in the game thread with results of a completed scan.""" from ba import _lang # 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 != '': _ba.screenmessage(_lang.Lstr(resource='scanScriptsErrorText'), color=(1, 0, 0)) _ba.playsound(_ba.getsound('error')) if results.warnings != '': _ba.log(results.warnings, to_server=False) if results.errors != '': _ba.log(results.errors)
def _launch_server_session(self) -> None: """Kick off a host-session based on the current server config.""" # pylint: disable=too-many-branches app = _ba.app appcfg = app.config sessiontype = self._get_session_type() if _ba.get_v1_account_state() != 'signed_in': print('WARNING: launch_server_session() expects to run ' 'with a signed in server account') # If we didn't fetch a playlist but there's an inline one in the # server-config, pull it in to the game config and use it. if (self._config.playlist_code is None and self._config.playlist_inline is not None): self._playlist_name = 'ServerModePlaylist' if sessiontype is FreeForAllSession: ptypename = 'Free-for-All' elif sessiontype is DualTeamSession: ptypename = 'Team Tournament' elif sessiontype is CoopSession: ptypename = 'Coop' else: raise RuntimeError(f'Unknown session type {sessiontype}') # Need to add this in a transaction instead of just setting # it directly or it will get overwritten by the master-server. _ba.add_transaction({ 'type': 'ADD_PLAYLIST', 'playlistType': ptypename, 'playlistName': self._playlist_name, 'playlist': self._config.playlist_inline }) _ba.run_transactions() 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) elif sessiontype is CoopSession: app.coop_session_args = { 'campaign': self._config.coop_campaign, 'level': self._config.coop_level, } 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