bastd.game.ninjafight

Provides Ninja Fight mini-game.

  1# Released under the MIT License. See LICENSE for details.
  2#
  3"""Provides Ninja Fight mini-game."""
  4
  5# ba_meta require api 7
  6# (see https://ballistica.net/wiki/meta-tag-system)
  7
  8from __future__ import annotations
  9
 10import random
 11from typing import TYPE_CHECKING
 12
 13import ba
 14from bastd.actor.spazbot import SpazBotSet, ChargerBot, SpazBotDiedMessage
 15from bastd.actor.onscreentimer import OnScreenTimer
 16
 17if TYPE_CHECKING:
 18    from typing import Any
 19
 20
 21class Player(ba.Player['Team']):
 22    """Our player type for this game."""
 23
 24
 25class Team(ba.Team[Player]):
 26    """Our team type for this game."""
 27
 28
 29# ba_meta export game
 30class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
 31    """
 32    A co-op game where you try to defeat a group
 33    of Ninjas as fast as possible
 34    """
 35
 36    name = 'Ninja Fight'
 37    description = 'How fast can you defeat the ninjas?'
 38    scoreconfig = ba.ScoreConfig(label='Time',
 39                                 scoretype=ba.ScoreType.MILLISECONDS,
 40                                 lower_is_better=True)
 41    default_music = ba.MusicType.TO_THE_DEATH
 42
 43    @classmethod
 44    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 45        # For now we're hard-coding spawn positions and whatnot
 46        # so we need to be sure to specify that we only support
 47        # a specific map.
 48        return ['Courtyard']
 49
 50    @classmethod
 51    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 52        # We currently support Co-Op only.
 53        return issubclass(sessiontype, ba.CoopSession)
 54
 55    # In the constructor we should load any media we need/etc.
 56    # ...but not actually create anything yet.
 57    def __init__(self, settings: dict):
 58        super().__init__(settings)
 59        self._winsound = ba.getsound('score')
 60        self._won = False
 61        self._timer: OnScreenTimer | None = None
 62        self._bots = SpazBotSet()
 63        self._preset = str(settings['preset'])
 64
 65    # Called when our game actually begins.
 66    def on_begin(self) -> None:
 67        super().on_begin()
 68        is_pro = self._preset == 'pro'
 69
 70        # In pro mode there's no powerups.
 71        if not is_pro:
 72            self.setup_standard_powerup_drops()
 73
 74        # Make our on-screen timer and start it roughly when our bots appear.
 75        self._timer = OnScreenTimer()
 76        ba.timer(4.0, self._timer.start)
 77
 78        # Spawn some baddies.
 79        ba.timer(
 80            1.0, lambda: self._bots.spawn_bot(
 81                ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
 82        ba.timer(
 83            2.0, lambda: self._bots.spawn_bot(
 84                ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
 85        ba.timer(
 86            3.0, lambda: self._bots.spawn_bot(
 87                ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
 88        ba.timer(
 89            4.0, lambda: self._bots.spawn_bot(
 90                ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
 91
 92        # Add some extras for multiplayer or pro mode.
 93        assert self.initialplayerinfos is not None
 94        if len(self.initialplayerinfos) > 2 or is_pro:
 95            ba.timer(
 96                5.0, lambda: self._bots.spawn_bot(
 97                    ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
 98        if len(self.initialplayerinfos) > 3 or is_pro:
 99            ba.timer(
100                6.0, lambda: self._bots.spawn_bot(
101                    ChargerBot, pos=(0, 3, 1), spawn_time=3.0))
102
103    # Called for each spawning player.
104    def spawn_player(self, player: Player) -> ba.Actor:
105
106        # Let's spawn close to the center.
107        spawn_center = (0, 3, -2)
108        pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
109               spawn_center[2] + random.uniform(-1.5, 1.5))
110        return self.spawn_player_spaz(player, position=pos)
111
112    def _check_if_won(self) -> None:
113        # Simply end the game if there's no living bots.
114        # FIXME: Should also make sure all bots have been spawned;
115        #  if spawning is spread out enough that we're able to kill
116        #  all living bots before the next spawns, it would incorrectly
117        #  count as a win.
118        if not self._bots.have_living_bots():
119            self._won = True
120            self.end_game()
121
122    # Called for miscellaneous messages.
123    def handlemessage(self, msg: Any) -> Any:
124
125        # A player has died.
126        if isinstance(msg, ba.PlayerDiedMessage):
127            super().handlemessage(msg)  # Augment standard behavior.
128            self.respawn_player(msg.getplayer(Player))
129
130        # A spaz-bot has died.
131        elif isinstance(msg, SpazBotDiedMessage):
132            # Unfortunately the bot-set will always tell us there are living
133            # bots if we ask here (the currently-dying bot isn't officially
134            # marked dead yet) ..so lets push a call into the event loop to
135            # check once this guy has finished dying.
136            ba.pushcall(self._check_if_won)
137
138        # Let the base class handle anything we don't.
139        else:
140            return super().handlemessage(msg)
141        return None
142
143    # When this is called, we should fill out results and end the game
144    # *regardless* of whether is has been won. (this may be called due
145    # to a tournament ending or other external reason).
146    def end_game(self) -> None:
147
148        # Stop our on-screen timer so players can see what they got.
149        assert self._timer is not None
150        self._timer.stop()
151
152        results = ba.GameResults()
153
154        # If we won, set our score to the elapsed time in milliseconds.
155        # (there should just be 1 team here since this is co-op).
156        # ..if we didn't win, leave scores as default (None) which means
157        # we lost.
158        if self._won:
159            elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0)
160            ba.cameraflash()
161            ba.playsound(self._winsound)
162            for team in self.teams:
163                for player in team.players:
164                    if player.actor:
165                        player.actor.handlemessage(ba.CelebrateMessage())
166                results.set_team_score(team, elapsed_time_ms)
167
168        # Ends the activity.
169        self.end(results)
class Player(ba._player.Player[ForwardRef('Team')]):
22class Player(ba.Player['Team']):
23    """Our player type for this game."""

Our player type for this game.

Player()
class Team(ba._team.Team[bastd.game.ninjafight.Player]):
26class Team(ba.Team[Player]):
27    """Our team type for this game."""

Our team type for this game.

Team()
Inherited Members
ba._team.Team
manual_init
customdata
on_expire
sessionteam
class NinjaFightGame(ba._teamgame.TeamGameActivity[bastd.game.ninjafight.Player, bastd.game.ninjafight.Team]):
 31class NinjaFightGame(ba.TeamGameActivity[Player, Team]):
 32    """
 33    A co-op game where you try to defeat a group
 34    of Ninjas as fast as possible
 35    """
 36
 37    name = 'Ninja Fight'
 38    description = 'How fast can you defeat the ninjas?'
 39    scoreconfig = ba.ScoreConfig(label='Time',
 40                                 scoretype=ba.ScoreType.MILLISECONDS,
 41                                 lower_is_better=True)
 42    default_music = ba.MusicType.TO_THE_DEATH
 43
 44    @classmethod
 45    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
 46        # For now we're hard-coding spawn positions and whatnot
 47        # so we need to be sure to specify that we only support
 48        # a specific map.
 49        return ['Courtyard']
 50
 51    @classmethod
 52    def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool:
 53        # We currently support Co-Op only.
 54        return issubclass(sessiontype, ba.CoopSession)
 55
 56    # In the constructor we should load any media we need/etc.
 57    # ...but not actually create anything yet.
 58    def __init__(self, settings: dict):
 59        super().__init__(settings)
 60        self._winsound = ba.getsound('score')
 61        self._won = False
 62        self._timer: OnScreenTimer | None = None
 63        self._bots = SpazBotSet()
 64        self._preset = str(settings['preset'])
 65
 66    # Called when our game actually begins.
 67    def on_begin(self) -> None:
 68        super().on_begin()
 69        is_pro = self._preset == 'pro'
 70
 71        # In pro mode there's no powerups.
 72        if not is_pro:
 73            self.setup_standard_powerup_drops()
 74
 75        # Make our on-screen timer and start it roughly when our bots appear.
 76        self._timer = OnScreenTimer()
 77        ba.timer(4.0, self._timer.start)
 78
 79        # Spawn some baddies.
 80        ba.timer(
 81            1.0, lambda: self._bots.spawn_bot(
 82                ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
 83        ba.timer(
 84            2.0, lambda: self._bots.spawn_bot(
 85                ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
 86        ba.timer(
 87            3.0, lambda: self._bots.spawn_bot(
 88                ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
 89        ba.timer(
 90            4.0, lambda: self._bots.spawn_bot(
 91                ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
 92
 93        # Add some extras for multiplayer or pro mode.
 94        assert self.initialplayerinfos is not None
 95        if len(self.initialplayerinfos) > 2 or is_pro:
 96            ba.timer(
 97                5.0, lambda: self._bots.spawn_bot(
 98                    ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
 99        if len(self.initialplayerinfos) > 3 or is_pro:
100            ba.timer(
101                6.0, lambda: self._bots.spawn_bot(
102                    ChargerBot, pos=(0, 3, 1), spawn_time=3.0))
103
104    # Called for each spawning player.
105    def spawn_player(self, player: Player) -> ba.Actor:
106
107        # Let's spawn close to the center.
108        spawn_center = (0, 3, -2)
109        pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
110               spawn_center[2] + random.uniform(-1.5, 1.5))
111        return self.spawn_player_spaz(player, position=pos)
112
113    def _check_if_won(self) -> None:
114        # Simply end the game if there's no living bots.
115        # FIXME: Should also make sure all bots have been spawned;
116        #  if spawning is spread out enough that we're able to kill
117        #  all living bots before the next spawns, it would incorrectly
118        #  count as a win.
119        if not self._bots.have_living_bots():
120            self._won = True
121            self.end_game()
122
123    # Called for miscellaneous messages.
124    def handlemessage(self, msg: Any) -> Any:
125
126        # A player has died.
127        if isinstance(msg, ba.PlayerDiedMessage):
128            super().handlemessage(msg)  # Augment standard behavior.
129            self.respawn_player(msg.getplayer(Player))
130
131        # A spaz-bot has died.
132        elif isinstance(msg, SpazBotDiedMessage):
133            # Unfortunately the bot-set will always tell us there are living
134            # bots if we ask here (the currently-dying bot isn't officially
135            # marked dead yet) ..so lets push a call into the event loop to
136            # check once this guy has finished dying.
137            ba.pushcall(self._check_if_won)
138
139        # Let the base class handle anything we don't.
140        else:
141            return super().handlemessage(msg)
142        return None
143
144    # When this is called, we should fill out results and end the game
145    # *regardless* of whether is has been won. (this may be called due
146    # to a tournament ending or other external reason).
147    def end_game(self) -> None:
148
149        # Stop our on-screen timer so players can see what they got.
150        assert self._timer is not None
151        self._timer.stop()
152
153        results = ba.GameResults()
154
155        # If we won, set our score to the elapsed time in milliseconds.
156        # (there should just be 1 team here since this is co-op).
157        # ..if we didn't win, leave scores as default (None) which means
158        # we lost.
159        if self._won:
160            elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0)
161            ba.cameraflash()
162            ba.playsound(self._winsound)
163            for team in self.teams:
164                for player in team.players:
165                    if player.actor:
166                        player.actor.handlemessage(ba.CelebrateMessage())
167                results.set_team_score(team, elapsed_time_ms)
168
169        # Ends the activity.
170        self.end(results)

A co-op game where you try to defeat a group of Ninjas as fast as possible

NinjaFightGame(settings: dict)
58    def __init__(self, settings: dict):
59        super().__init__(settings)
60        self._winsound = ba.getsound('score')
61        self._won = False
62        self._timer: OnScreenTimer | None = None
63        self._bots = SpazBotSet()
64        self._preset = str(settings['preset'])

Instantiate the Activity.

name: str | None = 'Ninja Fight'
description: str | None = 'How fast can you defeat the ninjas?'
scoreconfig: ba._score.ScoreConfig | None = ScoreConfig(label='Time', scoretype=<ScoreType.MILLISECONDS: 'ms'>, lower_is_better=True, none_is_winner=False, version='')
default_music: ba._music.MusicType | None = <MusicType.TO_THE_DEATH: 'ToTheDeath'>
@classmethod
def get_supported_maps(cls, sessiontype: type[ba._session.Session]) -> list[str]:
44    @classmethod
45    def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]:
46        # For now we're hard-coding spawn positions and whatnot
47        # so we need to be sure to specify that we only support
48        # a specific map.
49        return ['Courtyard']

Called by the default ba.GameActivity.create_settings_ui() implementation; should return a list of map names valid for this game-type for the given ba.Session type.

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

Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.

def on_begin(self) -> None:
 67    def on_begin(self) -> None:
 68        super().on_begin()
 69        is_pro = self._preset == 'pro'
 70
 71        # In pro mode there's no powerups.
 72        if not is_pro:
 73            self.setup_standard_powerup_drops()
 74
 75        # Make our on-screen timer and start it roughly when our bots appear.
 76        self._timer = OnScreenTimer()
 77        ba.timer(4.0, self._timer.start)
 78
 79        # Spawn some baddies.
 80        ba.timer(
 81            1.0, lambda: self._bots.spawn_bot(
 82                ChargerBot, pos=(3, 3, -2), spawn_time=3.0))
 83        ba.timer(
 84            2.0, lambda: self._bots.spawn_bot(
 85                ChargerBot, pos=(-3, 3, -2), spawn_time=3.0))
 86        ba.timer(
 87            3.0, lambda: self._bots.spawn_bot(
 88                ChargerBot, pos=(5, 3, -2), spawn_time=3.0))
 89        ba.timer(
 90            4.0, lambda: self._bots.spawn_bot(
 91                ChargerBot, pos=(-5, 3, -2), spawn_time=3.0))
 92
 93        # Add some extras for multiplayer or pro mode.
 94        assert self.initialplayerinfos is not None
 95        if len(self.initialplayerinfos) > 2 or is_pro:
 96            ba.timer(
 97                5.0, lambda: self._bots.spawn_bot(
 98                    ChargerBot, pos=(0, 3, -5), spawn_time=3.0))
 99        if len(self.initialplayerinfos) > 3 or is_pro:
100            ba.timer(
101                6.0, lambda: self._bots.spawn_bot(
102                    ChargerBot, pos=(0, 3, 1), spawn_time=3.0))

Called once the previous ba.Activity has finished transitioning out.

At this point the activity's initial players and teams are filled in and it should begin its actual game logic.

def spawn_player(self, player: bastd.game.ninjafight.Player) -> ba._actor.Actor:
105    def spawn_player(self, player: Player) -> ba.Actor:
106
107        # Let's spawn close to the center.
108        spawn_center = (0, 3, -2)
109        pos = (spawn_center[0] + random.uniform(-1.5, 1.5), spawn_center[1],
110               spawn_center[2] + random.uniform(-1.5, 1.5))
111        return self.spawn_player_spaz(player, position=pos)

Spawn something for the provided ba.Player.

The default implementation simply calls spawn_player_spaz().

def handlemessage(self, msg: Any) -> Any:
124    def handlemessage(self, msg: Any) -> Any:
125
126        # A player has died.
127        if isinstance(msg, ba.PlayerDiedMessage):
128            super().handlemessage(msg)  # Augment standard behavior.
129            self.respawn_player(msg.getplayer(Player))
130
131        # A spaz-bot has died.
132        elif isinstance(msg, SpazBotDiedMessage):
133            # Unfortunately the bot-set will always tell us there are living
134            # bots if we ask here (the currently-dying bot isn't officially
135            # marked dead yet) ..so lets push a call into the event loop to
136            # check once this guy has finished dying.
137            ba.pushcall(self._check_if_won)
138
139        # Let the base class handle anything we don't.
140        else:
141            return super().handlemessage(msg)
142        return None

General message handling; can be passed any message object.

def end_game(self) -> None:
147    def end_game(self) -> None:
148
149        # Stop our on-screen timer so players can see what they got.
150        assert self._timer is not None
151        self._timer.stop()
152
153        results = ba.GameResults()
154
155        # If we won, set our score to the elapsed time in milliseconds.
156        # (there should just be 1 team here since this is co-op).
157        # ..if we didn't win, leave scores as default (None) which means
158        # we lost.
159        if self._won:
160            elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0)
161            ba.cameraflash()
162            ba.playsound(self._winsound)
163            for team in self.teams:
164                for player in team.players:
165                    if player.actor:
166                        player.actor.handlemessage(ba.CelebrateMessage())
167                results.set_team_score(team, elapsed_time_ms)
168
169        # Ends the activity.
170        self.end(results)

Tell the game to wrap up and call ba.Activity.end() immediately.

This method should be overridden by subclasses. A game should always be prepared to end and deliver results, even if there is no 'winner' yet; this way things like the standard time-limit (ba.GameActivity.setup_standard_time_limit()) will work with the game.

Inherited Members
ba._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
ba._gameactivity.GameActivity
tips
available_settings
allow_pausing
allow_kick_idle_players
show_kill_points
create_settings_ui
getscoreconfig
getname
get_display_string
get_team_display_string
get_description
get_description_display_string
get_available_settings
get_settings_display_string
map
get_instance_display_string
get_instance_scoreboard_display_string
get_instance_description
get_instance_description_short
on_continue
is_waiting_for_continue
continue_or_end_game
on_player_join
respawn_player
spawn_player_if_exists
setup_standard_powerup_drops
setup_standard_time_limit
show_zoom_message
ba._activity.Activity
settings_raw
teams
players
announce_player_deaths
is_joining_activity
use_fixed_vr_overlay
slow_motion
inherits_slow_motion
inherits_music
inherits_vr_camera_offset
inherits_vr_overlay_center
inherits_tint
allow_mid_activity_joins
transition_time
can_show_ad_on_death
globalsnode
stats
on_expire
customdata
expired
playertype
teamtype
retain_actor
add_actor_weak_ref
session
on_player_leave
on_team_join
on_team_leave
on_transition_out
has_transitioned_in
has_begun
has_ended
is_transitioning_out
transition_out
create_player
create_team
ba._dependency.DependencyComponent
dep_is_present
get_dynamic_deps