コード例 #1
0
ファイル: easteregghunt.py プロジェクト: snow-1711/ballistica
class EasterEggHuntGame(ba.TeamGameActivity[Player, Team]):
    """A game where score is based on collecting eggs."""

    name = 'Easter Egg Hunt'
    description = 'Gather eggs!'
    available_settings = [ba.BoolSetting('Pro Mode', default=False)]
    scoreconfig = ba.ScoreConfig(label='Score', scoretype=ba.ScoreType.POINTS)

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

    # 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.CoopSession)
                or issubclass(sessiontype, ba.DualTeamSession)
                or issubclass(sessiontype, ba.FreeForAllSession))

    def __init__(self, settings: dict):
        super().__init__(settings)
        shared = SharedObjects.get()
        self._last_player_death_time = None
        self._scoreboard = Scoreboard()
        self.egg_model = ba.getmodel('egg')
        self.egg_tex_1 = ba.gettexture('eggTex1')
        self.egg_tex_2 = ba.gettexture('eggTex2')
        self.egg_tex_3 = ba.gettexture('eggTex3')
        self._collect_sound = ba.getsound('powerup01')
        self._pro_mode = settings.get('Pro Mode', False)
        self._max_eggs = 1.0
        self.egg_material = ba.Material()
        self.egg_material.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(('call', 'at_connect', self._on_egg_player_collide), ))
        self._eggs: List[Egg] = []
        self._update_timer: Optional[ba.Timer] = None
        self._countdown: Optional[OnScreenCountdown] = None
        self._bots: Optional[SpazBotSet] = None

        # Base class overrides
        self.default_music = ba.MusicType.FORWARD_MARCH

    def on_team_join(self, team: Team) -> None:
        if self.has_begun():
            self._update_scoreboard()

    # Called when our game actually starts.
    def on_begin(self) -> None:
        from bastd.maps import TowerD

        # There's a player-wall on the tower-d level to prevent
        # players from getting up on the stairs.. we wanna kill that.
        gamemap = self.map
        assert isinstance(gamemap, TowerD)
        gamemap.player_wall.delete()
        super().on_begin()
        self._update_scoreboard()
        self._update_timer = ba.Timer(0.25, self._update, repeat=True)
        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
        ba.timer(4.0, self._countdown.start)
        self._bots = SpazBotSet()

        # Spawn evil bunny in co-op only.
        if isinstance(self.session, ba.CoopSession) and self._pro_mode:
            self._spawn_evil_bunny()

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

    def _spawn_evil_bunny(self) -> None:
        assert self._bots is not None
        self._bots.spawn_bot(BouncyBot, pos=(6, 4, -7.8), spawn_time=10.0)

    def _on_egg_player_collide(self) -> None:
        if self.has_ended():
            return
        collision = ba.getcollision()

        # Be defensive here; we could be hitting the corpse of a player
        # who just left/etc.
        try:
            egg = collision.sourcenode.getdelegate(Egg, True)
            player = collision.opposingnode.getdelegate(PlayerSpaz,
                                                        True).getplayer(
                                                            Player, True)
        except ba.NotFoundError:
            return

        player.team.score += 1

        # Displays a +1 (and adds to individual player score in
        # teams mode).
        self.stats.player_scored(player, 1, screenmessage=False)
        if self._max_eggs < 5:
            self._max_eggs += 1.0
        elif self._max_eggs < 10:
            self._max_eggs += 0.5
        elif self._max_eggs < 30:
            self._max_eggs += 0.3
        self._update_scoreboard()
        ba.playsound(self._collect_sound, 0.5, position=egg.node.position)

        # Create a flash.
        light = ba.newnode('light',
                           attrs={
                               'position': egg.node.position,
                               'height_attenuated': False,
                               'radius': 0.1,
                               'color': (1, 1, 0)
                           })
        ba.animate(light, 'intensity', {0: 0, 0.1: 1.0, 0.2: 0}, loop=False)
        ba.timer(0.200, light.delete)
        egg.handlemessage(ba.DieMessage())

    def _update(self) -> None:
        # Misc. periodic updating.
        xpos = random.uniform(-7.1, 6.0)
        ypos = random.uniform(3.5, 3.5)
        zpos = random.uniform(-8.2, 3.7)

        # Prune dead eggs from our list.
        self._eggs = [e for e in self._eggs if e]

        # Spawn more eggs if we've got space.
        if len(self._eggs) < int(self._max_eggs):

            # Occasionally spawn a land-mine in addition.
            if self._pro_mode and random.random() < 0.25:
                mine = Bomb(position=(xpos, ypos, zpos),
                            bomb_type='land_mine').autoretain()
                mine.arm()
            else:
                self._eggs.append(Egg(position=(xpos, ypos, zpos)))

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

        # Respawn dead players.
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)

            # Respawn them shortly.
            player = msg.getplayer(Player)
            assert self.initialplayerinfos is not None
            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
            player.respawn_timer = ba.Timer(
                respawn_time, ba.Call(self.spawn_player_if_exists, player))
            player.respawn_icon = RespawnIcon(player, respawn_time)

        # Whenever our evil bunny dies, respawn him and spew some eggs.
        elif isinstance(msg, SpazBotDiedMessage):
            self._spawn_evil_bunny()
            assert msg.spazbot.node
            pos = msg.spazbot.node.position
            for _i in range(6):
                spread = 0.4
                self._eggs.append(
                    Egg(position=(pos[0] + random.uniform(-spread, spread),
                                  pos[1] + random.uniform(-spread, spread),
                                  pos[2] + random.uniform(-spread, spread))))
        else:
            # Default handler.
            return super().handlemessage(msg)
        return None

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score)

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results)
コード例 #2
0
class KeepAwayGame(ba.TeamGameActivity[Player, Team]):
    """Game where you try to keep the flag away from your enemies."""

    name = 'Keep Away'
    description = 'Carry the flag for a set length of time.'
    available_settings = [
        ba.IntSetting(
            'Hold Time',
            min_value=10,
            default=30,
            increment=10,
        ),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
    ]
    scoreconfig = ba.ScoreConfig(label='Time Held')
    default_music = ba.MusicType.KEEP_AWAY

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._swipsound = ba.getsound('swip')
        self._tick_sound = ba.getsound('tick')
        self._countdownsounds = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_spawn_pos: Optional[Sequence[float]] = None
        self._update_timer: Optional[ba.Timer] = None
        self._holding_players: List[Player] = []
        self._flag_state: Optional[FlagState] = None
        self._flag_light: Optional[ba.Node] = None
        self._scoring_team: Optional[Team] = None
        self._flag: Optional[Flag] = None
        self._hold_time = int(settings['Hold Time'])
        self._time_limit = float(settings['Time Limit'])

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Carry the flag for ${ARG1} seconds.', self._hold_time

    def get_instance_description_short(self) -> Union[str, Sequence]:
        return 'carry the flag for ${ARG1} seconds', self._hold_time

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
        return Team(timeremaining=self._hold_time)

    def on_team_join(self, team: Team) -> None:
        self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        self._flag_spawn_pos = self.map.get_flag_position(None)
        self._spawn_flag()
        self._update_timer = ba.Timer(1.0, call=self._tick, repeat=True)
        self._update_flag_state()
        Flag.project_stand(self._flag_spawn_pos)

    def _tick(self) -> None:
        self._update_flag_state()

        # Award points to all living players holding the flag.
        for player in self._holding_players:
            if player:
                assert self.stats
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)

        scoreteam = self._scoring_team

        if scoreteam is not None:

            if scoreteam.timeremaining > 0:
                ba.playsound(self._tick_sound)

            scoreteam.timeremaining = max(0, scoreteam.timeremaining - 1)
            self._update_scoreboard()
            if scoreteam.timeremaining > 0:
                assert self._flag is not None
                self._flag.set_score_text(str(scoreteam.timeremaining))

            # Announce numbers we have sounds for.
            if scoreteam.timeremaining in self._countdownsounds:
                ba.playsound(self._countdownsounds[scoreteam.timeremaining])

            # Winner.
            if scoreteam.timeremaining <= 0:
                self.end_game()

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, self._hold_time - team.timeremaining)
        self.end(results=results, announce_delay=0)

    def _update_flag_state(self) -> None:
        for team in self.teams:
            team.holdingflag = False
        self._holding_players = []
        for player in self.players:
            holdingflag = False
            try:
                assert isinstance(player.actor, (PlayerSpaz, type(None)))
                if (player.actor and player.actor.node
                        and player.actor.node.hold_node):
                    holdingflag = (
                        player.actor.node.hold_node.getnodetype() == 'flag')
            except Exception:
                ba.print_exception('exception checking hold flag')
            if holdingflag:
                self._holding_players.append(player)
                player.team.holdingflag = True

        holdingteams = set(t for t in self.teams if t.holdingflag)
        prevstate = self._flag_state
        assert self._flag is not None
        assert self._flag_light
        assert self._flag.node
        if len(holdingteams) > 1:
            self._flag_state = FlagState.CONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.6, 0.6, 0.1)
            self._flag.node.color = (1.0, 1.0, 0.4)
        elif len(holdingteams) == 1:
            holdingteam = list(holdingteams)[0]
            self._flag_state = FlagState.HELD
            self._scoring_team = holdingteam
            self._flag_light.color = ba.normalized_color(holdingteam.color)
            self._flag.node.color = holdingteam.color
        else:
            self._flag_state = FlagState.UNCONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.2, 0.2, 0.2)
            self._flag.node.color = (1, 1, 1)

        if self._flag_state != prevstate:
            ba.playsound(self._swipsound)

    def _spawn_flag(self) -> None:
        ba.playsound(self._swipsound)
        self._flash_flag_spawn()
        assert self._flag_spawn_pos is not None
        self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos)
        self._flag_state = FlagState.NEW
        self._flag_light = ba.newnode('light',
                                      owner=self._flag.node,
                                      attrs={
                                          'intensity': 0.2,
                                          'radius': 0.3,
                                          'color': (0.2, 0.2, 0.2)
                                      })
        assert self._flag.node
        self._flag.node.connectattr('position', self._flag_light, 'position')
        self._update_flag_state()

    def _flash_flag_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'color': (1, 1, 1),
                               'radius': 0.3,
                               'height_attenuated': False
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.timeremaining,
                                            self._hold_time,
                                            countdown=True)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)
            self.respawn_player(msg.getplayer(Player))
        elif isinstance(msg, FlagDiedMessage):
            self._spawn_flag()
        elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)):
            self._update_flag_state()
        else:
            super().handlemessage(msg)
コード例 #3
0
class TheLastStandGame(ba.CoopGameActivity[Player, Team]):
    """Slow motion how-long-can-you-last game."""

    name = 'The Last Stand'
    description = 'Final glorious epic slow motion battle to the death.'
    tips = [
        'This level never ends, but a high score here\n'
        'will earn you eternal respect throughout the world.'
    ]

    # Show messages when players die since it matters here.
    announce_player_deaths = True

    # And of course the most important part.
    slow_motion = True

    def __init__(self, settings: Dict[str, Any]):
        settings['map'] = 'Rampage'
        super().__init__(settings)
        self._new_wave_sound = ba.getsound('scoreHit01')
        self._winsound = ba.getsound('score')
        self._cashregistersound = ba.getsound('cashRegister')
        self._spawn_center = (0, 5.5, -4.14)
        self._tntspawnpos = (0, 5.5, -6)
        self._powerup_center = (0, 7, -4.14)
        self._powerup_spread = (7, 2)
        self._preset = self.settings_raw.get('preset', 'default')
        self._excludepowerups: List[str] = []
        self._scoreboard: Optional[Scoreboard] = None
        self._score = 0
        self._bots = spazbot.BotSet()
        self._dingsound = ba.getsound('dingSmall')
        self._dingsoundhigh = ba.getsound('dingSmallHigh')
        self._tntspawner: Optional[TNTSpawner] = None
        self._bot_update_interval: Optional[float] = None
        self._bot_update_timer: Optional[ba.Timer] = None
        self._powerup_drop_timer = None

        # For each bot type: [spawn-rate, increase, d_increase]
        self._bot_spawn_types = {
            spazbot.BomberBot:              [1.00, 0.00, 0.000],
            spazbot.BomberBotPro:           [0.00, 0.05, 0.001],
            spazbot.BomberBotProShielded:   [0.00, 0.02, 0.002],
            spazbot.BrawlerBot:             [1.00, 0.00, 0.000],
            spazbot.BrawlerBotPro:          [0.00, 0.05, 0.001],
            spazbot.BrawlerBotProShielded:  [0.00, 0.02, 0.002],
            spazbot.TriggerBot:             [0.30, 0.00, 0.000],
            spazbot.TriggerBotPro:          [0.00, 0.05, 0.001],
            spazbot.TriggerBotProShielded:  [0.00, 0.02, 0.002],
            spazbot.ChargerBot:             [0.30, 0.05, 0.000],
            spazbot.StickyBot:              [0.10, 0.03, 0.001],
            spazbot.ExplodeyBot:            [0.05, 0.02, 0.002]
        }  # yapf: disable

    def on_transition_in(self) -> None:
        from bastd.actor.scoreboard import Scoreboard
        self.default_music = ba.MusicType.EPIC
        super().on_transition_in()
        ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound))
        self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
                                      score_split=0.5)

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

        # Spit out a few powerups and start dropping more shortly.
        self._drop_powerups(standard_points=True)
        ba.timer(2.0, ba.WeakCall(self._start_powerup_drops))
        ba.timer(0.001, ba.WeakCall(self._start_bot_updates))
        self.setup_low_life_warning_sound()
        self._update_scores()

        # Our TNT spawner (if applicable).
        self._tntspawner = TNTSpawner(position=self._tntspawnpos,
                                      respawn_time=10.0)

    def spawn_player(self, player: Player) -> ba.Actor:
        pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
               self._spawn_center[1],
               self._spawn_center[2] + random.uniform(-1.5, 1.5))
        return self.spawn_player_spaz(player, position=pos)

    def _start_bot_updates(self) -> None:
        self._bot_update_interval = 3.3 - 0.3 * (len(self.players))
        self._update_bots()
        self._update_bots()
        if len(self.players) > 2:
            self._update_bots()
        if len(self.players) > 3:
            self._update_bots()
        self._bot_update_timer = ba.Timer(self._bot_update_interval,
                                          ba.WeakCall(self._update_bots))

    def _drop_powerup(self, index: int, poweruptype: str = None) -> None:
        from bastd.actor import powerupbox
        if poweruptype is None:
            poweruptype = (powerupbox.get_factory().get_random_powerup_type(
                excludetypes=self._excludepowerups))
        powerupbox.PowerupBox(position=self.map.powerup_spawn_points[index],
                              poweruptype=poweruptype).autoretain()

    def _start_powerup_drops(self) -> None:
        self._powerup_drop_timer = ba.Timer(3.0,
                                            ba.WeakCall(self._drop_powerups),
                                            repeat=True)

    def _drop_powerups(self,
                       standard_points: bool = False,
                       force_first: str = None) -> None:
        """Generic powerup drop."""
        from bastd.actor import powerupbox
        if standard_points:
            pts = self.map.powerup_spawn_points
            for i in range(len(pts)):
                ba.timer(
                    1.0 + i * 0.5,
                    ba.WeakCall(self._drop_powerup, i,
                                force_first if i == 0 else None))
        else:
            drop_pt = (self._powerup_center[0] + random.uniform(
                -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
                       self._powerup_center[1],
                       self._powerup_center[2] + random.uniform(
                           -self._powerup_spread[1], self._powerup_spread[1]))

            # Drop one random one somewhere.
            powerupbox.PowerupBox(
                position=drop_pt,
                poweruptype=powerupbox.get_factory().get_random_powerup_type(
                    excludetypes=self._excludepowerups)).autoretain()

    def do_end(self, outcome: str) -> None:
        """End the game."""
        if outcome == 'defeat':
            self.fade_to_red()
        self.end(delay=2.0,
                 results={
                     'outcome': outcome,
                     'score': self._score,
                     'player_info': self.initial_player_info
                 })

    def _update_bots(self) -> None:
        assert self._bot_update_interval is not None
        self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98)
        self._bot_update_timer = ba.Timer(self._bot_update_interval,
                                          ba.WeakCall(self._update_bots))
        botspawnpts: List[Sequence[float]] = [[-5.0, 5.5, -4.14],
                                              [0.0, 5.5, -4.14],
                                              [5.0, 5.5, -4.14]]
        dists = [0.0, 0.0, 0.0]
        playerpts: List[Sequence[float]] = []
        for player in self.players:
            try:
                if player.is_alive():
                    assert isinstance(player.actor, playerspaz.PlayerSpaz)
                    assert player.actor.node
                    playerpts.append(player.actor.node.position)
            except Exception as exc:
                print('ERROR in _update_bots', exc)
        for i in range(3):
            for playerpt in playerpts:
                dists[i] += abs(playerpt[0] - botspawnpts[i][0])

            # Little random variation.
            dists[i] += random.random() * 5.0
        if dists[0] > dists[1] and dists[0] > dists[2]:
            spawnpt = botspawnpts[0]
        elif dists[1] > dists[2]:
            spawnpt = botspawnpts[1]
        else:
            spawnpt = botspawnpts[2]

        spawnpt = (spawnpt[0] + 3.0 * (random.random() - 0.5), spawnpt[1],
                   2.0 * (random.random() - 0.5) + spawnpt[2])

        # Normalize our bot type total and find a random number within that.
        total = 0.0
        for spawntype in self._bot_spawn_types.items():
            total += spawntype[1][0]
        randval = random.random() * total

        # Now go back through and see where this value falls.
        total = 0
        bottype: Optional[Type[spazbot.SpazBot]] = None
        for spawntype in self._bot_spawn_types.items():
            total += spawntype[1][0]
            if randval <= total:
                bottype = spawntype[0]
                break
        spawn_time = 1.0
        assert bottype is not None
        self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time)

        # After every spawn we adjust our ratios slightly to get more
        # difficult.
        for spawntype in self._bot_spawn_types.items():
            spawntype[1][0] += spawntype[1][1]  # incr spawn rate
            spawntype[1][1] += spawntype[1][2]  # incr spawn rate incr rate

    def _update_scores(self) -> None:
        # Achievements in default preset only.
        score = self._score
        if self._preset == 'default':
            if score >= 250:
                self._award_achievement('Last Stand Master')
            if score >= 500:
                self._award_achievement('Last Stand Wizard')
            if score >= 1000:
                self._award_achievement('Last Stand God')
        assert self._scoreboard is not None
        self._scoreboard.set_team_value(self.teams[0], score, max_score=None)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            player = msg.getplayer(Player)
            self.stats.player_was_killed(player)
            ba.timer(0.1, self._checkroundover)

        elif isinstance(msg, ba.PlayerScoredMessage):
            self._score += msg.score
            self._update_scores()

        elif isinstance(msg, spazbot.SpazBotDeathMessage):
            pts, importance = msg.badguy.get_death_points(msg.how)
            target: Optional[Sequence[float]]
            if msg.killerplayer:
                try:
                    assert msg.badguy.node
                    target = msg.badguy.node.position
                except Exception:
                    ba.print_exception()
                    target = None
                try:
                    self.stats.player_scored(msg.killerplayer,
                                             pts,
                                             target=target,
                                             kill=True,
                                             screenmessage=False,
                                             importance=importance)
                    ba.playsound(self._dingsound
                                 if importance == 1 else self._dingsoundhigh,
                                 volume=0.6)
                except Exception as exc:
                    print('EXC on last-stand SpazBotDeathMessage', exc)

            # Normally we pull scores from the score-set, but if there's no
            # player lets be explicit.
            else:
                self._score += pts
            self._update_scores()
        else:
            super().handlemessage(msg)

    def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
        # FIXME: Unify args.
        self._show_standard_scores_to_beat_ui(scores)

    def end_game(self) -> None:
        # Tell our bots to celebrate just to rub it in.
        self._bots.final_celebrate()
        ba.setmusic(None)
        ba.pushcall(ba.WeakCall(self.do_end, 'defeat'))

    def _checkroundover(self) -> None:
        """End the round if conditions are met."""
        if not any(player.is_alive() for player in self.teams[0].players):
            self.end_game()
コード例 #4
0
ファイル: conquest.py プロジェクト: Dmitry450/ballistica
class ConquestGame(ba.TeamGameActivity):
    """A game where teams try to claim all flags on the map."""
    @classmethod
    def get_name(cls) -> str:
        return 'Conquest'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Secure all flags on the map to win.'

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [
            ("Time Limit", {
                'choices': [('None', 0), ('1 Minute', 60),
                            ('2 Minutes', 120),
                            ('5 Minutes', 300),
                            ('10 Minutes', 600),
                            ('20 Minutes', 1200)],
                'default': 0
            }),
            ('Respawn Times', {
                'choices': [('Shorter', 0.25),
                            ('Short', 0.5),
                            ('Normal', 1.0),
                            ('Long', 2.0),
                            ('Longer', 4.0)],
                'default': 1.0
            }),
            ('Epic Mode', {'default': False})]  # yapf: disable

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        if self.settings['Epic Mode']:
            self.slow_motion = True
        self._scoreboard = Scoreboard()
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._extraflagmat = ba.Material()
        self._flags: List[ConquestFlag] = []

        # We want flags to tell us they've been hit but not react physically.
        self._extraflagmat.add_actions(
            conditions=('they_have_material', ba.sharedobj('player_material')),
            actions=(('modify_part_collision', 'collide', True),
                     ('call', 'at_connect', self._handle_flag_player_collide)))

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Secure all ${ARG1} flags.', len(self.map.flag_points)

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        return 'secure all ${ARG1} flags', len(self.map.flag_points)

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

    def on_team_join(self, team: ba.Team) -> None:
        if self.has_begun():
            self._update_scores()
        team.gamedata['flags_held'] = 0

    def on_player_join(self, player: ba.Player) -> None:
        player.gamedata['respawn_timer'] = None

        # Only spawn if this player's team has a flag currently.
        if player.team.gamedata['flags_held'] > 0:
            self.spawn_player(player)

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()

        # Set up flags with marker lights.
        for i in range(len(self.map.flag_points)):
            point = self.map.flag_points[i]
            flag = ConquestFlag(position=point,
                                touchable=False,
                                materials=[self._extraflagmat])
            self._flags.append(flag)
            # FIXME: Move next few lines to the flag class.
            self.project_flag_stand(point)
            flag.light = ba.newnode('light',
                                    owner=flag.node,
                                    attrs={
                                        'position': point,
                                        'intensity': 0.25,
                                        'height_attenuated': False,
                                        'radius': 0.3,
                                        'color': (1, 1, 1)
                                    })

        # Give teams a flag to start with.
        for i in range(len(self.teams)):
            self._flags[i].team = self.teams[i]
            light = self._flags[i].light
            assert light
            node = self._flags[i].node
            assert node
            light.color = self.teams[i].color
            node.color = self.teams[i].color

        self._update_scores()

        # Initial joiners didn't spawn due to no flags being owned yet;
        # spawn them now.
        for player in self.players:
            self.spawn_player(player)

    def _update_scores(self) -> None:
        for team in self.teams:
            team.gamedata['flags_held'] = 0
        for flag in self._flags:
            try:
                flag.team.gamedata['flags_held'] += 1
            except Exception:
                pass
        for team in self.teams:

            # If a team finds themselves with no flags, cancel all
            # outstanding spawn-timers.
            if team.gamedata['flags_held'] == 0:
                for player in team.players:
                    player.gamedata['respawn_timer'] = None
                    player.gamedata['respawn_icon'] = None
            if team.gamedata['flags_held'] == len(self._flags):
                self.end_game()
            self._scoreboard.set_team_value(team, team.gamedata['flags_held'],
                                            len(self._flags))

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['flags_held'])
        self.end(results=results)

    def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
        assert flag.node
        assert flag.light
        light = ba.newnode('light',
                           attrs={
                               'position': flag.node.position,
                               'height_attenuated': False,
                               'color': flag.light.color
                           })
        ba.animate(light, "intensity", {0: 0, 0.25: 1, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def _handle_flag_player_collide(self) -> None:
        flagnode, playernode = ba.get_collision_info("source_node",
                                                     "opposing_node")
        try:
            player = playernode.getdelegate().getplayer()
            flag = flagnode.getdelegate()
        except Exception:
            return  # Player may have left and his body hit the flag.
        assert isinstance(player, ba.Player)
        assert isinstance(flag, ConquestFlag)
        assert flag.light

        if flag.team is not player.team:
            flag.team = player.team
            flag.light.color = player.team.color
            flag.node.color = player.team.color
            self.stats.player_scored(player, 10, screenmessage=False)
            ba.playsound(self._swipsound)
            self._flash_flag(flag)
            self._update_scores()

            # Respawn any players on this team that were in limbo due to the
            # lack of a flag for their team.
            for otherplayer in self.players:
                if (otherplayer.team is flag.team
                        and otherplayer.actor is not None
                        and not otherplayer.is_alive()
                        and otherplayer.gamedata['respawn_timer'] is None):
                    self.spawn_player(otherplayer)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, PlayerSpazDeathMessage):
            # Augment standard behavior.
            super().handlemessage(msg)

            # Respawn only if this team has a flag.
            player = msg.spaz.player
            if player.team.gamedata['flags_held'] > 0:
                self.respawn_player(player)
            else:
                player.gamedata['respawn_timer'] = None

        else:
            super().handlemessage(msg)

    def spawn_player(self, player: ba.Player) -> ba.Actor:
        # We spawn players at different places based on what flags are held.
        return self.spawn_player_spaz(player,
                                      self._get_player_spawn_position(player))

    def _get_player_spawn_position(self, player: ba.Player) -> Sequence[float]:

        # Iterate until we find a spawn owned by this team.
        spawn_count = len(self.map.spawn_by_flag_points)

        # Get all spawns owned by this team.
        spawns = [
            i for i in range(spawn_count) if self._flags[i].team is player.team
        ]

        closest_spawn = 0
        closest_distance = 9999.0

        # Now find the spawn that's closest to a spawn not owned by us;
        # we'll use that one.
        for spawn in spawns:
            spt = self.map.spawn_by_flag_points[spawn]
            our_pt = ba.Vec3(spt[0], spt[1], spt[2])
            for otherspawn in [
                    i for i in range(spawn_count)
                    if self._flags[i].team is not player.team
            ]:
                spt = self.map.spawn_by_flag_points[otherspawn]
                their_pt = ba.Vec3(spt[0], spt[1], spt[2])
                dist = (their_pt - our_pt).length()
                if dist < closest_distance:
                    closest_distance = dist
                    closest_spawn = spawn

        pos = self.map.spawn_by_flag_points[closest_spawn]
        x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3])
        z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5])
        pos = (pos[0] + random.uniform(*x_range), pos[1],
               pos[2] + random.uniform(*z_range))
        return pos
