bastd.game.conquest
Provides the Conquest game.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides the Conquest 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.flag import Flag 15from bastd.actor.scoreboard import Scoreboard 16from bastd.actor.playerspaz import PlayerSpaz 17from bastd.gameutils import SharedObjects 18 19if TYPE_CHECKING: 20 from typing import Any, Sequence 21 from bastd.actor.respawnicon import RespawnIcon 22 23 24class ConquestFlag(Flag): 25 """A custom flag for use with Conquest games.""" 26 27 def __init__(self, *args: Any, **keywds: Any): 28 super().__init__(*args, **keywds) 29 self._team: Team | None = None 30 self.light: ba.Node | None = None 31 32 @property 33 def team(self) -> Team | None: 34 """The team that owns this flag.""" 35 return self._team 36 37 @team.setter 38 def team(self, team: Team) -> None: 39 """Set the team that owns this flag.""" 40 self._team = team 41 42 43class Player(ba.Player['Team']): 44 """Our player type for this game.""" 45 46 # FIXME: We shouldn't be using customdata here 47 # (but need to update respawn funcs accordingly first). 48 @property 49 def respawn_timer(self) -> ba.Timer | None: 50 """Type safe access to standard respawn timer.""" 51 return self.customdata.get('respawn_timer', None) 52 53 @respawn_timer.setter 54 def respawn_timer(self, value: ba.Timer | None) -> None: 55 self.customdata['respawn_timer'] = value 56 57 @property 58 def respawn_icon(self) -> RespawnIcon | None: 59 """Type safe access to standard respawn icon.""" 60 return self.customdata.get('respawn_icon', None) 61 62 @respawn_icon.setter 63 def respawn_icon(self, value: RespawnIcon | None) -> None: 64 self.customdata['respawn_icon'] = value 65 66 67class Team(ba.Team[Player]): 68 """Our team type for this game.""" 69 70 def __init__(self) -> None: 71 self.flags_held = 0 72 73 74# ba_meta export game 75class ConquestGame(ba.TeamGameActivity[Player, Team]): 76 """A game where teams try to claim all flags on the map.""" 77 78 name = 'Conquest' 79 description = 'Secure all flags on the map to win.' 80 available_settings = [ 81 ba.IntChoiceSetting( 82 'Time Limit', 83 choices=[ 84 ('None', 0), 85 ('1 Minute', 60), 86 ('2 Minutes', 120), 87 ('5 Minutes', 300), 88 ('10 Minutes', 600), 89 ('20 Minutes', 1200), 90 ], 91 default=0, 92 ), 93 ba.FloatChoiceSetting( 94 'Respawn Times', 95 choices=[ 96 ('Shorter', 0.25), 97 ('Short', 0.5), 98 ('Normal', 1.0), 99 ('Long', 2.0), 100 ('Longer', 4.0), 101 ], 102 default=1.0, 103 ), 104 ba.BoolSetting('Epic Mode', default=False), 105 ] 106 107 @classmethod 108 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 109 return issubclass(sessiontype, ba.DualTeamSession) 110 111 @classmethod 112 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 113 return ba.getmaps('conquest') 114 115 def __init__(self, settings: dict): 116 super().__init__(settings) 117 shared = SharedObjects.get() 118 self._scoreboard = Scoreboard() 119 self._score_sound = ba.getsound('score') 120 self._swipsound = ba.getsound('swip') 121 self._extraflagmat = ba.Material() 122 self._flags: list[ConquestFlag] = [] 123 self._epic_mode = bool(settings['Epic Mode']) 124 self._time_limit = float(settings['Time Limit']) 125 126 # Base class overrides. 127 self.slow_motion = self._epic_mode 128 self.default_music = ( 129 ba.MusicType.EPIC if self._epic_mode else ba.MusicType.GRAND_ROMP 130 ) 131 132 # We want flags to tell us they've been hit but not react physically. 133 self._extraflagmat.add_actions( 134 conditions=('they_have_material', shared.player_material), 135 actions=( 136 ('modify_part_collision', 'collide', True), 137 ('call', 'at_connect', self._handle_flag_player_collide), 138 ), 139 ) 140 141 def get_instance_description(self) -> str | Sequence: 142 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 143 144 def get_instance_description_short(self) -> str | Sequence: 145 return 'secure all ${ARG1} flags', len(self.map.flag_points) 146 147 def on_team_join(self, team: Team) -> None: 148 if self.has_begun(): 149 self._update_scores() 150 151 def on_player_join(self, player: Player) -> None: 152 player.respawn_timer = None 153 154 # Only spawn if this player's team has a flag currently. 155 if player.team.flags_held > 0: 156 self.spawn_player(player) 157 158 def on_begin(self) -> None: 159 super().on_begin() 160 self.setup_standard_time_limit(self._time_limit) 161 self.setup_standard_powerup_drops() 162 163 # Set up flags with marker lights. 164 for i, flag_point in enumerate(self.map.flag_points): 165 point = flag_point 166 flag = ConquestFlag( 167 position=point, touchable=False, materials=[self._extraflagmat] 168 ) 169 self._flags.append(flag) 170 Flag.project_stand(point) 171 flag.light = ba.newnode( 172 'light', 173 owner=flag.node, 174 attrs={ 175 'position': point, 176 'intensity': 0.25, 177 'height_attenuated': False, 178 'radius': 0.3, 179 'color': (1, 1, 1), 180 }, 181 ) 182 183 # Give teams a flag to start with. 184 for i, team in enumerate(self.teams): 185 self._flags[i].team = team 186 light = self._flags[i].light 187 assert light 188 node = self._flags[i].node 189 assert node 190 light.color = team.color 191 node.color = team.color 192 193 self._update_scores() 194 195 # Initial joiners didn't spawn due to no flags being owned yet; 196 # spawn them now. 197 for player in self.players: 198 self.spawn_player(player) 199 200 def _update_scores(self) -> None: 201 for team in self.teams: 202 team.flags_held = 0 203 for flag in self._flags: 204 if flag.team is not None: 205 flag.team.flags_held += 1 206 for team in self.teams: 207 208 # If a team finds themselves with no flags, cancel all 209 # outstanding spawn-timers. 210 if team.flags_held == 0: 211 for player in team.players: 212 player.respawn_timer = None 213 player.respawn_icon = None 214 if team.flags_held == len(self._flags): 215 self.end_game() 216 self._scoreboard.set_team_value( 217 team, team.flags_held, len(self._flags) 218 ) 219 220 def end_game(self) -> None: 221 results = ba.GameResults() 222 for team in self.teams: 223 results.set_team_score(team, team.flags_held) 224 self.end(results=results) 225 226 def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: 227 assert flag.node 228 assert flag.light 229 light = ba.newnode( 230 'light', 231 attrs={ 232 'position': flag.node.position, 233 'height_attenuated': False, 234 'color': flag.light.color, 235 }, 236 ) 237 ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 238 ba.timer(length, light.delete) 239 240 def _handle_flag_player_collide(self) -> None: 241 collision = ba.getcollision() 242 try: 243 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 244 player = collision.opposingnode.getdelegate( 245 PlayerSpaz, True 246 ).getplayer(Player, True) 247 except ba.NotFoundError: 248 return 249 assert flag.light 250 251 if flag.team is not player.team: 252 flag.team = player.team 253 flag.light.color = player.team.color 254 flag.node.color = player.team.color 255 self.stats.player_scored(player, 10, screenmessage=False) 256 ba.playsound(self._swipsound) 257 self._flash_flag(flag) 258 self._update_scores() 259 260 # Respawn any players on this team that were in limbo due to the 261 # lack of a flag for their team. 262 for otherplayer in self.players: 263 if ( 264 otherplayer.team is flag.team 265 and otherplayer.actor is not None 266 and not otherplayer.is_alive() 267 and otherplayer.respawn_timer is None 268 ): 269 self.spawn_player(otherplayer) 270 271 def handlemessage(self, msg: Any) -> Any: 272 if isinstance(msg, ba.PlayerDiedMessage): 273 # Augment standard behavior. 274 super().handlemessage(msg) 275 276 # Respawn only if this team has a flag. 277 player = msg.getplayer(Player) 278 if player.team.flags_held > 0: 279 self.respawn_player(player) 280 else: 281 player.respawn_timer = None 282 283 else: 284 super().handlemessage(msg) 285 286 def spawn_player(self, player: Player) -> ba.Actor: 287 # We spawn players at different places based on what flags are held. 288 return self.spawn_player_spaz( 289 player, self._get_player_spawn_position(player) 290 ) 291 292 def _get_player_spawn_position(self, player: Player) -> Sequence[float]: 293 294 # Iterate until we find a spawn owned by this team. 295 spawn_count = len(self.map.spawn_by_flag_points) 296 297 # Get all spawns owned by this team. 298 spawns = [ 299 i for i in range(spawn_count) if self._flags[i].team is player.team 300 ] 301 302 closest_spawn = 0 303 closest_distance = 9999.0 304 305 # Now find the spawn that's closest to a spawn not owned by us; 306 # we'll use that one. 307 for spawn in spawns: 308 spt = self.map.spawn_by_flag_points[spawn] 309 our_pt = ba.Vec3(spt[0], spt[1], spt[2]) 310 for otherspawn in [ 311 i 312 for i in range(spawn_count) 313 if self._flags[i].team is not player.team 314 ]: 315 spt = self.map.spawn_by_flag_points[otherspawn] 316 their_pt = ba.Vec3(spt[0], spt[1], spt[2]) 317 dist = (their_pt - our_pt).length() 318 if dist < closest_distance: 319 closest_distance = dist 320 closest_spawn = spawn 321 322 pos = self.map.spawn_by_flag_points[closest_spawn] 323 x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3]) 324 z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5]) 325 pos = ( 326 pos[0] + random.uniform(*x_range), 327 pos[1], 328 pos[2] + random.uniform(*z_range), 329 ) 330 return pos
25class ConquestFlag(Flag): 26 """A custom flag for use with Conquest games.""" 27 28 def __init__(self, *args: Any, **keywds: Any): 29 super().__init__(*args, **keywds) 30 self._team: Team | None = None 31 self.light: ba.Node | None = None 32 33 @property 34 def team(self) -> Team | None: 35 """The team that owns this flag.""" 36 return self._team 37 38 @team.setter 39 def team(self, team: Team) -> None: 40 """Set the team that owns this flag.""" 41 self._team = team
A custom flag for use with Conquest games.
28 def __init__(self, *args: Any, **keywds: Any): 29 super().__init__(*args, **keywds) 30 self._team: Team | None = None 31 self.light: ba.Node | None = None
Instantiate a flag.
If 'touchable' is False, the flag will only touch terrain; useful for things like king-of-the-hill where players should not be moving the flag around.
'materials can be a list of extra ba.Material
s to apply to the flag.
If 'dropped_timeout' is provided (in seconds), the flag will die after remaining untouched for that long once it has been moved from its initial position.
Inherited Members
- ba._actor.Actor
- autoretain
- on_expire
- expired
- exists
- is_alive
- activity
- getactivity
44class Player(ba.Player['Team']): 45 """Our player type for this game.""" 46 47 # FIXME: We shouldn't be using customdata here 48 # (but need to update respawn funcs accordingly first). 49 @property 50 def respawn_timer(self) -> ba.Timer | None: 51 """Type safe access to standard respawn timer.""" 52 return self.customdata.get('respawn_timer', None) 53 54 @respawn_timer.setter 55 def respawn_timer(self, value: ba.Timer | None) -> None: 56 self.customdata['respawn_timer'] = value 57 58 @property 59 def respawn_icon(self) -> RespawnIcon | None: 60 """Type safe access to standard respawn icon.""" 61 return self.customdata.get('respawn_icon', None) 62 63 @respawn_icon.setter 64 def respawn_icon(self, value: RespawnIcon | None) -> None: 65 self.customdata['respawn_icon'] = value
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
68class Team(ba.Team[Player]): 69 """Our team type for this game.""" 70 71 def __init__(self) -> None: 72 self.flags_held = 0
Our team type for this game.
Inherited Members
- ba._team.Team
- manual_init
- customdata
- on_expire
- sessionteam
76class ConquestGame(ba.TeamGameActivity[Player, Team]): 77 """A game where teams try to claim all flags on the map.""" 78 79 name = 'Conquest' 80 description = 'Secure all flags on the map to win.' 81 available_settings = [ 82 ba.IntChoiceSetting( 83 'Time Limit', 84 choices=[ 85 ('None', 0), 86 ('1 Minute', 60), 87 ('2 Minutes', 120), 88 ('5 Minutes', 300), 89 ('10 Minutes', 600), 90 ('20 Minutes', 1200), 91 ], 92 default=0, 93 ), 94 ba.FloatChoiceSetting( 95 'Respawn Times', 96 choices=[ 97 ('Shorter', 0.25), 98 ('Short', 0.5), 99 ('Normal', 1.0), 100 ('Long', 2.0), 101 ('Longer', 4.0), 102 ], 103 default=1.0, 104 ), 105 ba.BoolSetting('Epic Mode', default=False), 106 ] 107 108 @classmethod 109 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 110 return issubclass(sessiontype, ba.DualTeamSession) 111 112 @classmethod 113 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 114 return ba.getmaps('conquest') 115 116 def __init__(self, settings: dict): 117 super().__init__(settings) 118 shared = SharedObjects.get() 119 self._scoreboard = Scoreboard() 120 self._score_sound = ba.getsound('score') 121 self._swipsound = ba.getsound('swip') 122 self._extraflagmat = ba.Material() 123 self._flags: list[ConquestFlag] = [] 124 self._epic_mode = bool(settings['Epic Mode']) 125 self._time_limit = float(settings['Time Limit']) 126 127 # Base class overrides. 128 self.slow_motion = self._epic_mode 129 self.default_music = ( 130 ba.MusicType.EPIC if self._epic_mode else ba.MusicType.GRAND_ROMP 131 ) 132 133 # We want flags to tell us they've been hit but not react physically. 134 self._extraflagmat.add_actions( 135 conditions=('they_have_material', shared.player_material), 136 actions=( 137 ('modify_part_collision', 'collide', True), 138 ('call', 'at_connect', self._handle_flag_player_collide), 139 ), 140 ) 141 142 def get_instance_description(self) -> str | Sequence: 143 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 144 145 def get_instance_description_short(self) -> str | Sequence: 146 return 'secure all ${ARG1} flags', len(self.map.flag_points) 147 148 def on_team_join(self, team: Team) -> None: 149 if self.has_begun(): 150 self._update_scores() 151 152 def on_player_join(self, player: Player) -> None: 153 player.respawn_timer = None 154 155 # Only spawn if this player's team has a flag currently. 156 if player.team.flags_held > 0: 157 self.spawn_player(player) 158 159 def on_begin(self) -> None: 160 super().on_begin() 161 self.setup_standard_time_limit(self._time_limit) 162 self.setup_standard_powerup_drops() 163 164 # Set up flags with marker lights. 165 for i, flag_point in enumerate(self.map.flag_points): 166 point = flag_point 167 flag = ConquestFlag( 168 position=point, touchable=False, materials=[self._extraflagmat] 169 ) 170 self._flags.append(flag) 171 Flag.project_stand(point) 172 flag.light = ba.newnode( 173 'light', 174 owner=flag.node, 175 attrs={ 176 'position': point, 177 'intensity': 0.25, 178 'height_attenuated': False, 179 'radius': 0.3, 180 'color': (1, 1, 1), 181 }, 182 ) 183 184 # Give teams a flag to start with. 185 for i, team in enumerate(self.teams): 186 self._flags[i].team = team 187 light = self._flags[i].light 188 assert light 189 node = self._flags[i].node 190 assert node 191 light.color = team.color 192 node.color = team.color 193 194 self._update_scores() 195 196 # Initial joiners didn't spawn due to no flags being owned yet; 197 # spawn them now. 198 for player in self.players: 199 self.spawn_player(player) 200 201 def _update_scores(self) -> None: 202 for team in self.teams: 203 team.flags_held = 0 204 for flag in self._flags: 205 if flag.team is not None: 206 flag.team.flags_held += 1 207 for team in self.teams: 208 209 # If a team finds themselves with no flags, cancel all 210 # outstanding spawn-timers. 211 if team.flags_held == 0: 212 for player in team.players: 213 player.respawn_timer = None 214 player.respawn_icon = None 215 if team.flags_held == len(self._flags): 216 self.end_game() 217 self._scoreboard.set_team_value( 218 team, team.flags_held, len(self._flags) 219 ) 220 221 def end_game(self) -> None: 222 results = ba.GameResults() 223 for team in self.teams: 224 results.set_team_score(team, team.flags_held) 225 self.end(results=results) 226 227 def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: 228 assert flag.node 229 assert flag.light 230 light = ba.newnode( 231 'light', 232 attrs={ 233 'position': flag.node.position, 234 'height_attenuated': False, 235 'color': flag.light.color, 236 }, 237 ) 238 ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 239 ba.timer(length, light.delete) 240 241 def _handle_flag_player_collide(self) -> None: 242 collision = ba.getcollision() 243 try: 244 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 245 player = collision.opposingnode.getdelegate( 246 PlayerSpaz, True 247 ).getplayer(Player, True) 248 except ba.NotFoundError: 249 return 250 assert flag.light 251 252 if flag.team is not player.team: 253 flag.team = player.team 254 flag.light.color = player.team.color 255 flag.node.color = player.team.color 256 self.stats.player_scored(player, 10, screenmessage=False) 257 ba.playsound(self._swipsound) 258 self._flash_flag(flag) 259 self._update_scores() 260 261 # Respawn any players on this team that were in limbo due to the 262 # lack of a flag for their team. 263 for otherplayer in self.players: 264 if ( 265 otherplayer.team is flag.team 266 and otherplayer.actor is not None 267 and not otherplayer.is_alive() 268 and otherplayer.respawn_timer is None 269 ): 270 self.spawn_player(otherplayer) 271 272 def handlemessage(self, msg: Any) -> Any: 273 if isinstance(msg, ba.PlayerDiedMessage): 274 # Augment standard behavior. 275 super().handlemessage(msg) 276 277 # Respawn only if this team has a flag. 278 player = msg.getplayer(Player) 279 if player.team.flags_held > 0: 280 self.respawn_player(player) 281 else: 282 player.respawn_timer = None 283 284 else: 285 super().handlemessage(msg) 286 287 def spawn_player(self, player: Player) -> ba.Actor: 288 # We spawn players at different places based on what flags are held. 289 return self.spawn_player_spaz( 290 player, self._get_player_spawn_position(player) 291 ) 292 293 def _get_player_spawn_position(self, player: Player) -> Sequence[float]: 294 295 # Iterate until we find a spawn owned by this team. 296 spawn_count = len(self.map.spawn_by_flag_points) 297 298 # Get all spawns owned by this team. 299 spawns = [ 300 i for i in range(spawn_count) if self._flags[i].team is player.team 301 ] 302 303 closest_spawn = 0 304 closest_distance = 9999.0 305 306 # Now find the spawn that's closest to a spawn not owned by us; 307 # we'll use that one. 308 for spawn in spawns: 309 spt = self.map.spawn_by_flag_points[spawn] 310 our_pt = ba.Vec3(spt[0], spt[1], spt[2]) 311 for otherspawn in [ 312 i 313 for i in range(spawn_count) 314 if self._flags[i].team is not player.team 315 ]: 316 spt = self.map.spawn_by_flag_points[otherspawn] 317 their_pt = ba.Vec3(spt[0], spt[1], spt[2]) 318 dist = (their_pt - our_pt).length() 319 if dist < closest_distance: 320 closest_distance = dist 321 closest_spawn = spawn 322 323 pos = self.map.spawn_by_flag_points[closest_spawn] 324 x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3]) 325 z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5]) 326 pos = ( 327 pos[0] + random.uniform(*x_range), 328 pos[1], 329 pos[2] + random.uniform(*z_range), 330 ) 331 return pos
A game where teams try to claim all flags on the map.
116 def __init__(self, settings: dict): 117 super().__init__(settings) 118 shared = SharedObjects.get() 119 self._scoreboard = Scoreboard() 120 self._score_sound = ba.getsound('score') 121 self._swipsound = ba.getsound('swip') 122 self._extraflagmat = ba.Material() 123 self._flags: list[ConquestFlag] = [] 124 self._epic_mode = bool(settings['Epic Mode']) 125 self._time_limit = float(settings['Time Limit']) 126 127 # Base class overrides. 128 self.slow_motion = self._epic_mode 129 self.default_music = ( 130 ba.MusicType.EPIC if self._epic_mode else ba.MusicType.GRAND_ROMP 131 ) 132 133 # We want flags to tell us they've been hit but not react physically. 134 self._extraflagmat.add_actions( 135 conditions=('they_have_material', shared.player_material), 136 actions=( 137 ('modify_part_collision', 'collide', True), 138 ('call', 'at_connect', self._handle_flag_player_collide), 139 ), 140 )
Instantiate the Activity.
108 @classmethod 109 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 110 return issubclass(sessiontype, ba.DualTeamSession)
Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.
112 @classmethod 113 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 114 return ba.getmaps('conquest')
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.
142 def get_instance_description(self) -> str | Sequence: 143 return 'Secure all ${ARG1} flags.', len(self.map.flag_points)
Return a description for this game instance, in English.
This is shown in the center of the screen below the game name at the start of a game. It should start with a capital letter and end with a period, and can be a bit more verbose than the version returned by get_instance_description_short().
Note that translation is applied by looking up the specific returned value as a key, so the number of returned variations should be limited; ideally just one or two. To include arbitrary values in the description, you can return a sequence of values in the following form instead of just a string:
This will give us something like 'Score 3 goals.' in English
and can properly translate to 'Anota 3 goles.' in Spanish.
If we just returned the string 'Score 3 Goals' here, there would
have to be a translation entry for each specific number. ew.
return ['Score ${ARG1} goals.', self.settings_raw['Score to Win']]
This way the first string can be consistently translated, with any arg values then substituted into the result. ${ARG1} will be replaced with the first value, ${ARG2} with the second, etc.
145 def get_instance_description_short(self) -> str | Sequence: 146 return 'secure all ${ARG1} flags', len(self.map.flag_points)
Return a short description for this game instance in English.
This description is used above the game scoreboard in the corner of the screen, so it should be as concise as possible. It should be lowercase and should not contain periods or other punctuation.
Note that translation is applied by looking up the specific returned value as a key, so the number of returned variations should be limited; ideally just one or two. To include arbitrary values in the description, you can return a sequence of values in the following form instead of just a string:
This will give us something like 'score 3 goals' in English
and can properly translate to 'anota 3 goles' in Spanish.
If we just returned the string 'score 3 goals' here, there would
have to be a translation entry for each specific number. ew.
return ['score ${ARG1} goals', self.settings_raw['Score to Win']]
This way the first string can be consistently translated, with any arg values then substituted into the result. ${ARG1} will be replaced with the first value, ${ARG2} with the second, etc.
Called when a new ba.Team joins the Activity.
(including the initial set of Teams)
152 def on_player_join(self, player: Player) -> None: 153 player.respawn_timer = None 154 155 # Only spawn if this player's team has a flag currently. 156 if player.team.flags_held > 0: 157 self.spawn_player(player)
Called when a new ba.Player has joined the Activity.
(including the initial set of Players)
159 def on_begin(self) -> None: 160 super().on_begin() 161 self.setup_standard_time_limit(self._time_limit) 162 self.setup_standard_powerup_drops() 163 164 # Set up flags with marker lights. 165 for i, flag_point in enumerate(self.map.flag_points): 166 point = flag_point 167 flag = ConquestFlag( 168 position=point, touchable=False, materials=[self._extraflagmat] 169 ) 170 self._flags.append(flag) 171 Flag.project_stand(point) 172 flag.light = ba.newnode( 173 'light', 174 owner=flag.node, 175 attrs={ 176 'position': point, 177 'intensity': 0.25, 178 'height_attenuated': False, 179 'radius': 0.3, 180 'color': (1, 1, 1), 181 }, 182 ) 183 184 # Give teams a flag to start with. 185 for i, team in enumerate(self.teams): 186 self._flags[i].team = team 187 light = self._flags[i].light 188 assert light 189 node = self._flags[i].node 190 assert node 191 light.color = team.color 192 node.color = team.color 193 194 self._update_scores() 195 196 # Initial joiners didn't spawn due to no flags being owned yet; 197 # spawn them now. 198 for player in self.players: 199 self.spawn_player(player)
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.
221 def end_game(self) -> None: 222 results = ba.GameResults() 223 for team in self.teams: 224 results.set_team_score(team, team.flags_held) 225 self.end(results=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.
272 def handlemessage(self, msg: Any) -> Any: 273 if isinstance(msg, ba.PlayerDiedMessage): 274 # Augment standard behavior. 275 super().handlemessage(msg) 276 277 # Respawn only if this team has a flag. 278 player = msg.getplayer(Player) 279 if player.team.flags_held > 0: 280 self.respawn_player(player) 281 else: 282 player.respawn_timer = None 283 284 else: 285 super().handlemessage(msg)
General message handling; can be passed any message object.
287 def spawn_player(self, player: Player) -> ba.Actor: 288 # We spawn players at different places based on what flags are held. 289 return self.spawn_player_spaz( 290 player, self._get_player_spawn_position(player) 291 )
Spawn something for the provided ba.Player.
The default implementation simply calls spawn_player_spaz().
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
- on_continue
- is_waiting_for_continue
- continue_or_end_game
- 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
- 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_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