コード例 #1
0
ファイル: _session.py プロジェクト: bseditor/ballistica
 def _complete_end_activity(self, activity: ba.Activity,
                            results: Any) -> None:
     # Run the subclass callback in the session context.
     try:
         with _ba.Context(self):
             self.on_activity_end(activity, results)
     except Exception:
         from ba import _error
         _error.print_exception(
             'exception in on_activity_end() for session', self, 'activity',
             activity, 'with results', results)
コード例 #2
0
    def transition_in(self, prev_globals: Optional[ba.Node]) -> None:
        """Called by Session to kick off transition-in.

        (internal)
        """
        from ba._general import WeakCall
        from ba._gameutils import sharedobj
        assert not self._has_transitioned_in
        self._has_transitioned_in = True

        # Set up the globals node based on our settings.
        with _ba.Context(self):
            # 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 = 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_camera_vr_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 transient actors periodically.
            self._prune_dead_actors_timer = _ba.Timer(
                5.17, WeakCall(self._prune_dead_actors), repeat=True)
            self._prune_dead_actors()

            # Also start our low-level scene running.
            self._activity_data.start()

            try:
                self.on_transition_in()
            except Exception:
                print_exception('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()
コード例 #3
0
    def on_player_leave(self, sessionplayer: ba.SessionPlayer) -> None:
        """Called when a previously-accepted ba.SessionPlayer leaves."""

        if sessionplayer not in self.sessionplayers:
            print('ERROR: Session.on_player_leave called'
                  ' for player not in our list.')
            return

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

        activity = self._activity_weak()

        if not sessionplayer.in_game:

            # Ok, the player is still in the lobby; simply remove them.
            with _ba.Context(self):
                try:
                    self.lobby.remove_chooser(sessionplayer)
                except Exception:
                    print_exception('Error in Lobby.remove_chooser().')
        else:
            # Ok, they've already entered the game. Remove them from
            # teams/activities/etc.
            sessionteam = sessionplayer.sessionteam
            assert sessionteam is not None

            _ba.screenmessage(
                Lstr(resource='playerLeftText',
                     subs=[('${PLAYER}', sessionplayer.getname(full=True))]))

            # Remove them from their SessionTeam.
            if sessionplayer in sessionteam.players:
                sessionteam.players.remove(sessionplayer)
            else:
                print('SessionPlayer not found in SessionTeam'
                      ' in on_player_leave.')

            # Grab their activity-specific player instance.
            player = sessionplayer.activityplayer
            assert isinstance(player, (Player, type(None)))

            # Remove them from any current Activity.
            if activity is not None:
                if player in activity.players:
                    activity.remove_player(sessionplayer)
                else:
                    print('Player not found in Activity in on_player_leave.')

            # If we're a non-team session, remove their team too.
            if not self.use_teams:
                self._remove_player_team(sessionteam, activity)

        # Now remove them from the session list.
        self.sessionplayers.remove(sessionplayer)
コード例 #4
0
ファイル: _dependency.py プロジェクト: Dmitry450/ballistica
    def __init__(self) -> None:
        super().__init__()

        # This is used internally by the get_package_xxx calls.
        self.context = _ba.Context('current')

        entry = self._dep_entry()
        assert entry is not None
        assert isinstance(entry.config, str)
        self.package_id = entry.config
        print(f'LOADING ASSET PACKAGE {self.package_id}')
コード例 #5
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()
コード例 #6
0
    def add_team(self, sessionteam: ba.SessionTeam) -> None:
        """(internal)"""
        assert not self.expired

        with _ba.Context(self):
            sessionteam.gameteam = team = self.create_team(sessionteam)
            team.postinit(sessionteam)
            self.teams.append(team)
            try:
                self.on_team_join(team)
            except Exception:
                print_exception(f'Error in on_team_join for {self}')
コード例 #7
0
 def show_post_purchase_message(self) -> None:
     """(internal)"""
     from ba._language import Lstr
     from ba._generated.enums import TimeType
     cur_time = _ba.time(TimeType.REAL)
     if (self.last_post_purchase_message_time is None
             or cur_time - self.last_post_purchase_message_time > 3.0):
         self.last_post_purchase_message_time = cur_time
         with _ba.Context('ui'):
             _ba.screenmessage(Lstr(resource='updatingAccountText',
                                    fallback_resource='purchasingText'),
                               color=(0, 1, 0))
             _ba.playsound(_ba.getsound('click01'))
コード例 #8
0
    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)
