Ejemplo n.º 1
0
    def begin_next_activity(self) -> None:
        """Called once the previous activity has been totally torn down.

        This means we're ready to begin the next one
        """
        if self._next_activity is None:
            # Should this ever happen?
            print_error('begin_next_activity() called with no _next_activity')
            return

        # We store both a weak and a strong ref to the new activity;
        # the strong is to keep it alive and the weak is so we can access
        # it even after we've released the strong-ref to allow it to die.
        self._activity_retained = self._next_activity
        self._activity_weak = weakref.ref(self._next_activity)
        self._next_activity = None
        self._activity_should_end_immediately = False

        # Kick out anyone loitering in the lobby.
        self.lobby.remove_all_choosers_and_kick_players()

        # Kick off the activity.
        self._activity_retained.begin(self)

        # If we want to completely end the session, we can now kick that off.
        if self._wants_to_end:
            self._launch_end_session_activity()
        else:
            # Otherwise, if the activity has already been told to end,
            # do so now.
            if self._activity_should_end_immediately:
                self._activity_retained.end(
                    self._activity_should_end_immediately_results,
                    self._activity_should_end_immediately_delay)
Ejemplo n.º 2
0
def handlemessage(self, msg: Any) -> Any:
    """Standard generic message handler."""

    if isinstance(msg, ChangeMessage):
        self._handle_repeat_message_attack()

        # If we've been removed from the lobby, ignore this stuff.
        if self._dead:
            print_error('chooser got ChangeMessage after dying')
            return

        if not self._text_node:
            print_error('got ChangeMessage after nodes died')
            return
        if msg.what == 'characterchooser':
            _ba.playsound(self._click_sound)
            # update our index in our local list of characters
            self._character_index = ((self._character_index + msg.value) %
                                     len(self._character_names))
            self._update_text()
            self._update_icon()

        if msg.what == 'team':
            sessionteams = self.lobby.sessionteams
            if len(sessionteams) > 1:
                _ba.playsound(self._swish_sound)
            self._selected_team_index = (
                (self._selected_team_index + msg.value) % len(sessionteams))
            self._update_text()
            self.update_position()
            self._update_icon()

        elif msg.what == 'profileindex':
            if len(self._profilenames) == 1:

                # This should be pretty hard to hit now with
                # automatic local accounts.
                _ba.playsound(_ba.getsound('error'))
            else:

                # Pick the next player profile and assign our name
                # and character based on that.
                _ba.playsound(self._deek_sound)
                self._profileindex = ((self._profileindex + msg.value) %
                                      len(self._profilenames))
                self.update_from_profile()

        elif msg.what == 'character':
            _ba.playsound(self._click_sound)
            self.characterchooser = True
            # update our index in our local list of characters
            self._character_index = ((self._character_index + msg.value) %
                                     len(self._character_names))
            self._update_text()
            self._update_icon()

        elif msg.what == 'ready':
            self._handle_ready_msg(msg.value)
Ejemplo n.º 3
0
 def _sanity_check_begin_call(self) -> None:
     # Make sure ba.Activity.on_transition_in() got called at some point.
     if not self._called_activity_on_transition_in:
         print_error(
             'ba.Activity.on_transition_in() never got called for ' +
             str(self) + '; did you forget to call it'
             ' in your on_transition_in override?')
     # Make sure that ba.Activity.on_begin() got called at some point.
     if not self._called_activity_on_begin:
         print_error(
             'ba.Activity.on_begin() never got called for ' + str(self) +
             '; did you forget to call it in your on_begin override?')