コード例 #5
0
class DeathMatchGame(ba.TeamGameActivity[Player, Team]):
    """A game type based on acquiring kills."""

    name = 'Death Match'
    description = 'Kill a set number of enemies to win.'

    # Print messages when players die since it matters here.
    announce_player_deaths = True

    @classmethod
    def get_available_settings(
            cls, sessiontype: Type[ba.Session]) -> List[ba.Setting]:
        settings = [
            ba.IntSetting(
                'Kills to Win Per Player',
                min_value=1,
                default=5,
                increment=1,
            ),
            ba.IntChoiceSetting(
                'Time Limit',
                choices=[
                    ('None', 0),
                    ('1 Minute', 60),
                    ('2 Minutes', 120),
                    ('5 Minutes', 300),
                    ('10 Minutes', 600),
                    ('20 Minutes', 1200),
                ],
                default=0,
            ),
            ba.FloatChoiceSetting(
                'Respawn Times',
                choices=[
                    ('Shorter', 0.25),
                    ('Short', 0.5),
                    ('Normal', 1.0),
                    ('Long', 2.0),
                    ('Longer', 4.0),
                ],
                default=1.0,
            ),
            ba.BoolSetting('Epic Mode', default=False),
        ]

        # In teams mode, a suicide gives a point to the other team, but in
        # free-for-all it subtracts from your own score. By default we clamp
        # this at zero to benefit new players, but pro players might like to
        # be able to go negative. (to avoid a strategy of just
        # suiciding until you get a good drop)
        if issubclass(sessiontype, ba.FreeForAllSession):
            settings.append(
                ba.BoolSetting('Allow Negative Scores', default=False))

        return settings

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._score_to_win: Optional[int] = None
        self._dingsound = ba.getsound('dingSmall')
        self._epic_mode = bool(settings['Epic Mode'])
        self._kills_to_win_per_player = int(
            settings['Kills to Win Per Player'])
        self._time_limit = float(settings['Time Limit'])
        self._allow_negative_scores = bool(
            settings.get('Allow Negative Scores', False))

        # Base class overrides.
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC if self._epic_mode else
                              ba.MusicType.TO_THE_DEATH)

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Crush ${ARG1} of your enemies.', self._score_to_win

    def get_instance_description_short(self) -> Union[str, Sequence]:
        return 'kill ${ARG1} enemies', self._score_to_win

    def on_team_join(self, team: Team) -> None:
        if self.has_begun():
            self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()

        # Base kills needed to win on the size of the largest team.
        self._score_to_win = (self._kills_to_win_per_player *
                              max(1, max(len(t.players) for t in self.teams)))
        self._update_scoreboard()

    def handlemessage(self, msg: Any) -> Any:

        if isinstance(msg, ba.PlayerDiedMessage):

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

            player = msg.getplayer(Player)
            self.respawn_player(player)

            killer = msg.getkillerplayer(Player)
            if killer is None:
                return None

            # Handle team-kills.
            if killer.team is player.team:

                # In free-for-all, killing yourself loses you a point.
                if isinstance(self.session, ba.FreeForAllSession):
                    new_score = player.team.score - 1
                    if not self._allow_negative_scores:
                        new_score = max(0, new_score)
                    player.team.score = new_score

                # In teams-mode it gives a point to the other team.
                else:
                    ba.playsound(self._dingsound)
                    for team in self.teams:
                        if team is not killer.team:
                            team.score += 1

            # Killing someone on another team nets a kill.
            else:
                killer.team.score += 1
                ba.playsound(self._dingsound)

                # In FFA show scores since its hard to find on the scoreboard.
                if isinstance(killer.actor, PlayerSpaz) and killer.actor:
                    killer.actor.set_score_text(str(killer.team.score) + '/' +
                                                str(self._score_to_win),
                                                color=killer.team.color,
                                                flash=True)

            self._update_scoreboard()

            # If someone has won, set a timer to end shortly.
            # (allows the dust to clear and draws to occur if deaths are
            # close enough)
            assert self._score_to_win is not None
            if any(team.score >= self._score_to_win for team in self.teams):
                ba.timer(0.5, self.end_game)

        else:
            return super().handlemessage(msg)
        return None

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score,
                                            self._score_to_win)

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results=results)
コード例 #6
0
class HockeyGame(ba.TeamGameActivity[Player, Team]):
    """Ice hockey game."""

    name = 'Hockey'
    description = 'Score some goals.'
    available_settings = [
        ba.IntSetting(
            'Score to Win',
            min_value=1,
            default=1,
            increment=1,
        ),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
    ]
    default_music = ba.MusicType.HOCKEY

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        shared = SharedObjects.get()
        self._scoreboard = Scoreboard()
        self._cheer_sound = ba.getsound('cheer')
        self._chant_sound = ba.getsound('crowdChant')
        self._foghorn_sound = ba.getsound('foghorn')
        self._swipsound = ba.getsound('swip')
        self._whistle_sound = ba.getsound('refWhistle')
        self.puck_model = ba.getmodel('puck')
        self.puck_tex = ba.gettexture('puckColor')
        self._puck_sound = ba.getsound('metalHit')
        self.puck_material = ba.Material()
        self.puck_material.add_actions(actions=(('modify_part_collision',
                                                 'friction', 0.5)))
        self.puck_material.add_actions(conditions=('they_have_material',
                                                   shared.pickup_material),
                                       actions=('modify_part_collision',
                                                'collide', False))
        self.puck_material.add_actions(
            conditions=(
                ('we_are_younger_than', 100),
                'and',
                ('they_have_material', shared.object_material),
            ),
            actions=('modify_node_collision', 'collide', False),
        )
        self.puck_material.add_actions(conditions=('they_have_material',
                                                   shared.footing_material),
                                       actions=('impact_sound',
                                                self._puck_sound, 0.2, 5))

        # Keep track of which player last touched the puck
        self.puck_material.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(('call', 'at_connect',
                      self._handle_puck_player_collide), ))

        # We want the puck to kill powerups; not get stopped by them
        self.puck_material.add_actions(
            conditions=('they_have_material',
                        PowerupBoxFactory.get().powerup_material),
            actions=(('modify_part_collision', 'physical', False),
                     ('message', 'their_node', 'at_connect', ba.DieMessage())))
        self._score_region_material = ba.Material()
        self._score_region_material.add_actions(
            conditions=('they_have_material', self.puck_material),
            actions=(('modify_part_collision', 'collide',
                      True), ('modify_part_collision', 'physical', False),
                     ('call', 'at_connect', self._handle_score)))
        self._puck_spawn_pos: Optional[Sequence[float]] = None
        self._score_regions: Optional[List[ba.NodeActor]] = None
        self._puck: Optional[Puck] = None
        self._score_to_win = int(settings['Score to Win'])
        self._time_limit = float(settings['Time Limit'])

    def get_instance_description(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'Score a goal.'
        return 'Score ${ARG1} goals.', self._score_to_win

    def get_instance_description_short(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'score a goal'
        return 'score ${ARG1} goals', self._score_to_win

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

        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        self._puck_spawn_pos = self.map.get_flag_position(None)
        self._spawn_puck()

        # Set up the two score regions.
        defs = self.map.defs
        self._score_regions = []
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode('region',
                           attrs={
                               'position': defs.boxes['goal1'][0:3],
                               'scale': defs.boxes['goal1'][6:9],
                               'type': 'box',
                               'materials': [self._score_region_material]
                           })))
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode('region',
                           attrs={
                               'position': defs.boxes['goal2'][0:3],
                               'scale': defs.boxes['goal2'][6:9],
                               'type': 'box',
                               'materials': [self._score_region_material]
                           })))
        self._update_scoreboard()
        ba.playsound(self._chant_sound)

    def on_team_join(self, team: Team) -> None:
        self._update_scoreboard()

    def _handle_puck_player_collide(self) -> None:
        collision = ba.getcollision()
        try:
            puck = collision.sourcenode.getdelegate(Puck, True)
            player = collision.opposingnode.getdelegate(PlayerSpaz,
                                                        True).getplayer(
                                                            Player, True)
        except ba.NotFoundError:
            return

        puck.last_players_to_touch[player.team.id] = player

    def _kill_puck(self) -> None:
        self._puck = None

    def _handle_score(self) -> None:
        """A point has been scored."""

        assert self._puck is not None
        assert self._score_regions is not None

        # Our puck might stick around for a second or two
        # we don't want it to be able to score again.
        if self._puck.scored:
            return

        region = ba.getcollision().sourcenode
        index = 0
        for index, score_region in enumerate(self._score_regions):
            if region == score_region.node:
                break

        for team in self.teams:
            if team.id == index:
                scoring_team = team
                team.score += 1

                # Tell all players to celebrate.
                for player in team.players:
                    if player.actor:
                        player.actor.handlemessage(ba.CelebrateMessage(2.0))

                # If we've got the player from the scoring team that last
                # touched us, give them points.
                if (scoring_team.id in self._puck.last_players_to_touch
                        and self._puck.last_players_to_touch[scoring_team.id]):
                    self.stats.player_scored(
                        self._puck.last_players_to_touch[scoring_team.id],
                        100,
                        big_message=True)

                # End game if we won.
                if team.score >= self._score_to_win:
                    self.end_game()

        ba.playsound(self._foghorn_sound)
        ba.playsound(self._cheer_sound)

        self._puck.scored = True

        # Kill the puck (it'll respawn itself shortly).
        ba.timer(1.0, self._kill_puck)

        light = ba.newnode('light',
                           attrs={
                               'position': ba.getcollision().position,
                               'height_attenuated': False,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
        ba.timer(1.0, light.delete)

        ba.cameraflash(duration=10.0)
        self._update_scoreboard()

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results=results)

    def _update_scoreboard(self) -> None:
        winscore = self._score_to_win
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score, winscore)

    def handlemessage(self, msg: Any) -> Any:

        # Respawn dead players if they're still in the game.
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior...
            super().handlemessage(msg)
            self.respawn_player(msg.getplayer(Player))

        # Respawn dead pucks.
        elif isinstance(msg, PuckDiedMessage):
            if not self.has_ended():
                ba.timer(3.0, self._spawn_puck)
        else:
            super().handlemessage(msg)

    def _flash_puck_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._puck_spawn_pos,
                               'height_attenuated': False,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _spawn_puck(self) -> None:
        ba.playsound(self._swipsound)
        ba.playsound(self._whistle_sound)
        self._flash_puck_spawn()
        assert self._puck_spawn_pos is not None
        self._puck = Puck(position=self._puck_spawn_pos)
コード例 #7
0
ファイル: capturetheflag.py プロジェクト: bseditor/ballistica
class CaptureTheFlagGame(ba.TeamGameActivity):
    """Game of stealing other team's flag and returning it to your base."""
    @classmethod
    def get_name(cls) -> str:
        return 'Capture the Flag'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Return the enemy flag to score.'

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [
            ('Score to Win', {'min_value': 1, 'default': 3}),
            ('Flag Touch Return Time', {
                'min_value': 0, 'default': 0, 'increment': 1}),
            ('Flag Idle Return Time', {
                'min_value': 5, 'default': 30, 'increment': 5}),
            ('Time Limit', {
                'choices': [('None', 0), ('1 Minute', 60),
                            ('2 Minutes', 120), ('5 Minutes', 300),
                            ('10 Minutes', 600), ('20 Minutes', 1200)],
                'default': 0}),
            ('Respawn Times', {
                'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
                            ('Long', 2.0), ('Longer', 4.0)],
                'default': 1.0}),
            ('Epic Mode', {'default': False})]  # yapf: disable

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        if self.settings_raw['Epic Mode']:
            self.slow_motion = True
        self._alarmsound = ba.getsound('alarm')
        self._ticking_sound = ba.getsound('ticking')
        self._last_score_time = 0
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._all_bases_material = ba.Material()
        self._last_home_flag_notice_print_time = 0.0

    def get_instance_description(self) -> Union[str, Sequence]:
        if self.settings_raw['Score to Win'] == 1:
            return 'Steal the enemy flag.'
        return ('Steal the enemy flag ${ARG1} times.',
                self.settings_raw['Score to Win'])

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        if self.settings_raw['Score to Win'] == 1:
            return 'return 1 flag'
        return 'return ${ARG1} flags', self.settings_raw['Score to Win']

    def on_transition_in(self) -> None:
        self.default_music = (ba.MusicType.EPIC
                              if self.settings_raw['Epic Mode'] else
                              ba.MusicType.FLAG_CATCHER)
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['score'] = 0
        team.gamedata['flag_return_touches'] = 0
        team.gamedata['home_flag_at_base'] = True
        team.gamedata['touch_return_timer'] = None
        team.gamedata['enemy_flag_at_base'] = False
        team.gamedata['base_pos'] = (self.map.get_flag_position(team.get_id()))

        self.project_flag_stand(team.gamedata['base_pos'])

        ba.newnode('light',
                   attrs={
                       'position': team.gamedata['base_pos'],
                       'intensity': 0.6,
                       'height_attenuated': False,
                       'volume_intensity_scale': 0.1,
                       'radius': 0.1,
                       'color': team.color
                   })

        base_region_mat = team.gamedata['base_region_material'] = ba.Material()
        pos = team.gamedata['base_pos']
        team.gamedata['base_region'] = ba.newnode(
            'region',
            attrs={
                'position': (pos[0], pos[1] + 0.75, pos[2]),
                'scale': (0.5, 0.5, 0.5),
                'type': 'sphere',
                'materials': [base_region_mat, self._all_bases_material]
            })

        # create some materials for this team
        spaz_mat_no_flag_physical = team.gamedata[
            'spaz_material_no_flag_physical'] = ba.Material()
        spaz_mat_no_flag_collide = team.gamedata[
            'spaz_material_no_flag_collide'] = ba.Material()
        flagmat = team.gamedata['flagmaterial'] = ba.Material()

        # Some parts of our spazzes don't collide physically with our
        # flags but generate callbacks.
        spaz_mat_no_flag_physical.add_actions(
            conditions=('they_have_material', flagmat),
            actions=(('modify_part_collision', 'physical',
                      False), ('call', 'at_connect',
                               lambda: self._handle_hit_own_flag(team, 1)),
                     ('call', 'at_disconnect',
                      lambda: self._handle_hit_own_flag(team, 0))))

        # Other parts of our spazzes don't collide with our flags at all.
        spaz_mat_no_flag_collide.add_actions(conditions=('they_have_material',
                                                         flagmat),
                                             actions=('modify_part_collision',
                                                      'collide', False))

        # We wanna know when *any* flag enters/leaves our base.
        base_region_mat.add_actions(
            conditions=('they_have_material',
                        stdflag.get_factory().flagmaterial),
            actions=(('modify_part_collision', 'collide',
                      True), ('modify_part_collision', 'physical', False),
                     ('call', 'at_connect',
                      lambda: self._handle_flag_entered_base(team)),
                     ('call', 'at_disconnect',
                      lambda: self._handle_flag_left_base(team))))

        self._spawn_flag_for_team(team)
        self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings_raw['Time Limit'])
        self.setup_standard_powerup_drops()
        ba.timer(1.0, call=self._tick, repeat=True)

    def _spawn_flag_for_team(self, team: ba.Team) -> None:
        flag = team.gamedata['flag'] = CTFFlag(team)
        team.gamedata['flag_return_touches'] = 0
        self._flash_base(team, length=1.0)
        assert flag.node
        ba.playsound(self._swipsound, position=flag.node.position)

    def _handle_flag_entered_base(self, team: ba.Team) -> None:
        node = ba.get_collision_info('opposing_node')
        assert isinstance(node, (ba.Node, type(None)))
        flag = CTFFlag.from_node(node)
        if not flag:
            print('Unable to get flag in _handle_flag_entered_base')
            return

        if flag.team is team:
            team.gamedata['home_flag_at_base'] = True

            # If the enemy flag is already here, score!
            if team.gamedata['enemy_flag_at_base']:
                self._score(team)
        else:
            team.gamedata['enemy_flag_at_base'] = True
            if team.gamedata['home_flag_at_base']:
                # Award points to whoever was carrying the enemy flag.
                player = flag.last_player_to_hold
                if player and player.team is team:
                    assert self.stats
                    self.stats.player_scored(player, 50, big_message=True)

                # Update score and reset flags.
                self._score(team)

            # If the home-team flag isn't here, print a message to that effect.
            else:
                # Don't want slo-mo affecting this
                curtime = ba.time(ba.TimeType.BASE)
                if curtime - self._last_home_flag_notice_print_time > 5.0:
                    self._last_home_flag_notice_print_time = curtime
                    bpos = team.gamedata['base_pos']
                    tval = ba.Lstr(resource='ownFlagAtYourBaseWarning')
                    tnode = ba.newnode('text',
                                       attrs={
                                           'text':
                                           tval,
                                           'in_world':
                                           True,
                                           'scale':
                                           0.013,
                                           'color': (1, 1, 0, 1),
                                           'h_align':
                                           'center',
                                           'position':
                                           (bpos[0], bpos[1] + 3.2, bpos[2])
                                       })
                    ba.timer(5.1, tnode.delete)
                    ba.animate(tnode, 'scale', {
                        0.0: 0,
                        0.2: 0.013,
                        4.8: 0.013,
                        5.0: 0
                    })

    def _tick(self) -> None:
        # If either flag is away from base and not being held, tick down its
        # respawn timer.
        for team in self.teams:
            flag = team.gamedata['flag']

            if (not team.gamedata['home_flag_at_base']
                    and flag.held_count == 0):
                time_out_counting_down = True
                if flag.time_out_respawn_time is None:
                    flag.reset_return_times()
                assert flag.time_out_respawn_time is not None
                flag.time_out_respawn_time -= 1
                if flag.time_out_respawn_time <= 0:
                    flag.handlemessage(ba.DieMessage())
            else:
                time_out_counting_down = False

            if flag.node and flag.counter:
                pos = flag.node.position
                flag.counter.position = (pos[0], pos[1] + 1.3, pos[2])

                # If there's no self-touches on this flag, set its text
                # to show its auto-return counter.  (if there's self-touches
                # its showing that time).
                if team.gamedata['flag_return_touches'] == 0:
                    flag.counter.text = (str(flag.time_out_respawn_time) if
                                         (time_out_counting_down
                                          and flag.time_out_respawn_time <= 10)
                                         else '')
                    flag.counter.color = (1, 1, 1, 0.5)
                    flag.counter.scale = 0.014

    def _score(self, team: ba.Team) -> None:
        team.gamedata['score'] += 1
        ba.playsound(self._score_sound)
        self._flash_base(team)
        self._update_scoreboard()

        # Have teammates celebrate
        for player in team.players:
            if player.actor:
                player.actor.handlemessage(ba.CelebrateMessage(2.0))

        # Reset all flags/state.
        for reset_team in self.teams:
            if not reset_team.gamedata['home_flag_at_base']:
                reset_team.gamedata['flag'].handlemessage(ba.DieMessage())
            reset_team.gamedata['enemy_flag_at_base'] = False
        if team.gamedata['score'] >= self.settings_raw['Score to Win']:
            self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results=results, announce_delay=0.8)

    def _handle_flag_left_base(self, team: ba.Team) -> None:
        cur_time = ba.time()
        op_node = ba.get_collision_info('opposing_node')
        assert isinstance(op_node, (ba.Node, type(None)))
        flag = CTFFlag.from_node(op_node)
        if not flag:
            return

        if flag.team is team:

            # Check times here to prevent too much flashing.
            if ('last_flag_leave_time' not in team.gamedata
                    or cur_time - team.gamedata['last_flag_leave_time'] > 3.0):
                ba.playsound(self._alarmsound,
                             position=team.gamedata['base_pos'])
                self._flash_base(team)
            team.gamedata['last_flag_leave_time'] = cur_time
            team.gamedata['home_flag_at_base'] = False
        else:
            team.gamedata['enemy_flag_at_base'] = False

    def _touch_return_update(self, team: ba.Team) -> None:

        # Count down only while its away from base and not being held.
        if (team.gamedata['home_flag_at_base']
                or team.gamedata['flag'].held_count > 0):
            team.gamedata['touch_return_timer_ticking'] = None
            return  # No need to return when its at home.
        if team.gamedata['touch_return_timer_ticking'] is None:
            team.gamedata['touch_return_timer_ticking'] = ba.NodeActor(
                ba.newnode('sound',
                           attrs={
                               'sound': self._ticking_sound,
                               'positional': False,
                               'loop': True
                           }))
        flag = team.gamedata['flag']
        flag.touch_return_time -= 0.1
        if flag.counter:
            flag.counter.text = '%.1f' % flag.touch_return_time
            flag.counter.color = (1, 1, 0, 1)
            flag.counter.scale = 0.02

        if flag.touch_return_time <= 0.0:
            self._award_players_touching_own_flag(team)
            flag.handlemessage(ba.DieMessage())

    def _award_players_touching_own_flag(self, team: ba.Team) -> None:
        for player in team.players:
            if player.gamedata['touching_own_flag'] > 0:
                return_score = 10 + 5 * int(
                    self.settings_raw['Flag Touch Return Time'])
                self.stats.player_scored(player,
                                         return_score,
                                         screenmessage=False)

    @staticmethod
    def _player_from_node(node: Optional[ba.Node]) -> Optional[ba.Player]:
        """Return a player if given a node that is part of one's actor."""
        if not node:
            return None
        delegate = node.getdelegate()
        if not isinstance(delegate, PlayerSpaz):
            return None
        return delegate.getplayer()

    def _handle_hit_own_flag(self, team: ba.Team, val: int) -> None:
        """
        keep track of when each player is touching their
        own flag so we can award points when returned
        """
        srcnode = ba.get_collision_info('source_node')
        assert isinstance(srcnode, (ba.Node, type(None)))
        player = self._player_from_node(srcnode)
        if player:
            player.gamedata['touching_own_flag'] += (1 if val else -1)

        # If return-time is zero, just kill it immediately.. otherwise keep
        # track of touches and count down.
        if float(self.settings_raw['Flag Touch Return Time']) <= 0.0:
            if (not team.gamedata['home_flag_at_base']
                    and team.gamedata['flag'].held_count == 0):

                # Use a node message to kill the flag instead of just killing
                # our team's. (avoids redundantly killing new flags if
                # multiple body parts generate callbacks in one step).
                node = ba.get_collision_info('opposing_node')
                if node:
                    self._award_players_touching_own_flag(team)
                    node.handlemessage(ba.DieMessage())

        # Takes a non-zero amount of time to return.
        else:
            if val:
                team.gamedata['flag_return_touches'] += 1
                if team.gamedata['flag_return_touches'] == 1:
                    team.gamedata['touch_return_timer'] = ba.Timer(
                        0.1,
                        call=ba.Call(self._touch_return_update, team),
                        repeat=True)
                    team.gamedata['touch_return_timer_ticking'] = None
            else:
                team.gamedata['flag_return_touches'] -= 1
                if team.gamedata['flag_return_touches'] == 0:
                    team.gamedata['touch_return_timer'] = None
                    team.gamedata['touch_return_timer_ticking'] = None
            if team.gamedata['flag_return_touches'] < 0:
                ba.print_error(
                    "CTF: flag_return_touches < 0; this shouldn't happen.")

    def _flash_base(self, team: ba.Team, length: float = 2.0) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': team.gamedata['base_pos'],
                               'height_attenuated': False,
                               'radius': 0.3,
                               'color': team.color
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def spawn_player_spaz(self, *args: Any, **keywds: Any) -> Any:
        """Intercept new spazzes and add our team material for them."""
        # (chill pylint; we're passing our exact args to parent call)
        # pylint: disable=signature-differs
        spaz = super().spawn_player_spaz(*args, **keywds)
        player = spaz.player
        player.gamedata['touching_own_flag'] = 0

        # Ignore false alarm for gamedata member.
        no_physical_mats = [
            player.team.gamedata['spaz_material_no_flag_physical']
        ]
        no_collide_mats = [
            player.team.gamedata['spaz_material_no_flag_collide']
        ]
        # pylint: enable=arguments-differ

        # Our normal parts should still collide; just not physically
        # (so we can calc restores).
        assert spaz.node
        spaz.node.materials = list(spaz.node.materials) + no_physical_mats
        spaz.node.roller_materials = list(
            spaz.node.roller_materials) + no_physical_mats

        # Pickups and punches shouldn't hit at all though.
        spaz.node.punch_materials = list(
            spaz.node.punch_materials) + no_collide_mats
        spaz.node.pickup_materials = list(
            spaz.node.pickup_materials) + no_collide_mats
        spaz.node.extras_material = list(
            spaz.node.extras_material) + no_collide_mats
        return spaz

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'],
                                            self.settings_raw['Score to Win'])

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, PlayerSpazDeathMessage):
            # Augment standard behavior.
            super().handlemessage(msg)
            self.respawn_player(msg.spaz.player)
        elif isinstance(msg, stdflag.FlagDeathMessage):
            assert isinstance(msg.flag, CTFFlag)
            ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team))
        elif isinstance(msg, stdflag.FlagPickedUpMessage):
            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, CTFFlag)
            msg.flag.last_player_to_hold = msg.node.getdelegate().getplayer()
            msg.flag.held_count += 1
            msg.flag.reset_return_times()
        elif isinstance(msg, stdflag.FlagDroppedMessage):
            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, CTFFlag)
            msg.flag.held_count -= 1
        else:
            super().handlemessage(msg)