コード例 #9
0
ファイル: _activity.py プロジェクト: imayushsaini/ballistica
    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))
コード例 #10
0
ファイル: _session.py プロジェクト: ShifengHuGit/ballistica
    def handlemessage(self, msg: Any) -> Any:
        """General message handling; can be passed any message object."""
        from ba._lobby import PlayerReadyMessage
        from ba._messages import PlayerProfilesChangedMessage, UNHANDLED
        if isinstance(msg, PlayerReadyMessage):
            self._on_player_ready(msg.chooser)
            return None

        if isinstance(msg, PlayerProfilesChangedMessage):
            # If we have a current activity with a lobby, ask it to reload
            # profiles.
            with _ba.Context(self):
                self.lobby.reload_profiles()
            return None

        return UNHANDLED
コード例 #11
0
    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)
コード例 #12
0
def display_achievement_banner(achname: str) -> None:
    """Display a completion banner for an achievement.

    Used for server-driven achievements.
    """
    try:
        # FIXME: Need to get these using the UI context or some other
        #  purely local context somehow instead of trying to inject these
        #  into whatever activity happens to be active
        #  (since that won't work while in client mode).
        activity = _ba.get_foreground_host_activity()
        if activity is not None:
            with _ba.Context(activity):
                get_achievement(achname).announce_completion()
    except Exception:
        from ba import _error
        _error.print_exception('error showing server ach')
コード例 #13
0
    def continue_or_end_game(self) -> None:
        """If continues are allowed, prompts the player to purchase a continue
        and calls either end_game or continue_game depending on the result"""
        # pylint: disable=too-many-nested-blocks
        # pylint: disable=cyclic-import
        from bastd.ui.continues import ContinuesWindow
        from ba._coopsession import CoopSession
        from ba._enums import TimeType

        try:
            if _ba.get_account_misc_read_val('enableContinues', False):
                session = self.session

                # We only support continuing in non-tournament games.
                tournament_id = session.tournament_id
                if tournament_id is None:

                    # We currently only support continuing in sequential
                    # co-op campaigns.
                    if isinstance(session, CoopSession):
                        assert session.campaign is not None
                        if session.campaign.sequential:
                            gnode = self.globalsnode

                            # Only attempt this if we're not currently paused
                            # and there appears to be no UI.
                            if (not gnode.paused
                                    and not _ba.app.ui.has_main_menu_window()):
                                self._is_waiting_for_continue = True
                                with _ba.Context('ui'):
                                    _ba.timer(
                                        0.5,
                                        lambda: ContinuesWindow(
                                            self,
                                            self._continue_cost,
                                            continue_call=WeakCall(
                                                self._continue_choice, True),
                                            cancel_call=WeakCall(
                                                self._continue_choice, False)),
                                        timetype=TimeType.REAL)
                                return

        except Exception:
            print_exception('Error handling continues.')

        self.end_game()
コード例 #14
0
    def __init__(self, request: str, request_type: str,
                 data: Optional[Dict[str, Any]],
                 callback: Optional[ServerCallbackType],
                 response_type: ServerResponseType):
        super().__init__()
        self._request = request
        self._request_type = request_type
        if not isinstance(response_type, ServerResponseType):
            raise Exception(f'Invalid response type: {response_type}')
        self._response_type = response_type
        self._data = {} if data is None else copy.deepcopy(data)
        self._callback: Optional[ServerCallbackType] = callback
        self._context = _ba.Context('current')

        # Save and restore the context we were created from.
        activity: Optional[ba.Activity] = _ba.getactivity(doraise=False)
        self._activity = weakref.ref(
            activity) if activity is not None else None
