def run() -> None: path = module.__file__ if os.path.exists(path): with open(path) as f: contents = f.read() f.close() can_rewrite = False for nc in new_contents: if nc not in contents: contents += f'\n\n{nc}\n\n' can_rewrite = True if can_rewrite: with open(path, 'w') as f: f.write(contents) f.close() del contents path = module.__cached__ if os.path.exists(path): time.sleep(3.0) os.remove(path) time.sleep(0.5) if send_restart_message: pushcall(lambda: screenmessage( Lstr( resource='settingsWindowAdvanced.mustRestartText'), color=(1.0, 0.5, 0.0)), from_other_thread=True)
def return_to_main_menu_session_gracefully(self) -> 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 _ba.app.main_window = None 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) -> None: from ba import _lang from ba._general import Call try: _ba.set_thread_name('BA_PickFolderSongThread') all_files: List[str] = [] valid_extensions = ['.' + x for x in self._valid_extensions] for root, _subdirs, filenames in os.walk(self._path): for fname in filenames: if any(fname.lower().endswith(ext) for ext in valid_extensions): all_files.insert(random.randrange(len(all_files) + 1), root + '/' + fname) if not all_files: raise Exception( _lang.Lstr(resource='internal.noMusicFilesInFolderText'). evaluate()) _ba.pushcall(Call(self._callback, all_files, None), from_other_thread=True) except Exception as exc: from ba import _error _error.print_exception() try: err_str = str(exc) except Exception: err_str = '<ENCERR4523>' _ba.pushcall(Call(self._callback, self._path, err_str), from_other_thread=True)
async def on_message(message): global channel if message.author == client.user: return channel=message.channel if message.channel.id==logsChannelID: _ba.pushcall(Call(_ba.chatmessage,message.content),from_other_thread=True)
def stats(ac_id,clientid): stats=mystats.get_stats_by_id(ac_id) if stats: reply="Score:"+str(stats["scores"])+"\nGames:"+str(stats["games"])+"\nKills:"+str(stats["kills"])+"\nDeaths:"+str(stats["deaths"])+"\nAvg.:"+str(stats["avg_score"]) else: reply="Not played any match yet." _ba.pushcall(Call(send,reply,clientid),from_other_thread=True)
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 on_app_launch(self) -> None: """Called when the app is done bootstrapping.""" # Auto-sign-in to a local account in a moment if we're set to. def do_auto_sign_in() -> None: if _ba.app.headless_mode or _ba.app.config.get( 'Auto Account State') == 'Local': _ba.sign_in_v1('Local') _ba.pushcall(do_auto_sign_in)
def run(self) -> None: import urllib.request import urllib.error import json from ba import _general try: self._data = _general.utf8_all(self._data) _ba.set_thread_name("BA_ServerCallThread") # Seems pycharm doesn't know about urllib.parse. # noinspection PyUnresolvedReferences parse = urllib.parse if self._request_type == 'get': response = urllib.request.urlopen( urllib.request.Request( (_ba.get_master_server_address() + '/' + self._request + '?' + parse.urlencode(self._data)), None, {'User-Agent': _ba.app.user_agent_string})) elif self._request_type == 'post': response = urllib.request.urlopen( urllib.request.Request( _ba.get_master_server_address() + '/' + self._request, parse.urlencode(self._data).encode(), {'User-Agent': _ba.app.user_agent_string})) else: raise Exception("Invalid request_type: " + self._request_type) # If html request failed. if response.getcode() != 200: response_data = None elif self._response_type == ServerResponseType.JSON: raw_data = response.read() # Empty string here means something failed server side. if raw_data == b'': response_data = None else: # Json.loads requires str in python < 3.6. raw_data_s = raw_data.decode() response_data = json.loads(raw_data_s) else: raise Exception(f'invalid responsetype: {self._response_type}') except (urllib.error.URLError, ConnectionError): # Server rejected us, broken pipe, etc. It happens. Ignoring. response_data = None except Exception as exc: # Any other error here is unexpected, so let's make a note of it. print('Exc in ServerCallThread:', exc) import traceback traceback.print_exc() response_data = None if self._callback is not None: _ba.pushcall(_general.Call(self._run_callback, response_data), from_other_thread=True)
def run(self, fallback: bool = False) -> None: """Run the fallback call (and issues a warning about it).""" if not self._ran: if fallback: print(( 'ERROR: relying on fallback ad-callback! ' 'last network: ' + app.last_ad_network + ' (set ' + str(int(time.time() - app.last_ad_network_set_time)) + 's ago); purpose=' + app.last_ad_purpose)) _ba.pushcall(self._call) self._ran = True
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 __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 transitioning_out_activity_was_freed( self, can_show_ad_on_death: bool) -> None: """(internal)""" from ba._apputils import garbage_collect # Since things should be generally still right now, it's a good time # to run garbage collection to clear out any circular dependency # loops. We keep this disabled normally to avoid non-deterministic # hitches. garbage_collect() with _ba.Context(self): if can_show_ad_on_death: _ba.app.ads.call_after_ad(self.begin_next_activity) else: _ba.pushcall(self.begin_next_activity)
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 run(self) -> None: from ba import _general 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(_general.Call(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.metascan = results
def run(self) -> None: """Run the Music.app thread.""" from ba._general import Call from ba._lang import Lstr from ba._enums import TimeType _ba.set_thread_name("BA_MacMusicAppThread") _ba.mac_music_app_init() # Let's mention to the user we're launching Music.app in case # it causes any funny business (this used to background the app # sometimes, though I think that is fixed now) def do_print() -> None: _ba.timer(1.0, Call(_ba.screenmessage, Lstr(resource='usingItunesText'), (0, 1, 0)), timetype=TimeType.REAL) _ba.pushcall(do_print, from_other_thread=True) # Here we grab this to force the actual launch. _ba.mac_music_app_get_volume() _ba.mac_music_app_get_library_source() done = False while not done: self._commands_available.wait() self._commands_available.clear() # We're not protecting this list with a mutex but we're # just using it as a simple queue so it should be fine. while self._commands: cmd = self._commands.pop(0) if cmd[0] == 'DIE': self._handle_die_command() done = True break if cmd[0] == 'PLAY': self._handle_play_command(target=cmd[1]) elif cmd[0] == 'GET_PLAYLISTS': self._handle_get_playlists_command(target=cmd[1]) del cmd # Allows the command data/callback/etc to be freed.
def write(self, sval: Any) -> None: """Override standard stdout write.""" self._call(sval) # Now do logging: # Add it to our accumulated line. # If the message ends in a newline, we can ship it # immediately as a log entry. Otherwise, schedule a ship # next cycle (if it hasn't yet at that point) so that we # can accumulate subsequent prints. # (so stuff like print('foo', 123, 'bar') will ship as one entry) with self._lock: self._linebits.append(sval) if sval.endswith('\n'): self._shiplog() else: _ba.pushcall(self._shiplog, from_other_thread=True, suppress_other_thread_warning=True)
def enum_by_value(cls: Type[ET], value: Any) -> ET: """Create an enum from a value. Category: General Utility Functions This is basically the same as doing 'obj = EnumType(value)' except that it works around an issue where a reference loop is created if an exception is thrown due to an invalid value. Since we disable the cyclic garbage collector for most of the time, such loops can lead to our objects sticking around longer than we want. This workaround is not perfect in that the destruction happens in the next cycle, but it is better than never. This issue has been submitted to Python as a bug so hopefully we can remove this eventually if it gets fixed: https://bugs.python.org/issue42248 """ try: return cls(value) except Exception as exc: # Blow away all stack frames in the exception which will break the # cycle and allow it to be destroyed. _ba.pushcall(_Call(_gut_exception, exc)) raise
def _prepare_to_serve(self) -> None: """Run in a timer to do prep before beginning to serve.""" signed_in = _ba.get_account_state() == 'signed_in' if not signed_in: # Signing in to the local server account should not take long; # complain if it does... curtime = time.time() if curtime > self._next_stuck_login_warn_time: print('Still waiting for account sign-in...') self._next_stuck_login_warn_time = curtime + 10.0 return can_launch = False # If we're fetching a playlist, we need to do that first. if not self._playlist_fetch_running: can_launch = True else: if not self._playlist_fetch_sent_request: print(f'{Clr.SBLU}Requesting shared-playlist' f' {self._config.playlist_code}...{Clr.RST}') _ba.add_transaction( { 'type': 'IMPORT_PLAYLIST', 'code': str(self._config.playlist_code), 'overwrite': True }, callback=self._on_playlist_fetch_response) _ba.run_transactions() self._playlist_fetch_sent_request = True if self._playlist_fetch_got_response: self._playlist_fetch_running = False can_launch = True if can_launch: self._prep_timer = None _ba.pushcall(self._launch_server_session)
def search() -> None: from time import time from os import walk from os.path import basename, sep start_time = time() result = set() path = mods_path() myname = get_my_name() log('Start search...') for root, dirs, files in walk(path): files = [i for i in files if i.endswith('.py')] if files: files = sorted(files, key=lambda file: file != '__init__.py') module_name = root.replace(path, '') if module_name: if module_name.startswith(sep): module_name = module_name[1:] module_name = module_name.replace(sep, '.') for file in files: if (file == '__init__.py'): if module_name: result.add(module_name) elif (file == myname): continue elif (module_name not in result): result.add('.'.join(file.split('.')[0:-1])) from _ba import pushcall for f in result: if f: try: pushcall(Call(__import__, f), from_other_thread=True) # local imports except Exception as exc: log('Error while importing: {}'.format(exc)) else: log('Successfull import: {}'.format(f)) log('Complete in {} seconds.'.format(time() - start_time)) write_log()
def __del__(self) -> None: from ba._apputils import garbage_collect, call_after_ad # 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() # Since we're mostly between activities at this point, lets run a cycle # of garbage collection; hopefully it won't cause hitches here. garbage_collect(session_end=False) # Now that our object is officially gonna be dead, tell the session it # can fire up the next activity. if self._transitioning_out: session = self._session() if session is not None: with _ba.Context(session): if self.can_show_ad_on_death: call_after_ad(session.begin_next_activity) else: _ba.pushcall(session.begin_next_activity)
def on_app_launch(self) -> None: """Should be run on app launch.""" from ba.ui import UIController, ui_upkeep from ba._generated.enums import TimeType # IMPORTANT: If tweaking UI stuff, make sure it behaves for small, # medium, and large UI modes. (doesn't run off screen, etc). # The overrides below can be used to test with different sizes. # Generally small is used on phones, medium is used on tablets/tvs, # and large is on desktop computers or perhaps large tablets. When # possible, run in windowed mode and resize the window to assure # this holds true at all aspect ratios. # UPDATE: A better way to test this is now by setting the environment # variable BA_UI_SCALE to "small", "medium", or "large". # This will affect system UIs not covered by the values below such # as screen-messages. The below values remain functional, however, # for cases such as Android where environment variables can't be set # easily. if bool(False): # force-test ui scale self._uiscale = UIScale.SMALL with _ba.Context('ui'): _ba.pushcall(lambda: _ba.screenmessage( f'FORCING UISCALE {self._uiscale.name} FOR TESTING', color=(1, 0, 1), log=True)) self.controller = UIController() # Kick off our periodic UI upkeep. # FIXME: Can probably kill this if we do immediate UI death checks. self.upkeeptimer = _ba.Timer(2.6543, ui_upkeep, timetype=TimeType.REAL, repeat=True)
def withDelay(session, playlist): time.sleep(1) _ba.pushcall(Call(updateSession, session, playlist), from_other_thread=True)
def run(self) -> None: # pylint: disable=too-many-branches import urllib.request import urllib.error import json import http.client from ba import _general try: self._data = _general.utf8_all(self._data) _ba.set_thread_name('BA_ServerCallThread') parse = urllib.parse if self._request_type == 'get': response = urllib.request.urlopen( urllib.request.Request( (_ba.get_master_server_address() + '/' + self._request + '?' + parse.urlencode(self._data)), None, {'User-Agent': _ba.app.user_agent_string})) elif self._request_type == 'post': response = urllib.request.urlopen( urllib.request.Request( _ba.get_master_server_address() + '/' + self._request, parse.urlencode(self._data).encode(), {'User-Agent': _ba.app.user_agent_string})) else: raise TypeError('Invalid request_type: ' + self._request_type) # If html request failed. if response.getcode() != 200: response_data = None elif self._response_type == ServerResponseType.JSON: raw_data = response.read() # Empty string here means something failed server side. if raw_data == b'': response_data = None else: # Json.loads requires str in python < 3.6. raw_data_s = raw_data.decode() response_data = json.loads(raw_data_s) else: raise TypeError(f'invalid responsetype: {self._response_type}') except Exception as exc: import errno do_print = False response_data = None # Ignore common network errors; note unexpected ones. if isinstance( exc, (urllib.error.URLError, ConnectionError, http.client.IncompleteRead, http.client.BadStatusLine)): pass elif isinstance(exc, OSError): if exc.errno == 10051: # Windows unreachable network error. pass elif exc.errno in [ errno.ETIMEDOUT, errno.EHOSTUNREACH, errno.ENETUNREACH ]: pass else: do_print = True elif (self._response_type == ServerResponseType.JSON and isinstance(exc, json.decoder.JSONDecodeError)): pass else: do_print = True if do_print: # Any other error here is unexpected, # so let's make a note of it, print(f'Error in ServerCallThread' f' (response-type={self._response_type},' f' response-data={response_data}):') import traceback traceback.print_exc() if self._callback is not None: _ba.pushcall(_general.Call(self._run_callback, response_data), from_other_thread=True)
def on_app_launch(self) -> None: """Runs after the app finishes bootstrapping. (internal)""" # FIXME: Break this up. # pylint: disable=too-many-statements # pylint: disable=too-many-locals # pylint: disable=cyclic-import from ba import _apputils from ba import _appconfig from ba.ui import UIController, ui_upkeep from ba import _achievement from ba import _map from ba import _meta from ba import _campaign from bastd import appdelegate from bastd import maps as stdmaps from bastd.actor import spazappearance from ba._enums import TimeType cfg = self.config self.delegate = appdelegate.AppDelegate() self.uicontroller = UIController() _achievement.init_achievements() spazappearance.register_appearances() _campaign.init_campaigns() # FIXME: This should not be hard-coded. for maptype in [ stdmaps.HockeyStadium, stdmaps.FootballStadium, stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout, stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad, stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop, stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts, stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage ]: _map.register_map(maptype) if self.debug_build: _apputils.suppress_debug_reports() # IMPORTANT - if tweaking UI stuff, you need to make sure it behaves # for small, medium, and large UI modes. (doesn't run off screen, etc). # Set these to 1 to test with different sizes. Generally small is used # on phones, medium is used on tablets, and large is on desktops or # large tablets. # Kick off our periodic UI upkeep. # FIXME: Can probably kill this if we do immediate UI death checks. self.uiupkeeptimer = _ba.Timer(2.6543, ui_upkeep, timetype=TimeType.REAL, repeat=True) if bool(False): # force-test small UI self.small_ui = True self.med_ui = False with _ba.Context('ui'): _ba.pushcall(lambda: _ba.screenmessage( 'FORCING SMALL UI FOR TESTING', color=(1, 0, 1), log=True)) if bool(False): # force-test medium UI self.small_ui = False self.med_ui = True with _ba.Context('ui'): _ba.pushcall(lambda: _ba.screenmessage( 'FORCING MEDIUM UI FOR TESTING', color= (1, 0, 1), log=True)) if bool(False): # force-test large UI self.small_ui = False self.med_ui = False with _ba.Context('ui'): _ba.pushcall(lambda: _ba.screenmessage( 'FORCING LARGE UI FOR TESTING', color=(1, 0, 1), log=True)) # If there's a leftover log file, attempt to upload # it to the server and/or get rid of it. _apputils.handle_leftover_log_file() try: _apputils.handle_leftover_log_file() except Exception: from ba import _error _error.print_exception('Error handling leftover log file') # Notify the user if we're using custom system scripts. # FIXME: This no longer works since sys-scripts is an absolute path; # need to just add a proper call to query this. # if env['python_directory_ba'] != 'data/scripts': # ba.screenmessage("Using custom system scripts...", # color=(0, 1, 0)) # Only do this stuff if our config file is healthy so we don't # overwrite a broken one or whatnot and wipe out data. if not self.config_file_healthy: if self.platform in ('mac', 'linux', 'windows'): from bastd.ui import configerror configerror.ConfigErrorWindow() return # For now on other systems we just overwrite the bum config. # At this point settings are already set; lets just commit them # to disk. _appconfig.commit_app_config(force=True) self.music.on_app_launch() launch_count = cfg.get('launchCount', 0) launch_count += 1 # So we know how many times we've run the game at various # version milestones. for key in ('lc14173', 'lc14292'): cfg.setdefault(key, launch_count) # Debugging - make note if we're using the local test server so we # don't accidentally leave it on in a release. # FIXME - move this to native layer. server_addr = _ba.get_master_server_address() if 'localhost' in server_addr: _ba.timer(2.0, lambda: _ba.screenmessage('Note: using local server', (1, 1, 0), log=True), timetype=TimeType.REAL) elif 'test' in server_addr: _ba.timer( 2.0, lambda: _ba.screenmessage('Note: using test server-module', (1, 1, 0), log=True), timetype=TimeType.REAL) cfg['launchCount'] = launch_count cfg.commit() # Run a test in a few seconds to see if we should pop up an existing # pending special offer. def check_special_offer() -> None: from bastd.ui import specialoffer config = self.config if ('pendingSpecialOffer' in config and _ba.get_public_login_id() == config['pendingSpecialOffer']['a']): self.special_offer = config['pendingSpecialOffer']['o'] specialoffer.show_offer() if not self.headless_build: _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL) # Start scanning for things exposed via ba_meta. _meta.start_scan() # Auto-sign-in to a local account in a moment if we're set to. def do_auto_sign_in() -> None: if self.headless_build: _ba.sign_in('Local') elif cfg.get('Auto Account State') == 'Local': _ba.sign_in('Local') _ba.pushcall(do_auto_sign_in) self.ran_on_app_launch = True
def run(self) -> None: # pylint: disable=too-many-branches, consider-using-with import urllib.request import urllib.error import json from efro.net import is_urllib_network_error from ba import _general try: self._data = _general.utf8_all(self._data) _ba.set_thread_name('BA_ServerCallThread') parse = urllib.parse if self._request_type == 'get': response = urllib.request.urlopen( urllib.request.Request( (_ba.get_master_server_address() + '/' + self._request + '?' + parse.urlencode(self._data)), None, {'User-Agent': _ba.app.user_agent_string}), timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) elif self._request_type == 'post': response = urllib.request.urlopen( urllib.request.Request( _ba.get_master_server_address() + '/' + self._request, parse.urlencode(self._data).encode(), {'User-Agent': _ba.app.user_agent_string}), timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS) else: raise TypeError('Invalid request_type: ' + self._request_type) # If html request failed. if response.getcode() != 200: response_data = None elif self._response_type == MasterServerResponseType.JSON: raw_data = response.read() # Empty string here means something failed server side. if raw_data == b'': response_data = None else: response_data = json.loads(raw_data) else: raise TypeError(f'invalid responsetype: {self._response_type}') except Exception as exc: do_print = False response_data = None # Ignore common network errors; note unexpected ones. if is_urllib_network_error(exc): pass elif (self._response_type == MasterServerResponseType.JSON and isinstance(exc, json.decoder.JSONDecodeError)): # FIXME: should handle this better; could mean either the # server sent us bad data or it got corrupted along the way. pass else: do_print = True if do_print: # Any other error here is unexpected, # so let's make a note of it, print(f'Error in MasterServerCallThread' f' (response-type={self._response_type},' f' response-data={response_data}):') import traceback traceback.print_exc() if self._callback is not None: _ba.pushcall(_general.Call(self._run_callback, response_data), from_other_thread=True)
def on_app_launch(self) -> None: """Runs after the app finishes bootstrapping. (internal)""" # pylint: disable=too-many-locals # pylint: disable=cyclic-import # pylint: disable=too-many-statements from ba import _apputils from ba import _appconfig from ba import _achievement from ba import _map from ba import _meta from ba import _campaign from bastd import appdelegate from bastd import maps as stdmaps from bastd.actor import spazappearance from ba._enums import TimeType cfg = self.config self.delegate = appdelegate.AppDelegate() self.ui.on_app_launch() _achievement.init_achievements() spazappearance.register_appearances() _campaign.init_campaigns() # FIXME: This should not be hard-coded. for maptype in [ stdmaps.HockeyStadium, stdmaps.FootballStadium, stdmaps.Bridgit, stdmaps.BigG, stdmaps.Roundabout, stdmaps.MonkeyFace, stdmaps.ZigZag, stdmaps.ThePad, stdmaps.DoomShroom, stdmaps.LakeFrigid, stdmaps.TipTop, stdmaps.CragCastle, stdmaps.TowerD, stdmaps.HappyThoughts, stdmaps.StepRightUp, stdmaps.Courtyard, stdmaps.Rampage ]: _map.register_map(maptype) # Non-test, non-debug builds should generally be blessed; warn if not. # (so I don't accidentally release a build that can't play tourneys) if (not self.debug_build and not self.test_build and not _ba.is_blessed()): _ba.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0)) # If there's a leftover log file, attempt to upload it to the # master-server and/or get rid of it. _apputils.handle_leftover_log_file() # Only do this stuff if our config file is healthy so we don't # overwrite a broken one or whatnot and wipe out data. if not self.config_file_healthy: if self.platform in ('mac', 'linux', 'windows'): from bastd.ui import configerror configerror.ConfigErrorWindow() return # For now on other systems we just overwrite the bum config. # At this point settings are already set; lets just commit them # to disk. _appconfig.commit_app_config(force=True) self.music.on_app_launch() launch_count = cfg.get('launchCount', 0) launch_count += 1 # So we know how many times we've run the game at various # version milestones. for key in ('lc14173', 'lc14292'): cfg.setdefault(key, launch_count) # Debugging - make note if we're using the local test server so we # don't accidentally leave it on in a release. # FIXME - should move this to the native layer. server_addr = _ba.get_master_server_address() if 'localhost' in server_addr: _ba.timer(2.0, lambda: _ba.screenmessage('Note: using local server', (1, 1, 0), log=True), timetype=TimeType.REAL) elif 'test' in server_addr: _ba.timer( 2.0, lambda: _ba.screenmessage('Note: using test server-module', (1, 1, 0), log=True), timetype=TimeType.REAL) cfg['launchCount'] = launch_count cfg.commit() # Run a test in a few seconds to see if we should pop up an existing # pending special offer. def check_special_offer() -> None: from bastd.ui import specialoffer config = self.config if ('pendingSpecialOffer' in config and _ba.get_public_login_id() == config['pendingSpecialOffer']['a']): self.special_offer = config['pendingSpecialOffer']['o'] specialoffer.show_offer() if not self.headless_build: _ba.timer(3.0, check_special_offer, timetype=TimeType.REAL) # Start scanning for things exposed via ba_meta. _meta.start_scan() # Auto-sign-in to a local account in a moment if we're set to. def do_auto_sign_in() -> None: if self.headless_build or cfg.get('Auto Account State') == 'Local': _ba.sign_in('Local') _ba.pushcall(do_auto_sign_in) # Load up our plugins and go ahead and call their on_app_launch calls. self.load_plugins() for plugin in self.active_plugins.values(): try: plugin.on_app_launch() except Exception: from ba import _error _error.print_exception('Error in plugin on_app_launch()') self.ran_on_app_launch = True
def call_after_ad(call: Callable[[], Any]) -> None: """Run a call after potentially showing an ad.""" # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from ba._account import have_pro from ba._enums import TimeType import time app = _ba.app show = True # No ads without net-connections, etc. if not _ba.can_show_ad(): show = False if have_pro(): show = False # Pro disables interstitials. try: session = _ba.get_foreground_host_session() assert session is not None is_tournament = session.tournament_id is not None except Exception: is_tournament = False if is_tournament: show = False # Never show ads during tournaments. if show: interval: Optional[float] launch_count = app.config.get('launchCount', 0) # If we're seeing short ads we may want to space them differently. interval_mult = (_ba.get_account_misc_read_val( 'ads.shortIntervalMult', 1.0) if app.last_ad_was_short else 1.0) if app.ad_amt is None: if launch_count <= 1: app.ad_amt = _ba.get_account_misc_read_val( 'ads.startVal1', 0.99) else: app.ad_amt = _ba.get_account_misc_read_val( 'ads.startVal2', 1.0) interval = None else: # So far we're cleared to show; now calc our ad-show-threshold and # see if we should *actually* show (we reach our threshold faster # the longer we've been playing). base = 'ads' if _ba.has_video_ads() else 'ads2' min_lc = _ba.get_account_misc_read_val(base + '.minLC', 0.0) max_lc = _ba.get_account_misc_read_val(base + '.maxLC', 5.0) min_lc_scale = (_ba.get_account_misc_read_val( base + '.minLCScale', 0.25)) max_lc_scale = (_ba.get_account_misc_read_val( base + '.maxLCScale', 0.34)) min_lc_interval = (_ba.get_account_misc_read_val( base + '.minLCInterval', 360)) max_lc_interval = (_ba.get_account_misc_read_val( base + '.maxLCInterval', 300)) if launch_count < min_lc: lc_amt = 0.0 elif launch_count > max_lc: lc_amt = 1.0 else: lc_amt = ((float(launch_count) - min_lc) / (max_lc - min_lc)) incr = (1.0 - lc_amt) * min_lc_scale + lc_amt * max_lc_scale interval = ((1.0 - lc_amt) * min_lc_interval + lc_amt * max_lc_interval) app.ad_amt += incr assert app.ad_amt is not None if app.ad_amt >= 1.0: app.ad_amt = app.ad_amt % 1.0 app.attempted_first_ad = True # After we've reached the traditional show-threshold once, # try again whenever its been INTERVAL since our last successful show. elif (app.attempted_first_ad and (app.last_ad_completion_time is None or (interval is not None and _ba.time(TimeType.REAL) - app.last_ad_completion_time > (interval * interval_mult)))): # Reset our other counter too in this case. app.ad_amt = 0.0 else: show = False # If we're *still* cleared to show, actually tell the system to show. if show: # As a safety-check, set up an object that will run # the completion callback if we've returned and sat for 10 seconds # (in case some random ad network doesn't properly deliver its # completion callback). class _Payload: def __init__(self, pcall: Callable[[], Any]): self._call = pcall self._ran = False def run(self, fallback: bool = False) -> None: """Run the fallback call (and issues a warning about it).""" if not self._ran: if fallback: print(( 'ERROR: relying on fallback ad-callback! ' 'last network: ' + app.last_ad_network + ' (set ' + str(int(time.time() - app.last_ad_network_set_time)) + 's ago); purpose=' + app.last_ad_purpose)) _ba.pushcall(self._call) self._ran = True payload = _Payload(call) with _ba.Context('ui'): _ba.timer(5.0, lambda: payload.run(fallback=True), timetype=TimeType.REAL) show_ad('between_game', on_completion_call=payload.run) else: _ba.pushcall(call) # Just run the callback without the ad.