コード例 #8
0
class FootballCoopGame(ba.CoopGameActivity[Player, Team]):
    """Co-op variant of football."""

    name = 'Football'
    tips = ['Use the pick-up button to grab the flag < ${PICKUP} >']
    scoreconfig = ba.ScoreConfig(scoretype=ba.ScoreType.MILLISECONDS,
                                 version='B')
    default_music = ba.MusicType.FOOTBALL

    # FIXME: Need to update co-op games to use getscoreconfig.
    def get_score_type(self) -> str:
        return 'time'

    def get_instance_description(self) -> Union[str, Sequence]:
        touchdowns = self._score_to_win / 7
        touchdowns = math.ceil(touchdowns)
        if touchdowns > 1:
            return 'Score ${ARG1} touchdowns.', touchdowns
        return 'Score a touchdown.'

    def get_instance_description_short(self) -> Union[str, Sequence]:
        touchdowns = self._score_to_win / 7
        touchdowns = math.ceil(touchdowns)
        if touchdowns > 1:
            return 'score ${ARG1} touchdowns', touchdowns
        return 'score a touchdown'

    def __init__(self, settings: dict):
        settings['map'] = 'Football Stadium'
        super().__init__(settings)
        self._preset = settings.get('preset', 'rookie')

        # Load some media we need.
        self._cheer_sound = ba.getsound('cheer')
        self._boo_sound = ba.getsound('boo')
        self._chant_sound = ba.getsound('crowdChant')
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._whistle_sound = ba.getsound('refWhistle')
        self._score_to_win = 21
        self._score_region_material = ba.Material()
        self._score_region_material.add_actions(
            conditions=('they_have_material', FlagFactory.get().flagmaterial),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect', self._handle_score),
            ))
        self._powerup_center = (0, 2, 0)
        self._powerup_spread = (10, 5.5)
        self._player_has_dropped_bomb = False
        self._player_has_punched = False
        self._scoreboard: Optional[Scoreboard] = None
        self._flag_spawn_pos: Optional[Sequence[float]] = None
        self._score_regions: List[ba.NodeActor] = []
        self._exclude_powerups: List[str] = []
        self._have_tnt = False
        self._bot_types_initial: Optional[List[Type[SpazBot]]] = None
        self._bot_types_7: Optional[List[Type[SpazBot]]] = None
        self._bot_types_14: Optional[List[Type[SpazBot]]] = None
        self._bot_team: Optional[Team] = None
        self._starttime_ms: Optional[int] = None
        self._time_text: Optional[ba.NodeActor] = None
        self._time_text_input: Optional[ba.NodeActor] = None
        self._tntspawner: Optional[TNTSpawner] = None
        self._bots = SpazBotSet()
        self._bot_spawn_timer: Optional[ba.Timer] = None
        self._powerup_drop_timer: Optional[ba.Timer] = None
        self._scoring_team: Optional[Team] = None
        self._final_time_ms: Optional[int] = None
        self._time_text_timer: Optional[ba.Timer] = None
        self._flag_respawn_light: Optional[ba.Actor] = None
        self._flag: Optional[FootballFlag] = None

    def on_transition_in(self) -> None:
        super().on_transition_in()
        self._scoreboard = Scoreboard()
        self._flag_spawn_pos = self.map.get_flag_position(None)
        self._spawn_flag()

        # Set up the two score regions.
        defs = self.map.defs
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode('region',
                           attrs={
                               'position': defs.boxes['goal1'][0:3],
                               'scale': defs.boxes['goal1'][6:9],
                               'type': 'box',
                               'materials': [self._score_region_material]
                           })))
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode('region',
                           attrs={
                               'position': defs.boxes['goal2'][0:3],
                               'scale': defs.boxes['goal2'][6:9],
                               'type': 'box',
                               'materials': [self._score_region_material]
                           })))
        ba.playsound(self._chant_sound)

    def on_begin(self) -> None:
        # FIXME: Split this up a bit.
        # pylint: disable=too-many-statements
        from bastd.actor import controlsguide
        super().on_begin()

        # Show controls help in kiosk mode.
        if ba.app.demo_mode or ba.app.arcade_mode:
            controlsguide.ControlsGuide(delay=3.0, lifespan=10.0,
                                        bright=True).autoretain()
        assert self.initialplayerinfos is not None
        abot: Type[SpazBot]
        bbot: Type[SpazBot]
        cbot: Type[SpazBot]
        if self._preset in ['rookie', 'rookie_easy']:
            self._exclude_powerups = ['curse']
            self._have_tnt = False
            abot = (BrawlerBotLite
                    if self._preset == 'rookie_easy' else BrawlerBot)
            self._bot_types_initial = [abot] * len(self.initialplayerinfos)
            bbot = (BomberBotLite
                    if self._preset == 'rookie_easy' else BomberBot)
            self._bot_types_7 = (
                [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
            cbot = (BomberBot if self._preset == 'rookie_easy' else TriggerBot)
            self._bot_types_14 = (
                [cbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
        elif self._preset == 'tournament':
            self._exclude_powerups = []
            self._have_tnt = True
            self._bot_types_initial = (
                [BrawlerBot] * (1 if len(self.initialplayerinfos) < 2 else 2))
            self._bot_types_7 = (
                [TriggerBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
            self._bot_types_14 = (
                [ChargerBot] * (1 if len(self.initialplayerinfos) < 4 else 2))
        elif self._preset in ['pro', 'pro_easy', 'tournament_pro']:
            self._exclude_powerups = ['curse']
            self._have_tnt = True
            self._bot_types_initial = [ChargerBot] * len(
                self.initialplayerinfos)
            abot = (BrawlerBot if self._preset == 'pro' else BrawlerBotLite)
            typed_bot_list: List[Type[SpazBot]] = []
            self._bot_types_7 = (
                typed_bot_list + [abot] + [BomberBot] *
                (1 if len(self.initialplayerinfos) < 3 else 2))
            bbot = (TriggerBotPro if self._preset == 'pro' else TriggerBot)
            self._bot_types_14 = (
                [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
        elif self._preset in ['uber', 'uber_easy']:
            self._exclude_powerups = []
            self._have_tnt = True
            abot = (BrawlerBotPro if self._preset == 'uber' else BrawlerBot)
            bbot = (TriggerBotPro if self._preset == 'uber' else TriggerBot)
            typed_bot_list_2: List[Type[SpazBot]] = []
            self._bot_types_initial = (typed_bot_list_2 + [StickyBot] +
                                       [abot] * len(self.initialplayerinfos))
            self._bot_types_7 = (
                [bbot] * (1 if len(self.initialplayerinfos) < 3 else 2))
            self._bot_types_14 = (
                [ExplodeyBot] * (1 if len(self.initialplayerinfos) < 3 else 2))
        else:
            raise Exception()

        self.setup_low_life_warning_sound()

        self._drop_powerups(standard_points=True)
        ba.timer(4.0, self._start_powerup_drops)

        # Make a bogus team for our bots.
        bad_team_name = self.get_team_display_string('Bad Guys')
        self._bot_team = Team()
        self._bot_team.manual_init(team_id=1,
                                   name=bad_team_name,
                                   color=(0.5, 0.4, 0.4))

        for team in [self.teams[0], self._bot_team]:
            team.score = 0

        self.update_scores()

        # Time display.
        starttime_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
        assert isinstance(starttime_ms, int)
        self._starttime_ms = starttime_ms
        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.3,
                           'text': ''
                       }))
        self._time_text_input = ba.NodeActor(
            ba.newnode('timedisplay', attrs={'showsubseconds': True}))
        self.globalsnode.connectattr('time', self._time_text_input.node,
                                     'time2')
        assert self._time_text_input.node
        assert self._time_text.node
        self._time_text_input.node.connectattr('output', self._time_text.node,
                                               'text')

        # Our TNT spawner (if applicable).
        if self._have_tnt:
            self._tntspawner = TNTSpawner(position=(0, 1, -1))

        self._bots = SpazBotSet()
        self._bot_spawn_timer = ba.Timer(1.0, self._update_bots, repeat=True)

        for bottype in self._bot_types_initial:
            self._spawn_bot(bottype)

    def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
        self._show_standard_scores_to_beat_ui(scores)

    def _on_bot_spawn(self, spaz: SpazBot) -> None:
        # We want to move to the left by default.
        spaz.target_point_default = ba.Vec3(0, 0, 0)

    def _spawn_bot(self,
                   spaz_type: Type[SpazBot],
                   immediate: bool = False) -> None:
        assert self._bot_team is not None
        pos = self.map.get_start_position(self._bot_team.id)
        self._bots.spawn_bot(spaz_type,
                             pos=pos,
                             spawn_time=0.001 if immediate else 3.0,
                             on_spawn_call=self._on_bot_spawn)

    def _update_bots(self) -> None:
        bots = self._bots.get_living_bots()
        for bot in bots:
            bot.target_flag = None

        # If we're waiting on a continue, stop here so they don't keep scoring.
        if self.is_waiting_for_continue():
            self._bots.stop_moving()
            return

        # If we've got a flag and no player are holding it, find the closest
        # bot to it, and make them the designated flag-bearer.
        assert self._flag is not None
        if self._flag.node:
            for player in self.players:
                if player.actor:
                    assert isinstance(player.actor, PlayerSpaz)
                    if (player.actor.is_alive() and player.actor.node.hold_node
                            == self._flag.node):
                        return

            flagpos = ba.Vec3(self._flag.node.position)
            closest_bot: Optional[SpazBot] = None
            closest_dist = 0.0  # Always gets assigned first time through.
            for bot in bots:
                # If a bot is picked up, he should forget about the flag.
                if bot.held_count > 0:
                    continue
                assert bot.node
                botpos = ba.Vec3(bot.node.position)
                botdist = (botpos - flagpos).length()
                if closest_bot is None or botdist < closest_dist:
                    closest_bot = bot
                    closest_dist = botdist
            if closest_bot is not None:
                closest_bot.target_flag = self._flag

    def _drop_powerup(self, index: int, poweruptype: str = None) -> None:
        if poweruptype is None:
            poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
                excludetypes=self._exclude_powerups))
        PowerupBox(position=self.map.powerup_spawn_points[index],
                   poweruptype=poweruptype).autoretain()

    def _start_powerup_drops(self) -> None:
        self._powerup_drop_timer = ba.Timer(3.0,
                                            self._drop_powerups,
                                            repeat=True)

    def _drop_powerups(self,
                       standard_points: bool = False,
                       poweruptype: str = None) -> None:
        """Generic powerup drop."""
        if standard_points:
            spawnpoints = self.map.powerup_spawn_points
            for i, _point in enumerate(spawnpoints):
                ba.timer(1.0 + i * 0.5,
                         ba.Call(self._drop_powerup, i, poweruptype))
        else:
            point = (self._powerup_center[0] + random.uniform(
                -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
                     self._powerup_center[1],
                     self._powerup_center[2] + random.uniform(
                         -self._powerup_spread[1], self._powerup_spread[1]))

            # Drop one random one somewhere.
            PowerupBox(
                position=point,
                poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
                    excludetypes=self._exclude_powerups)).autoretain()

    def _kill_flag(self) -> None:
        try:
            assert self._flag is not None
            self._flag.handlemessage(ba.DieMessage())
        except Exception:
            ba.print_exception('Error in _kill_flag.')

    def _handle_score(self) -> None:
        """ a point has been scored """
        # FIXME tidy this up
        # pylint: disable=too-many-branches

        # Our flag might stick around for a second or two;
        # we don't want it to be able to score again.
        assert self._flag is not None
        if self._flag.scored:
            return

        # See which score region it was.
        region = ba.getcollision().sourcenode
        i = None
        for i in range(len(self._score_regions)):
            if region == self._score_regions[i].node:
                break

        for team in [self.teams[0], self._bot_team]:
            assert team is not None
            if team.id == i:
                team.score += 7

                # Tell all players (or bots) to celebrate.
                if i == 0:
                    for player in team.players:
                        if player.actor:
                            player.actor.handlemessage(
                                ba.CelebrateMessage(2.0))
                else:
                    self._bots.celebrate(2.0)

        # If the good guys scored, add more enemies.
        if i == 0:
            if self.teams[0].score == 7:
                assert self._bot_types_7 is not None
                for bottype in self._bot_types_7:
                    self._spawn_bot(bottype)
            elif self.teams[0].score == 14:
                assert self._bot_types_14 is not None
                for bottype in self._bot_types_14:
                    self._spawn_bot(bottype)

        ba.playsound(self._score_sound)
        if i == 0:
            ba.playsound(self._cheer_sound)
        else:
            ba.playsound(self._boo_sound)

        # Kill the flag (it'll respawn shortly).
        self._flag.scored = True

        ba.timer(0.2, self._kill_flag)

        self.update_scores()
        light = ba.newnode('light',
                           attrs={
                               'position': ba.getcollision().position,
                               'height_attenuated': False,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
        ba.timer(1.0, light.delete)
        if i == 0:
            ba.cameraflash(duration=10.0)

    def end_game(self) -> None:
        ba.setmusic(None)
        self._bots.final_celebrate()
        ba.timer(0.001, ba.Call(self.do_end, 'defeat'))

    def on_continue(self) -> None:
        # Subtract one touchdown from the bots and get them moving again.
        assert self._bot_team is not None
        self._bot_team.score -= 7
        self._bots.start_moving()
        self.update_scores()

    def update_scores(self) -> None:
        """ update scoreboard and check for winners """
        # FIXME: tidy this up
        # pylint: disable=too-many-nested-blocks
        have_scoring_team = False
        win_score = self._score_to_win
        for team in [self.teams[0], self._bot_team]:
            assert team is not None
            assert self._scoreboard is not None
            self._scoreboard.set_team_value(team, team.score, win_score)
            if team.score >= win_score:
                if not have_scoring_team:
                    self._scoring_team = team
                    if team is self._bot_team:
                        self.continue_or_end_game()
                    else:
                        ba.setmusic(ba.MusicType.VICTORY)

                        # Completion achievements.
                        assert self._bot_team is not None
                        if self._preset in ['rookie', 'rookie_easy']:
                            self._award_achievement('Rookie Football Victory',
                                                    sound=False)
                            if self._bot_team.score == 0:
                                self._award_achievement(
                                    'Rookie Football Shutout', sound=False)
                        elif self._preset in ['pro', 'pro_easy']:
                            self._award_achievement('Pro Football Victory',
                                                    sound=False)
                            if self._bot_team.score == 0:
                                self._award_achievement('Pro Football Shutout',
                                                        sound=False)
                        elif self._preset in ['uber', 'uber_easy']:
                            self._award_achievement('Uber Football Victory',
                                                    sound=False)
                            if self._bot_team.score == 0:
                                self._award_achievement(
                                    'Uber Football Shutout', sound=False)
                            if (not self._player_has_dropped_bomb
                                    and not self._player_has_punched):
                                self._award_achievement('Got the Moves',
                                                        sound=False)
                        self._bots.stop_moving()
                        self.show_zoom_message(ba.Lstr(resource='victoryText'),
                                               scale=1.0,
                                               duration=4.0)
                        self.celebrate(10.0)
                        assert self._starttime_ms is not None
                        self._final_time_ms = int(
                            ba.time(timeformat=ba.TimeFormat.MILLISECONDS) -
                            self._starttime_ms)
                        self._time_text_timer = None
                        assert (self._time_text_input is not None
                                and self._time_text_input.node)
                        self._time_text_input.node.timemax = (
                            self._final_time_ms)

                        # FIXME: Does this still need to be deferred?
                        ba.pushcall(ba.Call(self.do_end, 'victory'))

    def do_end(self, outcome: str) -> None:
        """End the game with the specified outcome."""
        if outcome == 'defeat':
            self.fade_to_red()
        assert self._final_time_ms is not None
        scoreval = (None if outcome == 'defeat' else int(self._final_time_ms //
                                                         10))
        self.end(delay=3.0,
                 results={
                     'outcome': outcome,
                     'score': scoreval,
                     'score_order': 'decreasing',
                     'playerinfos': self.initialplayerinfos
                 })

    def handlemessage(self, msg: Any) -> Any:
        """ handle high-level game messages """
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)

            # Respawn them shortly.
            player = msg.getplayer(Player)
            assert self.initialplayerinfos is not None
            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
            player.respawn_timer = ba.Timer(
                respawn_time, ba.Call(self.spawn_player_if_exists, player))
            player.respawn_icon = RespawnIcon(player, respawn_time)

        elif isinstance(msg, SpazBotDiedMessage):

            # Every time a bad guy dies, spawn a new one.
            ba.timer(3.0, ba.Call(self._spawn_bot, (type(msg.spazbot))))

        elif isinstance(msg, SpazBotPunchedMessage):
            if self._preset in ['rookie', 'rookie_easy']:
                if msg.damage >= 500:
                    self._award_achievement('Super Punch')
            elif self._preset in ['pro', 'pro_easy']:
                if msg.damage >= 1000:
                    self._award_achievement('Super Mega Punch')

        # Respawn dead flags.
        elif isinstance(msg, FlagDiedMessage):
            assert isinstance(msg.flag, FootballFlag)
            msg.flag.respawn_timer = ba.Timer(3.0, self._spawn_flag)
            self._flag_respawn_light = ba.NodeActor(
                ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'height_attenuated': False,
                               'radius': 0.15,
                               'color': (1.0, 1.0, 0.3)
                           }))
            assert self._flag_respawn_light.node
            ba.animate(self._flag_respawn_light.node,
                       'intensity', {
                           0: 0,
                           0.25: 0.15,
                           0.5: 0
                       },
                       loop=True)
            ba.timer(3.0, self._flag_respawn_light.node.delete)
        else:
            return super().handlemessage(msg)
        return None

    def _handle_player_dropped_bomb(self, player: Spaz,
                                    bomb: ba.Actor) -> None:
        del player, bomb  # Unused.
        self._player_has_dropped_bomb = True

    def _handle_player_punched(self, player: Spaz) -> None:
        del player  # Unused.
        self._player_has_punched = True

    def spawn_player(self, player: Player) -> ba.Actor:
        spaz = self.spawn_player_spaz(player,
                                      position=self.map.get_start_position(
                                          player.team.id))
        if self._preset in ['rookie_easy', 'pro_easy', 'uber_easy']:
            spaz.impact_scale = 0.25
        spaz.add_dropped_bomb_callback(self._handle_player_dropped_bomb)
        spaz.punch_callback = self._handle_player_punched
        return spaz

    def _flash_flag_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'height_attenuated': False,
                               'color': (1, 1, 0)
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 0.25, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _spawn_flag(self) -> None:
        ba.playsound(self._swipsound)
        ba.playsound(self._whistle_sound)
        self._flash_flag_spawn()
        assert self._flag_spawn_pos is not None
        self._flag = FootballFlag(position=self._flag_spawn_pos)
コード例 #9
0
class CaptureTheFlagGame(ba.TeamGameActivity[Player, Team]):
    """Game of stealing other team's flag and returning it to your base."""

    name = 'Capture the Flag'
    description = 'Return the enemy flag to score.'
    available_settings = [
        ba.IntSetting('Score to Win', min_value=1, default=3),
        ba.IntSetting(
            'Flag Touch Return Time',
            min_value=0,
            default=0,
            increment=1,
        ),
        ba.IntSetting(
            'Flag Idle Return Time',
            min_value=5,
            default=30,
            increment=5,
        ),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
        ba.BoolSetting('Epic Mode', default=False),
    ]

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._alarmsound = ba.getsound('alarm')
        self._ticking_sound = ba.getsound('ticking')
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._last_score_time = 0
        self._all_bases_material = ba.Material()
        self._last_home_flag_notice_print_time = 0.0
        self._score_to_win = int(settings['Score to Win'])
        self._epic_mode = bool(settings['Epic Mode'])
        self._time_limit = float(settings['Time Limit'])

        self.flag_touch_return_time = float(settings['Flag Touch Return Time'])
        self.flag_idle_return_time = float(settings['Flag Idle Return Time'])

        # Base class overrides.
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC if self._epic_mode else
                              ba.MusicType.FLAG_CATCHER)

    def get_instance_description(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'Steal the enemy flag.'
        return 'Steal the enemy flag ${ARG1} times.', self._score_to_win

    def get_instance_description_short(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'return 1 flag'
        return 'return ${ARG1} flags', self._score_to_win

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:

        # Create our team instance and its initial values.

        base_pos = self.map.get_flag_position(sessionteam.id)
        Flag.project_stand(base_pos)

        ba.newnode('light',
                   attrs={
                       'position': base_pos,
                       'intensity': 0.6,
                       'height_attenuated': False,
                       'volume_intensity_scale': 0.1,
                       'radius': 0.1,
                       'color': sessionteam.color
                   })

        base_region_mat = ba.Material()
        pos = base_pos
        base_region = ba.newnode(
            'region',
            attrs={
                'position': (pos[0], pos[1] + 0.75, pos[2]),
                'scale': (0.5, 0.5, 0.5),
                'type': 'sphere',
                'materials': [base_region_mat, self._all_bases_material]
            })

        spaz_mat_no_flag_physical = ba.Material()
        spaz_mat_no_flag_collide = ba.Material()
        flagmat = ba.Material()

        team = Team(base_pos=base_pos,
                    base_region_material=base_region_mat,
                    base_region=base_region,
                    spaz_material_no_flag_physical=spaz_mat_no_flag_physical,
                    spaz_material_no_flag_collide=spaz_mat_no_flag_collide,
                    flagmaterial=flagmat)

        # Some parts of our spazzes don't collide physically with our
        # flags but generate callbacks.
        spaz_mat_no_flag_physical.add_actions(
            conditions=('they_have_material', flagmat),
            actions=(
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 lambda: self._handle_touching_own_flag(team, True)),
                ('call', 'at_disconnect',
                 lambda: self._handle_touching_own_flag(team, False)),
            ))

        # Other parts of our spazzes don't collide with our flags at all.
        spaz_mat_no_flag_collide.add_actions(
            conditions=('they_have_material', flagmat),
            actions=('modify_part_collision', 'collide', False),
        )

        # We wanna know when *any* flag enters/leaves our base.
        base_region_mat.add_actions(
            conditions=('they_have_material', FlagFactory.get().flagmaterial),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 lambda: self._handle_flag_entered_base(team)),
                ('call', 'at_disconnect',
                 lambda: self._handle_flag_left_base(team)),
            ))

        return team

    def on_team_join(self, team: Team) -> None:
        # Can't do this in create_team because the team's color/etc. have
        # not been wired up yet at that point.
        self._spawn_flag_for_team(team)
        self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        ba.timer(1.0, call=self._tick, repeat=True)

    def _spawn_flag_for_team(self, team: Team) -> None:
        team.flag = CTFFlag(team)
        team.flag_return_touches = 0
        self._flash_base(team, length=1.0)
        assert team.flag.node
        ba.playsound(self._swipsound, position=team.flag.node.position)

    def _handle_flag_entered_base(self, team: Team) -> None:
        try:
            flag = ba.getcollision().opposingnode.getdelegate(CTFFlag, True)
        except ba.NotFoundError:
            # Don't think this should logically ever happen.
            print('Error getting CTFFlag in entering-base callback.')
            return

        if flag.team is team:
            team.home_flag_at_base = True

            # If the enemy flag is already here, score!
            if team.enemy_flag_at_base:
                self._score(team)
        else:
            team.enemy_flag_at_base = True
            if team.home_flag_at_base:
                # Award points to whoever was carrying the enemy flag.
                player = flag.last_player_to_hold
                if player and player.team is team:
                    assert self.stats
                    self.stats.player_scored(player, 50, big_message=True)

                # Update score and reset flags.
                self._score(team)

            # If the home-team flag isn't here, print a message to that effect.
            else:
                # Don't want slo-mo affecting this
                curtime = ba.time(ba.TimeType.BASE)
                if curtime - self._last_home_flag_notice_print_time > 5.0:
                    self._last_home_flag_notice_print_time = curtime
                    bpos = team.base_pos
                    tval = ba.Lstr(resource='ownFlagAtYourBaseWarning')
                    tnode = ba.newnode('text',
                                       attrs={
                                           'text':
                                           tval,
                                           'in_world':
                                           True,
                                           'scale':
                                           0.013,
                                           'color': (1, 1, 0, 1),
                                           'h_align':
                                           'center',
                                           'position':
                                           (bpos[0], bpos[1] + 3.2, bpos[2])
                                       })
                    ba.timer(5.1, tnode.delete)
                    ba.animate(tnode, 'scale', {
                        0.0: 0,
                        0.2: 0.013,
                        4.8: 0.013,
                        5.0: 0
                    })

    def _tick(self) -> None:
        # If either flag is away from base and not being held, tick down its
        # respawn timer.
        for team in self.teams:
            flag = team.flag
            assert flag is not None

            if not team.home_flag_at_base and flag.held_count == 0:
                time_out_counting_down = True
                if flag.time_out_respawn_time is None:
                    flag.reset_return_times()
                assert flag.time_out_respawn_time is not None
                flag.time_out_respawn_time -= 1
                if flag.time_out_respawn_time <= 0:
                    flag.handlemessage(ba.DieMessage())
            else:
                time_out_counting_down = False

            if flag.node and flag.counter:
                pos = flag.node.position
                flag.counter.position = (pos[0], pos[1] + 1.3, pos[2])

                # If there's no self-touches on this flag, set its text
                # to show its auto-return counter.  (if there's self-touches
                # its showing that time).
                if team.flag_return_touches == 0:
                    flag.counter.text = (str(flag.time_out_respawn_time) if (
                        time_out_counting_down
                        and flag.time_out_respawn_time is not None
                        and flag.time_out_respawn_time <= 10) else '')
                    flag.counter.color = (1, 1, 1, 0.5)
                    flag.counter.scale = 0.014

    def _score(self, team: Team) -> None:
        team.score += 1
        ba.playsound(self._score_sound)
        self._flash_base(team)
        self._update_scoreboard()

        # Have teammates celebrate.
        for player in team.players:
            if player.actor:
                player.actor.handlemessage(ba.CelebrateMessage(2.0))

        # Reset all flags/state.
        for reset_team in self.teams:
            if not reset_team.home_flag_at_base:
                assert reset_team.flag is not None
                reset_team.flag.handlemessage(ba.DieMessage())
            reset_team.enemy_flag_at_base = False
        if team.score >= self._score_to_win:
            self.end_game()

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results=results, announce_delay=0.8)

    def _handle_flag_left_base(self, team: Team) -> None:
        cur_time = ba.time()
        try:
            flag = ba.getcollision().opposingnode.getdelegate(CTFFlag, True)
        except ba.NotFoundError:
            # This can happen if the flag stops touching us due to being
            # deleted; that's ok.
            return

        if flag.team is team:

            # Check times here to prevent too much flashing.
            if (team.last_flag_leave_time is None
                    or cur_time - team.last_flag_leave_time > 3.0):
                ba.playsound(self._alarmsound, position=team.base_pos)
                self._flash_base(team)
            team.last_flag_leave_time = cur_time
            team.home_flag_at_base = False
        else:
            team.enemy_flag_at_base = False

    def _touch_return_update(self, team: Team) -> None:
        # Count down only while its away from base and not being held.
        assert team.flag is not None
        if team.home_flag_at_base or team.flag.held_count > 0:
            team.touch_return_timer_ticking = None
            return  # No need to return when its at home.
        if team.touch_return_timer_ticking is None:
            team.touch_return_timer_ticking = ba.NodeActor(
                ba.newnode('sound',
                           attrs={
                               'sound': self._ticking_sound,
                               'positional': False,
                               'loop': True
                           }))
        flag = team.flag
        if flag.touch_return_time is not None:
            flag.touch_return_time -= 0.1
            if flag.counter:
                flag.counter.text = f'{flag.touch_return_time:.1f}'
                flag.counter.color = (1, 1, 0, 1)
                flag.counter.scale = 0.02

            if flag.touch_return_time <= 0.0:
                self._award_players_touching_own_flag(team)
                flag.handlemessage(ba.DieMessage())

    def _award_players_touching_own_flag(self, team: Team) -> None:
        for player in team.players:
            if player.touching_own_flag > 0:
                return_score = 10 + 5 * int(self.flag_touch_return_time)
                self.stats.player_scored(player,
                                         return_score,
                                         screenmessage=False)

    def _handle_touching_own_flag(self, team: Team, connecting: bool) -> None:
        """Called when a player touches or stops touching their own team flag.

        We keep track of when each player is touching their own flag so we
        can award points when returned.
        """
        player: Optional[Player]
        try:
            player = ba.getcollision().sourcenode.getdelegate(
                PlayerSpaz, True).getplayer(Player, True)
        except ba.NotFoundError:
            # This can happen if the player leaves but his corpse touches/etc.
            player = None

        if player:
            player.touching_own_flag += (1 if connecting else -1)

        # If return-time is zero, just kill it immediately.. otherwise keep
        # track of touches and count down.
        if float(self.flag_touch_return_time) <= 0.0:
            assert team.flag is not None
            if (connecting and not team.home_flag_at_base
                    and team.flag.held_count == 0):
                self._award_players_touching_own_flag(team)
                ba.getcollision().opposingnode.handlemessage(ba.DieMessage())

        # Takes a non-zero amount of time to return.
        else:
            if connecting:
                team.flag_return_touches += 1
                if team.flag_return_touches == 1:
                    team.touch_return_timer = ba.Timer(
                        0.1,
                        call=ba.Call(self._touch_return_update, team),
                        repeat=True)
                    team.touch_return_timer_ticking = None
            else:
                team.flag_return_touches -= 1
                if team.flag_return_touches == 0:
                    team.touch_return_timer = None
                    team.touch_return_timer_ticking = None
            if team.flag_return_touches < 0:
                ba.print_error('CTF flag_return_touches < 0')

    def _flash_base(self, team: Team, length: float = 2.0) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': team.base_pos,
                               'height_attenuated': False,
                               'radius': 0.3,
                               'color': team.color
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def spawn_player_spaz(self,
                          player: Player,
                          position: Sequence[float] = None,
                          angle: float = None) -> PlayerSpaz:
        """Intercept new spazzes and add our team material for them."""
        spaz = super().spawn_player_spaz(player, position, angle)
        player = spaz.getplayer(Player, True)
        team: Team = player.team
        player.touching_own_flag = 0
        no_physical_mats: List[ba.Material] = [
            team.spaz_material_no_flag_physical
        ]
        no_collide_mats: List[ba.Material] = [
            team.spaz_material_no_flag_collide
        ]

        # Our normal parts should still collide; just not physically
        # (so we can calc restores).
        assert spaz.node
        spaz.node.materials = list(spaz.node.materials) + no_physical_mats
        spaz.node.roller_materials = list(
            spaz.node.roller_materials) + no_physical_mats

        # Pickups and punches shouldn't hit at all though.
        spaz.node.punch_materials = list(
            spaz.node.punch_materials) + no_collide_mats
        spaz.node.pickup_materials = list(
            spaz.node.pickup_materials) + no_collide_mats
        spaz.node.extras_material = list(
            spaz.node.extras_material) + no_collide_mats
        return spaz

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score,
                                            self._score_to_win)

    def handlemessage(self, msg: Any) -> Any:

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

        elif isinstance(msg, FlagDiedMessage):
            assert isinstance(msg.flag, CTFFlag)
            ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team))

        elif isinstance(msg, FlagPickedUpMessage):

            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, CTFFlag)
            try:
                msg.flag.last_player_to_hold = msg.node.getdelegate(
                    PlayerSpaz, True).getplayer(Player, True)
            except ba.NotFoundError:
                pass

            msg.flag.held_count += 1
            msg.flag.reset_return_times()

        elif isinstance(msg, FlagDroppedMessage):
            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, CTFFlag)
            msg.flag.held_count -= 1

        else:
            super().handlemessage(msg)