Ejemplo n.º 4
0
    def setactivity(self, activity: Optional[ba.Activity]) -> None:
        """Set the current activity for this instance."""

        self._activity = None if activity is None else weakref.ref(activity)

        # Load our media into this activity's context.
        if activity is not None:
            if activity.expired:
                print_error('unexpected finalized activity')
            else:
                with _ba.Context(activity):
                    self._load_activity_media()
    def create_default_game_settings_ui(
            self, gameclass: type[ba.GameActivity],
            sessiontype: type[ba.Session], settings: Optional[dict],
            completion_call: Callable[[Optional[dict]], None]) -> None:
        """Launch a UI to configure the given game config.

        It should manipulate the contents of config and call completion_call
        when done.
        """
        del gameclass, sessiontype, settings, completion_call  # Unused.
        from ba import _error
        _error.print_error(
            "create_default_game_settings_ui needs to be overridden")
Ejemplo n.º 6
0
    def create_default_game_config_ui(
            self, gameclass: Type[ba.GameActivity],
            sessionclass: Type[ba.Session], config: Optional[Dict[str, Any]],
            completion_call: Callable[[Optional[Dict[str, Any]]],
                                      None]) -> None:
        """Launch a UI to configure the given game config.

        It should manipulate the contents of config and call completion_call
        when done.
        """
        del gameclass, sessionclass, config, completion_call  # unused
        from ba import _error
        _error.print_error(
            "create_default_game_config_ui needs to be overridden")