コード例 #15
0
 def _execute_shutdown(self) -> None:
     from ba._lang import Lstr
     if self._executing_shutdown:
         return
     self._executing_shutdown = True
     timestrval = time.strftime('%c')
     if self._shutdown_reason is ShutdownReason.RESTARTING:
         _ba.screenmessage(Lstr(resource='internal.serverRestartingText'),
                           color=(1, 0.5, 0.0))
         print(f'{Clr.SBLU}Exiting for server-restart'
               f' at {timestrval}{Clr.RST}')
     else:
         _ba.screenmessage(Lstr(resource='internal.serverShuttingDownText'),
                           color=(1, 0.5, 0.0))
         print(f'{Clr.SBLU}Exiting for server-shutdown'
               f' at {timestrval}{Clr.RST}')
     with _ba.Context('ui'):
         _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
コード例 #16
0
 def _continue_choice(self, do_continue: bool) -> None:
     self._is_waiting_for_continue = False
     if self.has_ended():
         return
     with _ba.Context(self):
         if do_continue:
             _ba.playsound(_ba.getsound('shieldUp'))
             _ba.playsound(_ba.getsound('cashRegister'))
             _ba.add_transaction({
                 'type': 'CONTINUE',
                 'cost': self._continue_cost
             })
             _ba.run_transactions()
             self._continue_cost = (
                 self._continue_cost * self._continue_cost_mult +
                 self._continue_cost_offset)
             self.on_continue()
         else:
             self.end_game()
コード例 #17
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.
コード例 #18
0
def verify_object_death(obj: object) -> None:
    """Warn if an object does not get freed within a short period.

    Category: General Utility Functions

    This can be handy to detect and prevent memory/resource leaks.
    """
    try:
        ref = weakref.ref(obj)
    except Exception:
        print_exception('Unable to create weak-ref in verify_object_death')

    # Use a slight range for our checks so they don't all land at once
    # if we queue a lot of them.
    delay = random.uniform(2.0, 5.5)
    with _ba.Context('ui'):
        _ba.timer(delay,
                  lambda: _verify_object_death(ref),
                  timetype=TimeType.REAL)
コード例 #19
0
    def resume(self) -> None:
        """Resume the game due to a user request or menu closing.

        If there's a foreground host-activity that's currently paused, tell it
        to resume.
        """

        # FIXME: Shouldn't be touching scene stuff here;
        #  should just pass the request on to the host-session.
        activity = _ba.get_foreground_host_activity()
        if activity is not None:
            with _ba.Context(activity):
                globs = activity.globalsnode
                if globs.paused:
                    _ba.playsound(_ba.getsound('refWhistle'))
                    globs.paused = False

                    # FIXME: This should not be an actor attr.
                    activity.paused_text = None
コード例 #20
0
    def restart(self) -> None:
        """Restart the current game activity."""

        # Tell the current activity to end with a 'restart' outcome.
        # We use 'force' so that we apply even if end has already been called
        # (but is in its delay period).

        # Make an exception if there's no players left. Otherwise this
        # can override the default session end that occurs in that case.
        if not self.players:
            return

        # This method may get called from the UI context so make sure we
        # explicitly run in the activity's context.
        activity = self.getactivity()
        if activity is not None and not activity.is_expired():
            activity.can_show_ad_on_death = True
            with _ba.Context(activity):
                activity.end(results={'outcome': 'restart'}, force=True)
コード例 #21
0
ファイル: _activity.py プロジェクト: imayushsaini/ballistica
    def remove_player(self, sessionplayer: ba.SessionPlayer) -> None:
        """Remove a player from the Activity while it is running.

        (internal)
        """
        assert not self.expired

        player: Any = sessionplayer.activityplayer
        assert isinstance(player, self._playertype)
        team: Any = sessionplayer.sessionteam.activityteam
        assert isinstance(team, self._teamtype)

        assert player in team.players
        team.players.remove(player)
        assert player not in team.players

        assert player in self.players
        self.players.remove(player)
        assert player not in self.players

        # This should allow our ba.Player instance to die.
        # Complain if that doesn't happen.
        # verify_object_death(player)

        with _ba.Context(self):
            try:
                self.on_player_leave(player)
            except Exception:
                print_exception(f'Error in on_player_leave for {self}.')
            try:
                player.leave()
            except Exception:
                print_exception(f'Error on leave for {player} in {self}.')

            self._reset_session_player_for_no_activity(sessionplayer)

        # Add the player to a list to keep it around for a while. This is
        # to discourage logic from firing on player object death, which
        # may not happen until activity end if something is holding refs
        # to it.
        self._delay_delete_players.append(player)
        self._players_that_left.append(weakref.ref(player))