コード例 #10
0
ファイル: kingofthehill.py プロジェクト: zwl1671/ballistica
class KingOfTheHillGame(ba.TeamGameActivity[Player, Team]):
    """Game where a team wins by holding a 'hill' for a set amount of time."""

    name = 'King of the Hill'
    description = 'Secure the flag for a set length of time.'
    game_settings = [
        ('Hold Time', {
            'min_value': 10,
            'default': 30,
            'increment': 10
        }),
        ('Time Limit', {
            'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
                        ('5 Minutes', 300), ('10 Minutes', 600),
                        ('20 Minutes', 1200)],
            'default':
            0
        }),
        ('Respawn Times', {
            'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
                        ('Long', 2.0), ('Longer', 4.0)],
            'default':
            1.0
        }),
    ]
    score_info = ba.ScoreInfo(label='Time Held')

    @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('king_of_the_hill')

    def __init__(self, settings: Dict[str, Any]):
        super().__init__(settings)
        shared = SharedObjects.get()
        self._scoreboard = Scoreboard()
        self._swipsound = ba.getsound('swip')
        self._tick_sound = ba.getsound('tick')
        self._countdownsounds = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_pos: Optional[Sequence[float]] = None
        self._flag_state: Optional[FlagState] = None
        self._flag: Optional[Flag] = None
        self._flag_light: Optional[ba.Node] = None
        self._scoring_team: Optional[ReferenceType[Team]] = None
        self._hold_time = int(settings['Hold Time'])
        self._time_limit = float(settings['Time Limit'])
        self._flag_region_material = ba.Material()
        self._flag_region_material.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 ba.Call(self._handle_player_flag_region_collide, True)),
                ('call', 'at_disconnect',
                 ba.Call(self._handle_player_flag_region_collide, False)),
            ))

        # Base class overrides.
        self.default_music = ba.MusicType.SCARY

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Secure the flag for ${ARG1} seconds.', self._hold_time

    def get_instance_description_short(self) -> Union[str, Sequence]:
        return 'secure the flag for ${ARG1} seconds', self._hold_time

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
        return Team(time_remaining=self._hold_time)

    def on_begin(self) -> None:
        super().on_begin()
        shared = SharedObjects.get()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        self._flag_pos = self.map.get_flag_position(None)
        ba.timer(1.0, self._tick, repeat=True)
        self._flag_state = FlagState.NEW
        Flag.project_stand(self._flag_pos)

        self._flag = Flag(position=self._flag_pos,
                          touchable=False,
                          color=(1, 1, 1))
        self._flag_light = ba.newnode('light',
                                      attrs={
                                          'position': self._flag_pos,
                                          'intensity': 0.2,
                                          'height_attenuated': False,
                                          'radius': 0.4,
                                          'color': (0.2, 0.2, 0.2)
                                      })
        # Flag region.
        flagmats = [self._flag_region_material, shared.region_material]
        ba.newnode('region',
                   attrs={
                       'position': self._flag_pos,
                       'scale': (1.8, 1.8, 1.8),
                       'type': 'sphere',
                       'materials': flagmats
                   })
        self._update_flag_state()

    def _tick(self) -> None:
        self._update_flag_state()

        # Give holding players points.
        for player in self.players:
            if player.time_at_flag > 0:
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)
        if self._scoring_team is None:
            scoring_team = None
        else:
            scoring_team = self._scoring_team()
        if scoring_team:

            if scoring_team.time_remaining > 0:
                ba.playsound(self._tick_sound)

            scoring_team.time_remaining = max(0,
                                              scoring_team.time_remaining - 1)
            self._update_scoreboard()
            if scoring_team.time_remaining > 0:
                assert self._flag is not None
                self._flag.set_score_text(str(scoring_team.time_remaining))

            # Announce numbers we have sounds for.
            try:
                ba.playsound(
                    self._countdownsounds[scoring_team.time_remaining])
            except Exception:
                pass

            # winner
            if scoring_team.time_remaining <= 0:
                self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, self._hold_time - team.time_remaining)
        self.end(results=results, announce_delay=0)

    def _update_flag_state(self) -> None:
        holding_teams = set(player.team for player in self.players
                            if player.time_at_flag)
        prev_state = self._flag_state
        assert self._flag_light
        assert self._flag is not None
        assert self._flag.node
        if len(holding_teams) > 1:
            self._flag_state = FlagState.CONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.6, 0.6, 0.1)
            self._flag.node.color = (1.0, 1.0, 0.4)
        elif len(holding_teams) == 1:
            holding_team = list(holding_teams)[0]
            self._flag_state = FlagState.HELD
            self._scoring_team = weakref.ref(holding_team)
            self._flag_light.color = ba.normalized_color(holding_team.color)
            self._flag.node.color = holding_team.color
        else:
            self._flag_state = FlagState.UNCONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.2, 0.2, 0.2)
            self._flag.node.color = (1, 1, 1)
        if self._flag_state != prev_state:
            ba.playsound(self._swipsound)

    def _handle_player_flag_region_collide(self, colliding: bool) -> None:
        try:
            player = ba.getcollision().opposingnode.getdelegate(
                PlayerSpaz, True).getplayer(Player, True)
        except ba.NotFoundError:
            return

        # Different parts of us can collide so a single value isn't enough
        # also don't count it if we're dead (flying heads shouldn't be able to
        # win the game :-)
        if colliding and player.is_alive():
            player.time_at_flag += 1
        else:
            player.time_at_flag = max(0, player.time_at_flag - 1)

        self._update_flag_state()

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.time_remaining,
                                            self._hold_time,
                                            countdown=True)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            super().handlemessage(msg)  # Augment default.

            # No longer can count as time_at_flag once dead.
            player = msg.getplayer(Player)
            player.time_at_flag = 0
            self._update_flag_state()
            self.respawn_player(player)
コード例 #11
0
class QuakeGame(ba.TeamGameActivity[Player, Team]):
    """Quake Team Game Activity"""
    name = 'Quake'
    description = 'Kill a set number of enemies to win.'
    available_settings = [
        ba.IntSetting(
            'Kills to Win Per Player',
            default=15,
            min_value=1,
            increment=1,
        ),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
                     ('5 Minutes', 300), ('10 Minutes', 600),
                     ('20 Minutes', 1200)],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[('At once', 0.0), ('Shorter', 0.25), ('Short', 0.5),
                     ('Normal', 1.0), ('Long', 2.0), ('Longer', 4.0)],
            default=1.0,
        ),
        ba.BoolSetting(
            'Speed',
            default=True,
        ),
        ba.BoolSetting(
            'Enable Jump',
            default=True,
        ),
        ba.BoolSetting(
            'Enable Pickup',
            default=True,
        ),
        ba.BoolSetting(
            'Enable Bomb',
            default=False,
        ),
        ba.BoolSetting(
            'Obstacles',
            default=True,
        ),
        ba.IntChoiceSetting(
            'Obstacles Form',
            choices=[('Cube', ObstaclesForm.CUBE.value),
                     ('Sphere', ObstaclesForm.SPHERE.value),
                     ('Random', ObstaclesForm.RANDOM.value)],
            default=0,
        ),
        ba.IntChoiceSetting(
            'Weapon Type',
            choices=[('Rocket', WeaponType.ROCKET.value),
                     ('Railgun', WeaponType.RAILGUN.value)],
            default=WeaponType.ROCKET.value,
        ),
        ba.BoolSetting(
            'Obstacles Mirror Shots',
            default=False,
        ),
        ba.IntSetting(
            'Obstacles Count',
            default=16,
            min_value=0,
            increment=2,
        ),
        ba.BoolSetting(
            'Random Obstacles Color',
            default=True,
        ),
        ba.BoolSetting(
            'Epic Mode',
            default=False,
        ),
    ]

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

    @classmethod
    def get_supported_maps(cls, sessiontype: Type[ba.Session]) -> List[str]:
        # TODO add more maps
        return ['Football Stadium', 'Monkey Face', 'Doom Shroom']

    def __init__(self, settings) -> None:
        super().__init__(settings)
        self._epic_mode = self.settings_raw['Epic Mode']
        self._score_to_win = self.settings_raw['Kills to Win Per Player']
        self._time_limit = self.settings_raw['Time Limit']
        self._obstacles_enabled = self.settings_raw['Obstacles']
        self._obstacles_count = self.settings_raw['Obstacles Count']
        self._speed_enabled = self.settings_raw['Speed']
        self._bomb_enabled = self.settings_raw['Enable Bomb']
        self._pickup_enabled = self.settings_raw['Enable Pickup']
        self._jump_enabled = self.settings_raw['Enable Jump']
        self._weapon_type = WeaponType(self.settings_raw['Weapon Type'])
        self.default_music = (ba.MusicType.EPIC
                              if self._epic_mode else ba.MusicType.GRAND_ROMP)
        self.slow_motion = self._epic_mode

        self.announce_player_deaths = True
        self._scoreboard = Scoreboard()
        self._ding_sound = ba.getsound('dingSmall')

        self._shield_dropper: Optional[ba.Timer] = None

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Kill ${ARG1} enemies.', self._score_to_win

    def on_team_join(self, team: Team) -> None:
        team.score = 0
        if self.has_begun():
            self._update_scoreboard()

    def on_begin(self) -> None:
        ba.TeamGameActivity.on_begin(self)
        self.drop_shield()
        self._shield_dropper = ba.Timer(8,
                                        ba.WeakCall(self.drop_shield),
                                        repeat=True)
        self.setup_standard_time_limit(self._time_limit)
        if self._obstacles_enabled:
            count = self._obstacles_count
            gamemap = self.map.getname()
            for i in range(count):  # TODO: tidy up around here
                if gamemap == 'Football Stadium':
                    radius = (random.uniform(-10, 1),
                              6,
                              random.uniform(-4.5, 4.5)) \
                        if i > count / 2 else (
                        random.uniform(10, 1), 6, random.uniform(-4.5, 4.5))
                else:
                    radius = (random.uniform(-10, 1),
                              6,
                              random.uniform(-8, 8)) \
                        if i > count / 2 else (
                        random.uniform(10, 1), 6, random.uniform(-8, 8))

                Obstacle(
                    position=radius,
                    mirror=self.settings_raw['Obstacles mirror shots'],
                    form=self.settings_raw['Obstacles form']).autoretain()

        self._update_scoreboard()

    def drop_shield(self) -> None:
        """Drop a shield powerup in random place"""
        # FIXME: should use map defs
        shield = PowerupBox(poweruptype='shield',
                            position=(random.uniform(-10, 10), 6,
                                      random.uniform(-5, 5))).autoretain()

        ba.playsound(self._ding_sound)

        p_light = ba.newnode('light',
                             owner=shield.node,
                             attrs={
                                 'position': (0, 0, 0),
                                 'color': (0.3, 0.0, 0.4),
                                 'radius': 0.3,
                                 'intensity': 2,
                                 'volume_intensity_scale': 10.0
                             })

        shield.node.connectattr('position', p_light, 'position')

        ba.animate(p_light, 'intensity', {0: 2, 8: 0})

    def spawn_player(self, player: Player) -> None:
        spaz = self.spawn_player_spaz(player)
        if self._weapon_type == WeaponType.ROCKET:
            quake.rocket.RocketLauncher().give(spaz)
        elif self._weapon_type == WeaponType.RAILGUN:
            quake.railgun.Railgun().give(spaz)
        spaz.connect_controls_to_player(enable_jump=self._jump_enabled,
                                        enable_pickup=self._pickup_enabled,
                                        enable_bomb=self._bomb_enabled,
                                        enable_fly=False)

        spaz.node.hockey = self._speed_enabled
        spaz.spaz_light = ba.newnode('light',
                                     owner=spaz.node,
                                     attrs={
                                         'position': (0, 0, 0),
                                         'color': spaz.node.color,
                                         'radius': 0.12,
                                         'intensity': 1,
                                         'volume_intensity_scale': 10.0
                                     })

        spaz.node.connectattr('position', spaz.spaz_light, 'position')

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            ba.TeamGameActivity.handlemessage(self, msg)
            player = msg.getplayer(Player)
            self.respawn_player(player)
            killer = msg.getkillerplayer(Player)
            if killer is None:
                return

            # handle team-kills
            if killer.team is player.team:
                # in free-for-all, killing yourself loses you a point
                if isinstance(self.session, ba.FreeForAllSession):
                    new_score = player.team.score - 1
                    new_score = max(0, new_score)
                    player.team.score = new_score
                # in teams-mode it gives a point to the other team
                else:
                    ba.playsound(self._ding_sound)
                    for team in self.teams:
                        if team is not killer.team:
                            team.score += 1
            # killing someone on another team nets a kill
            else:
                killer.team.score += 1
                ba.playsound(self._ding_sound)
                # in FFA show our score since its hard to find on
                # the scoreboard
                assert killer.actor is not None
                # noinspection PyUnresolvedReferences
                killer.actor.set_score_text(str(killer.team.score) + '/' +
                                            str(self._score_to_win),
                                            color=killer.team.color,
                                            flash=True)

            self._update_scoreboard()

            # if someone has won, set a timer to end shortly
            # (allows the dust to clear and draws to occur if
            # deaths are close enough)
            if any(team.score >= self._score_to_win for team in self.teams):
                ba.timer(0.5, self.end_game)

        else:
            ba.TeamGameActivity.handlemessage(self, msg)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score,
                                            self._score_to_win)

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)

        self.end(results=results)
コード例 #12
0
ファイル: deathmatch.py プロジェクト: bseditor/ballistica
class DeathMatchGame(ba.TeamGameActivity):
    """A game type based on acquiring kills."""
    @classmethod
    def get_name(cls) -> str:
        return 'Death Match'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Kill a set number of enemies to win.'

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        settings: List[Tuple[str, Dict[str, Any]]] = [
            ('Kills to Win Per Player', {
                'min_value': 1,
                'default': 5,
                'increment': 1
            }),
            ('Time Limit', {
                 'choices':
                 [('None', 0),
                  ('1 Minute', 60), ('2 Minutes', 120),
                  ('5 Minutes', 300), ('10 Minutes', 600),
                  ('20 Minutes', 1200)],
                 'default': 0
            }),
            ('Respawn Times', {
                 'choices':
                 [('Shorter', 0.25), ('Short', 0.5),
                  ('Normal', 1.0), ('Long', 2.0),
                  ('Longer', 4.0)],
                 'default': 1.0
            }),
            ('Epic Mode', {
                'default': False
            })
        ]  # yapf: disable

        # In teams mode, a suicide gives a point to the other team, but in
        # free-for-all it subtracts from your own score. By default we clamp
        # this at zero to benefit new players, but pro players might like to
        # be able to go negative. (to avoid a strategy of just
        # suiciding until you get a good drop)
        if issubclass(sessiontype, ba.FreeForAllSession):
            settings.append(('Allow Negative Scores', {'default': False}))

        return settings

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        if self.settings_raw['Epic Mode']:
            self.slow_motion = True

        # Print messages when players die since it matters here.
        self.announce_player_deaths = True

        self._scoreboard = Scoreboard()
        self._score_to_win = None
        self._dingsound = ba.getsound('dingSmall')

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Crush ${ARG1} of your enemies.', self._score_to_win

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        return 'kill ${ARG1} enemies', self._score_to_win

    def on_transition_in(self) -> None:
        self.default_music = (ba.MusicType.EPIC
                              if self.settings_raw['Epic Mode'] else
                              ba.MusicType.TO_THE_DEATH)
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['score'] = 0
        if self.has_begun():
            self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings_raw['Time Limit'])
        self.setup_standard_powerup_drops()
        if self.teams:
            self._score_to_win = (
                self.settings_raw['Kills to Win Per Player'] *
                max(1, max(len(t.players) for t in self.teams)))
        else:
            self._score_to_win = self.settings_raw['Kills to Win Per Player']
        self._update_scoreboard()

    def handlemessage(self, msg: Any) -> Any:
        # pylint: disable=too-many-branches

        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):

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

            player = msg.spaz.player
            self.respawn_player(player)

            killer = msg.killerplayer
            if killer is None:
                return

            # Handle team-kills.
            if killer.team is player.team:

                # In free-for-all, killing yourself loses you a point.
                if isinstance(self.session, ba.FreeForAllSession):
                    new_score = player.team.gamedata['score'] - 1
                    if not self.settings_raw['Allow Negative Scores']:
                        new_score = max(0, new_score)
                    player.team.gamedata['score'] = new_score

                # In teams-mode it gives a point to the other team.
                else:
                    ba.playsound(self._dingsound)
                    for team in self.teams:
                        if team is not killer.team:
                            team.gamedata['score'] += 1

            # Killing someone on another team nets a kill.
            else:
                killer.team.gamedata['score'] += 1
                ba.playsound(self._dingsound)

                # In FFA show scores since its hard to find on the scoreboard.
                try:
                    if isinstance(killer.actor, stdspaz.Spaz):
                        killer.actor.set_score_text(
                            str(killer.team.gamedata['score']) + '/' +
                            str(self._score_to_win),
                            color=killer.team.color,
                            flash=True)
                except Exception:
                    pass

            self._update_scoreboard()

            # If someone has won, set a timer to end shortly.
            # (allows the dust to clear and draws to occur if deaths are
            # close enough)
            if any(team.gamedata['score'] >= self._score_to_win
                   for team in self.teams):
                ba.timer(0.5, self.end_game)

        else:
            super().handlemessage(msg)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'],
                                            self._score_to_win)

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results=results)
コード例 #13
0
class ChosenOneGame(ba.TeamGameActivity):
    """
    Game involving trying to remain the one 'chosen one'
    for a set length of time while everyone else tries to
    kill you and become the chosen one themselves.
    """

    @classmethod
    def get_name(cls) -> str:
        return 'Chosen One'

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

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return ('Be the chosen one for a length of time to win.\n'
                'Kill the chosen one to become it.')

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [('Chosen One Time', {
            'min_value': 10,
            'default': 30,
            'increment': 10
        }), ('Chosen One Gets Gloves', {
            'default': True
        }), ('Chosen One Gets Shield', {
            'default': False
        }),
                ('Time Limit', {
                    'choices': [('None', 0), ('1 Minute', 60),
                                ('2 Minutes', 120), ('5 Minutes', 300),
                                ('10 Minutes', 600), ('20 Minutes', 1200)],
                    'default': 0
                }),
                ('Respawn Times', {
                    'choices': [('Shorter', 0.25), ('Short', 0.5),
                                ('Normal', 1.0), ('Long', 2.0),
                                ('Longer', 4.0)],
                    'default': 1.0
                }), ('Epic Mode', {
                    'default': False
                })]

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        if self.settings['Epic Mode']:
            self.slow_motion = True
        self._scoreboard = Scoreboard()
        self._chosen_one_player: Optional[ba.Player] = None
        self._swipsound = ba.getsound('swip')
        self._countdownsounds: Dict[int, ba.Sound] = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_spawn_pos: Optional[Sequence[float]] = None
        self._reset_region_material: Optional[ba.Material] = None
        self._flag: Optional[flag.Flag] = None
        self._reset_region: Optional[ba.Node] = None

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'There can be only one.'

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

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['time_remaining'] = self.settings['Chosen One Time']
        self._update_scoreboard()

    def on_player_leave(self, player: ba.Player) -> None:
        ba.TeamGameActivity.on_player_leave(self, player)
        if self._get_chosen_one_player() is player:
            self._set_chosen_one_player(None)

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()
        self._flag_spawn_pos = self.map.get_flag_position(None)
        self.project_flag_stand(self._flag_spawn_pos)
        self._set_chosen_one_player(None)

        pos = self._flag_spawn_pos
        ba.timer(1.0, call=self._tick, repeat=True)

        mat = self._reset_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',
                                  ba.WeakCall(self._handle_reset_collide))))

        self._reset_region = ba.newnode('region',
                                        attrs={
                                            'position': (pos[0], pos[1] + 0.75,
                                                         pos[2]),
                                            'scale': (0.5, 0.5, 0.5),
                                            'type': 'sphere',
                                            'materials': [mat]
                                        })

    def _get_chosen_one_player(self) -> Optional[ba.Player]:
        if self._chosen_one_player:
            return self._chosen_one_player
        return None

    def _handle_reset_collide(self) -> None:
        # If we have a chosen one, ignore these.
        if self._get_chosen_one_player() is not None:
            return
        try:
            player = (ba.get_collision_info(
                'opposing_node').getdelegate().getplayer())
        except Exception:
            return
        if player is not None and player.is_alive():
            self._set_chosen_one_player(player)

    def _flash_flag_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'color': (1, 1, 1),
                               'radius': 0.3,
                               'height_attenuated': False
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _tick(self) -> None:

        # Give the chosen one points.
        player = self._get_chosen_one_player()
        if player is not None:

            # This shouldn't happen, but just in case.
            if not player.is_alive():
                ba.print_error('got dead player as chosen one in _tick')
                self._set_chosen_one_player(None)
            else:
                scoring_team = player.team
                assert self.stats
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)

                scoring_team.gamedata['time_remaining'] = max(
                    0, scoring_team.gamedata['time_remaining'] - 1)

                # show the count over their head
                try:
                    if scoring_team.gamedata['time_remaining'] > 0:
                        if isinstance(player.actor, spaz.Spaz):
                            player.actor.set_score_text(
                                str(scoring_team.gamedata['time_remaining']))
                except Exception:
                    pass

                self._update_scoreboard()

                # announce numbers we have sounds for
                try:
                    ba.playsound(self._countdownsounds[
                        scoring_team.gamedata['time_remaining']])
                except Exception:
                    pass

                # Winner!
                if scoring_team.gamedata['time_remaining'] <= 0:
                    self.end_game()

        else:
            # (player is None)
            # This shouldn't happen, but just in case.
            # (Chosen-one player ceasing to exist should
            # trigger on_player_leave which resets chosen-one)
            if self._chosen_one_player is not None:
                ba.print_error('got nonexistent player as chosen one in _tick')
                self._set_chosen_one_player(None)

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(
                team, self.settings['Chosen One Time'] -
                team.gamedata['time_remaining'])
        self.end(results=results, announce_delay=0)

    def _set_chosen_one_player(self, player: Optional[ba.Player]) -> None:
        try:
            for p_other in self.players:
                p_other.gamedata['chosen_light'] = None
            ba.playsound(self._swipsound)
            if not player:
                assert self._flag_spawn_pos is not None
                self._flag = flag.Flag(color=(1, 0.9, 0.2),
                                       position=self._flag_spawn_pos,
                                       touchable=False)
                self._chosen_one_player = None

                # Create a light to highlight the flag;
                # this will go away when the flag dies.
                ba.newnode('light',
                           owner=self._flag.node,
                           attrs={
                               'position': self._flag_spawn_pos,
                               'intensity': 0.6,
                               'height_attenuated': False,
                               'volume_intensity_scale': 0.1,
                               'radius': 0.1,
                               'color': (1.2, 1.2, 0.4)
                           })

                # Also an extra momentary flash.
                self._flash_flag_spawn()
            else:
                if player.actor is not None:
                    self._flag = None
                    self._chosen_one_player = player

                    if player.actor:
                        if self.settings['Chosen One Gets Shield']:
                            player.actor.handlemessage(
                                ba.PowerupMessage('shield'))
                        if self.settings['Chosen One Gets Gloves']:
                            player.actor.handlemessage(
                                ba.PowerupMessage('punch'))

                        # Use a color that's partway between their team color
                        # and white.
                        color = [
                            0.3 + c * 0.7
                            for c in ba.normalized_color(player.team.color)
                        ]
                        light = player.gamedata['chosen_light'] = ba.NodeActor(
                            ba.newnode('light',
                                       attrs={
                                           'intensity': 0.6,
                                           'height_attenuated': False,
                                           'volume_intensity_scale': 0.1,
                                           'radius': 0.13,
                                           'color': color
                                       }))

                        assert light.node
                        ba.animate(light.node,
                                   'intensity', {
                                       0: 1.0,
                                       0.2: 0.4,
                                       0.4: 1.0
                                   },
                                   loop=True)
                        assert isinstance(player.actor, playerspaz.PlayerSpaz)
                        player.actor.node.connectattr('position', light.node,
                                                      'position')

        except Exception:
            ba.print_exception('EXC in _set_chosen_one_player')

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            # Augment standard behavior.
            super().handlemessage(msg)
            player = msg.spaz.player
            if player is self._get_chosen_one_player():
                killerplayer = msg.killerplayer
                self._set_chosen_one_player(None if (
                    killerplayer is None or killerplayer is player
                    or not killerplayer.is_alive()) else killerplayer)
            self.respawn_player(player)
        else:
            super().handlemessage(msg)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.gamedata['time_remaining'],
                                            self.settings['Chosen One Time'],
                                            countdown=True)