Ejemplo n.º 7
0
    def add_actor_weak_ref(self, actor: ba.Actor) -> None:
        """Add a weak-reference to a ba.Actor to the ba.Activity.

        (called by the ba.Actor base class)
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to add_actor_weak_ref')
        if (self.has_transitioned_in()
                and _ba.time() - self._last_prune_dead_actors_time > 10.0):
            print_error('it looks like nodes/actors are '
                        'not being pruned in your activity;'
                        ' did you call Activity.on_transition_in()'
                        ' from your subclass?; ' + str(self) + ' (loc. b)')
        self._actor_weak_refs.append(weakref.ref(actor))
Ejemplo n.º 8
0
    def retain_actor(self, actor: ba.Actor) -> None:
        """Add a strong-reference to a ba.Actor to this Activity.

        The reference will be lazily released once ba.Actor.exists()
        returns False for the Actor. The ba.Actor.autoretain() method
        is a convenient way to access this same functionality.
        """
        from ba import _actor as bsactor
        if not isinstance(actor, bsactor.Actor):
            raise TypeError('non-actor passed to retain_actor')
        if (self.has_transitioned_in()
                and _ba.time() - self._last_prune_dead_actors_time > 10.0):
            print_error('it looks like nodes/actors are not'
                        ' being pruned in your activity;'
                        ' did you call Activity.on_transition_in()'
                        ' from your subclass?; ' + str(self) + ' (loc. a)')
        self._actor_refs.append(actor)
    def get_instance_scoreboard_display_string(self) -> ba.Lstr:
        """Return a name for this particular game instance.

        This name is used above the game scoreboard in the corner
        of the screen, so it should be as concise as possible.
        """
        # If we're in a co-op session, use the level name.
        # FIXME: Should clean this up.
        try:
            from ba._coopsession import CoopSession
            if isinstance(self.session, CoopSession):
                campaign = self.session.campaign
                assert campaign is not None
                return campaign.getlevel(
                    self.session.campaign_level_name).displayname
        except Exception:
            print_error('error getting campaign level name')
        return self.get_instance_display_string()
Ejemplo n.º 10
0
 def _launch_end_session_activity(self) -> None:
     """(internal)"""
     from ba._activitytypes import EndSessionActivity
     from ba._enums import TimeType
     with _ba.Context(self):
         curtime = _ba.time(TimeType.REAL)
         if self._ending:
             # Ignore repeats unless its been a while.
             assert self._launch_end_session_activity_time is not None
             since_last = (curtime - self._launch_end_session_activity_time)
             if since_last < 30.0:
                 return
             print_error(
                 '_launch_end_session_activity called twice (since_last=' +
                 str(since_last) + ')')
         self._launch_end_session_activity_time = curtime
         self.setactivity(_ba.newactivity(EndSessionActivity))
         self._wants_to_end = False
         self._ending = True  # Prevent further actions.
Ejemplo n.º 11
0
def json_prep(data: Any) -> Any:
    """Return a json-friendly version of the provided data.

    This converts any tuples to lists and any bytes to strings
    (interpreted as utf-8, ignoring errors). Logs errors (just once)
    if any data is modified/discarded/unsupported.
    """

    if isinstance(data, dict):
        return dict((json_prep(key), json_prep(value))
                    for key, value in list(data.items()))
    if isinstance(data, list):
        return [json_prep(element) for element in data]
    if isinstance(data, tuple):
        from ba import _error
        _error.print_error('json_prep encountered tuple', once=True)
        return [json_prep(element) for element in data]
    if isinstance(data, bytes):
        try:
            return data.decode(errors='ignore')
        except Exception:
            from ba import _error
            _error.print_error('json_prep encountered utf-8 decode error',
                               once=True)
            return data.decode(errors='ignore')
    if not isinstance(data, (str, float, bool, type(None), int)):
        from ba import _error
        _error.print_error('got unsupported type in json_prep:' +
                           str(type(data)),
                           once=True)
    return data
Ejemplo n.º 12
0
    def remove_chooser(self, player: ba.SessionPlayer) -> None:
        """Remove a single player's chooser; does not kick them.

        This is used when a player enters the game and no longer
        needs a chooser."""
        found = False
        chooser = None
        for chooser in self.choosers:
            if chooser.getplayer() is player:
                found = True

                # Mark it as dead since there could be more
                # change-commands/etc coming in still for it;
                # want to avoid duplicate player-adds/etc.
                chooser.set_dead(True)
                self.choosers.remove(chooser)
                break
        if not found:
            print_error(f'remove_chooser did not find player {player}')
        elif chooser in self.choosers:
            print_error(f'chooser remains after removal for {player}')
        self.update_positions()
Ejemplo n.º 13
0
    def begin(self, session: ba.Session) -> None:
        """Begin the activity.

        (internal)
        """

        # Is this ever still happening?...
        if self._has_begun:
            print_error("_begin called twice; this shouldn't happen")
            return

        # Inherit stats from the session.
        self._stats = session.stats

        # Add session's teams in.
        for team in session.teams:
            self.add_team(team)

        # Add session's players in.
        for player in session.players:
            self.add_player(player)

        # And finally tell the game to start.
        with _ba.Context(self):
            self._has_begun = True
            self.on_begin()

        self._sanity_check_begin_call()

        # If the whole session wants to die and was waiting on us,
        # can kick off that process now.
        if session.wants_to_end:
            session.launch_end_session_activity()
        else:
            # Otherwise, if we've already been told to die, do so now.
            if self._should_end_immediately:
                self.end(self._should_end_immediately_results,
                         self._should_end_immediately_delay)
Ejemplo n.º 14
0
    def _handlemessage_sanity_check(self) -> None:
        """Make sure things are kosher in handlemessage().

        Place this in an 'if __debug__:' clause at the top of handlemessage()
        overrides. This will will complain if anything is sending the Actor
        messages after the activity has ended, which should be explicitly
        avoided.
        """
        if not __debug__:
            print_error('This should only be called in __debug__ mode.',
                        once=True)
        if not getattr(self, '_root_actor_init_called', False):
            print_error('Root Actor __init__() not called.')
        if self.expired:
            print_error(f'handlemessage() called on expired actor: {self}')
Ejemplo n.º 15
0
    def _add_chosen_player(self, chooser: ba.Chooser) -> ba.Player:
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-branches
        from ba import _error
        from ba._lang import Lstr
        from ba._team import Team
        from ba import _freeforallsession
        player = chooser.getplayer()
        if player not in self.players:
            _error.print_error('player not found in session '
                               'player-list after chooser selection')

        activity = self._activity_weak()
        assert activity is not None

        # We need to reset the player's input here, as it is currently
        # referencing the chooser which could inadvertently keep it alive.
        player.reset_input()

        # Pass it to the current activity if it has already begun
        # (otherwise it'll get passed once begin is called).
        pass_to_activity = (activity.has_begun()
                            and not activity.is_joining_activity)

        # If we're not allowing mid-game joins, don't pass; just announce
        # the arrival.
        if pass_to_activity:
            if not self._allow_mid_activity_joins:
                pass_to_activity = False
                with _ba.Context(self):
                    _ba.screenmessage(Lstr(resource='playerDelayedJoinText',
                                           subs=[('${PLAYER}',
                                                  player.get_name(full=True))
                                                 ]),
                                      color=(0, 1, 0))

        # If we're a non-team game, each player gets their own team
        # (keeps mini-game coding simpler if we can always deal with teams).
        if self._use_teams:
            team = chooser.get_team()
        else:
            our_team_id = self._next_team_id
            team = Team(team_id=our_team_id,
                        name=chooser.getplayer().get_name(full=True,
                                                          icon=False),
                        color=chooser.get_color())
            self.teams.append(team)
            self._next_team_id += 1
            try:
                with _ba.Context(self):
                    self.on_team_join(team)
            except Exception:
                _error.print_exception(f'exception in on_team_join for {self}')

            if pass_to_activity:
                if team in activity.teams:
                    _error.print_error(
                        'Duplicate team ID in ba.Session._add_chosen_player')
                activity.teams.append(team)
                try:
                    with _ba.Context(activity):
                        activity.on_team_join(team)
                except Exception:
                    _error.print_exception(
                        f'ERROR: exception in on_team_join for {activity}')

        player.set_data(team=team,
                        character=chooser.get_character_name(),
                        color=chooser.get_color(),
                        highlight=chooser.get_highlight())

        self.stats.register_player(player)
        if pass_to_activity:
            if isinstance(self, _freeforallsession.FreeForAllSession):
                if player.team.players:
                    _error.print_error('expected 0 players in FFA team')

            # Don't actually add the player to their team list if we're not
            # in an activity. (players get (re)added to their team lists
            # when the activity begins).
            player.team.players.append(player)
            if player in activity.players:
                _error.print_exception(
                    f'Dup player in ba.Session._add_chosen_player: {player}')
            else:
                activity.players.append(player)
                player.set_activity(activity)
                pnode = activity.create_player_node(player)
                player.set_node(pnode)
                try:
                    with _ba.Context(activity):
                        activity.on_player_join(player)
                except Exception:
                    _error.print_exception(
                        f'Error on on_player_join for {activity}')
        return player
Ejemplo n.º 16
0
    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
Ejemplo n.º 17
0
    def on_player_leave(self, player: ba.Player) -> None:
        """Called when a previously-accepted ba.Player leaves the session."""
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-branches
        # pylint: disable=cyclic-import
        from ba._freeforallsession import FreeForAllSession
        from ba._lang import Lstr
        from ba import _error

        # Remove them from the game rosters.
        if player in self.players:

            _ba.playsound(_ba.getsound('playerLeft'))

            team: Optional[ba.Team]

            # The player will have no team if they are still in the lobby.
            try:
                team = player.team
            except _error.TeamNotFoundError:
                team = None

            activity = self._activity_weak()

            # If he had no team, he's in the lobby.
            # If we have a current activity with a lobby, ask them to
            # remove him.
            if team is None:
                with _ba.Context(self):
                    try:
                        self.lobby.remove_chooser(player)
                    except Exception:
                        _error.print_exception(
                            'Error in Lobby.remove_chooser()')

            # *If* they were actually in the game, announce their departure.
            if team is not None:
                _ba.screenmessage(
                    Lstr(resource='playerLeftText',
                         subs=[('${PLAYER}', player.get_name(full=True))]))

            # Remove him from his team and session lists.
            # (he may not be on the team list since player are re-added to
            # team lists every activity)
            if team is not None and player in team.players:

                # Testing; can remove this eventually.
                if isinstance(self, FreeForAllSession):
                    if len(team.players) != 1:
                        _error.print_error('expected 1 player in FFA team')
                team.players.remove(player)

            # Remove player from any current activity.
            if activity is not None and player in activity.players:
                activity.players.remove(player)

                # Run the activity callback unless its been expired.
                if not activity.is_expired():
                    try:
                        with _ba.Context(activity):
                            activity.on_player_leave(player)
                    except Exception:
                        _error.print_exception(
                            'exception in on_player_leave for activity',
                            activity)
                else:
                    _error.print_error('expired activity in on_player_leave;'
                                       " shouldn't happen")

                player.set_activity(None)
                player.set_node(None)

                # Reset the player; this will remove its actor-ref and clear
                # its calls/etc
                try:
                    with _ba.Context(activity):
                        player.reset()
                except Exception:
                    _error.print_exception(
                        'exception in player.reset in'
                        ' on_player_leave for player', player)

            # If we're a non-team session, remove the player's team completely.
            if not self._use_teams and team is not None:

                # If the team's in an activity, call its on_team_leave
                # callback.
                if activity is not None and team in activity.teams:
                    activity.teams.remove(team)

                    if not activity.is_expired():
                        try:
                            with _ba.Context(activity):
                                activity.on_team_leave(team)
                        except Exception:
                            _error.print_exception(
                                'exception in on_team_leave for activity',
                                activity)
                    else:
                        _error.print_error(
                            'expired activity in on_player_leave p2'
                            "; shouldn't happen")

                    # Clear the team's game-data (so dying stuff will
                    # have proper context).
                    try:
                        with _ba.Context(activity):
                            team.reset_gamedata()
                    except Exception:
                        _error.print_exception(
                            'exception clearing gamedata for team:', team,
                            'for player:', player, 'in activity:', activity)

                # Remove the team from the session.
                self.teams.remove(team)
                try:
                    with _ba.Context(self):
                        self.on_team_leave(team)
                except Exception:
                    _error.print_exception(
                        'exception in on_team_leave for session', self)

                # Clear the team's session-data (so dying stuff will
                # have proper context).
                try:
                    with _ba.Context(self):
                        team.reset_sessiondata()
                except Exception:
                    _error.print_exception(
                        'exception clearing sessiondata for team:', team,
                        'in session:', self)

            # Now remove them from the session list.
            self.players.remove(player)

        else:
            print('ERROR: Session.on_player_leave called'
                  ' for player not in our list.')
Ejemplo n.º 18
0
    def __init__(self, *args: Any, **keywds: Any) -> None:
        """Instantiate a Lstr.

        Pass a value for either 'resource', 'translate',
        or 'value'. (see Lstr help for examples).
        'subs' can be a sequence of 2-member sequences consisting of values
        and replacements.
        'fallback_resource' can be a resource key that will be used if the
        main one is not present for
        the current language in place of falling back to the english value
        ('resource' mode only).
        'fallback_value' can be a literal string that will be used if neither
        the resource nor the fallback resource is found ('resource' mode only).
        """
        # pylint: disable=too-many-branches
        if args:
            raise TypeError('Lstr accepts only keyword arguments')

        # Basically just store the exact args they passed.
        # However if they passed any Lstr values for subs,
        # replace them with that Lstr's dict.
        self.args = keywds
        our_type = type(self)

        if isinstance(self.args.get('value'), our_type):
            raise TypeError("'value' must be a regular string; not an Lstr")

        if 'subs' in self.args:
            subs_new = []
            for key, value in keywds['subs']:
                if isinstance(value, our_type):
                    subs_new.append((key, value.args))
                else:
                    subs_new.append((key, value))
            self.args['subs'] = subs_new

        # As of protocol 31 we support compact key names
        # ('t' instead of 'translate', etc). Convert as needed.
        if 'translate' in keywds:
            keywds['t'] = keywds['translate']
            del keywds['translate']
        if 'resource' in keywds:
            keywds['r'] = keywds['resource']
            del keywds['resource']
        if 'value' in keywds:
            keywds['v'] = keywds['value']
            del keywds['value']
        if 'fallback' in keywds:
            from ba import _error
            _error.print_error(
                'deprecated "fallback" arg passed to Lstr(); use '
                'either "fallback_resource" or "fallback_value"',
                once=True)
            keywds['f'] = keywds['fallback']
            del keywds['fallback']
        if 'fallback_resource' in keywds:
            keywds['f'] = keywds['fallback_resource']
            del keywds['fallback_resource']
        if 'subs' in keywds:
            keywds['s'] = keywds['subs']
            del keywds['subs']
        if 'fallback_value' in keywds:
            keywds['fv'] = keywds['fallback_value']
            del keywds['fallback_value']
Ejemplo n.º 19
0
 def _switch_to_score_screen(self, results: Any) -> None:
     """Switch to a score screen after leaving a round."""
     from ba import _error
     del results  # Unused arg.
     _error.print_error('this should be overridden')
Ejemplo n.º 20
0
    def begin(self, session: ba.Session) -> None:
        """Begin the activity. (should only be called by Session).

        (internal)"""

        # pylint: disable=too-many-branches
        from ba import _error

        if self._has_begun:
            _error.print_error("_begin called twice; this shouldn't happen")
            return

        self._stats = session.stats

        # Operate on the subset of session players who have passed team/char
        # selection.
        players = []
        chooser_players = []
        for player in session.players:
            assert player  # should we ever have invalid players?..
            if player:
                try:
                    team: Optional[ba.Team] = player.team
                except _error.TeamNotFoundError:
                    team = None

                if team is not None:
                    player.reset_input()
                    players.append(player)
                else:
                    # Simply ignore players sitting in the lobby.
                    # (though this technically shouldn't happen anymore since
                    # choosers now get cleared when starting new activities.)
                    print('unexpected: got no-team player in _begin')
                    chooser_players.append(player)
            else:
                _error.print_error(
                    "got nonexistent player in Activity._begin()")

        # Add teams in one by one and send team-joined messages for each.
        for team in session.teams:
            if team in self.teams:
                raise Exception("Duplicate Team Entry")
            self.teams.append(team)
            try:
                with _ba.Context(self):
                    self.on_team_join(team)
            except Exception:
                _error.print_exception('Error in on_team_join for', self)

        # Now add each player to the activity and to its team's list,
        # and send player-joined messages for each.
        for player in players:
            self.players.append(player)
            player.team.players.append(player)
            player.set_activity(self)
            pnode = self.create_player_node(player)
            player.set_node(pnode)
            try:
                with _ba.Context(self):
                    self.on_player_join(player)
            except Exception:
                _error.print_exception('Error in on_player_join for', self)

        with _ba.Context(self):
            # And finally tell the game to start.
            self._has_begun = True
            self.on_begin()

        # Make sure that ba.Activity.on_transition_in() got called
        # at some point.
        if not self._called_activity_on_transition_in:
            _error.print_error(
                "ba.Activity.on_transition_in() never got called for " +
                str(self) + "; did you forget to call it"
                " in your on_transition_in override?")

        # Make sure that ba.Activity.on_begin() got called at some point.
        if not self._called_activity_on_begin:
            _error.print_error(
                "ba.Activity.on_begin() never got called for " + str(self) +
                "; did you forget to call it in your on_begin override?")

        # If the whole session wants to die and was waiting on us, can get
        # that going now.
        if session.wants_to_end:
            session.launch_end_session_activity()
        else:
            # Otherwise, if we've already been told to die, do so now.
            if self._should_end_immediately:
                self.end(self._should_end_immediately_results,
                         self._should_end_immediately_delay)
Ejemplo n.º 21
0
    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