コード例 #22
0
ファイル: _app.py プロジェクト: zwl1671/ballistica
    def do_remove_in_game_ads_message(self) -> None:
        """(internal)"""
        from ba._lang import Lstr
        from ba._enums import TimeType

        # Print this message once every 10 minutes at most.
        tval = _ba.time(TimeType.REAL)
        if (self.last_in_game_ad_remove_message_show_time is None or
            (tval - self.last_in_game_ad_remove_message_show_time > 60 * 10)):
            self.last_in_game_ad_remove_message_show_time = tval
            with _ba.Context('ui'):
                _ba.timer(
                    1.0,
                    lambda: _ba.screenmessage(Lstr(
                        resource='removeInGameAdsText',
                        subs=[('${PRO}',
                               Lstr(resource='store.bombSquadProNameText')),
                              ('${APP_NAME}', Lstr(resource='titleText'))]),
                                              color=(1, 1, 0)),
                    timetype=TimeType.REAL)
コード例 #23
0
 def _execute_shutdown(self) -> None:
     from ba._lang import Lstr
     if self._executing_shutdown:
         return
     self._executing_shutdown = True
     timestrval = time.strftime('%c')
     if self._shutdown_reason is ShutdownReason.RESTARTING:
         # FIXME: Should add a server-screen-message call.
         # (so we could send this an an Lstr)
         _ba.chat_message(
             Lstr(resource='internal.serverRestartingText').evaluate())
         print(f'{Clr.SBLU}Exiting for server-restart'
               f' at {timestrval}{Clr.RST}')
     else:
         # FIXME: Should add a server-screen-message call.
         # (so we could send this an an Lstr)
         print(f'{Clr.SBLU}Exiting for server-shutdown'
               f' at {timestrval}{Clr.RST}')
         _ba.chat_message(
             Lstr(resource='internal.serverShuttingDownText').evaluate())
     with _ba.Context('ui'):
         _ba.timer(2.0, _ba.quit, timetype=TimeType.REAL)
コード例 #24
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)
コード例 #25
0
    def _handle_empty_activity(self) -> None:
        """Handle cases where all players have left the current activity."""

        from ba._gameactivity import GameActivity
        activity = self.getactivity()
        if activity is None:
            return  # Hmm what should we do in this case?

        # If there are still players in the current activity, we're good.
        if activity.players:
            return

        # If there are *not* players in the current activity but there
        # *are* in the session:
        if not activity.players and self.sessionplayers:

            # If we're in a game, we should restart to pull in players
            # currently waiting in the session.
            if isinstance(activity, GameActivity):

                # Never restart tourney games however; just end the session
                # if all players are gone.
                if self.tournament_id is not None:
                    self.end()
                else:
                    self.restart()

        # Hmm; no players anywhere. Let's end the entire session if we're
        # running a GUI (or just the current game if we're running headless).
        else:
            if not _ba.app.headless_mode:
                self.end()
            else:
                if isinstance(activity, GameActivity):
                    with _ba.Context(activity):
                        activity.end_game()
コード例 #26
0
    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)
コード例 #27
0
ファイル: _session.py プロジェクト: bseditor/ballistica
    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
コード例 #28
0
ファイル: _session.py プロジェクト: bseditor/ballistica
    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
