コード例 #1
0
ファイル: race.py プロジェクト: Awesome-Logic/ballistica
class RaceGame(ba.TeamGameActivity):
    """Game of racing around a track."""
    @classmethod
    def get_name(cls) -> str:
        return 'Race'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Run real fast!'

    @classmethod
    def get_score_info(cls) -> Dict[str, Any]:
        return {
            'score_name': 'Time',
            'lower_is_better': True,
            'score_type': 'milliseconds'
        }

    @classmethod
    def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
        return issubclass(sessiontype, ba.MultiTeamSession)

    @classmethod
    def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
        return ba.getmaps('race')

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        settings: List[Tuple[str, Dict[str, Any]]] = [
            ('Laps', {
                'min_value': 1,
                'default': 3,
                'increment': 1
            }),
            ('Time Limit', {
                'choices': [('None', 0), ('1 Minute', 60),
                            ('2 Minutes', 120), ('5 Minutes', 300),
                            ('10 Minutes', 600), ('20 Minutes', 1200)],
                'default': 0
            }),
            ('Mine Spawning', {
                'choices': [('No Mines', 0), ('8 Seconds', 8000),
                            ('4 Seconds', 4000), ('2 Seconds', 2000)],
                'default': 4000
            }),
            ('Bomb Spawning', {
                'choices': [('None', 0), ('8 Seconds', 8000),
                            ('4 Seconds', 4000), ('2 Seconds', 2000),
                            ('1 Second', 1000)],
            'default': 2000
            }),
            ('Epic Mode', {
                'default': False
            })]  # yapf: disable

        if issubclass(sessiontype, ba.DualTeamSession):
            settings.append(('Entire Team Must Finish', {'default': False}))
        return settings

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        self._race_started = False
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        if self.settings['Epic Mode']:
            self.slow_motion = True
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._last_team_time: Optional[float] = None
        self._front_race_region: Optional[int] = None
        self._nub_tex = ba.gettexture('nub')
        self._beep_1_sound = ba.getsound('raceBeep1')
        self._beep_2_sound = ba.getsound('raceBeep2')
        self.race_region_material: Optional[ba.Material] = None
        self._regions: List[RaceRegion] = []
        self._team_finish_pts: Optional[int] = None
        self._time_text: Optional[ba.Actor] = None
        self._timer: Optional[OnScreenTimer] = None
        self._race_mines: Optional[List[RaceMine]] = None
        self._race_mine_timer: Optional[ba.Timer] = None
        self._scoreboard_timer: Optional[ba.Timer] = None
        self._player_order_update_timer: Optional[ba.Timer] = None
        self._start_lights: Optional[List[ba.Node]] = None
        self._bomb_spawn_timer: Optional[ba.Timer] = None

    def get_instance_description(self) -> Union[str, Sequence]:
        if isinstance(self.session, ba.DualTeamSession) and self.settings.get(
                'Entire Team Must Finish', False):
            t_str = ' Your entire team has to finish.'
        else:
            t_str = ''

        if self.settings['Laps'] > 1:
            return 'Run ${ARG1} laps.' + t_str, self.settings['Laps']
        return 'Run 1 lap.' + t_str

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        if self.settings['Laps'] > 1:
            return 'run ${ARG1} laps', self.settings['Laps']
        return 'run 1 lap'

    def on_transition_in(self) -> None:
        self.default_music = (ba.MusicType.EPIC_RACE
                              if self.settings['Epic Mode'] else
                              ba.MusicType.RACE)
        super().on_transition_in()

        pts = self.map.get_def_points('race_point')
        mat = self.race_region_material = ba.Material()
        mat.add_actions(conditions=('they_have_material',
                                    ba.sharedobj('player_material')),
                        actions=(('modify_part_collision', 'collide', True),
                                 ('modify_part_collision', 'physical',
                                  False), ('call', 'at_connect',
                                           self._handle_race_point_collide)))
        for rpt in pts:
            self._regions.append(RaceRegion(rpt, len(self._regions)))

    def _flash_player(self, player: ba.Player, scale: float) -> None:
        assert isinstance(player.actor, PlayerSpaz)
        assert player.actor.node
        pos = player.actor.node.position
        light = ba.newnode('light',
                           attrs={
                               'position': pos,
                               'color': (1, 1, 0),
                               'height_attenuated': False,
                               'radius': 0.4
                           })
        ba.timer(0.5, light.delete)
        ba.animate(light, 'intensity', {0: 0, 0.1: 1.0 * scale, 0.5: 0})

    def _handle_race_point_collide(self) -> None:
        # FIXME: Tidy this up.
        # pylint: disable=too-many-statements
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-nested-blocks
        region_node, playernode = ba.get_collision_info(
            'source_node', 'opposing_node')
        try:
            player = playernode.getdelegate().getplayer()
        except Exception:
            player = None
        region = region_node.getdelegate()
        if not player or not region:
            return
        assert isinstance(player, ba.Player)
        assert isinstance(region, RaceRegion)

        last_region = player.gamedata['last_region']
        this_region = region.index

        if last_region != this_region:

            # If a player tries to skip regions, smite them.
            # Allow a one region leeway though (its plausible players can get
            # blown over a region, etc).
            if this_region > last_region + 2:
                if player.is_alive():
                    assert player.actor
                    player.actor.handlemessage(ba.DieMessage())
                    ba.screenmessage(ba.Lstr(
                        translate=('statements', 'Killing ${NAME} for'
                                   ' skipping part of the track!'),
                        subs=[('${NAME}', player.get_name(full=True))]),
                                     color=(1, 0, 0))
            else:
                # If this player is in first, note that this is the
                # front-most race-point.
                if player.gamedata['rank'] == 0:
                    self._front_race_region = this_region

                player.gamedata['last_region'] = this_region
                if last_region >= len(self._regions) - 2 and this_region == 0:
                    team = player.team
                    player.gamedata['lap'] = min(self.settings['Laps'],
                                                 player.gamedata['lap'] + 1)

                    # In teams mode with all-must-finish on, the team lap
                    # value is the min of all team players.
                    # Otherwise its the max.
                    if isinstance(self.session,
                                  ba.DualTeamSession) and self.settings.get(
                                      'Entire Team Must Finish'):
                        team.gamedata['lap'] = min(
                            [p.gamedata['lap'] for p in team.players])
                    else:
                        team.gamedata['lap'] = max(
                            [p.gamedata['lap'] for p in team.players])

                    # A player is finishing.
                    if player.gamedata['lap'] == self.settings['Laps']:

                        # In teams mode, hand out points based on the order
                        # players come in.
                        if isinstance(self.session, ba.DualTeamSession):
                            assert self._team_finish_pts is not None
                            if self._team_finish_pts > 0:
                                self.stats.player_scored(player,
                                                         self._team_finish_pts,
                                                         screenmessage=False)
                            self._team_finish_pts -= 25

                        # Flash where the player is.
                        self._flash_player(player, 1.0)
                        player.gamedata['finished'] = True
                        assert player.actor
                        player.actor.handlemessage(
                            ba.DieMessage(immediate=True))

                        # Makes sure noone behind them passes them in rank
                        # while finishing.
                        player.gamedata['distance'] = 9999.0

                        # If the whole team has finished the race.
                        if team.gamedata['lap'] == self.settings['Laps']:
                            ba.playsound(self._score_sound)
                            player.team.gamedata['finished'] = True
                            assert self._timer is not None
                            cur_time = ba.time(
                                timeformat=ba.TimeFormat.MILLISECONDS)
                            start_time = self._timer.getstarttime(
                                timeformat=ba.TimeFormat.MILLISECONDS)
                            self._last_team_time = (
                                player.team.gamedata['time']) = (cur_time -
                                                                 start_time)
                            self._check_end_game()

                        # Team has yet to finish.
                        else:
                            ba.playsound(self._swipsound)

                    # They've just finished a lap but not the race.
                    else:
                        ba.playsound(self._swipsound)
                        self._flash_player(player, 0.3)

                        # Print their lap number over their head.
                        try:
                            assert isinstance(player.actor, PlayerSpaz)
                            mathnode = ba.newnode('math',
                                                  owner=player.actor.node,
                                                  attrs={
                                                      'input1': (0, 1.9, 0),
                                                      'operation': 'add'
                                                  })
                            player.actor.node.connectattr(
                                'torso_position', mathnode, 'input2')
                            tstr = ba.Lstr(resource='lapNumberText',
                                           subs=[('${CURRENT}',
                                                  str(player.gamedata['lap'] +
                                                      1)),
                                                 ('${TOTAL}',
                                                  str(self.settings['Laps']))])
                            txtnode = ba.newnode('text',
                                                 owner=mathnode,
                                                 attrs={
                                                     'text': tstr,
                                                     'in_world': True,
                                                     'color': (1, 1, 0, 1),
                                                     'scale': 0.015,
                                                     'h_align': 'center'
                                                 })
                            mathnode.connectattr('output', txtnode, 'position')
                            ba.animate(txtnode, 'scale', {
                                0.0: 0,
                                0.2: 0.019,
                                2.0: 0.019,
                                2.2: 0
                            })
                            ba.timer(2.3, mathnode.delete)
                        except Exception as exc:
                            print('Exception printing lap:', exc)

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['time'] = None
        team.gamedata['lap'] = 0
        team.gamedata['finished'] = False
        self._update_scoreboard()

    def on_player_join(self, player: ba.Player) -> None:
        player.gamedata['last_region'] = 0
        player.gamedata['lap'] = 0
        player.gamedata['distance'] = 0.0
        player.gamedata['finished'] = False
        player.gamedata['rank'] = None
        ba.TeamGameActivity.on_player_join(self, player)

    def on_player_leave(self, player: ba.Player) -> None:
        ba.TeamGameActivity.on_player_leave(self, player)

        # A player leaving disqualifies the team if 'Entire Team Must Finish'
        # is on (otherwise in teams mode everyone could just leave except the
        # leading player to win).
        if (isinstance(self.session, ba.DualTeamSession)
                and self.settings.get('Entire Team Must Finish')):
            ba.screenmessage(ba.Lstr(
                translate=('statements',
                           '${TEAM} is disqualified because ${PLAYER} left'),
                subs=[('${TEAM}', player.team.name),
                      ('${PLAYER}', player.get_name(full=True))]),
                             color=(1, 1, 0))
            player.team.gamedata['finished'] = True
            player.team.gamedata['time'] = None
            player.team.gamedata['lap'] = 0
            ba.playsound(ba.getsound('boo'))
            for otherplayer in player.team.players:
                otherplayer.gamedata['lap'] = 0
                otherplayer.gamedata['finished'] = True
                try:
                    if otherplayer.actor is not None:
                        otherplayer.actor.handlemessage(ba.DieMessage())
                except Exception:
                    ba.print_exception('Error sending diemessages')

        # Defer so team/player lists will be updated.
        ba.pushcall(self._check_end_game)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            distances = [
                player.gamedata['distance'] for player in team.players
            ]
            if not distances:
                teams_dist = 0
            else:
                if (isinstance(self.session, ba.DualTeamSession)
                        and self.settings.get('Entire Team Must Finish')):
                    teams_dist = min(distances)
                else:
                    teams_dist = max(distances)
            self._scoreboard.set_team_value(
                team,
                teams_dist,
                self.settings['Laps'],
                flash=(teams_dist >= float(self.settings['Laps'])),
                show_value=False)

    def on_begin(self) -> None:
        from bastd.actor.onscreentimer import OnScreenTimer
        super().on_begin()
        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()
        self._team_finish_pts = 100

        # Throw a timer up on-screen.
        self._time_text = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'v_attach': 'top',
                           'h_attach': 'center',
                           'h_align': 'center',
                           'color': (1, 1, 0.5, 1),
                           'flatness': 0.5,
                           'shadow': 0.5,
                           'position': (0, -50),
                           'scale': 1.4,
                           'text': ''
                       }))
        self._timer = OnScreenTimer()

        if self.settings['Mine Spawning'] != 0:
            self._race_mines = [
                RaceMine(point=p, mine=None)
                for p in self.map.get_def_points('race_mine')
            ]
            if self._race_mines:
                self._race_mine_timer = ba.Timer(
                    0.001 * self.settings['Mine Spawning'],
                    self._update_race_mine,
                    repeat=True)

        self._scoreboard_timer = ba.Timer(0.25,
                                          self._update_scoreboard,
                                          repeat=True)
        self._player_order_update_timer = ba.Timer(0.25,
                                                   self._update_player_order,
                                                   repeat=True)

        if self.slow_motion:
            t_scale = 0.4
            light_y = 50
        else:
            t_scale = 1.0
            light_y = 150
        lstart = 7.1 * t_scale
        inc = 1.25 * t_scale

        ba.timer(lstart, self._do_light_1)
        ba.timer(lstart + inc, self._do_light_2)
        ba.timer(lstart + 2 * inc, self._do_light_3)
        ba.timer(lstart + 3 * inc, self._start_race)

        self._start_lights = []
        for i in range(4):
            lnub = ba.newnode('image',
                              attrs={
                                  'texture': ba.gettexture('nub'),
                                  'opacity': 1.0,
                                  'absolute_scale': True,
                                  'position': (-75 + i * 50, light_y),
                                  'scale': (50, 50),
                                  'attach': 'center'
                              })
            ba.animate(
                lnub, 'opacity', {
                    4.0 * t_scale: 0,
                    5.0 * t_scale: 1.0,
                    12.0 * t_scale: 1.0,
                    12.5 * t_scale: 0.0
                })
            ba.timer(13.0 * t_scale, lnub.delete)
            self._start_lights.append(lnub)

        self._start_lights[0].color = (0.2, 0, 0)
        self._start_lights[1].color = (0.2, 0, 0)
        self._start_lights[2].color = (0.2, 0.05, 0)
        self._start_lights[3].color = (0.0, 0.3, 0)

    def _do_light_1(self) -> None:
        assert self._start_lights is not None
        self._start_lights[0].color = (1.0, 0, 0)
        ba.playsound(self._beep_1_sound)

    def _do_light_2(self) -> None:
        assert self._start_lights is not None
        self._start_lights[1].color = (1.0, 0, 0)
        ba.playsound(self._beep_1_sound)

    def _do_light_3(self) -> None:
        assert self._start_lights is not None
        self._start_lights[2].color = (1.0, 0.3, 0)
        ba.playsound(self._beep_1_sound)

    def _start_race(self) -> None:
        assert self._start_lights is not None
        self._start_lights[3].color = (0.0, 1.0, 0)
        ba.playsound(self._beep_2_sound)
        for player in self.players:
            if player.actor is not None:
                try:
                    assert isinstance(player.actor, PlayerSpaz)
                    player.actor.connect_controls_to_player()
                except Exception as exc:
                    print('Exception in race player connects:', exc)
        assert self._timer is not None
        self._timer.start()

        if self.settings['Bomb Spawning'] != 0:
            self._bomb_spawn_timer = ba.Timer(0.001 *
                                              self.settings['Bomb Spawning'],
                                              self._spawn_bomb,
                                              repeat=True)

        self._race_started = True

    def _update_player_order(self) -> None:
        # FIXME: tidy this up

        # Calc all player distances.
        for player in self.players:
            pos: Optional[ba.Vec3]
            try:
                assert isinstance(player.actor, PlayerSpaz)
                assert player.actor.node
                pos = ba.Vec3(player.actor.node.position)
            except Exception:
                pos = None
            if pos is not None:
                r_index = player.gamedata['last_region']
                rg1 = self._regions[r_index]
                r1pt = ba.Vec3(rg1.pos[:3])
                rg2 = self._regions[0] if r_index == len(
                    self._regions) - 1 else self._regions[r_index + 1]
                r2pt = ba.Vec3(rg2.pos[:3])
                r2dist = (pos - r2pt).length()
                amt = 1.0 - (r2dist / (r2pt - r1pt).length())
                amt = player.gamedata['lap'] + (r_index + amt) * (
                    1.0 / len(self._regions))
                player.gamedata['distance'] = amt

        # Sort players by distance and update their ranks.
        p_list = [[player.gamedata['distance'], player]
                  for player in self.players]

        p_list.sort(reverse=True, key=lambda x: x[0])
        for i, plr in enumerate(p_list):
            try:
                plr[1].gamedata['rank'] = i
                if plr[1].actor is not None:
                    # noinspection PyUnresolvedReferences
                    node = plr[1].actor.distance_txt
                    if node:
                        node.text = str(i + 1) if plr[1].is_alive() else ''
            except Exception:
                ba.print_exception('error updating player orders')

    def _spawn_bomb(self) -> None:
        if self._front_race_region is None:
            return
        region = (self._front_race_region + 3) % len(self._regions)
        pos = self._regions[region].pos

        # Don't use the full region so we're less likely to spawn off a cliff.
        region_scale = 0.8
        x_range = ((-0.5, 0.5) if pos[3] == 0 else
                   (-region_scale * pos[3], region_scale * pos[3]))
        z_range = ((-0.5, 0.5) if pos[5] == 0 else
                   (-region_scale * pos[5], region_scale * pos[5]))
        pos = (pos[0] + random.uniform(*x_range), pos[1] + 1.0,
               pos[2] + random.uniform(*z_range))
        ba.timer(random.uniform(0.0, 2.0),
                 ba.WeakCall(self._spawn_bomb_at_pos, pos))

    def _spawn_bomb_at_pos(self, pos: Sequence[float]) -> None:
        if self.has_ended():
            return
        Bomb(position=pos, bomb_type='normal').autoretain()

    def _make_mine(self, i: int) -> None:
        assert self._race_mines is not None
        rmine = self._race_mines[i]
        rmine.mine = Bomb(position=rmine.point[:3], bomb_type='land_mine')
        rmine.mine.arm()

    def _flash_mine(self, i: int) -> None:
        assert self._race_mines is not None
        rmine = self._race_mines[i]
        light = ba.newnode('light',
                           attrs={
                               'position': rmine.point[:3],
                               'color': (1, 0.2, 0.2),
                               'radius': 0.1,
                               'height_attenuated': False
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.1: 1.0, 0.2: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _update_race_mine(self) -> None:
        assert self._race_mines is not None
        m_index = -1
        rmine = None
        for _i in range(3):
            m_index = random.randrange(len(self._race_mines))
            rmine = self._race_mines[m_index]
            if not rmine.mine:
                break
        assert rmine is not None
        if not rmine.mine:
            self._flash_mine(m_index)
            ba.timer(0.95, ba.Call(self._make_mine, m_index))

    def spawn_player(self, player: ba.Player) -> ba.Actor:
        if player.team.gamedata['finished']:
            # FIXME: This is not type-safe
            #  (this call is expected to return an Actor).
            # noinspection PyTypeChecker
            return None  # type: ignore
        pos = self._regions[player.gamedata['last_region']].pos

        # Don't use the full region so we're less likely to spawn off a cliff.
        region_scale = 0.8
        x_range = ((-0.5, 0.5) if pos[3] == 0 else
                   (-region_scale * pos[3], region_scale * pos[3]))
        z_range = ((-0.5, 0.5) if pos[5] == 0 else
                   (-region_scale * pos[5], region_scale * pos[5]))
        pos = (pos[0] + random.uniform(*x_range), pos[1],
               pos[2] + random.uniform(*z_range))
        spaz = self.spawn_player_spaz(
            player, position=pos, angle=90 if not self._race_started else None)
        assert spaz.node

        # Prevent controlling of characters before the start of the race.
        if not self._race_started:
            spaz.disconnect_controls_from_player()

        mathnode = ba.newnode('math',
                              owner=spaz.node,
                              attrs={
                                  'input1': (0, 1.4, 0),
                                  'operation': 'add'
                              })
        spaz.node.connectattr('torso_position', mathnode, 'input2')

        distance_txt = ba.newnode('text',
                                  owner=spaz.node,
                                  attrs={
                                      'text': '',
                                      'in_world': True,
                                      'color': (1, 1, 0.4),
                                      'scale': 0.02,
                                      'h_align': 'center'
                                  })
        # FIXME store this in a type-safe way
        # noinspection PyTypeHints
        spaz.distance_txt = distance_txt  # type: ignore
        mathnode.connectattr('output', distance_txt, 'position')
        return spaz

    def _check_end_game(self) -> None:

        # If there's no teams left racing, finish.
        teams_still_in = len(
            [t for t in self.teams if not t.gamedata['finished']])
        if teams_still_in == 0:
            self.end_game()
            return

        # Count the number of teams that have completed the race.
        teams_completed = len([
            t for t in self.teams
            if t.gamedata['finished'] and t.gamedata['time'] is not None
        ])

        if teams_completed > 0:
            session = self.session

            # In teams mode its over as soon as any team finishes the race

            # FIXME: The get_ffa_point_awards code looks dangerous.
            if isinstance(session, ba.DualTeamSession):
                self.end_game()
            else:
                # In ffa we keep the race going while there's still any points
                # to be handed out. Find out how many points we have to award
                # and how many teams have finished, and once that matches
                # we're done.
                assert isinstance(session, ba.FreeForAllSession)
                points_to_award = len(session.get_ffa_point_awards())
                if teams_completed >= points_to_award - teams_completed:
                    self.end_game()
                    return

    def end_game(self) -> None:

        # Stop updating our time text, and set it to show the exact last
        # finish time if we have one. (so users don't get upset if their
        # final time differs from what they see onscreen by a tiny bit)
        assert self._timer is not None
        if self._timer.has_started():
            cur_time = self._timer.getstarttime(
                timeformat=ba.TimeFormat.MILLISECONDS)
            self._timer.stop(
                endtime=None if self._last_team_time is None else (
                    cur_time + self._last_team_time))

        results = ba.TeamGameResults()

        for team in self.teams:
            if team.gamedata['time'] is not None:
                results.set_team_score(team, team.gamedata['time'])
            # If game have ended before we
            # get any result, use 'fail' screen

        # We don't announce a winner in ffa mode since its probably been a
        # while since the first place guy crossed the finish line so it seems
        # odd to be announcing that now.
        self.end(results=results,
                 announce_winning_team=isinstance(self.session,
                                                  ba.DualTeamSession))

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, PlayerSpazDeathMessage):
            # Augment default behavior.
            super().handlemessage(msg)
            player = msg.spaz.getplayer()
            if not player:
                ba.print_error('got no player in PlayerSpazDeathMessage')
                return
            if not player.gamedata['finished']:
                self.respawn_player(player, respawn_time=1)
        else:
            super().handlemessage(msg)
コード例 #2
0
class MeteorShowerGame(ba.TeamGameActivity):
    """Minigame involving dodging falling bombs."""
    @classmethod
    def get_name(cls) -> str:
        return 'Meteor Shower'

    @classmethod
    def get_score_info(cls) -> ba.ScoreInfo:
        return ba.ScoreInfo(label='Survived',
                            scoretype=ba.ScoreType.MILLISECONDS,
                            version='B')

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Dodge the falling bombs.'

    # we're currently hard-coded for one map..
    @classmethod
    def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
        return ['Rampage']

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [('Epic Mode', {'default': False})]

    # We support teams, free-for-all, and co-op sessions.
    @classmethod
    def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
        return (issubclass(sessiontype, ba.DualTeamSession)
                or issubclass(sessiontype, ba.FreeForAllSession)
                or issubclass(sessiontype, ba.CoopSession))

    def __init__(self, settings: Dict[str, Any]):
        super().__init__(settings)

        self._epic_mode = settings.get('Epic Mode', False)
        self._last_player_death_time: Optional[float] = None
        self._meteor_time = 2.0
        self._timer: Optional[OnScreenTimer] = None

        # Some base class overrides:
        self.default_music = (ba.MusicType.EPIC
                              if self._epic_mode else ba.MusicType.SURVIVAL)
        if self._epic_mode:
            self.slow_motion = True

        # Print messages when players die (since its meaningful in this game).
        self.announce_player_deaths = True

    def on_begin(self) -> None:
        from bastd.actor.onscreentimer import OnScreenTimer

        super().on_begin()

        # Drop a wave every few seconds.. and every so often drop the time
        # between waves ..lets have things increase faster if we have fewer
        # players.
        delay = 5.0 if len(self.players) > 2 else 2.5
        if self._epic_mode:
            delay *= 0.25
        ba.timer(delay, self._decrement_meteor_time, repeat=True)

        # Kick off the first wave in a few seconds.
        delay = 3.0
        if self._epic_mode:
            delay *= 0.25
        ba.timer(delay, self._set_meteor_timer)

        self._timer = OnScreenTimer()
        self._timer.start()

        # Check for immediate end (if we've only got 1 player, etc).
        ba.timer(5.0, self._check_end_game)

    def on_player_join(self, player: ba.Player) -> None:
        # Don't allow joining after we start
        # (would enable leave/rejoin tomfoolery).
        if self.has_begun():
            ba.screenmessage(ba.Lstr(resource='playerDelayedJoinText',
                                     subs=[('${PLAYER}',
                                            player.get_name(full=True))]),
                             color=(0, 1, 0))
            # For score purposes, mark them as having died right as the
            # game started.
            assert self._timer is not None
            PlayerData.get(player).death_time = self._timer.getstarttime()
            return
        self.spawn_player(player)

    def on_player_leave(self, player: ba.Player) -> None:
        # Augment default behavior.
        super().on_player_leave(player)

        # A departing player may trigger game-over.
        self._check_end_game()

    # overriding the default character spawning..
    def spawn_player(self, player: ba.Player) -> ba.Actor:
        spaz = self.spawn_player_spaz(player)

        # Let's reconnect this player's controls to this
        # spaz but *without* the ability to attack or pick stuff up.
        spaz.connect_controls_to_player(enable_punch=False,
                                        enable_bomb=False,
                                        enable_pickup=False)

        # Also lets have them make some noise when they die.
        spaz.play_big_death_sound = True
        return spaz

    # Various high-level game events come through this method.
    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, PlayerSpazDeathMessage):

            # Augment standard behavior.
            super().handlemessage(msg)

            curtime = ba.time()

            # Record the player's moment of death.
            PlayerData.get(msg.spaz.player).death_time = curtime

            # In co-op mode, end the game the instant everyone dies
            # (more accurate looking).
            # In teams/ffa, allow a one-second fudge-factor so we can
            # get more draws if players die basically at the same time.
            if isinstance(self.session, ba.CoopSession):
                # Teams will still show up if we check now.. check in
                # the next cycle.
                ba.pushcall(self._check_end_game)

                # Also record this for a final setting of the clock.
                self._last_player_death_time = curtime
            else:
                ba.timer(1.0, self._check_end_game)

        else:
            # Default handler:
            super().handlemessage(msg)

    def _check_end_game(self) -> None:
        living_team_count = 0
        for team in self.teams:
            for player in team.players:
                if player.is_alive():
                    living_team_count += 1
                    break

        # In co-op, we go till everyone is dead.. otherwise we go
        # until one team remains.
        if isinstance(self.session, ba.CoopSession):
            if living_team_count <= 0:
                self.end_game()
        else:
            if living_team_count <= 1:
                self.end_game()

    def _set_meteor_timer(self) -> None:
        ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
                 self._drop_bomb_cluster)

    def _drop_bomb_cluster(self) -> None:

        # Random note: code like this is a handy way to plot out extents
        # and debug things.
        loc_test = False
        if loc_test:
            ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
            ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
            ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
            ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})

        # Drop several bombs in series.
        delay = 0.0
        for _i in range(random.randrange(1, 3)):
            # Drop them somewhere within our bounds with velocity pointing
            # toward the opposite side.
            pos = (-7.3 + 15.3 * random.random(), 11,
                   -5.5 + 2.1 * random.random())
            dropdir = (-1.0 if pos[0] > 0 else 1.0)
            vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
            ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
            delay += 0.1
        self._set_meteor_timer()

    def _drop_bomb(self, position: Sequence[float],
                   velocity: Sequence[float]) -> None:
        Bomb(position=position, velocity=velocity).autoretain()

    def _decrement_meteor_time(self) -> None:
        self._meteor_time = max(0.01, self._meteor_time * 0.9)

    def end_game(self) -> None:
        cur_time = ba.time()
        assert self._timer is not None
        start_time = self._timer.getstarttime()

        # Mark death-time as now for any still-living players
        # and award players points for how long they lasted.
        # (these per-player scores are only meaningful in team-games)
        for team in self.teams:
            for player in team.players:
                playerdata = PlayerData.get(player)
                survived = False

                # Throw an extra fudge factor in so teams that
                # didn't die come out ahead of teams that did.
                if playerdata.death_time is None:
                    survived = True
                    playerdata.death_time = cur_time + 1

                # Award a per-player score depending on how many seconds
                # they lasted (per-player scores only affect teams mode;
                # everywhere else just looks at the per-team score).
                score = int(playerdata.death_time - self._timer.getstarttime())
                if survived:
                    score += 50  # A bit extra for survivors.
                self.stats.player_scored(player, score, screenmessage=False)

        # Stop updating our time text, and set the final time to match
        # exactly when our last guy died.
        self._timer.stop(endtime=self._last_player_death_time)

        # Ok now calc game results: set a score for each team and then tell
        # the game to end.
        results = ba.TeamGameResults()

        # Remember that 'free-for-all' mode is simply a special form
        # of 'teams' mode where each player gets their own team, so we can
        # just always deal in teams and have all cases covered.
        for team in self.teams:

            # Set the team score to the max time survived by any player on
            # that team.
            longest_life = 0.0
            for player in team.players:
                playerdata = PlayerData.get(player)
                assert playerdata.death_time is not None
                longest_life = max(longest_life,
                                   playerdata.death_time - start_time)

            # Submit the score value in milliseconds.
            results.set_team_score(team, int(1000.0 * longest_life))

        self.end(results=results)
