def setactivity(self, activity: ba.Activity) -> None: """Assign a new current ba.Activity for the session. Note that this will not change the current context to the new Activity's. Code must be run in the new activity's methods (on_transition_in, etc) to get it. (so you can't do session.setactivity(foo) and then ba.newnode() to add a node to foo) """ from ba._enums import TimeType # Make sure we don't get called recursively. _rlock = self._SetActivityScopedLock(self) if activity.session is not _ba.getsession(): raise RuntimeError("Provided Activity's Session is not current.") # Quietly ignore this if the whole session is going down. if self._ending: return if activity is self._activity_retained: print_error('Activity set to already-current activity.') return if self._next_activity is not None: raise RuntimeError('Activity switch already in progress (to ' + str(self._next_activity) + ')') prev_activity = self._activity_retained prev_globals = (prev_activity.globalsnode if prev_activity is not None else None) # Let the activity do its thing. activity.transition_in(prev_globals) self._next_activity = activity # If we have a current activity, tell it it's transitioning out; # the next one will become current once this one dies. if prev_activity is not None: prev_activity.transition_out() # Setting this to None should free up the old activity to die, # which will call begin_next_activity. # We can still access our old activity through # self._activity_weak() to keep it up to date on player # joins/departures/etc until it dies. self._activity_retained = None # There's no existing activity; lets just go ahead with the begin call. else: self.begin_next_activity() # We want to call destroy() for the previous activity once it should # tear itself down, clear out any self-refs, etc. After this call # the activity should have no refs left to it and should die (which # will trigger the next activity to run). if prev_activity is not None: with _ba.Context('ui'): _ba.timer(max(0.0, activity.transition_time), prev_activity.expire, timetype=TimeType.REAL) self._in_set_activity = False
def on_activity_end(self, activity: ba.Activity, results: Any) -> None: """Method override for co-op sessions. Jumps between co-op games and score screens. """ # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements # pylint: disable=cyclic-import from ba._activitytypes import JoinActivity, TransitionActivity from ba._lang import Lstr from ba._general import WeakCall from ba._coopgame import CoopGameActivity from ba._gameresults import GameResults from ba._score import ScoreType from ba._player import PlayerInfo from bastd.tutorial import TutorialActivity from bastd.activity.coopscore import CoopScoreScreen app = _ba.app # If we're running a TeamGameActivity we'll have a GameResults # as results. Otherwise its an old CoopGameActivity so its giving # us a dict of random stuff. if isinstance(results, GameResults): outcome = 'defeat' # This can't be 'beaten'. else: outcome = '' if results is None else results.get('outcome', '') # If at any point we have no in-game players, quit out of the session # (this can happen if someone leaves in the tutorial for instance). active_players = [p for p in self.players if p.in_game] if not active_players: self.end() return # If we're in a between-round activity or a restart-activity, # hop into a round. if (isinstance(activity, (JoinActivity, CoopScoreScreen, TransitionActivity))): if outcome == 'next_level': if self._next_game_instance is None: raise RuntimeError() assert self._next_game_level_name is not None self.campaign_level_name = self._next_game_level_name next_game = self._next_game_instance else: next_game = self._current_game_instance # Special case: if we're coming from a joining-activity # and will be going into onslaught-training, show the # tutorial first. if (isinstance(activity, JoinActivity) and self.campaign_level_name == 'Onslaught Training' and not app.kiosk_mode): if self._tutorial_activity is None: raise RuntimeError('Tutorial not preloaded properly.') self.setactivity(self._tutorial_activity) self._tutorial_activity = None self._ran_tutorial_activity = True self._custom_menu_ui = [] # Normal case; launch the next round. else: # Reset stats for the new activity. self.stats.reset() for player in self.players: # Skip players that are still choosing a team. if player.in_game: self.stats.register_sessionplayer(player) self.stats.setactivity(next_game) # Now flip the current activity.. self.setactivity(next_game) if not app.kiosk_mode: if self.tournament_id is not None: self._custom_menu_ui = [{ 'label': Lstr(resource='restartText'), 'resume_on_call': False, 'call': WeakCall(self._on_tournament_restart_menu_press) }] else: self._custom_menu_ui = [{ 'label': Lstr(resource='restartText'), 'call': WeakCall(self.restart) }] # If we were in a tutorial, just pop a transition to get to the # actual round. elif isinstance(activity, TutorialActivity): self.setactivity(_ba.newactivity(TransitionActivity)) else: playerinfos: List[ba.PlayerInfo] # Generic team games. if isinstance(results, GameResults): playerinfos = results.playerinfos score = results.get_sessionteam_score(results.sessionteams[0]) fail_message = None score_order = ('decreasing' if results.lower_is_better else 'increasing') if results.scoretype in (ScoreType.SECONDS, ScoreType.MILLISECONDS): scoretype = 'time' # ScoreScreen wants hundredths of a second. if score is not None: if results.scoretype is ScoreType.SECONDS: score *= 100 elif results.scoretype is ScoreType.MILLISECONDS: score //= 10 else: raise RuntimeError('FIXME') else: if results.scoretype is not ScoreType.POINTS: print(f'Unknown ScoreType:' f' "{results.scoretype}"') scoretype = 'points' # Old coop-game-specific results; should migrate away from these. else: playerinfos = results.get('playerinfos') score = results['score'] if 'score' in results else None fail_message = (results['fail_message'] if 'fail_message' in results else None) score_order = (results['score_order'] if 'score_order' in results else 'increasing') activity_score_type = (activity.get_score_type() if isinstance( activity, CoopGameActivity) else None) assert activity_score_type is not None scoretype = activity_score_type # Validate types. if playerinfos is not None: assert isinstance(playerinfos, list) assert (isinstance(i, PlayerInfo) for i in playerinfos) # Looks like we were in a round - check the outcome and # go from there. if outcome == 'restart': # This will pop up back in the same round. self.setactivity(_ba.newactivity(TransitionActivity)) else: self.setactivity( _ba.newactivity( CoopScoreScreen, { 'playerinfos': playerinfos, 'score': score, 'fail_message': fail_message, 'score_order': score_order, 'score_type': scoretype, 'outcome': outcome, 'campaign': self.campaign, 'level': self.campaign_level_name })) # No matter what, get the next 2 levels ready to go. self._update_on_deck_game_instances()
def set_activity(self, activity: ba.Activity) -> None: """Assign a new current ba.Activity for the session. Note that this will not change the current context to the new Activity's. Code must be run in the new activity's methods (on_transition_in, etc) to get it. (so you can't do session.set_activity(foo) and then ba.newnode() to add a node to foo) """ # pylint: disable=too-many-statements # pylint: disable=too-many-branches from ba import _error from ba._gameutils import sharedobj from ba._enums import TimeType # Sanity test: make sure this doesn't get called recursively. if self._in_set_activity: raise Exception( "Session.set_activity() cannot be called recursively.") if activity.session is not _ba.getsession(): raise Exception("Provided Activity's Session is not current.") # Quietly ignore this if the whole session is going down. if self._ending: return if activity is self._activity_retained: _error.print_error("activity set to already-current activity") return if self._next_activity is not None: raise Exception("Activity switch already in progress (to " + str(self._next_activity) + ")") self._in_set_activity = True prev_activity = self._activity_retained if prev_activity is not None: with _ba.Context(prev_activity): gprev = sharedobj('globals') else: gprev = None with _ba.Context(activity): # Now that it's going to be front and center, # set some global values based on what the activity wants. glb = sharedobj('globals') glb.use_fixed_vr_overlay = activity.use_fixed_vr_overlay glb.allow_kick_idle_players = activity.allow_kick_idle_players if activity.inherits_slow_motion and gprev is not None: glb.slow_motion = gprev.slow_motion else: glb.slow_motion = activity.slow_motion if activity.inherits_music and gprev is not None: glb.music_continuous = True # Prevent restarting same music. glb.music = gprev.music glb.music_count += 1 if activity.inherits_camera_vr_offset and gprev is not None: glb.vr_camera_offset = gprev.vr_camera_offset if activity.inherits_vr_overlay_center and gprev is not None: glb.vr_overlay_center = gprev.vr_overlay_center glb.vr_overlay_center_enabled = gprev.vr_overlay_center_enabled # If they want to inherit tint from the previous activity. if activity.inherits_tint and gprev is not None: glb.tint = gprev.tint glb.vignette_outer = gprev.vignette_outer glb.vignette_inner = gprev.vignette_inner # Let the activity do its thing. activity.start_transition_in() self._next_activity = activity # If we have a current activity, tell it it's transitioning out; # the next one will become current once this one dies. if prev_activity is not None: # pylint: disable=protected-access prev_activity._transitioning_out = True # pylint: enable=protected-access # Activity will be None until the next one begins. with _ba.Context(prev_activity): prev_activity.on_transition_out() # Setting this to None should free up the old activity to die, # which will call begin_next_activity. # We can still access our old activity through # self._activity_weak() to keep it up to date on player # joins/departures/etc until it dies. self._activity_retained = None # There's no existing activity; lets just go ahead with the begin call. else: self.begin_next_activity() # Tell the C layer that this new activity is now 'foregrounded'. # This means that its globals node controls global stuff and stuff # like console operations, keyboard shortcuts, etc will run in it. # pylint: disable=protected-access # noinspection PyProtectedMember activity._activity_data.make_foreground() # pylint: enable=protected-access # We want to call _destroy() for the previous activity once it should # tear itself down, clear out any self-refs, etc. If the new activity # has a transition-time, set it up to be called after that passes; # otherwise call it immediately. After this call the activity should # have no refs left to it and should die (which will trigger the next # activity to run). if prev_activity is not None: if activity.transition_time > 0.0: # FIXME: We should tweak the activity to not allow # node-creation/etc when we call _destroy (or after). with _ba.Context('ui'): # pylint: disable=protected-access # noinspection PyProtectedMember _ba.timer(activity.transition_time, prev_activity._destroy, timetype=TimeType.REAL) # Just run immediately. else: # noinspection PyProtectedMember prev_activity._destroy() # pylint: disable=protected-access self._in_set_activity = False