コード例 #14
0
ファイル: hockey.py プロジェクト: Dmitry450/ballistica
class HockeyGame(ba.TeamGameActivity):
    """Ice hockey game."""
    @classmethod
    def get_name(cls) -> str:
        return 'Hockey'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Score some goals.'

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [
            ("Score to Win", {
                'min_value': 1, 'default': 1, 'increment': 1
            }),
            ("Time Limit", {
                'choices': [('None', 0), ('1 Minute', 60),
                            ('2 Minutes', 120), ('5 Minutes', 300),
                            ('10 Minutes', 600), ('20 Minutes', 1200)],
                'default': 0
            }),
            ("Respawn Times", {
                'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
                            ('Long', 2.0), ('Longer', 4.0)],
                'default': 1.0
            })]  # yapf: disable

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        from bastd.actor import powerupbox
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._cheer_sound = ba.getsound("cheer")
        self._chant_sound = ba.getsound("crowdChant")
        self._foghorn_sound = ba.getsound("foghorn")
        self._swipsound = ba.getsound("swip")
        self._whistle_sound = ba.getsound("refWhistle")
        self.puck_model = ba.getmodel("puck")
        self.puck_tex = ba.gettexture("puckColor")
        self._puck_sound = ba.getsound("metalHit")
        self.puck_material = ba.Material()
        self.puck_material.add_actions(actions=(("modify_part_collision",
                                                 "friction", 0.5)))
        self.puck_material.add_actions(
            conditions=("they_have_material", ba.sharedobj('pickup_material')),
            actions=("modify_part_collision", "collide", False))
        self.puck_material.add_actions(
            conditions=(("we_are_younger_than", 100),
                        'and', ("they_have_material",
                                ba.sharedobj('object_material'))),
            actions=("modify_node_collision", "collide", False))
        self.puck_material.add_actions(
            conditions=("they_have_material",
                        ba.sharedobj('footing_material')),
            actions=("impact_sound", self._puck_sound, 0.2, 5))

        # Keep track of which player last touched the puck
        self.puck_material.add_actions(
            conditions=("they_have_material", ba.sharedobj('player_material')),
            actions=(("call", "at_connect",
                      self._handle_puck_player_collide), ))

        # We want the puck to kill powerups; not get stopped by them
        self.puck_material.add_actions(
            conditions=("they_have_material",
                        powerupbox.get_factory().powerup_material),
            actions=(("modify_part_collision", "physical", False),
                     ("message", "their_node", "at_connect", ba.DieMessage())))
        self._score_region_material = ba.Material()
        self._score_region_material.add_actions(
            conditions=("they_have_material", self.puck_material),
            actions=(("modify_part_collision", "collide",
                      True), ("modify_part_collision", "physical", False),
                     ("call", "at_connect", self._handle_score)))
        self._puck_spawn_pos: Optional[Sequence[float]] = None
        self._score_regions: Optional[List[ba.NodeActor]] = None
        self._puck: Optional[Puck] = None

    def get_instance_description(self) -> Union[str, Sequence]:
        if self.settings['Score to Win'] == 1:
            return 'Score a goal.'
        return 'Score ${ARG1} goals.', self.settings['Score to Win']

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        if self.settings['Score to Win'] == 1:
            return 'score a goal'
        return 'score ${ARG1} goals', self.settings['Score to Win']

    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.HOCKEY
        super().on_transition_in()

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

        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()
        self._puck_spawn_pos = self.map.get_flag_position(None)
        self._spawn_puck()

        # set up the two score regions
        defs = self.map.defs
        self._score_regions = []
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode("region",
                           attrs={
                               'position': defs.boxes["goal1"][0:3],
                               'scale': defs.boxes["goal1"][6:9],
                               'type': "box",
                               'materials': [self._score_region_material]
                           })))
        self._score_regions.append(
            ba.NodeActor(
                ba.newnode("region",
                           attrs={
                               'position': defs.boxes["goal2"][0:3],
                               'scale': defs.boxes["goal2"][6:9],
                               'type': "box",
                               'materials': [self._score_region_material]
                           })))
        self._update_scoreboard()
        ba.playsound(self._chant_sound)

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

    def _handle_puck_player_collide(self) -> None:
        try:
            pucknode, playernode = ba.get_collision_info(
                'source_node', 'opposing_node')
            puck = pucknode.getdelegate()
            player = playernode.getdelegate().getplayer()
        except Exception:
            player = puck = None
        if player and puck:
            puck.last_players_to_touch[player.team.get_id()] = player

    def _kill_puck(self) -> None:
        self._puck = None

    def _handle_score(self) -> None:
        """A point has been scored."""

        assert self._puck is not None
        assert self._score_regions is not None

        # Our puck might stick around for a second or two
        # we don't want it to be able to score again.
        if self._puck.scored:
            return

        region = ba.get_collision_info("source_node")
        index = 0
        for index in range(len(self._score_regions)):
            if region == self._score_regions[index].node:
                break

        for team in self.teams:
            if team.get_id() == index:
                scoring_team = team
                team.gamedata['score'] += 1

                # Tell all players to celebrate.
                for player in team.players:
                    if player.actor:
                        player.actor.handlemessage(ba.CelebrateMessage(2.0))

                # If we've got the player from the scoring team that last
                # touched us, give them points.
                if (scoring_team.get_id() in self._puck.last_players_to_touch
                        and self._puck.last_players_to_touch[
                            scoring_team.get_id()]):
                    self.stats.player_scored(self._puck.last_players_to_touch[
                        scoring_team.get_id()],
                                             100,
                                             big_message=True)

                # End game if we won.
                if team.gamedata['score'] >= self.settings['Score to Win']:
                    self.end_game()

        ba.playsound(self._foghorn_sound)
        ba.playsound(self._cheer_sound)

        self._puck.scored = True

        # Kill the puck (it'll respawn itself shortly).
        ba.timer(1.0, self._kill_puck)

        light = ba.newnode('light',
                           attrs={
                               'position': ba.get_collision_info('position'),
                               'height_attenuated': False,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0: 0, 0.5: 1, 1.0: 0}, loop=True)
        ba.timer(1.0, light.delete)

        ba.cameraflash(duration=10.0)
        self._update_scoreboard()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results=results)

    def _update_scoreboard(self) -> None:
        """ update scoreboard and check for winners """
        winscore = self.settings['Score to Win']
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'],
                                            winscore)

    def handlemessage(self, msg: Any) -> Any:

        # Respawn dead players if they're still in the game.
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            # Augment standard behavior...
            super().handlemessage(msg)
            self.respawn_player(msg.spaz.player)

        # Respawn dead pucks.
        elif isinstance(msg, PuckDeathMessage):
            if not self.has_ended():
                ba.timer(3.0, self._spawn_puck)
        else:
            super().handlemessage(msg)

    def _flash_puck_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._puck_spawn_pos,
                               'height_attenuated': False,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 1, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _spawn_puck(self) -> None:
        ba.playsound(self._swipsound)
        ba.playsound(self._whistle_sound)
        self._flash_puck_spawn()
        assert self._puck_spawn_pos is not None
        self._puck = Puck(position=self._puck_spawn_pos)
コード例 #15
0
class StickyStormCTFGame(ba.TeamGameActivity[Player, Team]):
    """Game of stealing other team's flag and returning it to your base."""

    name = 'Sticky Storm CTF'
    description = 'Return the enemy flag to score in a sticky rain'
    available_settings = [
        ba.IntSetting('Score to Win', min_value=1, default=3),
        ba.IntSetting(
            'Flag Touch Return Time',
            min_value=0,
            default=0,
            increment=1,
        ),
        ba.IntSetting(
            'Flag Idle Return Time',
            min_value=5,
            default=30,
            increment=5,
        ),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
        ba.BoolSetting('Epic Mode', default=False),
    ]

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._alarmsound = ba.getsound('alarm')
        self._ticking_sound = ba.getsound('ticking')
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._last_score_time = 0
        self._all_bases_material = ba.Material()
        self._last_home_flag_notice_print_time = 0.0
        self._score_to_win = int(settings['Score to Win'])
        self._epic_mode = bool(settings['Epic Mode'])
        self._time_limit = float(settings['Time Limit'])

        self.flag_touch_return_time = float(settings['Flag Touch Return Time'])
        self.flag_idle_return_time = float(settings['Flag Idle Return Time'])
        self._meteor_time = 2.0

        # Base class overrides.
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC if self._epic_mode else
                              ba.MusicType.FLAG_CATCHER)

    def get_instance_description(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'Steal the enemy flag.'
        return 'Steal the enemy flag ${ARG1} times.', self._score_to_win

    def get_instance_description_short(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'return 1 flag'
        return 'return ${ARG1} flags', self._score_to_win

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:

        # Create our team instance and its initial values.

        base_pos = self.map.get_flag_position(sessionteam.id)
        Flag.project_stand(base_pos)

        ba.newnode('light',
                   attrs={
                       'position': base_pos,
                       'intensity': 0.6,
                       'height_attenuated': False,
                       'volume_intensity_scale': 0.1,
                       'radius': 0.1,
                       'color': sessionteam.color
                   })

        base_region_mat = ba.Material()
        pos = base_pos
        base_region = ba.newnode(
            'region',
            attrs={
                'position': (pos[0], pos[1] + 0.75, pos[2]),
                'scale': (0.5, 0.5, 0.5),
                'type': 'sphere',
                'materials': [base_region_mat, self._all_bases_material]
            })

        spaz_mat_no_flag_physical = ba.Material()
        spaz_mat_no_flag_collide = ba.Material()
        flagmat = ba.Material()

        team = Team(base_pos=base_pos,
                    base_region_material=base_region_mat,
                    base_region=base_region,
                    spaz_material_no_flag_physical=spaz_mat_no_flag_physical,
                    spaz_material_no_flag_collide=spaz_mat_no_flag_collide,
                    flagmaterial=flagmat)

        # Some parts of our spazzes don't collide physically with our
        # flags but generate callbacks.
        spaz_mat_no_flag_physical.add_actions(
            conditions=('they_have_material', flagmat),
            actions=(
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 lambda: self._handle_touching_own_flag(team, True)),
                ('call', 'at_disconnect',
                 lambda: self._handle_touching_own_flag(team, False)),
            ))

        # Other parts of our spazzes don't collide with our flags at all.
        spaz_mat_no_flag_collide.add_actions(
            conditions=('they_have_material', flagmat),
            actions=('modify_part_collision', 'collide', False),
        )

        # We wanna know when *any* flag enters/leaves our base.
        base_region_mat.add_actions(
            conditions=('they_have_material', FlagFactory.get().flagmaterial),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 lambda: self._handle_flag_entered_base(team)),
                ('call', 'at_disconnect',
                 lambda: self._handle_flag_left_base(team)),
            ))

        return team

    def on_team_join(self, team: Team) -> None:
        # Can't do this in create_team because the team's color/etc. have
        # not been wired up yet at that point.
        self._spawn_flag_for_team(team)
        self._update_scoreboard()

    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.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        ba.timer(1.0, call=self._tick, repeat=True)

    def _spawn_flag_for_team(self, team: Team) -> None:
        team.flag = StickyStormCTFFlag(team)
        team.flag_return_touches = 0
        self._flash_base(team, length=1.0)
        assert team.flag.node
        ba.playsound(self._swipsound, position=team.flag.node.position)

    def _handle_flag_entered_base(self, team: Team) -> None:
        try:
            flag = ba.getcollision().opposingnode.getdelegate(
                StickyStormCTFFlag, True)
        except ba.NotFoundError:
            # Don't think this should logically ever happen.
            print(
                'Error getting StickyStormCTFFlag in entering-base callback.')
            return

        if flag.team is team:
            team.home_flag_at_base = True

            # If the enemy flag is already here, score!
            if team.enemy_flag_at_base:
                self._score(team)
        else:
            team.enemy_flag_at_base = True
            if team.home_flag_at_base:
                # Award points to whoever was carrying the enemy flag.
                player = flag.last_player_to_hold
                if player and player.team is team:
                    assert self.stats
                    self.stats.player_scored(player, 50, big_message=True)

                # Update score and reset flags.
                self._score(team)

            # If the home-team flag isn't here, print a message to that effect.
            else:
                # Don't want slo-mo affecting this
                curtime = ba.time(ba.TimeType.BASE)
                if curtime - self._last_home_flag_notice_print_time > 5.0:
                    self._last_home_flag_notice_print_time = curtime
                    bpos = team.base_pos
                    tval = ba.Lstr(resource='ownFlagAtYourBaseWarning')
                    tnode = ba.newnode('text',
                                       attrs={
                                           'text':
                                           tval,
                                           'in_world':
                                           True,
                                           'scale':
                                           0.013,
                                           'color': (1, 1, 0, 1),
                                           'h_align':
                                           'center',
                                           'position':
                                           (bpos[0], bpos[1] + 3.2, bpos[2])
                                       })
                    ba.timer(5.1, tnode.delete)
                    ba.animate(tnode, 'scale', {
                        0.0: 0,
                        0.2: 0.013,
                        4.8: 0.013,
                        5.0: 0
                    })

    def _tick(self) -> None:
        # If either flag is away from base and not being held, tick down its
        # respawn timer.
        for team in self.teams:
            flag = team.flag
            assert flag is not None

            if not team.home_flag_at_base and flag.held_count == 0:
                time_out_counting_down = True
                if flag.time_out_respawn_time is None:
                    flag.reset_return_times()
                assert flag.time_out_respawn_time is not None
                flag.time_out_respawn_time -= 1
                if flag.time_out_respawn_time <= 0:
                    flag.handlemessage(ba.DieMessage())
            else:
                time_out_counting_down = False

            if flag.node and flag.counter:
                pos = flag.node.position
                flag.counter.position = (pos[0], pos[1] + 1.3, pos[2])

                # If there's no self-touches on this flag, set its text
                # to show its auto-return counter.  (if there's self-touches
                # its showing that time).
                if team.flag_return_touches == 0:
                    flag.counter.text = (str(flag.time_out_respawn_time) if (
                        time_out_counting_down
                        and flag.time_out_respawn_time is not None
                        and flag.time_out_respawn_time <= 10) else '')
                    flag.counter.color = (1, 1, 1, 0.5)
                    flag.counter.scale = 0.014

    def _score(self, team: Team) -> None:
        team.score += 1
        ba.playsound(self._score_sound)
        self._flash_base(team)
        self._update_scoreboard()

        # Have teammates celebrate.
        for player in team.players:
            if player.actor:
                player.actor.handlemessage(ba.CelebrateMessage(2.0))

        # Reset all flags/state.
        for reset_team in self.teams:
            if not reset_team.home_flag_at_base:
                assert reset_team.flag is not None
                reset_team.flag.handlemessage(ba.DieMessage())
            reset_team.enemy_flag_at_base = False
        if team.score >= self._score_to_win:
            self.end_game()

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results=results, announce_delay=0.8)

    def _handle_flag_left_base(self, team: Team) -> None:
        cur_time = ba.time()
        try:
            flag = ba.getcollision().opposingnode.getdelegate(
                StickyStormCTFFlag, True)
        except ba.NotFoundError:
            # This can happen if the flag stops touching us due to being
            # deleted; that's ok.
            return

        if flag.team is team:

            # Check times here to prevent too much flashing.
            if (team.last_flag_leave_time is None
                    or cur_time - team.last_flag_leave_time > 3.0):
                ba.playsound(self._alarmsound, position=team.base_pos)
                self._flash_base(team)
            team.last_flag_leave_time = cur_time
            team.home_flag_at_base = False
        else:
            team.enemy_flag_at_base = False

    def _touch_return_update(self, team: Team) -> None:
        # Count down only while its away from base and not being held.
        assert team.flag is not None
        if team.home_flag_at_base or team.flag.held_count > 0:
            team.touch_return_timer_ticking = None
            return  # No need to return when its at home.
        if team.touch_return_timer_ticking is None:
            team.touch_return_timer_ticking = ba.NodeActor(
                ba.newnode('sound',
                           attrs={
                               'sound': self._ticking_sound,
                               'positional': False,
                               'loop': True
                           }))
        flag = team.flag
        if flag.touch_return_time is not None:
            flag.touch_return_time -= 0.1
            if flag.counter:
                flag.counter.text = f'{flag.touch_return_time:.1f}'
                flag.counter.color = (1, 1, 0, 1)
                flag.counter.scale = 0.02

            if flag.touch_return_time <= 0.0:
                self._award_players_touching_own_flag(team)
                flag.handlemessage(ba.DieMessage())

    def _award_players_touching_own_flag(self, team: Team) -> None:
        for player in team.players:
            if player.touching_own_flag > 0:
                return_score = 10 + 5 * int(self.flag_touch_return_time)
                self.stats.player_scored(player,
                                         return_score,
                                         screenmessage=False)

    def _handle_touching_own_flag(self, team: Team, connecting: bool) -> None:
        """Called when a player touches or stops touching their own team flag.

        We keep track of when each player is touching their own flag so we
        can award points when returned.
        """
        player: Optional[Player]
        try:
            player = ba.getcollision().sourcenode.getdelegate(
                PlayerSpaz, True).getplayer(Player, True)
        except ba.NotFoundError:
            # This can happen if the player leaves but his corpse touches/etc.
            player = None

        if player:
            player.touching_own_flag += (1 if connecting else -1)

        # If return-time is zero, just kill it immediately.. otherwise keep
        # track of touches and count down.
        if float(self.flag_touch_return_time) <= 0.0:
            assert team.flag is not None
            if (connecting and not team.home_flag_at_base
                    and team.flag.held_count == 0):
                self._award_players_touching_own_flag(team)
                ba.getcollision().opposingnode.handlemessage(ba.DieMessage())

        # Takes a non-zero amount of time to return.
        else:
            if connecting:
                team.flag_return_touches += 1
                if team.flag_return_touches == 1:
                    team.touch_return_timer = ba.Timer(
                        0.1,
                        call=ba.Call(self._touch_return_update, team),
                        repeat=True)
                    team.touch_return_timer_ticking = None
            else:
                team.flag_return_touches -= 1
                if team.flag_return_touches == 0:
                    team.touch_return_timer = None
                    team.touch_return_timer_ticking = None
            if team.flag_return_touches < 0:
                ba.print_error('CTF flag_return_touches < 0')

    def _flash_base(self, team: Team, length: float = 2.0) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': team.base_pos,
                               'height_attenuated': False,
                               'radius': 0.3,
                               'color': team.color
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def spawn_player_spaz(self,
                          player: Player,
                          position: Sequence[float] = None,
                          angle: float = None) -> PlayerSpaz:
        """Intercept new spazzes and add our team material for them."""
        spaz = super().spawn_player_spaz(player, position, angle)
        player = spaz.getplayer(Player, True)
        team: Team = player.team
        player.touching_own_flag = 0
        no_physical_mats: List[ba.Material] = [
            team.spaz_material_no_flag_physical
        ]
        no_collide_mats: List[ba.Material] = [
            team.spaz_material_no_flag_collide
        ]

        # Our normal parts should still collide; just not physically
        # (so we can calc restores).
        assert spaz.node
        spaz.node.materials = list(spaz.node.materials) + no_physical_mats
        spaz.node.roller_materials = list(
            spaz.node.roller_materials) + no_physical_mats

        # Pickups and punches shouldn't hit at all though.
        spaz.node.punch_materials = list(
            spaz.node.punch_materials) + no_collide_mats
        spaz.node.pickup_materials = list(
            spaz.node.pickup_materials) + no_collide_mats
        spaz.node.extras_material = list(
            spaz.node.extras_material) + no_collide_mats
        return spaz

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score,
                                            self._score_to_win)

    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 handlemessage(self, msg: Any) -> Any:

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

        elif isinstance(msg, FlagDiedMessage):
            assert isinstance(msg.flag, StickyStormCTFFlag)
            ba.timer(0.1, ba.Call(self._spawn_flag_for_team, msg.flag.team))

        elif isinstance(msg, FlagPickedUpMessage):

            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, StickyStormCTFFlag)
            try:
                msg.flag.last_player_to_hold = msg.node.getdelegate(
                    PlayerSpaz, True).getplayer(Player, True)
            except ba.NotFoundError:
                pass

            msg.flag.held_count += 1
            msg.flag.reset_return_times()

        elif isinstance(msg, FlagDroppedMessage):
            # Store the last player to hold the flag for scoring purposes.
            assert isinstance(msg.flag, StickyStormCTFFlag)
            msg.flag.held_count -= 1

        else:
            super().handlemessage(msg)


# 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
コード例 #16
0
class EasterEggHuntGame(ba.TeamGameActivity):
    """A game where score is based on collecting eggs."""

    @classmethod
    def get_name(cls) -> str:
        return 'Easter Egg Hunt'

    @classmethod
    def get_score_info(cls) -> Dict[str, Any]:
        return {'score_name': 'Score', 'score_type': 'points'}

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

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

    # 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.CoopSession)
                or issubclass(sessiontype, ba.TeamsSession)
                or issubclass(sessiontype, ba.FreeForAllSession))

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

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._last_player_death_time = None
        self._scoreboard = Scoreboard()
        self.egg_model = ba.getmodel('egg')
        self.egg_tex_1 = ba.gettexture('eggTex1')
        self.egg_tex_2 = ba.gettexture('eggTex2')
        self.egg_tex_3 = ba.gettexture('eggTex3')
        self._collect_sound = ba.getsound('powerup01')
        self._pro_mode = settings.get('Pro Mode', False)
        self._max_eggs = 1.0
        self.egg_material = ba.Material()
        self.egg_material.add_actions(
            conditions=("they_have_material", ba.sharedobj('player_material')),
            actions=(("call", "at_connect", self._on_egg_player_collide), ))
        self._eggs: List[Egg] = []
        self._update_timer: Optional[ba.Timer] = None
        self._countdown: Optional[OnScreenCountdown] = None
        self._bots: Optional[spazbot.BotSet] = None

    # Called when our game is transitioning in but not ready to start.
    # ..we can go ahead and set our music and whatnot.

    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.FORWARD_MARCH
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['score'] = 0
        if self.has_begun():
            self._update_scoreboard()

    # Called when our game actually starts.
    def on_begin(self) -> None:
        from bastd.maps import TowerD

        # There's a player-wall on the tower-d level to prevent
        # players from getting up on the stairs.. we wanna kill that.
        gamemap = self.map
        assert isinstance(gamemap, TowerD)
        gamemap.player_wall.delete()
        super().on_begin()
        self._update_scoreboard()
        self._update_timer = ba.Timer(0.25, self._update, repeat=True)
        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
        ba.timer(4.0, self._countdown.start)
        self._bots = spazbot.BotSet()

        # Spawn evil bunny in co-op only.
        if isinstance(self.session, ba.CoopSession) and self._pro_mode:
            self._spawn_evil_bunny()

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

    def _spawn_evil_bunny(self) -> None:
        assert self._bots is not None
        self._bots.spawn_bot(spazbot.BouncyBot,
                             pos=(6, 4, -7.8),
                             spawn_time=10.0)

    def _on_egg_player_collide(self) -> None:
        if not self.has_ended():
            egg_node, playernode = ba.get_collision_info(
                'source_node', 'opposing_node')
            if egg_node is not None and playernode is not None:
                egg = egg_node.getdelegate()
                assert isinstance(egg, Egg)
                spaz = playernode.getdelegate()
                assert isinstance(spaz, playerspaz.PlayerSpaz)
                player = (spaz.getplayer()
                          if hasattr(spaz, 'getplayer') else None)
                if player and egg:
                    player.team.gamedata['score'] += 1

                    # Displays a +1 (and adds to individual player score in
                    # teams mode).
                    self.stats.player_scored(player, 1, screenmessage=False)
                    if self._max_eggs < 5:
                        self._max_eggs += 1.0
                    elif self._max_eggs < 10:
                        self._max_eggs += 0.5
                    elif self._max_eggs < 30:
                        self._max_eggs += 0.3
                    self._update_scoreboard()
                    ba.playsound(self._collect_sound,
                                 0.5,
                                 position=egg.node.position)

                    # Create a flash.
                    light = ba.newnode('light',
                                       attrs={
                                           'position': egg_node.position,
                                           'height_attenuated': False,
                                           'radius': 0.1,
                                           'color': (1, 1, 0)
                                       })
                    ba.animate(light,
                               'intensity', {
                                   0: 0,
                                   0.1: 1.0,
                                   0.2: 0
                               },
                               loop=False)
                    ba.timer(0.200, light.delete)
                    egg.handlemessage(ba.DieMessage())

    def _update(self) -> None:
        # Misc. periodic updating.
        xpos = random.uniform(-7.1, 6.0)
        ypos = random.uniform(3.5, 3.5)
        zpos = random.uniform(-8.2, 3.7)

        # Prune dead eggs from our list.
        self._eggs = [e for e in self._eggs if e]

        # Spawn more eggs if we've got space.
        if len(self._eggs) < int(self._max_eggs):

            # Occasionally spawn a land-mine in addition.
            if self._pro_mode and random.random() < 0.25:
                mine = bomb.Bomb(position=(xpos, ypos, zpos),
                                 bomb_type='land_mine').autoretain()
                mine.arm()
            else:
                self._eggs.append(Egg(position=(xpos, ypos, zpos)))

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

        # Respawn dead players.
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            from bastd.actor import respawnicon

            # Augment standard behavior.
            super().handlemessage(msg)
            player = msg.spaz.getplayer()
            if not player:
                return
            self.stats.player_was_killed(player)

            # Respawn them shortly.
            assert self.initial_player_info is not None
            respawn_time = 2.0 + len(self.initial_player_info) * 1.0
            player.gamedata['respawn_timer'] = ba.Timer(
                respawn_time, ba.Call(self.spawn_player_if_exists, player))
            player.gamedata['respawn_icon'] = respawnicon.RespawnIcon(
                player, respawn_time)

        # Whenever our evil bunny dies, respawn him and spew some eggs.
        elif isinstance(msg, spazbot.SpazBotDeathMessage):
            self._spawn_evil_bunny()
            assert msg.badguy.node
            pos = msg.badguy.node.position
            for _i in range(6):
                spread = 0.4
                self._eggs.append(
                    Egg(position=(pos[0] + random.uniform(-spread, spread),
                                  pos[1] + random.uniform(-spread, spread),
                                  pos[2] + random.uniform(-spread, spread))))
        else:
            # Default handler.
            super().handlemessage(msg)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'])

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results)
コード例 #17
0
ファイル: assault.py プロジェクト: bseditor/ballistica
class AssaultGame(ba.TeamGameActivity):
    """Game where you score by touching the other team's flag."""
    @classmethod
    def get_name(cls) -> str:
        return 'Assault'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Reach the enemy flag to score.'

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [('Score to Win', {'min_value': 1, 'default': 3}),
                ('Time Limit', {
                    'choices': [('None', 0), ('1 Minute', 60),
                                ('2 Minutes', 120), ('5 Minutes', 300),
                                ('10 Minutes', 600), ('20 Minutes', 1200)],
                    'default': 0}),
                ('Respawn Times', {
                    'choices': [('Shorter', 0.25), ('Short', 0.5),
                                ('Normal', 1.0), ('Long', 2.0),
                                ('Longer', 4.0)],
                    'default': 1.0}),
                ('Epic Mode', {'default': False})]  # yapf: disable

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        if self.settings_raw['Epic Mode']:
            self.slow_motion = True
        self._last_score_time = 0.0
        self._score_sound = ba.getsound('score')
        self._base_region_materials: Dict[int, ba.Material] = {}

    def get_instance_description(self) -> Union[str, Sequence]:
        if self.settings_raw['Score to Win'] == 1:
            return 'Touch the enemy flag.'
        return ('Touch the enemy flag ${ARG1} times.',
                self.settings_raw['Score to Win'])

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        if self.settings_raw['Score to Win'] == 1:
            return 'touch 1 flag'
        return 'touch ${ARG1} flags', self.settings_raw['Score to Win']

    def on_transition_in(self) -> None:
        self.default_music = (ba.MusicType.EPIC
                              if self.settings_raw['Epic Mode'] else
                              ba.MusicType.FORWARD_MARCH)
        super().on_transition_in()

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

    def on_begin(self) -> None:
        from bastd.actor.flag import Flag
        super().on_begin()
        self.setup_standard_time_limit(self.settings_raw['Time Limit'])
        self.setup_standard_powerup_drops()
        for team in self.teams:
            mat = self._base_region_materials[team.get_id()] = 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',
                                      ba.Call(self._handle_base_collide,
                                              team))))

        # Create a score region and flag for each team.
        for team in self.teams:
            team.gamedata['base_pos'] = self.map.get_flag_position(
                team.get_id())

            ba.newnode('light',
                       attrs={
                           'position': team.gamedata['base_pos'],
                           'intensity': 0.6,
                           'height_attenuated': False,
                           'volume_intensity_scale': 0.1,
                           'radius': 0.1,
                           'color': team.color
                       })

            self.project_flag_stand(team.gamedata['base_pos'])
            team.gamedata['flag'] = Flag(touchable=False,
                                         position=team.gamedata['base_pos'],
                                         color=team.color)
            basepos = team.gamedata['base_pos']
            ba.newnode('region',
                       owner=team.gamedata['flag'].node,
                       attrs={
                           'position':
                           (basepos[0], basepos[1] + 0.75, basepos[2]),
                           'scale': (0.5, 0.5, 0.5),
                           'type':
                           'sphere',
                           'materials':
                           [self._base_region_materials[team.get_id()]]
                       })

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            super().handlemessage(msg)  # Augment standard.
            self.respawn_player(msg.spaz.player)
        else:
            super().handlemessage(msg)

    def _flash_base(self, team: ba.Team, length: float = 2.0) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': team.gamedata['base_pos'],
                               'height_attenuated': False,
                               'radius': 0.3,
                               'color': team.color
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def _handle_base_collide(self, team: ba.Team) -> None:

        # Attempt to pull a living ba.Player from what we hit.
        cnode = ba.get_collision_info('opposing_node')
        assert isinstance(cnode, ba.Node)
        actor = cnode.getdelegate()
        if not isinstance(actor, playerspaz.PlayerSpaz):
            return
        player = actor.getplayer()
        if not player or not player.is_alive():
            return

        # If its another team's player, they scored.
        player_team = player.team
        if player_team is not team:

            # Prevent multiple simultaneous scores.
            if ba.time() != self._last_score_time:
                self._last_score_time = ba.time()
                self.stats.player_scored(player, 50, big_message=True)
                ba.playsound(self._score_sound)
                self._flash_base(team)

                # Move all players on the scoring team back to their start
                # and add flashes of light so its noticeable.
                for player in player_team.players:
                    if player.is_alive():
                        if player.node:
                            pos = player.node.position
                            light = ba.newnode('light',
                                               attrs={
                                                   'position': pos,
                                                   'color': player_team.color,
                                                   'height_attenuated': False,
                                                   'radius': 0.4
                                               })
                            ba.timer(0.5, light.delete)
                            ba.animate(light, 'intensity', {
                                0: 0,
                                0.1: 1.0,
                                0.5: 0
                            })

                        new_pos = (self.map.get_start_position(
                            player_team.get_id()))
                        light = ba.newnode('light',
                                           attrs={
                                               'position': new_pos,
                                               'color': player_team.color,
                                               'radius': 0.4,
                                               'height_attenuated': False
                                           })
                        ba.timer(0.5, light.delete)
                        ba.animate(light, 'intensity', {
                            0: 0,
                            0.1: 1.0,
                            0.5: 0
                        })
                        if player.actor:
                            player.actor.handlemessage(
                                ba.StandMessage(new_pos,
                                                random.uniform(0, 360)))

                # Have teammates celebrate.
                for player in player_team.players:
                    if player.actor:
                        player.actor.handlemessage(ba.CelebrateMessage(2.0))

                player_team.gamedata['score'] += 1
                self._update_scoreboard()
                if (player_team.gamedata['score'] >=
                        self.settings_raw['Score to Win']):
                    self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results=results)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'],
                                            self.settings_raw['Score to Win'])