コード例 #29
0
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
コード例 #30
0
ファイル: _session.py プロジェクト: bseditor/ballistica
    def __init__(self,
                 depsets: Sequence[ba.DependencySet],
                 team_names: Sequence[str] = None,
                 team_colors: Sequence[Sequence[float]] = None,
                 use_team_colors: bool = True,
                 min_players: int = 1,
                 max_players: int = 8,
                 allow_mid_activity_joins: bool = True):
        """Instantiate a session.

        depsets should be a sequence of successfully resolved ba.DependencySet
        instances; one for each ba.Activity the session may potentially run.
        """
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-locals
        # pylint: disable=cyclic-import
        from ba._lobby import Lobby
        from ba._stats import Stats
        from ba._gameutils import sharedobj
        from ba._gameactivity import GameActivity
        from ba._team import Team
        from ba._error import DependencyError
        from ba._dependency import Dependency, AssetPackage

        # First off, resolve all dependency-sets we were passed.
        # If things are missing, we'll try to gather them into a single
        # missing-deps exception if possible to give the caller a clean
        # path to download missing stuff and try again.
        missing_asset_packages: Set[str] = set()
        for depset in depsets:
            try:
                depset.resolve()
            except DependencyError as exc:
                # Gather/report missing assets only; barf on anything else.
                if all(issubclass(d.cls, AssetPackage) for d in exc.deps):
                    for dep in exc.deps:
                        assert isinstance(dep.config, str)
                        missing_asset_packages.add(dep.config)
                else:
                    missing_info = [(d.cls, d.config) for d in exc.deps]
                    raise RuntimeError(
                        f'Missing non-asset dependencies: {missing_info}')

        # Throw a combined exception if we found anything missing.
        if missing_asset_packages:
            raise DependencyError([
                Dependency(AssetPackage, set_id)
                for set_id in missing_asset_packages
            ])

        # Ok; looks like our dependencies check out.
        # Now give the engine a list of asset-set-ids to pass along to clients.
        required_asset_packages: Set[str] = set()
        for depset in depsets:
            required_asset_packages.update(depset.get_asset_package_ids())

        # print('Would set host-session asset-reqs to:',
        # required_asset_packages)

        # First thing, wire up our internal engine data.
        self._sessiondata = _ba.register_session(self)

        self.tournament_id: Optional[str] = None

        # FIXME: This stuff shouldn't be here.
        self.sharedobjs: Dict[str, Any] = {}

        # TeamGameActivity uses this to display a help overlay on the first
        # activity only.
        self.have_shown_controls_help_overlay = False

        self.campaign = None

        # FIXME: Should be able to kill this I think.
        self.campaign_state: Dict[str, str] = {}

        self._use_teams = (team_names is not None)
        self._use_team_colors = use_team_colors
        self._in_set_activity = False
        self._allow_mid_activity_joins = allow_mid_activity_joins

        self.teams = []
        self.players = []
        self._next_team_id = 0
        self._activity_retained: Optional[ba.Activity] = None
        self.launch_end_session_activity_time: Optional[float] = None
        self._activity_end_timer: Optional[ba.Timer] = None

        # Hacky way to create empty weak ref; must be a better way.
        class _EmptyObj:
            pass

        self._activity_weak: ReferenceType[ba.Activity]
        self._activity_weak = weakref.ref(_EmptyObj())  # type: ignore
        if self._activity_weak() is not None:
            raise Exception('Error creating empty activity weak ref.')

        self._next_activity: Optional[ba.Activity] = None
        self.wants_to_end = False
        self._ending = False
        self.min_players = min_players
        self.max_players = max_players

        # Create Teams.
        if self._use_teams:
            assert team_names is not None
            assert team_colors is not None
            for i, color in enumerate(team_colors):
                team = Team(team_id=self._next_team_id,
                            name=GameActivity.get_team_display_string(
                                team_names[i]),
                            color=color)
                self.teams.append(team)
                self._next_team_id += 1

                try:
                    with _ba.Context(self):
                        self.on_team_join(team)
                except Exception:
                    from ba import _error
                    _error.print_exception(
                        f'Error in on_team_join for {self}.')

        self.lobby = Lobby()
        self.stats = Stats()

        # Instantiate our session globals node
        # (so it can apply default settings).
        sharedobj('globals')