コード例 #3
0
class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
    """
    A co-op game where you try to defeat a group
    of Ninjas as fast as possible
    """

    name = 'Ninja Fight'
    description = 'How fast can you defeat the ninjas?'
    score_info = ba.ScoreInfo(label='Time',
                              scoretype=ba.ScoreType.MILLISECONDS,
                              lower_is_better=True)

    @classmethod
    def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
        # For now we're hard-coding spawn positions and whatnot
        # so we need to be sure to specify that we only support
        # a specific map.
        return ['Courtyard']

    @classmethod
    def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
        # We currently support Co-Op only.
        return issubclass(sessiontype, ba.CoopSession)

    # In the constructor we should load any media we need/etc.
    # ...but not actually create anything yet.
    def __init__(self, settings: Dict[str, Any]):
        super().__init__(settings)
        self._winsound = ba.getsound('score')
        self._won = False
        self._timer: Optional[OnScreenTimer] = None
        self._bots = SpazBotSet()
        self._preset = str(settings['preset'])

    # Called when our game is transitioning in but not ready to begin;
    # we can go ahead and start creating stuff, playing music, etc.
    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.TO_THE_DEATH
        super().on_transition_in()

    # Called when our game actually begins.
    def on_begin(self) -> None:
        super().on_begin()
        is_pro = self._preset == 'pro'

        # In pro mode there's no powerups.
        if not is_pro:
            self.setup_standard_powerup_drops()

        # Make our on-screen timer and start it roughly when our bots appear.
        self._timer = OnScreenTimer()
        ba.timer(4.0, self._timer.start)

        # Spawn some baddies.
        ba.timer(
            1.0, lambda: self._bots.spawn_bot(
                ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
        ba.timer(
            2.0, lambda: self._bots.spawn_bot(
                ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
        ba.timer(
            3.0, lambda: self._bots.spawn_bot(
                ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
        ba.timer(
            4.0, lambda: self._bots.spawn_bot(
                ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))

        # Add some extras for multiplayer or pro mode.
        assert self.initial_player_info is not None
        if len(self.initial_player_info) > 2 or is_pro:
            ba.timer(
                5.0, lambda: self._bots.spawn_bot(
                    ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
        if len(self.initial_player_info) > 3 or is_pro:
            ba.timer(
                6.0, lambda: self._bots.spawn_bot(
                    ChargerBot, pos=(0, 3, 1), spawn_time=3.0))

    # Called for each spawning player.
    def spawn_player(self, player: Player) -> ba.Actor:

        # Let's spawn close to the center.
        spawn_center = (0, 3, -2)
        pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
               spawn_center[2] + random.uniform(-1.5, 1.5))
        return self.spawn_player_spaz(player, position=pos)

    def _check_if_won(self) -> None:
        # Simply end the game if there's no living bots.
        # FIXME: Should also make sure all bots have been spawned;
        #  if spawning is spread out enough that we're able to kill
        #  all living bots before the next spawns, it would incorrectly
        #  count as a win.
        if not self._bots.have_living_bots():
            self._won = True
            self.end_game()

    # Called for miscellaneous messages.
    def handlemessage(self, msg: Any) -> Any:

        # A player has died.
        if isinstance(msg, ba.PlayerDiedMessage):
            super().handlemessage(msg)  # Augment standard behavior.
            self.respawn_player(msg.getplayer(Player))

        # A spaz-bot has died.
        elif isinstance(msg, SpazBotDiedMessage):
            # Unfortunately the bot-set will always tell us there are living
            # bots if we ask here (the currently-dying bot isn't officially
            # marked dead yet) ..so lets push a call into the event loop to
            # check once this guy has finished dying.
            ba.pushcall(self._check_if_won)

        # Let the base class handle anything we don't.
        else:
            return super().handlemessage(msg)
        return None

    # When this is called, we should fill out results and end the game
    # *regardless* of whether is has been won. (this may be called due
    # to a tournament ending or other external reason).
    def end_game(self) -> None:

        # Stop our on-screen timer so players can see what they got.
        assert self._timer is not None
        self._timer.stop()

        results = ba.TeamGameResults()

        # If we won, set our score to the elapsed time in milliseconds.
        # (there should just be 1 team here since this is co-op).
        # ..if we didn't win, leave scores as default (None) which means
        # we lost.
        if self._won:
            elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0)
            ba.cameraflash()
            ba.playsound(self._winsound)
            for team in self.teams:
                for player in team.players:
                    if player.actor:
                        player.actor.handlemessage(ba.CelebrateMessage())
                results.set_team_score(team, elapsed_time_ms)

        # Ends the activity.
        self.end(results)
コード例 #4
0
class StickyStormGame(ba.TeamGameActivity[Player, Team]):
    """Minigame involving dodging falling ice bombs."""

    name = 'Sticky Storm'
    description = 'Dodge the falling sticky bombs.'
    available_settings = [ba.BoolSetting('Epic Mode', default=False)]
    scoreconfig = ba.ScoreConfig(label='Survived',
                                 scoretype=ba.ScoreType.MILLISECONDS,
                                 version='B')

    # Print messages when players die (since its meaningful in this game).
    announce_player_deaths = True

    # we're currently hard-coded for one map..
    @classmethod
    def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
        return ['Rampage']

    # We support teams, free-for-all, and co-op sessions.
    @classmethod
    def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
        return (issubclass(sessiontype, ba.DualTeamSession)
                or issubclass(sessiontype, ba.FreeForAllSession)
                or issubclass(sessiontype, ba.CoopSession))

    def __init__(self, settings: dict):
        super().__init__(settings)

        self._epic_mode = settings.get('Epic Mode', False)
        self._last_player_death_time: Optional[float] = None
        self._meteor_time = 2.0
        self._timer: Optional[OnScreenTimer] = None

        # Some base class overrides:
        self.default_music = (ba.MusicType.EPIC
                              if self._epic_mode else ba.MusicType.SURVIVAL)
        if self._epic_mode:
            self.slow_motion = True

    def on_begin(self) -> None:
        super().on_begin()

        # Drop a wave every few seconds.. and every so often drop the time
        # between waves ..lets have things increase faster if we have fewer
        # players.
        delay = 5.0 if len(self.players) > 2 else 2.5
        if self._epic_mode:
            delay *= 0.25
        ba.timer(delay, self._decrement_meteor_time, repeat=True)

        # Kick off the first wave in a few seconds.
        delay = 3.0
        if self._epic_mode:
            delay *= 0.25
        ba.timer(delay, self._set_meteor_timer)

        self._timer = OnScreenTimer()
        self._timer.start()

        # Check for immediate end (if we've only got 1 player, etc).
        ba.timer(5.0, self._check_end_game)

    def on_player_join(self, player: Player) -> None:
        # Don't allow joining after we start
        # (would enable leave/rejoin tomfoolery).
        if self.has_begun():
            ba.screenmessage(
                ba.Lstr(resource='playerDelayedJoinText',
                        subs=[('${PLAYER}', player.getname(full=True))]),
                color=(0, 1, 0),
            )
            # For score purposes, mark them as having died right as the
            # game started.
            assert self._timer is not None
            player.death_time = self._timer.getstarttime()
            return
        self.spawn_player(player)

    def on_player_leave(self, player: Player) -> None:
        # Augment default behavior.
        super().on_player_leave(player)

        # A departing player may trigger game-over.
        self._check_end_game()

    # overriding the default character spawning..
    def spawn_player(self, player: Player) -> ba.Actor:
        spaz = self.spawn_player_spaz(player)

        # Let's reconnect this player's controls to this
        # spaz but *without* the ability to attack or pick stuff up.
        spaz.connect_controls_to_player(enable_punch=False,
                                        enable_bomb=False,
                                        enable_pickup=False)

        # Also lets have them make some noise when they die.
        spaz.play_big_death_sound = True
        return spaz

    # Various high-level game events come through this method.
    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):

            # Augment standard behavior.
            super().handlemessage(msg)

            curtime = ba.time()

            # Record the player's moment of death.
            # assert isinstance(msg.spaz.player
            msg.getplayer(Player).death_time = curtime

            # In co-op mode, end the game the instant everyone dies
            # (more accurate looking).
            # In teams/ffa, allow a one-second fudge-factor so we can
            # get more draws if players die basically at the same time.
            if isinstance(self.session, ba.CoopSession):
                # Teams will still show up if we check now.. check in
                # the next cycle.
                ba.pushcall(self._check_end_game)

                # Also record this for a final setting of the clock.
                self._last_player_death_time = curtime
            else:
                ba.timer(1.0, self._check_end_game)

        else:
            # Default handler:
            return super().handlemessage(msg)
        return None

    def _check_end_game(self) -> None:
        living_team_count = 0
        for team in self.teams:
            for player in team.players:
                if player.is_alive():
                    living_team_count += 1
                    break

        # In co-op, we go till everyone is dead.. otherwise we go
        # until one team remains.
        if isinstance(self.session, ba.CoopSession):
            if living_team_count <= 0:
                self.end_game()
        else:
            if living_team_count <= 1:
                self.end_game()

    def _set_meteor_timer(self) -> None:
        ba.timer((1.0 + 0.2 * random.random()) * self._meteor_time,
                 self._drop_bomb_cluster)

    def _drop_bomb_cluster(self) -> None:

        # Random note: code like this is a handy way to plot out extents
        # and debug things.
        loc_test = False
        if loc_test:
            ba.newnode('locator', attrs={'position': (8, 6, -5.5)})
            ba.newnode('locator', attrs={'position': (8, 6, -2.3)})
            ba.newnode('locator', attrs={'position': (-7.3, 6, -5.5)})
            ba.newnode('locator', attrs={'position': (-7.3, 6, -2.3)})

        # Drop several bombs in series.
        delay = 0.0
        for _i in range(random.randrange(1, 3)):
            # Drop them somewhere within our bounds with velocity pointing
            # toward the opposite side.
            pos = (-7.3 + 15.3 * random.random(), 11,
                   -5.5 + 2.1 * random.random())
            dropdir = (-1.0 if pos[0] > 0 else 1.0)
            vel = ((-5.0 + random.random() * 30.0) * dropdir, -4.0, 0)
            ba.timer(delay, ba.Call(self._drop_bomb, pos, vel))
            delay += 0.1
        self._set_meteor_timer()

    def _drop_bomb(self, position: Sequence[float],
                   velocity: Sequence[float]) -> None:
        Bomb(position=position, velocity=velocity, bomb_type = 'sticky').autoretain()

    def _decrement_meteor_time(self) -> None:
        self._meteor_time = max(0.01, self._meteor_time * 0.9)

    def end_game(self) -> None:
        cur_time = ba.time()
        assert self._timer is not None
        start_time = self._timer.getstarttime()

        # Mark death-time as now for any still-living players
        # and award players points for how long they lasted.
        # (these per-player scores are only meaningful in team-games)
        for team in self.teams:
            for player in team.players:
                survived = False

                # Throw an extra fudge factor in so teams that
                # didn't die come out ahead of teams that did.
                if player.death_time is None:
                    survived = True
                    player.death_time = cur_time + 1

                # Award a per-player score depending on how many seconds
                # they lasted (per-player scores only affect teams mode;
                # everywhere else just looks at the per-team score).
                score = int(player.death_time - self._timer.getstarttime())
                if survived:
                    score += 50  # A bit extra for survivors.
                self.stats.player_scored(player, score, screenmessage=False)

        # Stop updating our time text, and set the final time to match
        # exactly when our last guy died.
        self._timer.stop(endtime=self._last_player_death_time)

        # Ok now calc game results: set a score for each team and then tell
        # the game to end.
        results = ba.GameResults()

        # Remember that 'free-for-all' mode is simply a special form
        # of 'teams' mode where each player gets their own team, so we can
        # just always deal in teams and have all cases covered.
        for team in self.teams:

            # Set the team score to the max time survived by any player on
            # that team.
            longest_life = 0.0
            for player in team.players:
                assert player.death_time is not None
                longest_life = max(longest_life,
                                   player.death_time - start_time)

            # Submit the score value in milliseconds.
            results.set_team_score(team, int(1000.0 * longest_life))

        self.end(results=results)


# Copyright (c) Lifetime Benefit-Zebra
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this mod without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the mod.
#
# THE MOD IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE MOD OR THE USE OR OTHER DEALINGS IN THE
# MOD.
# --------------------------------------

#By Benefit-Zebra
#https://github.com/Benefit-Zebra