コード例 #18
0
class ChosenOneGame(ba.TeamGameActivity[Player, Team]):
    """
    Game involving trying to remain the one 'chosen one'
    for a set length of time while everyone else tries to
    kill you and become the chosen one themselves.
    """

    name = 'Chosen One'
    description = ('Be the chosen one for a length of time to win.\n'
                   'Kill the chosen one to become it.')
    available_settings = [
        ba.IntSetting(
            'Chosen One Time',
            min_value=10,
            default=30,
            increment=10,
        ),
        ba.BoolSetting('Chosen One Gets Gloves', default=True),
        ba.BoolSetting('Chosen One Gets Shield', default=False),
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
        ba.BoolSetting('Epic Mode', default=False),
    ]
    scoreconfig = ba.ScoreConfig(label='Time Held')

    @classmethod
    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
        return ba.getmaps('keep_away')

    def __init__(self, settings: dict):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._chosen_one_player: Optional[Player] = None
        self._swipsound = ba.getsound('swip')
        self._countdownsounds: dict[int, ba.Sound] = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_spawn_pos: Optional[Sequence[float]] = None
        self._reset_region_material: Optional[ba.Material] = None
        self._flag: Optional[Flag] = None
        self._reset_region: Optional[ba.Node] = None
        self._epic_mode = bool(settings['Epic Mode'])
        self._chosen_one_time = int(settings['Chosen One Time'])
        self._time_limit = float(settings['Time Limit'])
        self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield'])
        self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves'])

        # Base class overrides
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC
                              if self._epic_mode else ba.MusicType.CHOSEN_ONE)

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'There can be only one.'

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
        return Team(time_remaining=self._chosen_one_time)

    def on_team_join(self, team: Team) -> None:
        self._update_scoreboard()

    def on_player_leave(self, player: Player) -> None:
        super().on_player_leave(player)
        if self._get_chosen_one_player() is player:
            self._set_chosen_one_player(None)

    def on_begin(self) -> None:
        super().on_begin()
        shared = SharedObjects.get()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()
        self._flag_spawn_pos = self.map.get_flag_position(None)
        Flag.project_stand(self._flag_spawn_pos)
        self._set_chosen_one_player(None)

        pos = self._flag_spawn_pos
        ba.timer(1.0, call=self._tick, repeat=True)

        mat = self._reset_region_material = ba.Material()
        mat.add_actions(
            conditions=(
                'they_have_material',
                shared.player_material,
            ),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect',
                 ba.WeakCall(self._handle_reset_collide)),
            ),
        )

        self._reset_region = ba.newnode('region',
                                        attrs={
                                            'position':
                                            (pos[0], pos[1] + 0.75, pos[2]),
                                            'scale': (0.5, 0.5, 0.5),
                                            'type':
                                            'sphere',
                                            'materials': [mat]
                                        })

    def _get_chosen_one_player(self) -> Optional[Player]:
        # Should never return invalid references; return None in that case.
        if self._chosen_one_player:
            return self._chosen_one_player
        return None

    def _handle_reset_collide(self) -> None:
        # If we have a chosen one, ignore these.
        if self._get_chosen_one_player() is not None:
            return

        # Attempt to get a Player controlling a Spaz that we hit.
        try:
            player = ba.getcollision().opposingnode.getdelegate(
                PlayerSpaz, True).getplayer(Player, True)
        except ba.NotFoundError:
            return

        if player.is_alive():
            self._set_chosen_one_player(player)

    def _flash_flag_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'color': (1, 1, 1),
                               'radius': 0.3,
                               'height_attenuated': False
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _tick(self) -> None:

        # Give the chosen one points.
        player = self._get_chosen_one_player()
        if player is not None:

            # This shouldn't happen, but just in case.
            if not player.is_alive():
                ba.print_error('got dead player as chosen one in _tick')
                self._set_chosen_one_player(None)
            else:
                scoring_team = player.team
                assert self.stats
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)

                scoring_team.time_remaining = max(
                    0, scoring_team.time_remaining - 1)

                # Show the count over their head
                if scoring_team.time_remaining > 0:
                    if isinstance(player.actor, PlayerSpaz) and player.actor:
                        player.actor.set_score_text(
                            str(scoring_team.time_remaining))

                self._update_scoreboard()

                # announce numbers we have sounds for
                if scoring_team.time_remaining in self._countdownsounds:
                    ba.playsound(
                        self._countdownsounds[scoring_team.time_remaining])

                # Winner!
                if scoring_team.time_remaining <= 0:
                    self.end_game()

        else:
            # (player is None)
            # This shouldn't happen, but just in case.
            # (Chosen-one player ceasing to exist should
            # trigger on_player_leave which resets chosen-one)
            if self._chosen_one_player is not None:
                ba.print_error('got nonexistent player as chosen one in _tick')
                self._set_chosen_one_player(None)

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team,
                                   self._chosen_one_time - team.time_remaining)
        self.end(results=results, announce_delay=0)

    def _set_chosen_one_player(self, player: Optional[Player]) -> None:
        existing = self._get_chosen_one_player()
        if existing:
            existing.chosen_light = None
        ba.playsound(self._swipsound)
        if not player:
            assert self._flag_spawn_pos is not None
            self._flag = Flag(color=(1, 0.9, 0.2),
                              position=self._flag_spawn_pos,
                              touchable=False)
            self._chosen_one_player = None

            # Create a light to highlight the flag;
            # this will go away when the flag dies.
            ba.newnode('light',
                       owner=self._flag.node,
                       attrs={
                           'position': self._flag_spawn_pos,
                           'intensity': 0.6,
                           'height_attenuated': False,
                           'volume_intensity_scale': 0.1,
                           'radius': 0.1,
                           'color': (1.2, 1.2, 0.4)
                       })

            # Also an extra momentary flash.
            self._flash_flag_spawn()
        else:
            if player.actor:
                self._flag = None
                self._chosen_one_player = player

                if self._chosen_one_gets_shield:
                    player.actor.handlemessage(ba.PowerupMessage('shield'))
                if self._chosen_one_gets_gloves:
                    player.actor.handlemessage(ba.PowerupMessage('punch'))

                # Use a color that's partway between their team color
                # and white.
                color = [
                    0.3 + c * 0.7
                    for c in ba.normalized_color(player.team.color)
                ]
                light = player.chosen_light = ba.NodeActor(
                    ba.newnode('light',
                               attrs={
                                   'intensity': 0.6,
                                   'height_attenuated': False,
                                   'volume_intensity_scale': 0.1,
                                   'radius': 0.13,
                                   'color': color
                               }))

                assert light.node
                ba.animate(light.node,
                           'intensity', {
                               0: 1.0,
                               0.2: 0.4,
                               0.4: 1.0
                           },
                           loop=True)
                assert isinstance(player.actor, PlayerSpaz)
                player.actor.node.connectattr('position', light.node,
                                              'position')

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)
            player = msg.getplayer(Player)
            if player is self._get_chosen_one_player():
                killerplayer = msg.getkillerplayer(Player)
                self._set_chosen_one_player(None if (
                    killerplayer is None or killerplayer is player
                    or not killerplayer.is_alive()) else killerplayer)
            self.respawn_player(player)
        else:
            super().handlemessage(msg)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.time_remaining,
                                            self._chosen_one_time,
                                            countdown=True)
コード例 #19
0
ファイル: keepaway.py プロジェクト: DemonSlayer114/ballistica
class KeepAwayGame(ba.TeamGameActivity):
    """Game where you try to keep the flag away from your enemies."""

    FLAG_NEW = 0
    FLAG_UNCONTESTED = 1
    FLAG_CONTESTED = 2
    FLAG_HELD = 3

    @classmethod
    def get_name(cls) -> str:
        return 'Keep Away'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Carry the flag for a set length of time.'

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

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [
            ('Hold Time', {
                'min_value': 10,
                'default': 30,
                'increment': 10
            }),
            ('Time Limit', {
                'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
                            ('5 Minutes', 300), ('10 Minutes', 600),
                            ('20 Minutes', 1200)],
                'default': 0
            }),
            ('Respawn Times', {
                'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
                            ('Long', 2.0), ('Longer', 4.0)],
                'default': 1.0
            })
        ]  # yapf: disable

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._swipsound = ba.getsound('swip')
        self._tick_sound = ba.getsound('tick')
        self._countdownsounds = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_spawn_pos: Optional[Sequence[float]] = None
        self._update_timer: Optional[ba.Timer] = None
        self._holding_players: List[ba.Player] = []
        self._flag_state: Optional[int] = None
        self._flag_light: Optional[ba.Node] = None
        self._scoring_team: Optional[ba.Team] = None
        self._flag: Optional[stdflag.Flag] = None

    def get_instance_description(self) -> Union[str, Sequence]:
        return ('Carry the flag for ${ARG1} seconds.',
                self.settings['Hold Time'])

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        return ('carry the flag for ${ARG1} seconds',
                self.settings['Hold Time'])

    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.KEEP_AWAY
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['time_remaining'] = self.settings['Hold Time']
        self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()
        self._flag_spawn_pos = self.map.get_flag_position(None)
        self._spawn_flag()
        self._update_timer = ba.Timer(1.0, call=self._tick, repeat=True)
        self._update_flag_state()
        self.project_flag_stand(self._flag_spawn_pos)

    def _tick(self) -> None:
        self._update_flag_state()

        # Award points to all living players holding the flag.
        for player in self._holding_players:
            if player:
                assert self.stats
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)

        scoring_team = self._scoring_team

        if scoring_team is not None:

            if scoring_team.gamedata['time_remaining'] > 0:
                ba.playsound(self._tick_sound)

            scoring_team.gamedata['time_remaining'] = max(
                0, scoring_team.gamedata['time_remaining'] - 1)
            self._update_scoreboard()
            if scoring_team.gamedata['time_remaining'] > 0:
                assert self._flag is not None
                self._flag.set_score_text(
                    str(scoring_team.gamedata['time_remaining']))

            # Announce numbers we have sounds for.
            try:
                ba.playsound(self._countdownsounds[
                    scoring_team.gamedata['time_remaining']])
            except Exception:
                pass

            # Winner.
            if scoring_team.gamedata['time_remaining'] <= 0:
                self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(
                team,
                self.settings['Hold Time'] - team.gamedata['time_remaining'])
        self.end(results=results, announce_delay=0)

    def _update_flag_state(self) -> None:
        for team in self.teams:
            team.gamedata['holding_flag'] = False
        self._holding_players = []
        for player in self.players:
            holding_flag = False
            try:
                assert isinstance(player.actor, playerspaz.PlayerSpaz)
                if (player.actor.is_alive() and player.actor.node
                        and player.actor.node.hold_node):
                    holding_flag = (
                        player.actor.node.hold_node.getnodetype() == 'flag')
            except Exception:
                ba.print_exception('exception checking hold flag')
            if holding_flag:
                self._holding_players.append(player)
                player.team.gamedata['holding_flag'] = True

        holding_teams = set(t for t in self.teams
                            if t.gamedata['holding_flag'])
        prev_state = self._flag_state
        assert self._flag is not None
        assert self._flag_light
        assert self._flag.node
        if len(holding_teams) > 1:
            self._flag_state = self.FLAG_CONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.6, 0.6, 0.1)
            self._flag.node.color = (1.0, 1.0, 0.4)
        elif len(holding_teams) == 1:
            holding_team = list(holding_teams)[0]
            self._flag_state = self.FLAG_HELD
            self._scoring_team = holding_team
            self._flag_light.color = ba.normalized_color(holding_team.color)
            self._flag.node.color = holding_team.color
        else:
            self._flag_state = self.FLAG_UNCONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.2, 0.2, 0.2)
            self._flag.node.color = (1, 1, 1)

        if self._flag_state != prev_state:
            ba.playsound(self._swipsound)

    def _spawn_flag(self) -> None:
        ba.playsound(self._swipsound)
        self._flash_flag_spawn()
        assert self._flag_spawn_pos is not None
        self._flag = stdflag.Flag(dropped_timeout=20,
                                  position=self._flag_spawn_pos)
        self._flag_state = self.FLAG_NEW
        self._flag_light = ba.newnode('light',
                                      owner=self._flag.node,
                                      attrs={
                                          'intensity': 0.2,
                                          'radius': 0.3,
                                          'color': (0.2, 0.2, 0.2)
                                      })
        assert self._flag.node
        self._flag.node.connectattr('position', self._flag_light, 'position')
        self._update_flag_state()

    def _flash_flag_spawn(self) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': self._flag_spawn_pos,
                               'color': (1, 1, 1),
                               'radius': 0.3,
                               'height_attenuated': False
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True)
        ba.timer(1.0, light.delete)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.gamedata['time_remaining'],
                                            self.settings['Hold Time'],
                                            countdown=True)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            # Augment standard behavior.
            super().handlemessage(msg)
            self.respawn_player(msg.spaz.player)
        elif isinstance(msg, stdflag.FlagDeathMessage):
            self._spawn_flag()
        elif isinstance(
                msg,
            (stdflag.FlagDroppedMessage, stdflag.FlagPickedUpMessage)):
            self._update_flag_state()
        else:
            super().handlemessage(msg)
コード例 #20
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)
コード例 #21
0
class TargetPracticeGame(ba.TeamGameActivity):
    """Game where players try to hit targets with bombs."""
    @classmethod
    def get_name(cls) -> str:
        return 'Target Practice'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Bomb as many targets as you can.'

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

    @classmethod
    def supports_session_type(cls, sessiontype: Type[ba.Session]) -> bool:
        # We support any teams or versus sessions.
        return (issubclass(sessiontype, ba.CoopSession)
                or issubclass(sessiontype, ba.MultiTeamSession))

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [('Target Count', {
            'min_value': 1,
            'default': 3
        }), ('Enable Impact Bombs', {
            'default': True
        }), ('Enable Triple Bombs', {
            'default': True
        })]

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._targets: List[Target] = []
        self._update_timer: Optional[ba.Timer] = None
        self._countdown: Optional[OnScreenCountdown] = None

    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.FORWARD_MARCH
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['score'] = 0
        if self.has_begun():
            self.update_scoreboard()

    def on_begin(self) -> None:
        from bastd.actor.onscreencountdown import OnScreenCountdown
        super().on_begin()
        self.update_scoreboard()

        # Number of targets is based on player count.
        num_targets = self.settings['Target Count']
        for i in range(num_targets):
            ba.timer(5.0 + i * 1.0, self._spawn_target)

        self._update_timer = ba.Timer(1.0, self._update, repeat=True)
        self._countdown = OnScreenCountdown(60, endcall=self.end_game)
        ba.timer(4.0, self._countdown.start)

    def spawn_player(self, player: ba.Player) -> ba.Actor:
        spawn_center = (0, 3, -5)
        pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
               spawn_center[2] + random.uniform(-1.5, 1.5))

        # Reset their streak.
        player.gamedata['streak'] = 0
        spaz = self.spawn_player_spaz(player, position=pos)

        # Give players permanent triple impact bombs and wire them up
        # to tell us when they drop a bomb.
        if self.settings['Enable Impact Bombs']:
            spaz.bomb_type = 'impact'
        if self.settings['Enable Triple Bombs']:
            spaz.set_bomb_count(3)
        spaz.add_dropped_bomb_callback(self._on_spaz_dropped_bomb)
        return spaz

    def _spawn_target(self) -> None:

        # Generate a few random points; we'll use whichever one is farthest
        # from our existing targets (don't want overlapping targets).
        points = []

        for _i in range(4):
            # Calc a random point within a circle.
            while True:
                xpos = random.uniform(-1.0, 1.0)
                ypos = random.uniform(-1.0, 1.0)
                if xpos * xpos + ypos * ypos < 1.0:
                    break
            points.append((8.0 * xpos, 2.2, -3.5 + 5.0 * ypos))

        def get_min_dist_from_target(pnt: Sequence[float]) -> float:
            return min((t.get_dist_from_point(pnt) for t in self._targets))

        # If we have existing targets, use the point with the highest
        # min-distance-from-targets.
        if self._targets:
            point = max(points, key=get_min_dist_from_target)
        else:
            point = points[0]

        self._targets.append(Target(position=point))

    def _on_spaz_dropped_bomb(self, spaz: ba.Actor, bomb: ba.Actor) -> None:
        del spaz  # Unused.
        from bastd.actor.bomb import Bomb

        # Wire up this bomb to inform us when it blows up.
        assert isinstance(bomb, Bomb)
        bomb.add_explode_callback(self._on_bomb_exploded)

    def _on_bomb_exploded(self, bomb: Bomb, blast: Blast) -> None:
        assert blast.node
        pos = blast.node.position

        # Debugging: throw a locator down where we landed.
        # ba.newnode('locator', attrs={'position':blast.node.position})

        # Feed the explosion point to all our targets and get points in return.
        # Note: we operate on a copy of self._targets since the list may change
        # under us if we hit stuff (don't wanna get points for new targets).
        player = bomb.get_source_player()
        if not player:
            return  # could happen if they leave after throwing a bomb..

        bullseye = any(
            target.do_hit_at_position(pos, player)
            for target in list(self._targets))
        if bullseye:
            player.gamedata['streak'] += 1
        else:
            player.gamedata['streak'] = 0

    def _update(self) -> None:
        """Misc. periodic updating."""
        # Clear out targets that have died.
        self._targets = [t for t in self._targets if t]

    def handlemessage(self, msg: Any) -> Any:
        # When players die, respawn them.
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            super().handlemessage(msg)  # Do standard stuff.
            player = msg.spaz.getplayer()
            assert player is not None
            self.respawn_player(player)  # Kick off a respawn.
        elif isinstance(msg, Target.TargetHitMessage):
            # A target is telling us it was hit and will die soon..
            # ..so make another one.
            self._spawn_target()
        else:
            super().handlemessage(msg)

    def update_scoreboard(self) -> None:
        """Update the game scoreboard with current team values."""
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.gamedata['score'])

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.gamedata['score'])
        self.end(results)
