示例#1
0
 def fade_to_red(self) -> None:
     """Fade the screen to red; (such as when the good guys have lost)."""
     from ba import _gameutils
     c_existing = _gameutils.sharedobj('globals').tint
     cnode = _ba.newnode("combine",
                         attrs={
                             'input0': c_existing[0],
                             'input1': c_existing[1],
                             'input2': c_existing[2],
                             'size': 3
                         })
     _gameutils.animate(cnode, 'input1', {0: c_existing[1], 2.0: 0})
     _gameutils.animate(cnode, 'input2', {0: c_existing[2], 2.0: 0})
     cnode.connectattr('output', _gameutils.sharedobj('globals'), 'tint')
示例#2
0
def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None:
    """Set or stop the current music based on a string musictype.

    category: Gameplay Functions

    This function will handle loading and playing sound media as necessary,
    and also supports custom user soundtracks on specific platforms so the
    user can override particular game music with their own.

    Pass None to stop music.

    if 'continuous' is True the musictype passed is the same as what is already
    playing, the playing track will not be restarted.
    """
    from ba import _gameutils

    # All we do here now is set a few music attrs on the current globals
    # node. The foreground globals' current playing music then gets fed to
    # the do_play_music call below. This way we can seamlessly support custom
    # soundtracks in replays/etc since we're replaying an attr value set;
    # not an actual sound node create.
    gnode = _gameutils.sharedobj('globals')
    gnode.music_continuous = continuous
    gnode.music = '' if musictype is None else musictype.value
    gnode.music_count += 1
示例#3
0
    def pause(self) -> None:
        """Pause the game due to a user request or menu popping up.

        If there's a foreground host-activity that says it's pausable, tell it
        to pause ..we now no longer pause if there are connected clients.
        """
        activity: Optional[ba.Activity] = _ba.get_foreground_host_activity()
        if (activity is not None and activity.allow_pausing
                and not _ba.have_connected_clients()):
            from ba import _gameutils, _lang
            from ba._nodeactor import NodeActor
            # FIXME: Shouldn't be touching scene stuff here;
            #  should just pass the request on to the host-session.
            with _ba.Context(activity):
                globs = _gameutils.sharedobj('globals')
                if not globs.paused:
                    _ba.playsound(_ba.getsound('refWhistle'))
                    globs.paused = True

                # FIXME: This should not be an attr on Actor.
                activity.paused_text = NodeActor(
                    _ba.newnode('text',
                                attrs={
                                    'text':
                                    _lang.Lstr(resource='pausedByHostText'),
                                    'client_only': True,
                                    'flatness': 1.0,
                                    'h_align': 'center'
                                }))
示例#4
0
def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None:
    """Tell the game to play (or stop playing) a certain type of music.

    category: Gameplay Functions

    This function will handle loading and playing sound assets as necessary,
    and also supports custom user soundtracks on specific platforms so the
    user can override particular game music with their own.

    Pass None to stop music.

    if 'continuous' is True and musictype is the same as what is already
    playing, the playing track will not be restarted.
    """
    from ba import _gameutils

    # All we do here now is set a few music attrs on the current globals
    # node. The foreground globals' current playing music then gets fed to
    # the do_play_music call in our music controller. This way we can
    # seamlessly support custom soundtracks in replays/etc since we're being
    # driven purely by node data.
    gnode = _gameutils.sharedobj('globals')
    gnode.music_continuous = continuous
    gnode.music = '' if musictype is None else musictype.value
    gnode.music_count += 1
