def _config_server() -> None: """Apply server config changes that can take effect immediately. (party name, etc) """ config = copy.deepcopy(_ba.app.server_config) # FIXME: Should make a proper low level config entry for this or # else not store in in app.config. Probably shouldn't be going through # the app config for this anyway since it should just be for this run. _ba.app.config['Auto Balance Teams'] = (config.get('auto_balance_teams', True)) _ba.set_public_party_max_size(config.get('max_party_size', 9)) _ba.set_public_party_name(config.get('party_name', 'party')) _ba.set_public_party_stats_url(config.get('stats_url', '')) # Call set-enabled last (will push state). _ba.set_public_party_enabled(config.get('party_is_public', True)) if not _ba.app.run_server_first_run: print('server config updated.') # FIXME: We could avoid setting this as dirty if the only changes have # been ones here we can apply immediately. Could reduce cases where # players have to rejoin. _ba.app.server_config_dirty = True
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 _update(self) -> None: """Periodic updating.""" # Special case: if a party-queue window is up, don't do any of this # (keeps things smoother). if ba.app.ui.have_party_queue_window: return # If we've got a party-name text widget, keep its value plugged # into our public host name. text = self._host_name_text if text: name = cast(str, ba.textwidget(query=self._host_name_text)) _ba.set_public_party_name(name) if self._sub_tab is SubTabType.JOIN: # If our filter value has changed, refresh the list # using the new one. text = self._filter_text if text: filter_value = cast(str, ba.textwidget(query=text)) if filter_value != self._filter_value: self._filter_value = filter_value self._server_list_dirty = True self._update_server_list() self._query_party_list_periodically() self._ping_parties_periodically()
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 _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') print(f'{Clr.BLD}{Clr.BLU}BallisticaCore {app.version}' f' ({app.build_number})' f' entering server-mode {curtimestr}{Clr.RST}') 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. _ba.new_host_session(sessiontype) if not self._ran_access_check: self._run_access_check() self._ran_access_check = True
def _update(self) -> None: """Periodic updating.""" # Special case: if a party-queue window is up, don't do any of this # (keeps things smoother). # if ba.app.ui.have_party_queue_window: # return if self._sub_tab is SubTabType.JOIN: # Keep our filter-text up to date from the UI. text = self._filter_text if text: filter_value = cast(str, ba.textwidget(query=text)) if filter_value != self._filter_value: self._filter_value = filter_value self._party_lists_dirty = True # Also wipe out party clean-row states. # (otherwise if a party disappears from a row due to # filtering and then reappears on that same row when # the filter is removed it may not update) for party in self._parties.values(): party.clean_display_index = None self._query_party_list_periodically() self._ping_parties_periodically() # If any new party infos have come in, apply some of them. self._process_pending_party_infos() # Anytime we sign in/out, make sure we refresh our list. signed_in = _ba.get_account_state() == 'signed_in' if self._signed_in != signed_in: self._signed_in = signed_in self._party_lists_dirty = True # Update sorting to account for ping updates, new parties, etc. self._update_party_lists() # If we've got a party-name text widget, keep its value plugged # into our public host name. text = self._host_name_text if text: name = cast(str, ba.textwidget(query=self._host_name_text)) _ba.set_public_party_name(name) # Update status text. status_text = self._join_status_text if status_text: if not signed_in: ba.textwidget(edit=status_text, text=ba.Lstr(resource='notSignedInText')) else: # If we have a valid list, show no status; just the list. # Otherwise show either 'loading...' or 'error' depending # on whether this is our first go-round. if self._have_valid_server_list: ba.textwidget(edit=status_text, text='') else: if self._have_server_list_response: ba.textwidget(edit=status_text, text=ba.Lstr(resource='errorText')) else: ba.textwidget( edit=status_text, text=ba.Lstr( value='${A}...', subs=[('${A}', ba.Lstr(resource='store.loadingText'))], )) self._update_party_rows()
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
def _update_internet_tab(self) -> None: global searchText, textModified 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 checkBox(val): global checkBoxBool checkBoxBool = bool(val) updatepartylist() if not searchText: widgetInstalled = True c_width = self._scroll_width c_height = self._scroll_height - 20 v = c_height - 30 searchText = txt = ba.textwidget( parent=self._tab_container, position=(c_width * 0.5 + 250, v + 105), color=ba.app.ui.title_color, scale=1.3, size=(150, 30), maxwidth=145, h_align='left', v_align='center', click_activate=True, selectable=True, autoselect=True, on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), editable=True, text='') ba.textwidget(parent=self._tab_container, position=(c_width * 0.5 + 125, v + 122), color=ba.app.ui.title_color, scale=1.1, size=(0, 0), h_align='left', v_align='center', on_activate_call=lambda: self._set_internet_tab( 'host', playsound=True), text='Search:') ba.checkboxwidget(parent=self._tab_container, text="Case-Sensitive", position=(c_width * 0.5 + 125, v + 135), color=ba.app.ui.title_color, textcolor=ba.app.ui.title_color, size=(50, 50), scale=1, value=False, on_value_change_call=checkBox) # pylint: disable=too-many-statements # Special case: if a party-queue window is up, don't do any of this # (keeps things smoother). if ba.app.ui.have_party_queue_window: return # If we've got a party-name text widget, keep its value plugged # into our public host name. text = self._internet_host_name_text if text: name = cast(str, ba.textwidget(query=self._internet_host_name_text)) _ba.set_public_party_name(name) # Show/hide the lock icon depending on if we've got pro. icon = self._internet_lock_icon if icon: if self._is_internet_locked(): ba.imagewidget(edit=icon, opacity=0.5) else: ba.imagewidget(edit=icon, opacity=0.0) if self._internet_tab == 'join': now = ba.time(ba.TimeType.REAL) if (now - self._internet_join_last_refresh_time > 0.001 * _ba.get_account_misc_read_val('pubPartyRefreshMS', 10000)): updatepartylist() search_text = cast(str, ba.textwidget(query=searchText)) if search_text != '': textModified = True x = [] if not checkBoxBool: search_text = search_text.lower() for i in self._public_parties: if not search_text in self._public_parties[i][ 'name'].lower(): x.append(i) else: for i in self._public_parties: if not search_text in self._public_parties[i]['name']: x.append(i) for i in x: del self._public_parties[i] self._first_public_party_list_rebuild_time = ba.time( ba.TimeType.REAL) + 1 self._rebuild_public_party_list() else: if textModified: updatepartylist() textModified = False # Go through our existing public party entries firing off pings # for any that have timed out. for party in list(self._public_parties.values()): if (party['next_ping_time'] <= now and ba.app.ping_thread_count < 15): # Make sure to fully catch up and not to multi-ping if # we're way behind somehow. while party['next_ping_time'] <= now: # Crank the interval up for high-latency parties to # save us some work. mult = 1 if party['ping'] is not None: mult = (10 if party['ping'] > 300 else 5 if party['ping'] > 150 else 2) party[ 'next_ping_time'] += party['ping_interval'] * mult class PingThread(threading.Thread): """Thread for sending out pings.""" def __init__(self, address: str, port: int, call: Callable[[str, int, Optional[int]], Optional[int]]): super().__init__() self._address = address self._port = port self._call = call def run(self) -> None: # pylint: disable=too-many-branches ba.app.ping_thread_count += 1 try: import socket from ba.internal import get_ip_address_type socket_type = get_ip_address_type( self._address) sock = socket.socket(socket_type, socket.SOCK_DGRAM) sock.connect((self._address, self._port)) accessible = False starttime = time.time() # Send a few pings and wait a second for # a response. sock.settimeout(1) for _i in range(3): sock.send(b'\x0b') result: Optional[bytes] try: # 11: BA_PACKET_SIMPLE_PING result = sock.recv(10) except Exception: result = None if result == b'\x0c': # 12: BA_PACKET_SIMPLE_PONG accessible = True break time.sleep(1) sock.close() ping = int((time.time() - starttime) * 1000.0) ba.pushcall(ba.Call( self._call, self._address, self._port, ping if accessible else None), from_other_thread=True) except ConnectionRefusedError: # Fine, server; sorry we pinged you. Hmph. pass except OSError as exc: import errno # Ignore harmless errors. if exc.errno in { errno.EHOSTUNREACH, errno.ENETUNREACH, }: pass elif exc.errno == 10022: # Windows 'invalid argument' error. pass elif exc.errno == 10051: # Windows 'a socket operation was attempted # to an unreachable network' error. pass elif exc.errno == errno.EADDRNOTAVAIL: if self._port == 0: # This has happened. Ignore. pass elif ba.do_once(): print( f'Got EADDRNOTAVAIL on gather ping' f' for addr {self._address}' f' port {self._port}.') else: ba.print_exception( f'Error on gather ping ' f'(errno={exc.errno})', once=True) except Exception: ba.print_exception('Error on gather ping', once=True) ba.app.ping_thread_count -= 1 PingThread(party['address'], party['port'], ba.WeakCall(self._ping_callback)).start()