コード例 #22
0
ファイル: assault.py プロジェクト: zwl1671/ballistica
class AssaultGame(ba.TeamGameActivity[Player, Team]):
    """Game where you score by touching the other team's flag."""

    name = 'Assault'
    description = 'Reach the enemy flag to score.'
    game_settings = [
        ('Score to Win', {
            'min_value': 1,
            'default': 3
        }),
        ('Time Limit', {
            'choices': [('None', 0), ('1 Minute', 60), ('2 Minutes', 120),
                        ('5 Minutes', 300), ('10 Minutes', 600),
                        ('20 Minutes', 1200)],
            'default':
            0
        }),
        ('Respawn Times', {
            'choices': [('Shorter', 0.25), ('Short', 0.5), ('Normal', 1.0),
                        ('Long', 2.0), ('Longer', 4.0)],
            'default':
            1.0
        }),
        ('Epic Mode', {
            'default': False
        }),
    ]

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

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

    def __init__(self, settings: Dict[str, Any]):
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._last_score_time = 0.0
        self._score_sound = ba.getsound('score')
        self._base_region_materials: Dict[int, ba.Material] = {}
        self._epic_mode = bool(settings['Epic Mode'])
        self._score_to_win = int(settings['Score to Win'])
        self._time_limit = float(settings['Time Limit'])

        # Base class overrides
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC if self._epic_mode else
                              ba.MusicType.FORWARD_MARCH)

    def get_instance_description(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'Touch the enemy flag.'
        return 'Touch the enemy flag ${ARG1} times.', self._score_to_win

    def get_instance_description_short(self) -> Union[str, Sequence]:
        if self._score_to_win == 1:
            return 'touch 1 flag'
        return 'touch ${ARG1} flags', self._score_to_win

    def create_team(self, sessionteam: ba.SessionTeam) -> Team:
        shared = SharedObjects.get()
        base_pos = self.map.get_flag_position(sessionteam.id)
        ba.newnode('light',
                   attrs={
                       'position': base_pos,
                       'intensity': 0.6,
                       'height_attenuated': False,
                       'volume_intensity_scale': 0.1,
                       'radius': 0.1,
                       'color': sessionteam.color
                   })
        Flag.project_stand(base_pos)
        flag = Flag(touchable=False,
                    position=base_pos,
                    color=sessionteam.color)
        team = Team(base_pos=base_pos, flag=flag)

        mat = self._base_region_materials[sessionteam.id] = ba.Material()
        mat.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect', ba.Call(self._handle_base_collide,
                                               team)),
            ),
        )

        ba.newnode('region',
                   owner=flag.node,
                   attrs={
                       'position':
                       (base_pos[0], base_pos[1] + 0.75, base_pos[2]),
                       'scale': (0.5, 0.5, 0.5),
                       'type': 'sphere',
                       'materials':
                       [self._base_region_materials[sessionteam.id]]
                   })

        return team

    def on_team_join(self, team: Team) -> None:
        # Can't do this in create_team because the team's color/etc. have
        # not been wired up yet at that point.
        self._update_scoreboard()

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            super().handlemessage(msg)  # Augment standard.
            self.respawn_player(msg.getplayer(Player))
        else:
            super().handlemessage(msg)

    def _flash_base(self, team: Team, length: float = 2.0) -> None:
        light = ba.newnode('light',
                           attrs={
                               'position': team.base_pos,
                               'height_attenuated': False,
                               'radius': 0.3,
                               'color': team.color
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 2.0, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def _handle_base_collide(self, team: Team) -> None:
        try:
            player = ba.getcollision().opposingnode.getdelegate(
                PlayerSpaz, True).getplayer(Player, True)
        except ba.NotFoundError:
            return

        if not player.is_alive():
            return

        # If its another team's player, they scored.
        player_team = player.team
        if player_team is not team:

            # Prevent multiple simultaneous scores.
            if ba.time() != self._last_score_time:
                self._last_score_time = ba.time()
                self.stats.player_scored(player, 50, big_message=True)
                ba.playsound(self._score_sound)
                self._flash_base(team)

                # Move all players on the scoring team back to their start
                # and add flashes of light so its noticeable.
                for player in player_team.players:
                    if player.is_alive():
                        pos = player.node.position
                        light = ba.newnode('light',
                                           attrs={
                                               'position': pos,
                                               'color': player_team.color,
                                               'height_attenuated': False,
                                               'radius': 0.4
                                           })
                        ba.timer(0.5, light.delete)
                        ba.animate(light, 'intensity', {
                            0: 0,
                            0.1: 1.0,
                            0.5: 0
                        })

                        new_pos = (self.map.get_start_position(player_team.id))
                        light = ba.newnode('light',
                                           attrs={
                                               'position': new_pos,
                                               'color': player_team.color,
                                               'radius': 0.4,
                                               'height_attenuated': False
                                           })
                        ba.timer(0.5, light.delete)
                        ba.animate(light, 'intensity', {
                            0: 0,
                            0.1: 1.0,
                            0.5: 0
                        })
                        if player.actor:
                            player.actor.handlemessage(
                                ba.StandMessage(new_pos,
                                                random.uniform(0, 360)))

                # Have teammates celebrate.
                for player in player_team.players:
                    if player.actor:
                        player.actor.handlemessage(ba.CelebrateMessage(2.0))

                player_team.score += 1
                self._update_scoreboard()
                if player_team.score >= self._score_to_win:
                    self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(team, team.score)
        self.end(results=results)

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team, team.score,
                                            self._score_to_win)
コード例 #23
0
ファイル: conquest.py プロジェクト: KoLenka12/ballistica
class ConquestGame(ba.TeamGameActivity[Player, Team]):
    """A game where teams try to claim all flags on the map."""

    name = 'Conquest'
    description = 'Secure all flags on the map to win.'
    available_settings = [
        ba.IntChoiceSetting(
            'Time Limit',
            choices=[
                ('None', 0),
                ('1 Minute', 60),
                ('2 Minutes', 120),
                ('5 Minutes', 300),
                ('10 Minutes', 600),
                ('20 Minutes', 1200),
            ],
            default=0,
        ),
        ba.FloatChoiceSetting(
            'Respawn Times',
            choices=[
                ('Shorter', 0.25),
                ('Short', 0.5),
                ('Normal', 1.0),
                ('Long', 2.0),
                ('Longer', 4.0),
            ],
            default=1.0,
        ),
        ba.BoolSetting('Epic Mode', default=False),
    ]

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

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

    def __init__(self, settings: dict):
        super().__init__(settings)
        shared = SharedObjects.get()
        self._scoreboard = Scoreboard()
        self._score_sound = ba.getsound('score')
        self._swipsound = ba.getsound('swip')
        self._extraflagmat = ba.Material()
        self._flags: List[ConquestFlag] = []
        self._epic_mode = bool(settings['Epic Mode'])
        self._time_limit = float(settings['Time Limit'])

        # Base class overrides.
        self.slow_motion = self._epic_mode
        self.default_music = (ba.MusicType.EPIC
                              if self._epic_mode else ba.MusicType.GRAND_ROMP)

        # We want flags to tell us they've been hit but not react physically.
        self._extraflagmat.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('call', 'at_connect', self._handle_flag_player_collide),
            ))

    def get_instance_description(self) -> Union[str, Sequence]:
        return 'Secure all ${ARG1} flags.', len(self.map.flag_points)

    def get_instance_description_short(self) -> Union[str, Sequence]:
        return 'secure all ${ARG1} flags', len(self.map.flag_points)

    def on_team_join(self, team: Team) -> None:
        if self.has_begun():
            self._update_scores()

    def on_player_join(self, player: Player) -> None:
        player.respawn_timer = None

        # Only spawn if this player's team has a flag currently.
        if player.team.flags_held > 0:
            self.spawn_player(player)

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self._time_limit)
        self.setup_standard_powerup_drops()

        # Set up flags with marker lights.
        for i in range(len(self.map.flag_points)):
            point = self.map.flag_points[i]
            flag = ConquestFlag(position=point,
                                touchable=False,
                                materials=[self._extraflagmat])
            self._flags.append(flag)
            Flag.project_stand(point)
            flag.light = ba.newnode('light',
                                    owner=flag.node,
                                    attrs={
                                        'position': point,
                                        'intensity': 0.25,
                                        'height_attenuated': False,
                                        'radius': 0.3,
                                        'color': (1, 1, 1)
                                    })

        # Give teams a flag to start with.
        for i in range(len(self.teams)):
            self._flags[i].team = self.teams[i]
            light = self._flags[i].light
            assert light
            node = self._flags[i].node
            assert node
            light.color = self.teams[i].color
            node.color = self.teams[i].color

        self._update_scores()

        # Initial joiners didn't spawn due to no flags being owned yet;
        # spawn them now.
        for player in self.players:
            self.spawn_player(player)

    def _update_scores(self) -> None:
        for team in self.teams:
            team.flags_held = 0
        for flag in self._flags:
            if flag.team is not None:
                flag.team.flags_held += 1
        for team in self.teams:

            # If a team finds themselves with no flags, cancel all
            # outstanding spawn-timers.
            if team.flags_held == 0:
                for player in team.players:
                    player.respawn_timer = None
                    player.respawn_icon = None
            if team.flags_held == len(self._flags):
                self.end_game()
            self._scoreboard.set_team_value(team, team.flags_held,
                                            len(self._flags))

    def end_game(self) -> None:
        results = ba.GameResults()
        for team in self.teams:
            results.set_team_score(team, team.flags_held)
        self.end(results=results)

    def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None:
        assert flag.node
        assert flag.light
        light = ba.newnode('light',
                           attrs={
                               'position': flag.node.position,
                               'height_attenuated': False,
                               'color': flag.light.color
                           })
        ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True)
        ba.timer(length, light.delete)

    def _handle_flag_player_collide(self) -> None:
        collision = ba.getcollision()
        try:
            flag = collision.sourcenode.getdelegate(ConquestFlag, True)
            player = collision.opposingnode.getdelegate(PlayerSpaz,
                                                        True).getplayer(
                                                            Player, True)
        except ba.NotFoundError:
            return
        assert flag.light

        if flag.team is not player.team:
            flag.team = player.team
            flag.light.color = player.team.color
            flag.node.color = player.team.color
            self.stats.player_scored(player, 10, screenmessage=False)
            ba.playsound(self._swipsound)
            self._flash_flag(flag)
            self._update_scores()

            # Respawn any players on this team that were in limbo due to the
            # lack of a flag for their team.
            for otherplayer in self.players:
                if (otherplayer.team is flag.team
                        and otherplayer.actor is not None
                        and not otherplayer.is_alive()
                        and otherplayer.respawn_timer is None):
                    self.spawn_player(otherplayer)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)

            # Respawn only if this team has a flag.
            player = msg.getplayer(Player)
            if player.team.flags_held > 0:
                self.respawn_player(player)
            else:
                player.respawn_timer = None

        else:
            super().handlemessage(msg)

    def spawn_player(self, player: Player) -> ba.Actor:
        # We spawn players at different places based on what flags are held.
        return self.spawn_player_spaz(player,
                                      self._get_player_spawn_position(player))

    def _get_player_spawn_position(self, player: Player) -> Sequence[float]:

        # Iterate until we find a spawn owned by this team.
        spawn_count = len(self.map.spawn_by_flag_points)

        # Get all spawns owned by this team.
        spawns = [
            i for i in range(spawn_count) if self._flags[i].team is player.team
        ]

        closest_spawn = 0
        closest_distance = 9999.0

        # Now find the spawn that's closest to a spawn not owned by us;
        # we'll use that one.
        for spawn in spawns:
            spt = self.map.spawn_by_flag_points[spawn]
            our_pt = ba.Vec3(spt[0], spt[1], spt[2])
            for otherspawn in [
                    i for i in range(spawn_count)
                    if self._flags[i].team is not player.team
            ]:
                spt = self.map.spawn_by_flag_points[otherspawn]
                their_pt = ba.Vec3(spt[0], spt[1], spt[2])
                dist = (their_pt - our_pt).length()
                if dist < closest_distance:
                    closest_distance = dist
                    closest_spawn = spawn

        pos = self.map.spawn_by_flag_points[closest_spawn]
        x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3])
        z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5])
        pos = (pos[0] + random.uniform(*x_range), pos[1],
               pos[2] + random.uniform(*z_range))
        return pos
