def start_stress_test(args: Dict[str, Any]) -> None: """(internal)""" from ba._general import Call from ba._dualteamsession import DualTeamSession from ba._freeforallsession import FreeForAllSession from ba._enums import TimeType, TimeFormat bs_config = _ba.app.config playlist_type = args['playlist_type'] if playlist_type == 'Random': if random.random() < 0.5: playlist_type = 'Teams' else: playlist_type = 'Free-For-All' _ba.screenmessage('Running Stress Test (listType="' + playlist_type + '", listName="' + args['playlist_name'] + '")...') if playlist_type == 'Teams': bs_config['Team Tournament Playlist Selection'] = args['playlist_name'] bs_config['Team Tournament Playlist Randomize'] = 1 _ba.timer(1.0, Call(_ba.pushcall, Call(_ba.new_host_session, DualTeamSession)), timetype=TimeType.REAL) else: bs_config['Free-for-All Playlist Selection'] = args['playlist_name'] bs_config['Free-for-All Playlist Randomize'] = 1 _ba.timer(1.0, Call(_ba.pushcall, Call(_ba.new_host_session, FreeForAllSession)), timetype=TimeType.REAL) _ba.set_stress_testing(True, args['player_count']) _ba.app.stress_test_reset_timer = _ba.Timer( args['round_duration'] * 1000, Call(_reset_stress_test, args), timetype=TimeType.REAL, timeformat=TimeFormat.MILLISECONDS)
def end_activity(self, activity: ba.Activity, results: Any, delay: float, force: bool) -> None: """Commence shutdown of a ba.Activity (if not already occurring). 'delay' is the time delay before the Activity actually ends (in seconds). Further calls to end() will be ignored up until this time, unless 'force' is True, in which case the new results will replace the old. """ from ba._general import Call from ba._enums import TimeType # Only pay attention if this is coming from our current activity. if activity is not self._activity_retained: return # If this activity hasn't begun yet, just set it up to end immediately # once it does. if not activity.has_begun(): # activity.set_immediate_end(results, delay, force) if not self._activity_should_end_immediately or force: self._activity_should_end_immediately = True self._activity_should_end_immediately_results = results self._activity_should_end_immediately_delay = delay # The activity has already begun; get ready to end it. else: if (not activity.has_ended()) or force: activity.set_has_ended(True) # Set a timer to set in motion this activity's demise. self._activity_end_timer = _ba.Timer( delay, Call(self._complete_end_activity, activity, results), timetype=TimeType.BASE)
def _spaz_spawn_holding_node(self) -> Optional[ba.Node]: if not self.node: return self.delete_holding_node() t = self.node.position t = (t[0], t[1] + 1, t[2]) self.holding_node = ba.newnode('prop', owner=self.node, delegate=self, attrs={ 'position': t, 'body': 'box', 'body_scale': 0.000001, 'model': None, 'model_scale': 0.000001, 'color_texture': None, 'max_speed': 0, 'owner': self.node, 'materials': [] }) self._combine = ba.newnode('combine', owner=self.holding_node, attrs={'size': 3}) self._offset = [0, 0, 0] self._combine.input0, self._combine.input1, self._combine.input2 = t self._combine.connectattr('output', self.holding_node, 'position') self.move_holding_node('xyz') self.fly_timer = _ba.Timer(0.1, ba.WeakCall(self.move_holding_node, 'xyz'), repeat=True) return self.holding_node
def __init__(self, config: ServerConfig) -> None: self._config = config self._playlist_name = '__default__' self._ran_access_check = False self._prep_timer: Optional[ba.Timer] = None self._next_stuck_login_warn_time = time.time() + 10.0 self._first_run = True self._shutdown_reason: Optional[ShutdownReason] = None self._executing_shutdown = False # Make note if they want us to import a playlist; # we'll need to do that first if so. self._playlist_fetch_running = self._config.playlist_code is not None self._playlist_fetch_sent_request = False self._playlist_fetch_got_response = False self._playlist_fetch_code = -1 # Now sit around doing any pre-launch prep such as waiting for # account sign-in or fetching playlists; this will kick off the # session once done. with _ba.Context('ui'): self._prep_timer = _ba.Timer(0.25, self._prepare_to_serve, timetype=TimeType.REAL, repeat=True)
def _destroy(self) -> None: from ba._general import Call from ba._enums import TimeType # Create a real-timer that watches a weak-ref of this activity # and reports any lingering references keeping it alive. # We store the timer on the activity so as soon as the activity dies # it gets cleaned up. with _ba.Context('ui'): ref = weakref.ref(self) self._activity_death_check_timer = _ba.Timer( 5.0, Call(self._check_activity_death, ref, [0]), repeat=True, timetype=TimeType.REAL) # Run _expire in an empty context; nothing should be happening in # there except deleting things which requires no context. # (plus, _expire() runs in the destructor for un-run activities # and we can't properly provide context in that situation anyway; might # as well be consistent). if not self._expired: with _ba.Context('empty'): self._expire() else: raise Exception("_destroy() called multiple times")
def announce_completion(self, sound: bool = True) -> None: """Kick off an announcement for this achievement's completion.""" from ba._enums import TimeType app = _ba.app # Even though there are technically achievements when we're not # signed in, lets not show them (otherwise we tend to get # confusing 'controller connected' achievements popping up while # waiting to log in which can be confusing). if _ba.get_account_state() != 'signed_in': return # If we're being freshly complete, display/report it and whatnot. if (self, sound) not in app.achievements_to_display: app.achievements_to_display.append((self, sound)) # If there's no achievement display timer going, kick one off # (if one's already running it will pick this up before it dies). # Need to check last time too; its possible our timer wasn't able to # clear itself if an activity died and took it down with it. if ((app.achievement_display_timer is None or _ba.time(TimeType.REAL) - app.last_achievement_display_time > 2.0) and _ba.getactivity(doraise=False) is not None): app.achievement_display_timer = _ba.Timer( 1.0, _display_next_achievement, repeat=True, timetype=TimeType.BASE) # Show the first immediately. _display_next_achievement()
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] = ('C' if _ba.app.iircade_mode else _ba.charstr( SpecialChar.LEFT_BUTTON)) self._press_to_bomb: Union[str, ba.Lstr] = ('B' if _ba.app.iircade_mode else _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.demo_mode or _ba.app.arcade_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 transition_in(self, prev_globals: Optional[ba.Node]) -> None: """Called by Session to kick off transition-in. (internal) """ assert not self._has_transitioned_in self._has_transitioned_in = True # Set up the globals node based on our settings. with _ba.Context(self): glb = self._globalsnode = _ba.newnode('globals') # Now that it's going to be front and center, # set some global values based on what the activity wants. glb.use_fixed_vr_overlay = self.use_fixed_vr_overlay glb.allow_kick_idle_players = self.allow_kick_idle_players if self.inherits_slow_motion and prev_globals is not None: glb.slow_motion = prev_globals.slow_motion else: glb.slow_motion = self.slow_motion if self.inherits_music and prev_globals is not None: glb.music_continuous = True # Prevent restarting same music. glb.music = prev_globals.music glb.music_count += 1 if self.inherits_vr_camera_offset and prev_globals is not None: glb.vr_camera_offset = prev_globals.vr_camera_offset if self.inherits_vr_overlay_center and prev_globals is not None: glb.vr_overlay_center = prev_globals.vr_overlay_center glb.vr_overlay_center_enabled = ( prev_globals.vr_overlay_center_enabled) # If they want to inherit tint from the previous self. if self.inherits_tint and prev_globals is not None: glb.tint = prev_globals.tint glb.vignette_outer = prev_globals.vignette_outer glb.vignette_inner = prev_globals.vignette_inner # Start pruning our various things periodically. self._prune_dead_actors() self._prune_dead_actors_timer = _ba.Timer(5.17, self._prune_dead_actors, repeat=True) _ba.timer(13.3, self._prune_delay_deletes, repeat=True) # Also start our low-level scene running. self._activity_data.start() try: self.on_transition_in() except Exception: print_exception(f'Error in on_transition_in for {self}.') # Tell the C++ layer that this activity is the main one, so it uses # settings from our globals, directs various events to us, etc. self._activity_data.make_foreground()
def setup_asyncio() -> asyncio.AbstractEventLoop: """Setup asyncio functionality for the logic thread.""" # pylint: disable=global-statement import _ba import ba from ba._generated.enums import TimeType assert _ba.in_game_thread() # Create our event-loop. We don't expect there to be one # running on this thread before we do. try: asyncio.get_running_loop() print('Found running asyncio loop; unexpected.') except RuntimeError: pass global _asyncio_event_loop # pylint: disable=invalid-name _asyncio_event_loop = asyncio.new_event_loop() _asyncio_event_loop.set_default_executor(ba.app.threadpool) # Ideally we should integrate asyncio into our C++ Thread class's # low level event loop so that asyncio timers/sockets/etc. could # be true first-class citizens. For now, though, we can explicitly # pump an asyncio loop periodically which gets us a decent # approximation of that, which should be good enough for # all but extremely time sensitive uses. # See https://stackoverflow.com/questions/29782377/ # is-it-possible-to-run-only-a-single-step-of-the-asyncio-event-loop def run_cycle() -> None: assert _asyncio_event_loop is not None _asyncio_event_loop.call_soon(_asyncio_event_loop.stop) _asyncio_event_loop.run_forever() global _asyncio_timer # pylint: disable=invalid-name _asyncio_timer = _ba.Timer(1.0 / 30.0, run_cycle, timetype=TimeType.REAL, repeat=True) if bool(False): async def aio_test() -> None: print('TEST AIO TASK STARTING') assert _asyncio_event_loop is not None assert asyncio.get_running_loop() is _asyncio_event_loop await asyncio.sleep(2.0) print('TEST AIO TASK ENDING') _asyncio_event_loop.create_task(aio_test()) return _asyncio_event_loop
def setup_standard_powerup_drops(self, enable_tnt: bool = True) -> None: """Create standard powerup drops for the current map.""" # pylint: disable=cyclic-import from bastd.actor.powerupbox import DEFAULT_POWERUP_INTERVAL self._powerup_drop_timer = _ba.Timer(DEFAULT_POWERUP_INTERVAL, WeakCall( self._standard_drop_powerups), repeat=True) self._standard_drop_powerups() if enable_tnt: self._tnt_spawners = {} self._setup_standard_tnt_drops()
def on_app_launch(self) -> None: """Should be run on app launch.""" from ba.ui import UIController, ui_upkeep from ba._enums import TimeType 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 on_transition_in(self) -> None: """Called when the Activity is first becoming visible. Upon this call, the Activity should fade in backgrounds, start playing music, etc. It does not yet have access to ba.Players or ba.Teams, however. They remain owned by the previous Activity up until ba.Activity.on_begin() is called. """ from ba._general import WeakCall self._called_activity_on_transition_in = True # Start pruning our transient actors periodically. self._prune_dead_objects_timer = _ba.Timer( 5.17, WeakCall(self._prune_dead_objects), repeat=True) self._prune_dead_objects() # Also start our low-level scene-graph running. self._activity_data.start()
def respawn_player(self, player: PlayerType, respawn_time: Optional[float] = None) -> None: """ Given a ba.Player, sets up a standard respawn timer, along with the standard counter display, etc. At the end of the respawn period spawn_player() will be called if the Player still exists. An explicit 'respawn_time' can optionally be provided (in seconds). """ # pylint: disable=cyclic-import assert player if respawn_time is None: teamsize = len(player.team.players) if teamsize == 1: respawn_time = 3.0 elif teamsize == 2: respawn_time = 5.0 elif teamsize == 3: respawn_time = 6.0 else: respawn_time = 7.0 # If this standard setting is present, factor it in. if 'Respawn Times' in self.settings_raw: respawn_time *= self.settings_raw['Respawn Times'] # We want whole seconds. assert respawn_time is not None respawn_time = round(max(1.0, respawn_time), 0) if player.actor and not self.has_ended(): from bastd.actor.respawnicon import RespawnIcon player.customdata['respawn_timer'] = _ba.Timer( respawn_time, WeakCall(self.spawn_player_if_exists, player)) player.customdata['respawn_icon'] = RespawnIcon( player, respawn_time)
def setup_standard_time_limit(self, duration: float) -> None: """ Create a standard game time-limit given the provided duration in seconds. This will be displayed at the top of the screen. If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor if duration <= 0.0: return self._standard_time_limit_time = int(duration) self._standard_time_limit_timer = _ba.Timer( 1.0, WeakCall(self._standard_time_limit_tick), repeat=True) self._standard_time_limit_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'top', 'h_attach': 'center', 'h_align': 'left', 'color': (1.0, 1.0, 1.0, 0.5), 'position': (-25, -30), 'flatness': 1.0, 'scale': 0.9 })) self._standard_time_limit_text_input = NodeActor( _ba.newnode('timedisplay', attrs={ 'time2': duration * 1000, 'timemin': 0 })) self.globalsnode.connectattr('time', self._standard_time_limit_text_input.node, 'time1') assert self._standard_time_limit_text_input.node assert self._standard_time_limit_text.node self._standard_time_limit_text_input.node.connectattr( 'output', self._standard_time_limit_text.node, 'text')
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 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, UIScale 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) # 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)) # 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) # 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_FORCE_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)) # 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) self.ran_on_app_launch = True
def __init__(self, lobby: ba.Lobby): # pylint: disable=too-many-locals from ba import _input from ba._lang import Lstr from ba._nodeactor import NodeActor from ba._general import WeakCall from ba._enums import SpecialChar can_switch_teams = (len(lobby.teams) > 1) self._state = 0 press_to_punch: Union[str, ba.Lstr] = _ba.charstr(SpecialChar.LEFT_BUTTON) press_to_bomb: Union[str, ba.Lstr] = _ba.charstr(SpecialChar.RIGHT_BUTTON) # 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: Optional[ba.InputDevice] = _ba.get_input_device( 'Keyboard', '#1', doraise=False) if keyboard is not None: punch_key = keyboard.get_button_name( _input.get_device_value(keyboard, 'buttonPunch')) press_to_punch = Lstr(resource='orText', subs=[('${A}', Lstr(value='\'${K}\'', subs=[('${K}', punch_key)])), ('${B}', press_to_punch)]) bomb_key = keyboard.get_button_name( _input.get_device_value(keyboard, 'buttonBomb')) press_to_bomb = Lstr(resource='orText', subs=[('${A}', Lstr(value='\'${K}\'', subs=[('${K}', bomb_key)])), ('${B}', press_to_bomb)]) join_str = Lstr(value='${A} < ${B} >', subs=[('${A}', Lstr(resource='pressPunchToJoinText')), ('${B}', press_to_punch)]) else: join_str = Lstr(resource='pressAnyButtonToJoinText') 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': join_str })) if _ba.app.kiosk_mode: self._messages = [join_str] 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}', 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] + [join_str]) self._timer = _ba.Timer(4.0, WeakCall(self._update), repeat=True)
def _setup_tournament_time_limit(self, duration: float) -> None: """ Create a tournament game time-limit given the provided duration in seconds. This will be displayed at the top of the screen. If the time-limit expires, end_game() will be called. """ from ba._nodeactor import NodeActor from ba._enums import TimeType if duration <= 0.0: return self._tournament_time_limit = int(duration) # We want this timer to match the server's time as close as possible, # so lets go with base-time. Theoretically we should do real-time but # then we have to mess with contexts and whatnot since its currently # not available in activity contexts. :-/ self._tournament_time_limit_timer = _ba.Timer( 1.0, WeakCall(self._tournament_time_limit_tick), repeat=True, timetype=TimeType.BASE) self._tournament_time_limit_title_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'bottom', 'h_attach': 'left', 'h_align': 'center', 'v_align': 'center', 'vr_depth': 300, 'maxwidth': 100, 'color': (1.0, 1.0, 1.0, 0.5), 'position': (60, 50), 'flatness': 1.0, 'scale': 0.5, 'text': Lstr(resource='tournamentText') })) self._tournament_time_limit_text = NodeActor( _ba.newnode('text', attrs={ 'v_attach': 'bottom', 'h_attach': 'left', 'h_align': 'center', 'v_align': 'center', 'vr_depth': 300, 'maxwidth': 100, 'color': (1.0, 1.0, 1.0, 0.5), 'position': (60, 30), 'flatness': 1.0, 'scale': 0.9 })) self._tournament_time_limit_text_input = NodeActor( _ba.newnode('timedisplay', attrs={ 'timemin': 0, 'time2': self._tournament_time_limit * 1000 })) assert self._tournament_time_limit_text.node assert self._tournament_time_limit_text_input.node self._tournament_time_limit_text_input.node.connectattr( 'output', self._tournament_time_limit_text.node, 'text')
def config_server(config_file: str = None) -> None: """Run the game in server mode with the provided server config file.""" from ba._enums import TimeType app = _ba.app # Read and store the provided server config and then delete the file it # came from. if config_file is not None: with open(config_file) as infile: app.server_config = json.loads(infile.read()) os.remove(config_file) else: app.server_config = {} # Make note if they want us to import a playlist; # we'll need to do that first if so. playlist_code = app.server_config.get('playlist_code') if playlist_code is not None: app.server_playlist_fetch = { 'sent_request': False, 'got_response': False, 'playlist_code': str(playlist_code) } # Apply config stuff that can take effect immediately (party name, etc). _config_server() # Launch the server only the first time through; # after that it will be self-sustaining. if not app.launched_server: # Now sit around until we're signed in and then kick off the server. with _ba.Context('ui'): 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) app.run_server_wait_timer = _ba.Timer(0.25, do_it, timetype=TimeType.REAL, repeat=True) app.launched_server = True
def setup_low_life_warning_sound(self) -> None: """Set up a beeping noise to play when any players are near death.""" self._life_warning_beep = None self._life_warning_beep_timer = _ba.Timer( 1.0, WeakCall(self._update_life_warning), repeat=True)
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 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