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( 39 label='Time', scoretype=ba.ScoreType.MILLISECONDS, lower_is_better=True 40 ) 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, 81 lambda: self._bots.spawn_bot( 82 ChargerBot, pos=(3, 3, -2), spawn_time=3.0 83 ), 84 ) 85 ba.timer( 86 2.0, 87 lambda: self._bots.spawn_bot( 88 ChargerBot, pos=(-3, 3, -2), spawn_time=3.0 89 ), 90 ) 91 ba.timer( 92 3.0, 93 lambda: self._bots.spawn_bot( 94 ChargerBot, pos=(5, 3, -2), spawn_time=3.0 95 ), 96 ) 97 ba.timer( 98 4.0, 99 lambda: self._bots.spawn_bot( 100 ChargerBot, pos=(-5, 3, -2), spawn_time=3.0 101 ), 102 ) 103 104 # Add some extras for multiplayer or pro mode. 105 assert self.initialplayerinfos is not None 106 if len(self.initialplayerinfos) > 2 or is_pro: 107 ba.timer( 108 5.0, 109 lambda: self._bots.spawn_bot( 110 ChargerBot, pos=(0, 3, -5), spawn_time=3.0 111 ), 112 ) 113 if len(self.initialplayerinfos) > 3 or is_pro: 114 ba.timer( 115 6.0, 116 lambda: self._bots.spawn_bot( 117 ChargerBot, pos=(0, 3, 1), spawn_time=3.0 118 ), 119 ) 120 121 # Called for each spawning player. 122 def spawn_player(self, player: Player) -> ba.Actor: 123 124 # Let's spawn close to the center. 125 spawn_center = (0, 3, -2) 126 pos = ( 127 spawn_center[0] + random.uniform(-1.5, 1.5), 128 spawn_center[1], 129 spawn_center[2] + random.uniform(-1.5, 1.5), 130 ) 131 return self.spawn_player_spaz(player, position=pos) 132 133 def _check_if_won(self) -> None: 134 # Simply end the game if there's no living bots. 135 # FIXME: Should also make sure all bots have been spawned; 136 # if spawning is spread out enough that we're able to kill 137 # all living bots before the next spawns, it would incorrectly 138 # count as a win. 139 if not self._bots.have_living_bots(): 140 self._won = True 141 self.end_game() 142 143 # Called for miscellaneous messages. 144 def handlemessage(self, msg: Any) -> Any: 145 146 # A player has died. 147 if isinstance(msg, ba.PlayerDiedMessage): 148 super().handlemessage(msg) # Augment standard behavior. 149 self.respawn_player(msg.getplayer(Player)) 150 151 # A spaz-bot has died. 152 elif isinstance(msg, SpazBotDiedMessage): 153 # Unfortunately the bot-set will always tell us there are living 154 # bots if we ask here (the currently-dying bot isn't officially 155 # marked dead yet) ..so lets push a call into the event loop to 156 # check once this guy has finished dying. 157 ba.pushcall(self._check_if_won) 158 159 # Let the base class handle anything we don't. 160 else: 161 return super().handlemessage(msg) 162 return None 163 164 # When this is called, we should fill out results and end the game 165 # *regardless* of whether is has been won. (this may be called due 166 # to a tournament ending or other external reason). 167 def end_game(self) -> None: 168 169 # Stop our on-screen timer so players can see what they got. 170 assert self._timer is not None 171 self._timer.stop() 172 173 results = ba.GameResults() 174 175 # If we won, set our score to the elapsed time in milliseconds. 176 # (there should just be 1 team here since this is co-op). 177 # ..if we didn't win, leave scores as default (None) which means 178 # we lost. 179 if self._won: 180 elapsed_time_ms = int((ba.time() - self._timer.starttime) * 1000.0) 181 ba.cameraflash() 182 ba.playsound(self._winsound) 183 for team in self.teams: 184 for player in team.players: 185 if player.actor: 186 player.actor.handlemessage(ba.CelebrateMessage()) 187 results.set_team_score(team, elapsed_time_ms) 188 189 # Ends the activity. 190 self.end(results)
Our player type for this game.
Inherited Members
- ba._player.Player
- actor
- on_expire
- team
- customdata
- sessionplayer
- node
- position
- exists
- getname
- is_alive
- get_icon
- assigninput
- resetinput
Our team type for this game.
Inherited Members
- ba._team.Team
- manual_init
- customdata
- on_expire
- sessionteam
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( 40 label='Time', scoretype=ba.ScoreType.MILLISECONDS, lower_is_better=True 41 ) 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, 82 lambda: self._bots.spawn_bot( 83 ChargerBot, pos=(3, 3, -2), spawn_time=3.0 84 ), 85 ) 86 ba.timer( 87 2.0, 88 lambda: self._bots.spawn_bot( 89 ChargerBot, pos=(-3, 3, -2), spawn_time=3.0 90 ), 91 ) 92 ba.timer( 93 3.0, 94 lambda: self._bots.spawn_bot( 95 ChargerBot, pos=(5, 3, -2), spawn_time=3.0 96 ), 97 ) 98 ba.timer( 99 4.0, 100 lambda: self._bots.spawn_bot( 101 ChargerBot, pos=(-5, 3, -2), spawn_time=3.0 102 ), 103 ) 104 105 # Add some extras for multiplayer or pro mode. 106 assert self.initialplayerinfos is not None 107 if len(self.initialplayerinfos) > 2 or is_pro: 108 ba.timer( 109 5.0, 110 lambda: self._bots.spawn_bot( 111 ChargerBot, pos=(0, 3, -5), spawn_time=3.0 112 ), 113 ) 114 if len(self.initialplayerinfos) > 3 or is_pro: 115 ba.timer( 116 6.0, 117 lambda: self._bots.spawn_bot( 118 ChargerBot, pos=(0, 3, 1), spawn_time=3.0 119 ), 120 ) 121 122 # Called for each spawning player. 123 def spawn_player(self, player: Player) -> ba.Actor: 124 125 # Let's spawn close to the center. 126 spawn_center = (0, 3, -2) 127 pos = ( 128 spawn_center[0] + random.uniform(-1.5, 1.5), 129 spawn_center[1], 130 spawn_center[2] + random.uniform(-1.5, 1.5), 131 ) 132 return self.spawn_player_spaz(player, position=pos) 133 134 def _check_if_won(self) -> None: 135 # Simply end the game if there's no living bots. 136 # FIXME: Should also make sure all bots have been spawned; 137 # if spawning is spread out enough that we're able to kill 138 # all living bots before the next spawns, it would incorrectly 139 # count as a win. 140 if not self._bots.have_living_bots(): 141 self._won = True 142 self.end_game() 143 144 # Called for miscellaneous messages. 145 def handlemessage(self, msg: Any) -> Any: 146 147 # A player has died. 148 if isinstance(msg, ba.PlayerDiedMessage): 149 super().handlemessage(msg) # Augment standard behavior. 150 self.respawn_player(msg.getplayer(Player)) 151 152 # A spaz-bot has died. 153 elif isinstance(msg, SpazBotDiedMessage): 154 # Unfortunately the bot-set will always tell us there are living 155 # bots if we ask here (the currently-dying bot isn't officially 156 # marked dead yet) ..so lets push a call into the event loop to 157 # check once this guy has finished dying. 158 ba.pushcall(self._check_if_won) 159 160 # Let the base class handle anything we don't. 161 else: 162 return super().handlemessage(msg) 163 return None 164 165 # When this is called, we should fill out results and end the game 166 # *regardless* of whether is has been won. (this may be called due 167 # to a tournament ending or other external reason). 168 def end_game(self) -> None: 169 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 = ba.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((ba.time() - self._timer.starttime) * 1000.0) 182 ba.cameraflash() 183 ba.playsound(self._winsound) 184 for team in self.teams: 185 for player in team.players: 186 if player.actor: 187 player.actor.handlemessage(ba.CelebrateMessage()) 188 results.set_team_score(team, elapsed_time_ms) 189 190 # Ends the activity. 191 self.end(results)
A co-op game where you try to defeat a group of Ninjas as fast as possible
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.
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.
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.
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, 82 lambda: self._bots.spawn_bot( 83 ChargerBot, pos=(3, 3, -2), spawn_time=3.0 84 ), 85 ) 86 ba.timer( 87 2.0, 88 lambda: self._bots.spawn_bot( 89 ChargerBot, pos=(-3, 3, -2), spawn_time=3.0 90 ), 91 ) 92 ba.timer( 93 3.0, 94 lambda: self._bots.spawn_bot( 95 ChargerBot, pos=(5, 3, -2), spawn_time=3.0 96 ), 97 ) 98 ba.timer( 99 4.0, 100 lambda: self._bots.spawn_bot( 101 ChargerBot, pos=(-5, 3, -2), spawn_time=3.0 102 ), 103 ) 104 105 # Add some extras for multiplayer or pro mode. 106 assert self.initialplayerinfos is not None 107 if len(self.initialplayerinfos) > 2 or is_pro: 108 ba.timer( 109 5.0, 110 lambda: self._bots.spawn_bot( 111 ChargerBot, pos=(0, 3, -5), spawn_time=3.0 112 ), 113 ) 114 if len(self.initialplayerinfos) > 3 or is_pro: 115 ba.timer( 116 6.0, 117 lambda: self._bots.spawn_bot( 118 ChargerBot, pos=(0, 3, 1), spawn_time=3.0 119 ), 120 )
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.
123 def spawn_player(self, player: Player) -> ba.Actor: 124 125 # Let's spawn close to the center. 126 spawn_center = (0, 3, -2) 127 pos = ( 128 spawn_center[0] + random.uniform(-1.5, 1.5), 129 spawn_center[1], 130 spawn_center[2] + random.uniform(-1.5, 1.5), 131 ) 132 return self.spawn_player_spaz(player, position=pos)
Spawn something for the provided ba.Player.
The default implementation simply calls spawn_player_spaz().
145 def handlemessage(self, msg: Any) -> Any: 146 147 # A player has died. 148 if isinstance(msg, ba.PlayerDiedMessage): 149 super().handlemessage(msg) # Augment standard behavior. 150 self.respawn_player(msg.getplayer(Player)) 151 152 # A spaz-bot has died. 153 elif isinstance(msg, SpazBotDiedMessage): 154 # Unfortunately the bot-set will always tell us there are living 155 # bots if we ask here (the currently-dying bot isn't officially 156 # marked dead yet) ..so lets push a call into the event loop to 157 # check once this guy has finished dying. 158 ba.pushcall(self._check_if_won) 159 160 # Let the base class handle anything we don't. 161 else: 162 return super().handlemessage(msg) 163 return None
General message handling; can be passed any message object.
168 def end_game(self) -> None: 169 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 = ba.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((ba.time() - self._timer.starttime) * 1000.0) 182 ba.cameraflash() 183 ba.playsound(self._winsound) 184 for team in self.teams: 185 for player in team.players: 186 if player.actor: 187 player.actor.handlemessage(ba.CelebrateMessage()) 188 results.set_team_score(team, elapsed_time_ms) 189 190 # Ends the activity. 191 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
- allow_pausing
- allow_kick_idle_players
- 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