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)
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
Our team type for this game.
Inherited Members
- bascenev1._team.Team
- players
- id
- name
- color
- manual_init
- customdata
- on_expire
- sessionteam
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
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.
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.
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.
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.
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().
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.
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