コード例 #24
0
class RunaroundGame(ba.CoopGameActivity[Player, Team]):
    """Game involving trying to bomb bots as they walk through the map."""

    name = 'Runaround'
    description = 'Prevent enemies from reaching the exit.'
    tips = [
        'Jump just as you\'re throwing to get bombs up to the highest levels.',
        'No, you can\'t get up on the ledge. You have to throw bombs.',
        'Whip back and forth to get more distance on your throws..'
    ]
    default_music = ba.MusicType.MARCHING

    # How fast our various bot types walk.
    _bot_speed_map: Dict[Type[SpazBot], float] = {
        BomberBot: 0.48,
        BomberBotPro: 0.48,
        BomberBotProShielded: 0.48,
        BrawlerBot: 0.57,
        BrawlerBotPro: 0.57,
        BrawlerBotProShielded: 0.57,
        TriggerBot: 0.73,
        TriggerBotPro: 0.78,
        TriggerBotProShielded: 0.78,
        ChargerBot: 1.0,
        ChargerBotProShielded: 1.0,
        ExplodeyBot: 1.0,
        StickyBot: 0.5
    }

    def __init__(self, settings: dict):
        settings['map'] = 'Tower D'
        super().__init__(settings)
        shared = SharedObjects.get()
        self._preset = Preset(settings.get('preset', 'pro'))

        self._player_death_sound = ba.getsound('playerDeath')
        self._new_wave_sound = ba.getsound('scoreHit01')
        self._winsound = ba.getsound('score')
        self._cashregistersound = ba.getsound('cashRegister')
        self._bad_guy_score_sound = ba.getsound('shieldDown')
        self._heart_tex = ba.gettexture('heart')
        self._heart_model_opaque = ba.getmodel('heartOpaque')
        self._heart_model_transparent = ba.getmodel('heartTransparent')

        self._a_player_has_been_killed = False
        self._spawn_center = self._map_type.defs.points['spawn1'][0:3]
        self._tntspawnpos = self._map_type.defs.points['tnt_loc'][0:3]
        self._powerup_center = self._map_type.defs.boxes['powerup_region'][0:3]
        self._powerup_spread = (
            self._map_type.defs.boxes['powerup_region'][6] * 0.5,
            self._map_type.defs.boxes['powerup_region'][8] * 0.5)

        self._score_region_material = ba.Material()
        self._score_region_material.add_actions(
            conditions=('they_have_material', shared.player_material),
            actions=(
                ('modify_part_collision', 'collide', True),
                ('modify_part_collision', 'physical', False),
                ('call', 'at_connect', self._handle_reached_end),
            ))

        self._last_wave_end_time = ba.time()
        self._player_has_picked_up_powerup = False
        self._scoreboard: Optional[Scoreboard] = None
        self._game_over = False
        self._wavenum = 0
        self._can_end_wave = True
        self._score = 0
        self._time_bonus = 0
        self._score_region: Optional[ba.Actor] = None
        self._dingsound = ba.getsound('dingSmall')
        self._dingsoundhigh = ba.getsound('dingSmallHigh')
        self._exclude_powerups: Optional[List[str]] = None
        self._have_tnt: Optional[bool] = None
        self._waves: Optional[List[Wave]] = None
        self._bots = SpazBotSet()
        self._tntspawner: Optional[TNTSpawner] = None
        self._lives_bg: Optional[ba.NodeActor] = None
        self._start_lives = 10
        self._lives = self._start_lives
        self._lives_text: Optional[ba.NodeActor] = None
        self._flawless = True
        self._time_bonus_timer: Optional[ba.Timer] = None
        self._time_bonus_text: Optional[ba.NodeActor] = None
        self._time_bonus_mult: Optional[float] = None
        self._wave_text: Optional[ba.NodeActor] = None
        self._flawless_bonus: Optional[int] = None
        self._wave_update_timer: Optional[ba.Timer] = None

    def on_transition_in(self) -> None:
        super().on_transition_in()
        self._scoreboard = Scoreboard(label=ba.Lstr(resource='scoreText'),
                                      score_split=0.5)
        self._score_region = ba.NodeActor(
            ba.newnode('region',
                       attrs={
                           'position':
                           self.map.defs.boxes['score_region'][0:3],
                           'scale': self.map.defs.boxes['score_region'][6:9],
                           'type': 'box',
                           'materials': [self._score_region_material]
                       }))

    def on_begin(self) -> None:
        super().on_begin()
        player_count = len(self.players)
        hard = self._preset not in {Preset.PRO_EASY, Preset.UBER_EASY}

        if self._preset in {Preset.PRO, Preset.PRO_EASY, Preset.TOURNAMENT}:
            self._exclude_powerups = ['curse']
            self._have_tnt = True
            self._waves = [
                Wave(entries=[
                    Spawn(BomberBot, path=3 if hard else 2),
                    Spawn(BomberBot, path=2),
                    Spawn(BomberBot, path=2) if hard else None,
                    Spawn(BomberBot, path=2) if player_count > 1 else None,
                    Spawn(BomberBot, path=1) if hard else None,
                    Spawn(BomberBot, path=1) if player_count > 2 else None,
                    Spawn(BomberBot, path=1) if player_count > 3 else None,
                ]),
                Wave(entries=[
                    Spawn(BomberBot, path=1) if hard else None,
                    Spawn(BomberBot, path=2) if hard else None,
                    Spawn(BomberBot, path=2),
                    Spawn(BomberBot, path=2),
                    Spawn(BomberBot, path=2) if player_count > 3 else None,
                    Spawn(BrawlerBot, path=3),
                    Spawn(BrawlerBot, path=3),
                    Spawn(BrawlerBot, path=3) if hard else None,
                    Spawn(BrawlerBot, path=3) if player_count > 1 else None,
                    Spawn(BrawlerBot, path=3) if player_count > 2 else None,
                ]),
                Wave(entries=[
                    Spawn(ChargerBot, path=2) if hard else None,
                    Spawn(ChargerBot, path=2) if player_count > 2 else None,
                    Spawn(TriggerBot, path=2),
                    Spawn(TriggerBot, path=2) if player_count > 1 else None,
                    Spacing(duration=3.0),
                    Spawn(BomberBot, path=2) if hard else None,
                    Spawn(BomberBot, path=2) if hard else None,
                    Spawn(BomberBot, path=2),
                    Spawn(BomberBot, path=3) if hard else None,
                    Spawn(BomberBot, path=3),
                    Spawn(BomberBot, path=3),
                    Spawn(BomberBot, path=3) if player_count > 3 else None,
                ]),
                Wave(entries=[
                    Spawn(TriggerBot, path=1) if hard else None,
                    Spacing(duration=1.0) if hard else None,
                    Spawn(TriggerBot, path=2),
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=3),
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=1) if hard else None,
                    Spacing(duration=1.0) if hard else None,
                    Spawn(TriggerBot, path=2),
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=3),
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=1) if (
                        player_count > 1 and hard) else None,
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=2) if player_count > 2 else None,
                    Spacing(duration=1.0),
                    Spawn(TriggerBot, path=3) if player_count > 3 else None,
                    Spacing(duration=1.0),
                ]),
                Wave(entries=[
                    Spawn(ChargerBotProShielded if hard else ChargerBot,
                          path=1),
                    Spawn(BrawlerBot, path=2) if hard else None,
                    Spawn(BrawlerBot, path=2),
                    Spawn(BrawlerBot, path=2),
                    Spawn(BrawlerBot, path=3) if hard else None,
                    Spawn(BrawlerBot, path=3),
                    Spawn(BrawlerBot, path=3),
                    Spawn(BrawlerBot, path=3) if player_count > 1 else None,
                    Spawn(BrawlerBot, path=3) if player_count > 2 else None,
                    Spawn(BrawlerBot, path=3) if player_count > 3 else None,
                ]),
                Wave(entries=[
                    Spawn(BomberBotProShielded, path=3),
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=2),
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=1) if hard else None,
                    Spacing(duration=1.0) if hard else None,
                    Spawn(BomberBotProShielded, path=3),
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=2),
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=1) if hard else None,
                    Spacing(duration=1.5) if hard else None,
                    Spawn(BomberBotProShielded, path=3
                          ) if player_count > 1 else None,
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=2
                          ) if player_count > 2 else None,
                    Spacing(duration=1.5),
                    Spawn(BomberBotProShielded, path=1
                          ) if player_count > 3 else None,
                ]),
            ]
        elif self._preset in {
                Preset.UBER_EASY, Preset.UBER, Preset.TOURNAMENT_UBER
        }:
            self._exclude_powerups = []
            self._have_tnt = True
            self._waves = [
                Wave(entries=[
                    Spawn(TriggerBot, path=1) if hard else None,
                    Spawn(TriggerBot, path=2),
                    Spawn(TriggerBot, path=2),
                    Spawn(TriggerBot, path=3),
                    Spawn(BrawlerBotPro if hard else BrawlerBot,
                          point=Point.BOTTOM_LEFT),
                    Spawn(BrawlerBotPro, point=Point.BOTTOM_RIGHT
                          ) if player_count > 2 else None,
                ]),
                Wave(entries=[
                    Spawn(ChargerBot, path=2),
                    Spawn(ChargerBot, path=3),
                    Spawn(ChargerBot, path=1) if hard else None,
                    Spawn(ChargerBot, path=2),
                    Spawn(ChargerBot, path=3),
                    Spawn(ChargerBot, path=1) if player_count > 2 else None,
                ]),
                Wave(entries=[
                    Spawn(BomberBotProShielded, path=1) if hard else None,
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(BomberBotProShielded, path=3),
                    Spawn(BomberBotProShielded, path=3),
                    Spawn(ChargerBot, point=Point.BOTTOM_RIGHT),
                    Spawn(ChargerBot, point=Point.BOTTOM_LEFT
                          ) if player_count > 2 else None,
                ]),
                Wave(entries=[
                    Spawn(TriggerBotPro, path=1) if hard else None,
                    Spawn(TriggerBotPro, path=1 if hard else 2),
                    Spawn(TriggerBotPro, path=1 if hard else 2),
                    Spawn(TriggerBotPro, path=1 if hard else 2),
                    Spawn(TriggerBotPro, path=1 if hard else 2),
                    Spawn(TriggerBotPro, path=1 if hard else 2),
                    Spawn(TriggerBotPro, path=1 if hard else 2
                          ) if player_count > 1 else None,
                    Spawn(TriggerBotPro, path=1 if hard else 2
                          ) if player_count > 3 else None,
                ]),
                Wave(entries=[
                    Spawn(TriggerBotProShielded if hard else TriggerBotPro,
                          point=Point.BOTTOM_LEFT),
                    Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT
                          ) if hard else None,
                    Spawn(TriggerBotProShielded, point=Point.BOTTOM_RIGHT
                          ) if player_count > 2 else None,
                    Spawn(BomberBot, path=3),
                    Spawn(BomberBot, path=3),
                    Spacing(duration=5.0),
                    Spawn(BrawlerBot, path=2),
                    Spawn(BrawlerBot, path=2),
                    Spacing(duration=5.0),
                    Spawn(TriggerBot, path=1) if hard else None,
                    Spawn(TriggerBot, path=1) if hard else None,
                ]),
                Wave(entries=[
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(BomberBotProShielded, path=2) if hard else None,
                    Spawn(StickyBot, point=Point.BOTTOM_RIGHT),
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(StickyBot, point=Point.BOTTOM_RIGHT
                          ) if player_count > 2 else None,
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(ExplodeyBot, point=Point.BOTTOM_LEFT),
                    Spawn(BomberBotProShielded, path=2),
                    Spawn(BomberBotProShielded, path=2
                          ) if player_count > 1 else None,
                    Spacing(duration=5.0),
                    Spawn(StickyBot, point=Point.BOTTOM_LEFT),
                    Spacing(duration=2.0),
                    Spawn(ExplodeyBot, point=Point.BOTTOM_RIGHT),
                ]),
            ]
        elif self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
            self._exclude_powerups = []
            self._have_tnt = True

        # Spit out a few powerups and start dropping more shortly.
        self._drop_powerups(standard_points=True)
        ba.timer(4.0, self._start_powerup_drops)
        self.setup_low_life_warning_sound()
        self._update_scores()

        # Our TNT spawner (if applicable).
        if self._have_tnt:
            self._tntspawner = TNTSpawner(position=self._tntspawnpos)

        # Make sure to stay out of the way of menu/party buttons in the corner.
        interface_type = ba.app.interface_type
        l_offs = (-80 if interface_type == 'small' else
                  -40 if interface_type == 'medium' else 0)

        self._lives_bg = ba.NodeActor(
            ba.newnode('image',
                       attrs={
                           'texture': self._heart_tex,
                           'model_opaque': self._heart_model_opaque,
                           'model_transparent': self._heart_model_transparent,
                           'attach': 'topRight',
                           'scale': (90, 90),
                           'position': (-110 + l_offs, -50),
                           'color': (1, 0.2, 0.2)
                       }))
        # FIXME; should not set things based on vr mode.
        #  (won't look right to non-vr connected clients, etc)
        vrmode = ba.app.vr_mode
        self._lives_text = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'v_attach':
                           'top',
                           'h_attach':
                           'right',
                           'h_align':
                           'center',
                           'color': (1, 1, 1, 1) if vrmode else
                           (0.8, 0.8, 0.8, 1.0),
                           'flatness':
                           1.0 if vrmode else 0.5,
                           'shadow':
                           1.0 if vrmode else 0.5,
                           'vr_depth':
                           10,
                           'position': (-113 + l_offs, -69),
                           'scale':
                           1.3,
                           'text':
                           str(self._lives)
                       }))

        ba.timer(2.0, self._start_updating_waves)

    def _handle_reached_end(self) -> None:
        spaz = ba.getcollision().opposingnode.getdelegate(SpazBot, True)
        if not spaz.is_alive():
            return  # Ignore bodies flying in.

        self._flawless = False
        pos = spaz.node.position
        ba.playsound(self._bad_guy_score_sound, position=pos)
        light = ba.newnode('light',
                           attrs={
                               'position': pos,
                               'radius': 0.5,
                               'color': (1, 0, 0)
                           })
        ba.animate(light, 'intensity', {0.0: 0, 0.1: 1, 0.5: 0}, loop=False)
        ba.timer(1.0, light.delete)
        spaz.handlemessage(
            ba.DieMessage(immediate=True, how=ba.DeathType.REACHED_GOAL))

        if self._lives > 0:
            self._lives -= 1
            if self._lives == 0:
                self._bots.stop_moving()
                self.continue_or_end_game()
            assert self._lives_text is not None
            assert self._lives_text.node
            self._lives_text.node.text = str(self._lives)
            delay = 0.0

            def _safesetattr(node: ba.Node, attr: str, value: Any) -> None:
                if node:
                    setattr(node, attr, value)

            for _i in range(4):
                ba.timer(
                    delay,
                    ba.Call(_safesetattr, self._lives_text.node, 'color',
                            (1, 0, 0, 1.0)))
                assert self._lives_bg is not None
                assert self._lives_bg.node
                ba.timer(
                    delay,
                    ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 0.5))
                delay += 0.125
                ba.timer(
                    delay,
                    ba.Call(_safesetattr, self._lives_text.node, 'color',
                            (1.0, 1.0, 0.0, 1.0)))
                ba.timer(
                    delay,
                    ba.Call(_safesetattr, self._lives_bg.node, 'opacity', 1.0))
                delay += 0.125
            ba.timer(
                delay,
                ba.Call(_safesetattr, self._lives_text.node, 'color',
                        (0.8, 0.8, 0.8, 1.0)))

    def on_continue(self) -> None:
        self._lives = 3
        assert self._lives_text is not None
        assert self._lives_text.node
        self._lives_text.node.text = str(self._lives)
        self._bots.start_moving()

    def spawn_player(self, player: Player) -> ba.Actor:
        pos = (self._spawn_center[0] + random.uniform(-1.5, 1.5),
               self._spawn_center[1],
               self._spawn_center[2] + random.uniform(-1.5, 1.5))
        spaz = self.spawn_player_spaz(player, position=pos)
        if self._preset in {Preset.PRO_EASY, Preset.UBER_EASY}:
            spaz.impact_scale = 0.25

        # Add the material that causes us to hit the player-wall.
        spaz.pick_up_powerup_callback = self._on_player_picked_up_powerup
        return spaz

    def _on_player_picked_up_powerup(self, player: ba.Actor) -> None:
        del player  # Unused.
        self._player_has_picked_up_powerup = True

    def _drop_powerup(self, index: int, poweruptype: str = None) -> None:
        if poweruptype is None:
            poweruptype = (PowerupBoxFactory.get().get_random_powerup_type(
                excludetypes=self._exclude_powerups))
        PowerupBox(position=self.map.powerup_spawn_points[index],
                   poweruptype=poweruptype).autoretain()

    def _start_powerup_drops(self) -> None:
        ba.timer(3.0, self._drop_powerups, repeat=True)

    def _drop_powerups(self,
                       standard_points: bool = False,
                       force_first: str = None) -> None:
        """Generic powerup drop."""

        # If its been a minute since our last wave finished emerging, stop
        # giving out land-mine powerups. (prevents players from waiting
        # around for them on purpose and filling the map up)
        if ba.time() - self._last_wave_end_time > 60.0:
            extra_excludes = ['land_mines']
        else:
            extra_excludes = []

        if standard_points:
            points = self.map.powerup_spawn_points
            for i in range(len(points)):
                ba.timer(
                    1.0 + i * 0.5,
                    ba.Call(self._drop_powerup, i,
                            force_first if i == 0 else None))
        else:
            pos = (self._powerup_center[0] + random.uniform(
                -1.0 * self._powerup_spread[0], 1.0 * self._powerup_spread[0]),
                   self._powerup_center[1],
                   self._powerup_center[2] + random.uniform(
                       -self._powerup_spread[1], self._powerup_spread[1]))

            # drop one random one somewhere..
            assert self._exclude_powerups is not None
            PowerupBox(
                position=pos,
                poweruptype=PowerupBoxFactory.get().get_random_powerup_type(
                    excludetypes=self._exclude_powerups +
                    extra_excludes)).autoretain()

    def end_game(self) -> None:
        ba.pushcall(ba.Call(self.do_end, 'defeat'))
        ba.setmusic(None)
        ba.playsound(self._player_death_sound)

    def do_end(self, outcome: str) -> None:
        """End the game now with the provided outcome."""

        if outcome == 'defeat':
            delay = 2.0
            self.fade_to_red()
        else:
            delay = 0

        score: Optional[int]
        if self._wavenum >= 2:
            score = self._score
            fail_message = None
        else:
            score = None
            fail_message = 'Reach wave 2 to rank.'

        self.end(delay=delay,
                 results={
                     'outcome': outcome,
                     'score': score,
                     'fail_message': fail_message,
                     'playerinfos': self.initialplayerinfos
                 })

    def _on_got_scores_to_beat(self, scores: List[Dict[str, Any]]) -> None:
        self._show_standard_scores_to_beat_ui(scores)

    def _update_waves(self) -> None:
        # pylint: disable=too-many-branches

        # If we have no living bots, go to the next wave.
        if (self._can_end_wave and not self._bots.have_living_bots()
                and not self._game_over and self._lives > 0):

            self._can_end_wave = False
            self._time_bonus_timer = None
            self._time_bonus_text = None

            if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
                won = False
            else:
                assert self._waves is not None
                won = (self._wavenum == len(self._waves))

            # Reward time bonus.
            base_delay = 4.0 if won else 0
            if self._time_bonus > 0:
                ba.timer(0, ba.Call(ba.playsound, self._cashregistersound))
                ba.timer(base_delay,
                         ba.Call(self._award_time_bonus, self._time_bonus))
                base_delay += 1.0

            # Reward flawless bonus.
            if self._wavenum > 0 and self._flawless:
                ba.timer(base_delay, self._award_flawless_bonus)
                base_delay += 1.0

            self._flawless = True  # reset

            if won:

                # Completion achievements:
                if self._preset in {Preset.PRO, Preset.PRO_EASY}:
                    self._award_achievement('Pro Runaround Victory',
                                            sound=False)
                    if self._lives == self._start_lives:
                        self._award_achievement('The Wall', sound=False)
                    if not self._player_has_picked_up_powerup:
                        self._award_achievement('Precision Bombing',
                                                sound=False)
                elif self._preset in {Preset.UBER, Preset.UBER_EASY}:
                    self._award_achievement('Uber Runaround Victory',
                                            sound=False)
                    if self._lives == self._start_lives:
                        self._award_achievement('The Great Wall', sound=False)
                    if not self._a_player_has_been_killed:
                        self._award_achievement('Stayin\' Alive', sound=False)

                # Give remaining players some points and have them celebrate.
                self.show_zoom_message(ba.Lstr(resource='victoryText'),
                                       scale=1.0,
                                       duration=4.0)

                self.celebrate(10.0)
                ba.timer(base_delay, self._award_lives_bonus)
                base_delay += 1.0
                ba.timer(base_delay, self._award_completion_bonus)
                base_delay += 0.85
                ba.playsound(self._winsound)
                ba.cameraflash()
                ba.setmusic(ba.MusicType.VICTORY)
                self._game_over = True
                ba.timer(base_delay, ba.Call(self.do_end, 'victory'))
                return

            self._wavenum += 1

            # Short celebration after waves.
            if self._wavenum > 1:
                self.celebrate(0.5)

            ba.timer(base_delay, self._start_next_wave)

    def _award_completion_bonus(self) -> None:
        bonus = 200
        ba.playsound(self._cashregistersound)
        PopupText(ba.Lstr(value='+${A} ${B}',
                          subs=[('${A}', str(bonus)),
                                ('${B}',
                                 ba.Lstr(resource='completionBonusText'))]),
                  color=(0.7, 0.7, 1.0, 1),
                  scale=1.6,
                  position=(0, 1.5, -1)).autoretain()
        self._score += bonus
        self._update_scores()

    def _award_lives_bonus(self) -> None:
        bonus = self._lives * 30
        ba.playsound(self._cashregistersound)
        PopupText(ba.Lstr(value='+${A} ${B}',
                          subs=[('${A}', str(bonus)),
                                ('${B}', ba.Lstr(resource='livesBonusText'))]),
                  color=(0.7, 1.0, 0.3, 1),
                  scale=1.3,
                  position=(0, 1, -1)).autoretain()
        self._score += bonus
        self._update_scores()

    def _award_time_bonus(self, bonus: int) -> None:
        ba.playsound(self._cashregistersound)
        PopupText(ba.Lstr(value='+${A} ${B}',
                          subs=[('${A}', str(bonus)),
                                ('${B}', ba.Lstr(resource='timeBonusText'))]),
                  color=(1, 1, 0.5, 1),
                  scale=1.0,
                  position=(0, 3, -1)).autoretain()

        self._score += self._time_bonus
        self._update_scores()

    def _award_flawless_bonus(self) -> None:
        ba.playsound(self._cashregistersound)
        PopupText(ba.Lstr(value='+${A} ${B}',
                          subs=[('${A}', str(self._flawless_bonus)),
                                ('${B}', ba.Lstr(resource='perfectWaveText'))
                                ]),
                  color=(1, 1, 0.2, 1),
                  scale=1.2,
                  position=(0, 2, -1)).autoretain()

        assert self._flawless_bonus is not None
        self._score += self._flawless_bonus
        self._update_scores()

    def _start_time_bonus_timer(self) -> None:
        self._time_bonus_timer = ba.Timer(1.0,
                                          self._update_time_bonus,
                                          repeat=True)

    def _start_next_wave(self) -> None:
        # FIXME: Need to split this up.
        # pylint: disable=too-many-locals
        # pylint: disable=too-many-branches
        # pylint: disable=too-many-statements
        self.show_zoom_message(ba.Lstr(value='${A} ${B}',
                                       subs=[('${A}',
                                              ba.Lstr(resource='waveText')),
                                             ('${B}', str(self._wavenum))]),
                               scale=1.0,
                               duration=1.0,
                               trail=True)
        ba.timer(0.4, ba.Call(ba.playsound, self._new_wave_sound))
        t_sec = 0.0
        base_delay = 0.5
        delay = 0.0
        bot_types: List[Union[Spawn, Spacing, None]] = []

        if self._preset in {Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT}:
            level = self._wavenum
            target_points = (level + 1) * 8.0
            group_count = random.randint(1, 3)
            entries: List[Union[Spawn, Spacing, None]] = []
            spaz_types: List[Tuple[Type[SpazBot], float]] = []
            if level < 6:
                spaz_types += [(BomberBot, 5.0)]
            if level < 10:
                spaz_types += [(BrawlerBot, 5.0)]
            if level < 15:
                spaz_types += [(TriggerBot, 6.0)]
            if level > 5:
                spaz_types += [(TriggerBotPro, 7.5)] * (1 + (level - 5) // 7)
            if level > 2:
                spaz_types += [(BomberBotProShielded, 8.0)
                               ] * (1 + (level - 2) // 6)
            if level > 6:
                spaz_types += [(TriggerBotProShielded, 12.0)
                               ] * (1 + (level - 6) // 5)
            if level > 1:
                spaz_types += ([(ChargerBot, 10.0)] * (1 + (level - 1) // 4))
            if level > 7:
                spaz_types += [(ChargerBotProShielded, 15.0)
                               ] * (1 + (level - 7) // 3)

            # Bot type, their effect on target points.
            defender_types: List[Tuple[Type[SpazBot], float]] = [
                (BomberBot, 0.9),
                (BrawlerBot, 0.9),
                (TriggerBot, 0.85),
            ]
            if level > 2:
                defender_types += [(ChargerBot, 0.75)]
            if level > 4:
                defender_types += ([(StickyBot, 0.7)] * (1 + (level - 5) // 6))
            if level > 6:
                defender_types += ([(ExplodeyBot, 0.7)] * (1 +
                                                           (level - 5) // 5))
            if level > 8:
                defender_types += ([(BrawlerBotProShielded, 0.65)] *
                                   (1 + (level - 5) // 4))
            if level > 10:
                defender_types += ([(TriggerBotProShielded, 0.6)] *
                                   (1 + (level - 6) // 3))

            for group in range(group_count):
                this_target_point_s = target_points / group_count

                # Adding spacing makes things slightly harder.
                rval = random.random()
                if rval < 0.07:
                    spacing = 1.5
                    this_target_point_s *= 0.85
                elif rval < 0.15:
                    spacing = 1.0
                    this_target_point_s *= 0.9
                else:
                    spacing = 0.0

                path = random.randint(1, 3)

                # Don't allow hard paths on early levels.
                if level < 3:
                    if path == 1:
                        path = 3

                # Easy path.
                if path == 3:
                    pass

                # Harder path.
                elif path == 2:
                    this_target_point_s *= 0.8

                # Even harder path.
                elif path == 1:
                    this_target_point_s *= 0.7

                # Looping forward.
                elif path == 4:
                    this_target_point_s *= 0.7

                # Looping backward.
                elif path == 5:
                    this_target_point_s *= 0.7

                # Random.
                elif path == 6:
                    this_target_point_s *= 0.7

                def _add_defender(defender_type: Tuple[Type[SpazBot], float],
                                  pnt: Point) -> Tuple[float, Spawn]:
                    # This is ok because we call it immediately.
                    # pylint: disable=cell-var-from-loop
                    return this_target_point_s * defender_type[1], Spawn(
                        defender_type[0], point=pnt)

                # Add defenders.
                defender_type1 = defender_types[random.randrange(
                    len(defender_types))]
                defender_type2 = defender_types[random.randrange(
                    len(defender_types))]
                defender1 = defender2 = None
                if ((group == 0) or (group == 1 and level > 3)
                        or (group == 2 and level > 5)):
                    if random.random() < min(0.75, (level - 1) * 0.11):
                        this_target_point_s, defender1 = _add_defender(
                            defender_type1, Point.BOTTOM_LEFT)
                    if random.random() < min(0.75, (level - 1) * 0.04):
                        this_target_point_s, defender2 = _add_defender(
                            defender_type2, Point.BOTTOM_RIGHT)

                spaz_type = spaz_types[random.randrange(len(spaz_types))]
                member_count = max(
                    1, int(round(this_target_point_s / spaz_type[1])))
                for i, _member in enumerate(range(member_count)):
                    if path == 4:
                        this_path = i % 3  # Looping forward.
                    elif path == 5:
                        this_path = 3 - (i % 3)  # Looping backward.
                    elif path == 6:
                        this_path = random.randint(1, 3)  # Random.
                    else:
                        this_path = path
                    entries.append(Spawn(spaz_type[0], path=this_path))
                    if spacing != 0.0:
                        entries.append(Spacing(duration=spacing))

                if defender1 is not None:
                    entries.append(defender1)
                if defender2 is not None:
                    entries.append(defender2)

                # Some spacing between groups.
                rval = random.random()
                if rval < 0.1:
                    spacing = 5.0
                elif rval < 0.5:
                    spacing = 1.0
                else:
                    spacing = 1.0
                entries.append(Spacing(duration=spacing))

            wave = Wave(entries=entries)

        else:
            assert self._waves is not None
            wave = self._waves[self._wavenum - 1]

        bot_types += wave.entries
        self._time_bonus_mult = 1.0
        this_flawless_bonus = 0
        non_runner_spawn_time = 1.0

        for info in bot_types:
            if info is None:
                continue
            if isinstance(info, Spacing):
                t_sec += info.duration
                continue
            bot_type = info.type
            path = info.path
            self._time_bonus_mult += bot_type.points_mult * 0.02
            this_flawless_bonus += bot_type.points_mult * 5

            # If its got a position, use that.
            if info.point is not None:
                point = info.point
            else:
                point = Point.START

            # Space our our slower bots.
            delay = base_delay
            delay /= self._get_bot_speed(bot_type)
            t_sec += delay * 0.5
            tcall = ba.Call(
                self.add_bot_at_point, point, bot_type, path,
                0.1 if point is Point.START else non_runner_spawn_time)
            ba.timer(t_sec, tcall)
            t_sec += delay * 0.5

        # We can end the wave after all the spawning happens.
        ba.timer(t_sec - delay * 0.5 + non_runner_spawn_time + 0.01,
                 self._set_can_end_wave)

        # Reset our time bonus.
        # In this game we use a constant time bonus so it erodes away in
        # roughly the same time (since the time limit a wave can take is
        # relatively constant) ..we then post-multiply a modifier to adjust
        # points.
        self._time_bonus = 150
        self._flawless_bonus = this_flawless_bonus
        assert self._time_bonus_mult is not None
        txtval = ba.Lstr(
            value='${A}: ${B}',
            subs=[('${A}', ba.Lstr(resource='timeBonusText')),
                  ('${B}', str(int(self._time_bonus * self._time_bonus_mult)))
                  ])
        self._time_bonus_text = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'v_attach': 'top',
                           'h_attach': 'center',
                           'h_align': 'center',
                           'color': (1, 1, 0.0, 1),
                           'shadow': 1.0,
                           'vr_depth': -30,
                           'flatness': 1.0,
                           'position': (0, -60),
                           'scale': 0.8,
                           'text': txtval
                       }))

        ba.timer(t_sec, self._start_time_bonus_timer)

        # Keep track of when this wave finishes emerging. We wanna stop
        # dropping land-mines powerups at some point (otherwise a crafty
        # player could fill the whole map with them)
        self._last_wave_end_time = ba.time() + t_sec
        totalwaves = str(len(self._waves)) if self._waves is not None else '??'
        txtval = ba.Lstr(value='${A} ${B}',
                         subs=[('${A}', ba.Lstr(resource='waveText')),
                               ('${B}',
                                str(self._wavenum) + ('' if self._preset in {
                                    Preset.ENDLESS, Preset.ENDLESS_TOURNAMENT
                                } else f'/{totalwaves}'))])
        self._wave_text = ba.NodeActor(
            ba.newnode('text',
                       attrs={
                           'v_attach': 'top',
                           'h_attach': 'center',
                           'h_align': 'center',
                           'vr_depth': -10,
                           'color': (1, 1, 1, 1),
                           'shadow': 1.0,
                           'flatness': 1.0,
                           'position': (0, -40),
                           'scale': 1.3,
                           'text': txtval
                       }))

    def _on_bot_spawn(self, path: int, spaz: SpazBot) -> None:

        # Add our custom update callback and set some info for this bot.
        spaz_type = type(spaz)
        assert spaz is not None
        spaz.update_callback = self._update_bot

        # Tack some custom attrs onto the spaz.
        setattr(spaz, 'r_walk_row', path)
        setattr(spaz, 'r_walk_speed', self._get_bot_speed(spaz_type))

    def add_bot_at_point(self,
                         point: Point,
                         spaztype: Type[SpazBot],
                         path: int,
                         spawn_time: float = 0.1) -> None:
        """Add the given type bot with the given delay (in seconds)."""

        # Don't add if the game has ended.
        if self._game_over:
            return
        pos = self.map.defs.points[point.value][:3]
        self._bots.spawn_bot(spaztype,
                             pos=pos,
                             spawn_time=spawn_time,
                             on_spawn_call=ba.Call(self._on_bot_spawn, path))

    def _update_time_bonus(self) -> None:
        self._time_bonus = int(self._time_bonus * 0.91)
        if self._time_bonus > 0 and self._time_bonus_text is not None:
            assert self._time_bonus_text.node
            assert self._time_bonus_mult
            self._time_bonus_text.node.text = ba.Lstr(
                value='${A}: ${B}',
                subs=[('${A}', ba.Lstr(resource='timeBonusText')),
                      ('${B}',
                       str(int(self._time_bonus * self._time_bonus_mult)))])
        else:
            self._time_bonus_text = None

    def _start_updating_waves(self) -> None:
        self._wave_update_timer = ba.Timer(2.0,
                                           self._update_waves,
                                           repeat=True)

    def _update_scores(self) -> None:
        score = self._score
        if self._preset is Preset.ENDLESS:
            if score >= 500:
                self._award_achievement('Runaround Master')
            if score >= 1000:
                self._award_achievement('Runaround Wizard')
            if score >= 2000:
                self._award_achievement('Runaround God')

        assert self._scoreboard is not None
        self._scoreboard.set_team_value(self.teams[0], score, max_score=None)

    def _update_bot(self, bot: SpazBot) -> bool:
        # Yup; that's a lot of return statements right there.
        # pylint: disable=too-many-return-statements

        if not bool(bot):
            return True

        assert bot.node

        # FIXME: Do this in a type safe way.
        r_walk_speed: float = getattr(bot, 'r_walk_speed')
        r_walk_row: int = getattr(bot, 'r_walk_row')

        speed = r_walk_speed
        pos = bot.node.position
        boxes = self.map.defs.boxes

        # Bots in row 1 attempt the high road..
        if r_walk_row == 1:
            if ba.is_point_in_box(pos, boxes['b4']):
                bot.node.move_up_down = speed
                bot.node.move_left_right = 0
                bot.node.run = 0.0
                return True

        # Row 1 and 2 bots attempt the middle road..
        if r_walk_row in [1, 2]:
            if ba.is_point_in_box(pos, boxes['b1']):
                bot.node.move_up_down = speed
                bot.node.move_left_right = 0
                bot.node.run = 0.0
                return True

        # All bots settle for the third row.
        if ba.is_point_in_box(pos, boxes['b7']):
            bot.node.move_up_down = speed
            bot.node.move_left_right = 0
            bot.node.run = 0.0
            return True
        if ba.is_point_in_box(pos, boxes['b2']):
            bot.node.move_up_down = -speed
            bot.node.move_left_right = 0
            bot.node.run = 0.0
            return True
        if ba.is_point_in_box(pos, boxes['b3']):
            bot.node.move_up_down = -speed
            bot.node.move_left_right = 0
            bot.node.run = 0.0
            return True
        if ba.is_point_in_box(pos, boxes['b5']):
            bot.node.move_up_down = -speed
            bot.node.move_left_right = 0
            bot.node.run = 0.0
            return True
        if ba.is_point_in_box(pos, boxes['b6']):
            bot.node.move_up_down = speed
            bot.node.move_left_right = 0
            bot.node.run = 0.0
            return True
        if ((ba.is_point_in_box(pos, boxes['b8'])
             and not ba.is_point_in_box(pos, boxes['b9']))
                or pos == (0.0, 0.0, 0.0)):

            # Default to walking right if we're still in the walking area.
            bot.node.move_left_right = speed
            bot.node.move_up_down = 0
            bot.node.run = 0.0
            return True

        # Revert to normal bot behavior otherwise..
        return False

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, ba.PlayerScoredMessage):
            self._score += msg.score
            self._update_scores()

        elif isinstance(msg, ba.PlayerDiedMessage):
            # Augment standard behavior.
            super().handlemessage(msg)

            self._a_player_has_been_killed = True

            # Respawn them shortly.
            player = msg.getplayer(Player)
            assert self.initialplayerinfos is not None
            respawn_time = 2.0 + len(self.initialplayerinfos) * 1.0
            player.respawn_timer = ba.Timer(
                respawn_time, ba.Call(self.spawn_player_if_exists, player))
            player.respawn_icon = RespawnIcon(player, respawn_time)

        elif isinstance(msg, SpazBotDiedMessage):
            if msg.how is ba.DeathType.REACHED_GOAL:
                return None
            pts, importance = msg.spazbot.get_death_points(msg.how)
            if msg.killerplayer is not None:
                target: Optional[Sequence[float]]
                try:
                    assert msg.spazbot is not None
                    assert msg.spazbot.node
                    target = msg.spazbot.node.position
                except Exception:
                    ba.print_exception()
                    target = None
                try:
                    if msg.killerplayer:
                        self.stats.player_scored(msg.killerplayer,
                                                 pts,
                                                 target=target,
                                                 kill=True,
                                                 screenmessage=False,
                                                 importance=importance)
                        ba.playsound(self._dingsound if importance == 1 else
                                     self._dingsoundhigh,
                                     volume=0.6)
                except Exception:
                    ba.print_exception('Error on SpazBotDiedMessage')

            # Normally we pull scores from the score-set, but if there's no
            # player lets be explicit.
            else:
                self._score += pts
            self._update_scores()

        else:
            return super().handlemessage(msg)
        return None

    def _get_bot_speed(self, bot_type: Type[SpazBot]) -> float:
        speed = self._bot_speed_map.get(bot_type)
        if speed is None:
            raise TypeError('Invalid bot type to _get_bot_speed(): ' +
                            str(bot_type))
        return speed

    def _set_can_end_wave(self) -> None:
        self._can_end_wave = True
コード例 #25
0
ファイル: kingofthehill.py プロジェクト: Dmitry450/ballistica
class KingOfTheHillGame(ba.TeamGameActivity):
    """Game where a team wins by holding a 'hill' for a set amount of time."""

    FLAG_NEW = 0
    FLAG_UNCONTESTED = 1
    FLAG_CONTESTED = 2
    FLAG_HELD = 3

    @classmethod
    def get_name(cls) -> str:
        return 'King of the Hill'

    @classmethod
    def get_description(cls, sessiontype: Type[ba.Session]) -> str:
        return 'Secure the flag for a set length of time.'

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

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

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

    @classmethod
    def get_settings(
            cls,
            sessiontype: Type[ba.Session]) -> List[Tuple[str, Dict[str, Any]]]:
        return [("Hold Time", {
            'min_value': 10,
            'default': 30,
            'increment': 10
        }),
                ("Time Limit", {
                    'choices': [('None', 0), ('1 Minute', 60),
                                ('2 Minutes', 120), ('5 Minutes', 300),
                                ('10 Minutes', 600), ('20 Minutes', 1200)],
                    'default': 0
                }),
                ("Respawn Times", {
                    'choices': [('Shorter', 0.25), ('Short', 0.5),
                                ('Normal', 1.0), ('Long', 2.0),
                                ('Longer', 4.0)],
                    'default': 1.0
                })]

    def __init__(self, settings: Dict[str, Any]):
        from bastd.actor.scoreboard import Scoreboard
        super().__init__(settings)
        self._scoreboard = Scoreboard()
        self._swipsound = ba.getsound("swip")
        self._tick_sound = ba.getsound('tick')
        self._countdownsounds = {
            10: ba.getsound('announceTen'),
            9: ba.getsound('announceNine'),
            8: ba.getsound('announceEight'),
            7: ba.getsound('announceSeven'),
            6: ba.getsound('announceSix'),
            5: ba.getsound('announceFive'),
            4: ba.getsound('announceFour'),
            3: ba.getsound('announceThree'),
            2: ba.getsound('announceTwo'),
            1: ba.getsound('announceOne')
        }
        self._flag_pos: Optional[Sequence[float]] = None
        self._flag_state: Optional[int] = None
        self._flag: Optional[stdflag.Flag] = None
        self._flag_light: Optional[ba.Node] = None
        self._scoring_team: Optional[ReferenceType[ba.Team]] = None

        self._flag_region_material = ba.Material()
        self._flag_region_material.add_actions(
            conditions=("they_have_material", ba.sharedobj('player_material')),
            actions=(("modify_part_collision", "collide",
                      True), ("modify_part_collision", "physical", False),
                     ("call", "at_connect",
                      ba.Call(self._handle_player_flag_region_collide, True)),
                     ("call", "at_disconnect",
                      ba.Call(self._handle_player_flag_region_collide,
                              False))))

    def get_instance_description(self) -> Union[str, Sequence]:
        return ('Secure the flag for ${ARG1} seconds.',
                self.settings['Hold Time'])

    def get_instance_scoreboard_description(self) -> Union[str, Sequence]:
        return ('secure the flag for ${ARG1} seconds',
                self.settings['Hold Time'])

    def on_transition_in(self) -> None:
        self.default_music = ba.MusicType.SCARY
        super().on_transition_in()

    def on_team_join(self, team: ba.Team) -> None:
        team.gamedata['time_remaining'] = self.settings["Hold Time"]
        self._update_scoreboard()

    def on_player_join(self, player: ba.Player) -> None:
        ba.TeamGameActivity.on_player_join(self, player)
        player.gamedata['at_flag'] = 0

    def on_begin(self) -> None:
        super().on_begin()
        self.setup_standard_time_limit(self.settings['Time Limit'])
        self.setup_standard_powerup_drops()
        self._flag_pos = self.map.get_flag_position(None)
        ba.timer(1.0, self._tick, repeat=True)
        self._flag_state = self.FLAG_NEW
        self.project_flag_stand(self._flag_pos)

        self._flag = stdflag.Flag(position=self._flag_pos,
                                  touchable=False,
                                  color=(1, 1, 1))
        self._flag_light = ba.newnode('light',
                                      attrs={
                                          'position': self._flag_pos,
                                          'intensity': 0.2,
                                          'height_attenuated': False,
                                          'radius': 0.4,
                                          'color': (0.2, 0.2, 0.2)
                                      })

        # Flag region.
        flagmats = [
            self._flag_region_material,
            ba.sharedobj('region_material')
        ]
        ba.newnode('region',
                   attrs={
                       'position': self._flag_pos,
                       'scale': (1.8, 1.8, 1.8),
                       'type': 'sphere',
                       'materials': flagmats
                   })
        self._update_flag_state()

    def _tick(self) -> None:
        self._update_flag_state()

        # Give holding players points.
        for player in self.players:
            if player.gamedata['at_flag'] > 0:
                self.stats.player_scored(player,
                                         3,
                                         screenmessage=False,
                                         display=False)

        if self._scoring_team is None:
            scoring_team = None
        else:
            scoring_team = self._scoring_team()
        if scoring_team:

            if scoring_team.gamedata['time_remaining'] > 0:
                ba.playsound(self._tick_sound)

            scoring_team.gamedata['time_remaining'] = max(
                0, scoring_team.gamedata['time_remaining'] - 1)
            self._update_scoreboard()
            if scoring_team.gamedata['time_remaining'] > 0:
                assert self._flag is not None
                self._flag.set_score_text(
                    str(scoring_team.gamedata['time_remaining']))

            # Announce numbers we have sounds for.
            try:
                ba.playsound(self._countdownsounds[
                    scoring_team.gamedata['time_remaining']])
            except Exception:
                pass

            # winner
            if scoring_team.gamedata['time_remaining'] <= 0:
                self.end_game()

    def end_game(self) -> None:
        results = ba.TeamGameResults()
        for team in self.teams:
            results.set_team_score(
                team,
                self.settings['Hold Time'] - team.gamedata['time_remaining'])
        self.end(results=results, announce_delay=0)

    def _update_flag_state(self) -> None:
        holding_teams = set(player.team for player in self.players
                            if player.gamedata['at_flag'])
        prev_state = self._flag_state
        assert self._flag_light
        assert self._flag is not None
        assert self._flag.node
        if len(holding_teams) > 1:
            self._flag_state = self.FLAG_CONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.6, 0.6, 0.1)
            self._flag.node.color = (1.0, 1.0, 0.4)
        elif len(holding_teams) == 1:
            holding_team = list(holding_teams)[0]
            self._flag_state = self.FLAG_HELD
            self._scoring_team = weakref.ref(holding_team)
            self._flag_light.color = ba.normalized_color(holding_team.color)
            self._flag.node.color = holding_team.color
        else:
            self._flag_state = self.FLAG_UNCONTESTED
            self._scoring_team = None
            self._flag_light.color = (0.2, 0.2, 0.2)
            self._flag.node.color = (1, 1, 1)
        if self._flag_state != prev_state:
            ba.playsound(self._swipsound)

    def _handle_player_flag_region_collide(self, colliding: bool) -> None:
        playernode = ba.get_collision_info("opposing_node")
        try:
            player = playernode.getdelegate().getplayer()
        except Exception:
            return

        # Different parts of us can collide so a single value isn't enough
        # also don't count it if we're dead (flying heads shouldn't be able to
        # win the game :-)
        if colliding and player.is_alive():
            player.gamedata['at_flag'] += 1
        else:
            player.gamedata['at_flag'] = max(0, player.gamedata['at_flag'] - 1)

        self._update_flag_state()

    def _update_scoreboard(self) -> None:
        for team in self.teams:
            self._scoreboard.set_team_value(team,
                                            team.gamedata['time_remaining'],
                                            self.settings['Hold Time'],
                                            countdown=True)

    def handlemessage(self, msg: Any) -> Any:
        if isinstance(msg, playerspaz.PlayerSpazDeathMessage):
            super().handlemessage(msg)  # Augment default.

            # No longer can count as at_flag once dead.
            player = msg.spaz.player
            player.gamedata['at_flag'] = 0
            self._update_flag_state()
            self.respawn_player(player)