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

Our player type for this game.

Inherited Members
bascenev1._player.Player
character
actor
color
highlight
on_expire
team
customdata
sessionplayer
node
position
exists
getname
is_alive
get_icon
assigninput
resetinput
class Team(bascenev1._team.Team[bascenev1lib.game.ninjafight.Player]):
30class Team(bs.Team[Player]):
31    """Our team type for this game."""

Our team type for this game.

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

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

NinjaFightGame(settings: dict)
62    def __init__(self, settings: dict):
63        super().__init__(settings)
64        self._winsound = bs.getsound('score')
65        self._won = False
66        self._timer: OnScreenTimer | None = None
67        self._bots = SpazBotSet()
68        self._preset = str(settings['preset'])

Instantiate the Activity.

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

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

@classmethod
def supports_session_type(cls, sessiontype: type[bascenev1._session.Session]) -> bool:
55    @classmethod
56    def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool:
57        # We currently support Co-Op only.
58        return issubclass(sessiontype, bs.CoopSession)

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

def on_begin(self) -> None:
 71    def on_begin(self) -> None:
 72        super().on_begin()
 73        is_pro = self._preset == 'pro'
 74
 75        # In pro mode there's no powerups.
 76        if not is_pro:
 77            self.setup_standard_powerup_drops()
 78
 79        # Make our on-screen timer and start it roughly when our bots appear.
 80        self._timer = OnScreenTimer()
 81        bs.timer(4.0, self._timer.start)
 82
 83        # Spawn some baddies.
 84        bs.timer(
 85            1.0,
 86            lambda: self._bots.spawn_bot(
 87                ChargerBot, pos=(3, 3, -2), spawn_time=3.0
 88            ),
 89        )
 90        bs.timer(
 91            2.0,
 92            lambda: self._bots.spawn_bot(
 93                ChargerBot, pos=(-3, 3, -2), spawn_time=3.0
 94            ),
 95        )
 96        bs.timer(
 97            3.0,
 98            lambda: self._bots.spawn_bot(
 99                ChargerBot, pos=(5, 3, -2), spawn_time=3.0
100            ),
101        )
102        bs.timer(
103            4.0,
104            lambda: self._bots.spawn_bot(
105                ChargerBot, pos=(-5, 3, -2), spawn_time=3.0
106            ),
107        )
108
109        # Add some extras for multiplayer or pro mode.
110        assert self.initialplayerinfos is not None
111        if len(self.initialplayerinfos) > 2 or is_pro:
112            bs.timer(
113                5.0,
114                lambda: self._bots.spawn_bot(
115                    ChargerBot, pos=(0, 3, -5), spawn_time=3.0
116                ),
117            )
118        if len(self.initialplayerinfos) > 3 or is_pro:
119            bs.timer(
120                6.0,
121                lambda: self._bots.spawn_bot(
122                    ChargerBot, pos=(0, 3, 1), spawn_time=3.0
123                ),
124            )

Called once the previous 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: Player) -> bascenev1._actor.Actor:
127    def spawn_player(self, player: Player) -> bs.Actor:
128        # Let's spawn close to the center.
129        spawn_center = (0, 3, -2)
130        pos = (
131            spawn_center[0] + random.uniform(-1.5, 1.5),
132            spawn_center[1],
133            spawn_center[2] + random.uniform(-1.5, 1.5),
134        )
135        return self.spawn_player_spaz(player, position=pos)

Spawn something for the provided bascenev1.Player.

The default implementation simply calls spawn_player_spaz().

def handlemessage(self, msg: Any) -> Any:
148    def handlemessage(self, msg: Any) -> Any:
149        # A player has died.
150        if isinstance(msg, bs.PlayerDiedMessage):
151            super().handlemessage(msg)  # Augment standard behavior.
152            self.respawn_player(msg.getplayer(Player))
153
154        # A spaz-bot has died.
155        elif isinstance(msg, SpazBotDiedMessage):
156            # Unfortunately the bot-set will always tell us there are living
157            # bots if we ask here (the currently-dying bot isn't officially
158            # marked dead yet) ..so lets push a call into the event loop to
159            # check once this guy has finished dying.
160            bs.pushcall(self._check_if_won)
161
162        # Let the base class handle anything we don't.
163        else:
164            return super().handlemessage(msg)
165        return None

General message handling; can be passed any message object.

def end_game(self) -> None:
170    def end_game(self) -> None:
171        # Stop our on-screen timer so players can see what they got.
172        assert self._timer is not None
173        self._timer.stop()
174
175        results = bs.GameResults()
176
177        # If we won, set our score to the elapsed time in milliseconds.
178        # (there should just be 1 team here since this is co-op).
179        # ..if we didn't win, leave scores as default (None) which means
180        # we lost.
181        if self._won:
182            elapsed_time_ms = int((bs.time() - self._timer.starttime) * 1000.0)
183            bs.cameraflash()
184            self._winsound.play()
185            for team in self.teams:
186                for player in team.players:
187                    if player.actor:
188                        player.actor.handlemessage(bs.CelebrateMessage())
189                results.set_team_score(team, elapsed_time_ms)
190
191        # Ends the activity.
192        self.end(results)

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

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 (bascenev1.GameActivity.setup_standard_time_limit()) will work with the game.

Inherited Members
bascenev1._teamgame.TeamGameActivity
on_transition_in
spawn_player_spaz
end
bascenev1._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
initialplayerinfos
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
bascenev1._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
paused_text
preloads
lobby
context
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
bascenev1._dependency.DependencyComponent
dep_is_present
get_dynamic_deps