示例#5
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()
示例#6
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.
        """
        from ba import _gameutils

        # 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 = _gameutils.sharedobj('globals')
                if globs.paused:
                    _ba.playsound(_ba.getsound('refWhistle'))
                    globs.paused = False

                    # FIXME: This should not be an actor attr.
                    activity.paused_text = None
示例#7
0
    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')
示例#8
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
示例#9
0
    def __init__(self,
                 vr_overlay_offset: Optional[Sequence[float]] = None) -> None:
        """Instantiate a map."""
        from ba import _gameutils
        super().__init__()

        # This is expected to always be a ba.Node object (whether valid or not)
        # should be set to something meaningful by child classes.
        self.node: Optional[_ba.Node] = None

        # Make our class' preload-data available to us
        # (and instruct the user if we weren't preloaded properly).
        try:
            self.preloaddata = _ba.getactivity().preloads[type(self)]
        except Exception:
            from ba import _error
            raise _error.NotFoundError(
                'Preload data not found for ' + str(type(self)) +
                '; make sure to call the type\'s preload()'
                ' staticmethod in the activity constructor')

        # Set various globals.
        gnode = _gameutils.sharedobj('globals')

        # Set area-of-interest bounds.
        aoi_bounds = self.get_def_bound_box('area_of_interest_bounds')
        if aoi_bounds is None:
            print('WARNING: no "aoi_bounds" found for map:', self.get_name())
            aoi_bounds = (-1, -1, -1, 1, 1, 1)
        gnode.area_of_interest_bounds = aoi_bounds

        # Set map bounds.
        map_bounds = self.get_def_bound_box('map_bounds')
        if map_bounds is None:
            print('WARNING: no "map_bounds" found for map:', self.get_name())
            map_bounds = (-30, -10, -30, 30, 100, 30)
        _ba.set_map_bounds(map_bounds)

        # Set shadow ranges.
        try:
            gnode.shadow_range = [
                self.defs.points[v][1] for v in [
                    'shadow_lower_bottom', 'shadow_lower_top',
                    'shadow_upper_bottom', 'shadow_upper_top'
                ]
            ]
        except Exception:
            pass

        # In vr, set a fixed point in space for the overlay to show up at.
        # By default we use the bounds center but allow the map to override it.
        center = ((aoi_bounds[0] + aoi_bounds[3]) * 0.5,
                  (aoi_bounds[1] + aoi_bounds[4]) * 0.5,
                  (aoi_bounds[2] + aoi_bounds[5]) * 0.5)
        if vr_overlay_offset is not None:
            center = (center[0] + vr_overlay_offset[0],
                      center[1] + vr_overlay_offset[1],
                      center[2] + vr_overlay_offset[2])
        gnode.vr_overlay_center = center
        gnode.vr_overlay_center_enabled = True

        self.spawn_points = (self.get_def_points('spawn')
                             or [(0, 0, 0, 0, 0, 0)])
        self.ffa_spawn_points = (self.get_def_points('ffa_spawn')
                                 or [(0, 0, 0, 0, 0, 0)])
        self.spawn_by_flag_points = (self.get_def_points('spawn_by_flag')
                                     or [(0, 0, 0, 0, 0, 0)])
        self.flag_points = self.get_def_points('flag') or [(0, 0, 0)]

        # We just want points.
        self.flag_points = [p[:3] for p in self.flag_points]
        self.flag_points_default = (self.get_def_point('flag_default')
                                    or (0, 1, 0))
        self.powerup_spawn_points = self.get_def_points('powerup_spawn') or [
            (0, 0, 0)
        ]

        # We just want points.
        self.powerup_spawn_points = ([
            p[:3] for p in self.powerup_spawn_points
        ])
        self.tnt_points = self.get_def_points('tnt') or []

        # We just want points.
        self.tnt_points = [p[:3] for p in self.tnt_points]

        self.is_hockey = False
        self.is_flying = False

        # FIXME: this should be part of game; not map.
        self._next_ffa_start_index = 0
示例#10
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)
        """
        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 RuntimeError(
                'Session.set_activity() cannot be called recursively.')
        self._in_set_activity = True

        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
        if prev_activity is not None:
            with _ba.Context(prev_activity):
                prev_globals = sharedobj('globals')
        else:
            prev_globals = 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.destroy,
                          timetype=TimeType.REAL)
        self._in_set_activity = False