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 = (ba.MusicType.EPIC 129 if self._epic_mode else ba.MusicType.GRAND_ROMP) 130 131 # We want flags to tell us they've been hit but not react physically. 132 self._extraflagmat.add_actions( 133 conditions=('they_have_material', shared.player_material), 134 actions=( 135 ('modify_part_collision', 'collide', True), 136 ('call', 'at_connect', self._handle_flag_player_collide), 137 )) 138 139 def get_instance_description(self) -> str | Sequence: 140 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 141 142 def get_instance_description_short(self) -> str | Sequence: 143 return 'secure all ${ARG1} flags', len(self.map.flag_points) 144 145 def on_team_join(self, team: Team) -> None: 146 if self.has_begun(): 147 self._update_scores() 148 149 def on_player_join(self, player: Player) -> None: 150 player.respawn_timer = None 151 152 # Only spawn if this player's team has a flag currently. 153 if player.team.flags_held > 0: 154 self.spawn_player(player) 155 156 def on_begin(self) -> None: 157 super().on_begin() 158 self.setup_standard_time_limit(self._time_limit) 159 self.setup_standard_powerup_drops() 160 161 # Set up flags with marker lights. 162 for i, flag_point in enumerate(self.map.flag_points): 163 point = flag_point 164 flag = ConquestFlag(position=point, 165 touchable=False, 166 materials=[self._extraflagmat]) 167 self._flags.append(flag) 168 Flag.project_stand(point) 169 flag.light = ba.newnode('light', 170 owner=flag.node, 171 attrs={ 172 'position': point, 173 'intensity': 0.25, 174 'height_attenuated': False, 175 'radius': 0.3, 176 'color': (1, 1, 1) 177 }) 178 179 # Give teams a flag to start with. 180 for i, team in enumerate(self.teams): 181 self._flags[i].team = team 182 light = self._flags[i].light 183 assert light 184 node = self._flags[i].node 185 assert node 186 light.color = team.color 187 node.color = team.color 188 189 self._update_scores() 190 191 # Initial joiners didn't spawn due to no flags being owned yet; 192 # spawn them now. 193 for player in self.players: 194 self.spawn_player(player) 195 196 def _update_scores(self) -> None: 197 for team in self.teams: 198 team.flags_held = 0 199 for flag in self._flags: 200 if flag.team is not None: 201 flag.team.flags_held += 1 202 for team in self.teams: 203 204 # If a team finds themselves with no flags, cancel all 205 # outstanding spawn-timers. 206 if team.flags_held == 0: 207 for player in team.players: 208 player.respawn_timer = None 209 player.respawn_icon = None 210 if team.flags_held == len(self._flags): 211 self.end_game() 212 self._scoreboard.set_team_value(team, team.flags_held, 213 len(self._flags)) 214 215 def end_game(self) -> None: 216 results = ba.GameResults() 217 for team in self.teams: 218 results.set_team_score(team, team.flags_held) 219 self.end(results=results) 220 221 def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: 222 assert flag.node 223 assert flag.light 224 light = ba.newnode('light', 225 attrs={ 226 'position': flag.node.position, 227 'height_attenuated': False, 228 'color': flag.light.color 229 }) 230 ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 231 ba.timer(length, light.delete) 232 233 def _handle_flag_player_collide(self) -> None: 234 collision = ba.getcollision() 235 try: 236 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 237 player = collision.opposingnode.getdelegate(PlayerSpaz, 238 True).getplayer( 239 Player, True) 240 except ba.NotFoundError: 241 return 242 assert flag.light 243 244 if flag.team is not player.team: 245 flag.team = player.team 246 flag.light.color = player.team.color 247 flag.node.color = player.team.color 248 self.stats.player_scored(player, 10, screenmessage=False) 249 ba.playsound(self._swipsound) 250 self._flash_flag(flag) 251 self._update_scores() 252 253 # Respawn any players on this team that were in limbo due to the 254 # lack of a flag for their team. 255 for otherplayer in self.players: 256 if (otherplayer.team is flag.team 257 and otherplayer.actor is not None 258 and not otherplayer.is_alive() 259 and otherplayer.respawn_timer is None): 260 self.spawn_player(otherplayer) 261 262 def handlemessage(self, msg: Any) -> Any: 263 if isinstance(msg, ba.PlayerDiedMessage): 264 # Augment standard behavior. 265 super().handlemessage(msg) 266 267 # Respawn only if this team has a flag. 268 player = msg.getplayer(Player) 269 if player.team.flags_held > 0: 270 self.respawn_player(player) 271 else: 272 player.respawn_timer = None 273 274 else: 275 super().handlemessage(msg) 276 277 def spawn_player(self, player: Player) -> ba.Actor: 278 # We spawn players at different places based on what flags are held. 279 return self.spawn_player_spaz(player, 280 self._get_player_spawn_position(player)) 281 282 def _get_player_spawn_position(self, player: Player) -> Sequence[float]: 283 284 # Iterate until we find a spawn owned by this team. 285 spawn_count = len(self.map.spawn_by_flag_points) 286 287 # Get all spawns owned by this team. 288 spawns = [ 289 i for i in range(spawn_count) if self._flags[i].team is player.team 290 ] 291 292 closest_spawn = 0 293 closest_distance = 9999.0 294 295 # Now find the spawn that's closest to a spawn not owned by us; 296 # we'll use that one. 297 for spawn in spawns: 298 spt = self.map.spawn_by_flag_points[spawn] 299 our_pt = ba.Vec3(spt[0], spt[1], spt[2]) 300 for otherspawn in [ 301 i for i in range(spawn_count) 302 if self._flags[i].team is not player.team 303 ]: 304 spt = self.map.spawn_by_flag_points[otherspawn] 305 their_pt = ba.Vec3(spt[0], spt[1], spt[2]) 306 dist = (their_pt - our_pt).length() 307 if dist < closest_distance: 308 closest_distance = dist 309 closest_spawn = spawn 310 311 pos = self.map.spawn_by_flag_points[closest_spawn] 312 x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3]) 313 z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5]) 314 pos = (pos[0] + random.uniform(*x_range), pos[1], 315 pos[2] + random.uniform(*z_range)) 316 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
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 = (ba.MusicType.EPIC 130 if self._epic_mode else ba.MusicType.GRAND_ROMP) 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 def get_instance_description(self) -> str | Sequence: 141 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 142 143 def get_instance_description_short(self) -> str | Sequence: 144 return 'secure all ${ARG1} flags', len(self.map.flag_points) 145 146 def on_team_join(self, team: Team) -> None: 147 if self.has_begun(): 148 self._update_scores() 149 150 def on_player_join(self, player: Player) -> None: 151 player.respawn_timer = None 152 153 # Only spawn if this player's team has a flag currently. 154 if player.team.flags_held > 0: 155 self.spawn_player(player) 156 157 def on_begin(self) -> None: 158 super().on_begin() 159 self.setup_standard_time_limit(self._time_limit) 160 self.setup_standard_powerup_drops() 161 162 # Set up flags with marker lights. 163 for i, flag_point in enumerate(self.map.flag_points): 164 point = flag_point 165 flag = ConquestFlag(position=point, 166 touchable=False, 167 materials=[self._extraflagmat]) 168 self._flags.append(flag) 169 Flag.project_stand(point) 170 flag.light = ba.newnode('light', 171 owner=flag.node, 172 attrs={ 173 'position': point, 174 'intensity': 0.25, 175 'height_attenuated': False, 176 'radius': 0.3, 177 'color': (1, 1, 1) 178 }) 179 180 # Give teams a flag to start with. 181 for i, team in enumerate(self.teams): 182 self._flags[i].team = team 183 light = self._flags[i].light 184 assert light 185 node = self._flags[i].node 186 assert node 187 light.color = team.color 188 node.color = team.color 189 190 self._update_scores() 191 192 # Initial joiners didn't spawn due to no flags being owned yet; 193 # spawn them now. 194 for player in self.players: 195 self.spawn_player(player) 196 197 def _update_scores(self) -> None: 198 for team in self.teams: 199 team.flags_held = 0 200 for flag in self._flags: 201 if flag.team is not None: 202 flag.team.flags_held += 1 203 for team in self.teams: 204 205 # If a team finds themselves with no flags, cancel all 206 # outstanding spawn-timers. 207 if team.flags_held == 0: 208 for player in team.players: 209 player.respawn_timer = None 210 player.respawn_icon = None 211 if team.flags_held == len(self._flags): 212 self.end_game() 213 self._scoreboard.set_team_value(team, team.flags_held, 214 len(self._flags)) 215 216 def end_game(self) -> None: 217 results = ba.GameResults() 218 for team in self.teams: 219 results.set_team_score(team, team.flags_held) 220 self.end(results=results) 221 222 def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: 223 assert flag.node 224 assert flag.light 225 light = ba.newnode('light', 226 attrs={ 227 'position': flag.node.position, 228 'height_attenuated': False, 229 'color': flag.light.color 230 }) 231 ba.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 232 ba.timer(length, light.delete) 233 234 def _handle_flag_player_collide(self) -> None: 235 collision = ba.getcollision() 236 try: 237 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 238 player = collision.opposingnode.getdelegate(PlayerSpaz, 239 True).getplayer( 240 Player, True) 241 except ba.NotFoundError: 242 return 243 assert flag.light 244 245 if flag.team is not player.team: 246 flag.team = player.team 247 flag.light.color = player.team.color 248 flag.node.color = player.team.color 249 self.stats.player_scored(player, 10, screenmessage=False) 250 ba.playsound(self._swipsound) 251 self._flash_flag(flag) 252 self._update_scores() 253 254 # Respawn any players on this team that were in limbo due to the 255 # lack of a flag for their team. 256 for otherplayer in self.players: 257 if (otherplayer.team is flag.team 258 and otherplayer.actor is not None 259 and not otherplayer.is_alive() 260 and otherplayer.respawn_timer is None): 261 self.spawn_player(otherplayer) 262 263 def handlemessage(self, msg: Any) -> Any: 264 if isinstance(msg, ba.PlayerDiedMessage): 265 # Augment standard behavior. 266 super().handlemessage(msg) 267 268 # Respawn only if this team has a flag. 269 player = msg.getplayer(Player) 270 if player.team.flags_held > 0: 271 self.respawn_player(player) 272 else: 273 player.respawn_timer = None 274 275 else: 276 super().handlemessage(msg) 277 278 def spawn_player(self, player: Player) -> ba.Actor: 279 # We spawn players at different places based on what flags are held. 280 return self.spawn_player_spaz(player, 281 self._get_player_spawn_position(player)) 282 283 def _get_player_spawn_position(self, player: Player) -> Sequence[float]: 284 285 # Iterate until we find a spawn owned by this team. 286 spawn_count = len(self.map.spawn_by_flag_points) 287 288 # Get all spawns owned by this team. 289 spawns = [ 290 i for i in range(spawn_count) if self._flags[i].team is player.team 291 ] 292 293 closest_spawn = 0 294 closest_distance = 9999.0 295 296 # Now find the spawn that's closest to a spawn not owned by us; 297 # we'll use that one. 298 for spawn in spawns: 299 spt = self.map.spawn_by_flag_points[spawn] 300 our_pt = ba.Vec3(spt[0], spt[1], spt[2]) 301 for otherspawn in [ 302 i for i in range(spawn_count) 303 if self._flags[i].team is not player.team 304 ]: 305 spt = self.map.spawn_by_flag_points[otherspawn] 306 their_pt = ba.Vec3(spt[0], spt[1], spt[2]) 307 dist = (their_pt - our_pt).length() 308 if dist < closest_distance: 309 closest_distance = dist 310 closest_spawn = spawn 311 312 pos = self.map.spawn_by_flag_points[closest_spawn] 313 x_range = (-0.5, 0.5) if pos[3] == 0.0 else (-pos[3], pos[3]) 314 z_range = (-0.5, 0.5) if pos[5] == 0.0 else (-pos[5], pos[5]) 315 pos = (pos[0] + random.uniform(*x_range), pos[1], 316 pos[2] + random.uniform(*z_range)) 317 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 = (ba.MusicType.EPIC 130 if self._epic_mode else ba.MusicType.GRAND_ROMP) 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 ))
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.
140 def get_instance_description(self) -> str | Sequence: 141 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.
143 def get_instance_description_short(self) -> str | Sequence: 144 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)
150 def on_player_join(self, player: Player) -> None: 151 player.respawn_timer = None 152 153 # Only spawn if this player's team has a flag currently. 154 if player.team.flags_held > 0: 155 self.spawn_player(player)
Called when a new ba.Player has joined the Activity.
(including the initial set of Players)
157 def on_begin(self) -> None: 158 super().on_begin() 159 self.setup_standard_time_limit(self._time_limit) 160 self.setup_standard_powerup_drops() 161 162 # Set up flags with marker lights. 163 for i, flag_point in enumerate(self.map.flag_points): 164 point = flag_point 165 flag = ConquestFlag(position=point, 166 touchable=False, 167 materials=[self._extraflagmat]) 168 self._flags.append(flag) 169 Flag.project_stand(point) 170 flag.light = ba.newnode('light', 171 owner=flag.node, 172 attrs={ 173 'position': point, 174 'intensity': 0.25, 175 'height_attenuated': False, 176 'radius': 0.3, 177 'color': (1, 1, 1) 178 }) 179 180 # Give teams a flag to start with. 181 for i, team in enumerate(self.teams): 182 self._flags[i].team = team 183 light = self._flags[i].light 184 assert light 185 node = self._flags[i].node 186 assert node 187 light.color = team.color 188 node.color = team.color 189 190 self._update_scores() 191 192 # Initial joiners didn't spawn due to no flags being owned yet; 193 # spawn them now. 194 for player in self.players: 195 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.
216 def end_game(self) -> None: 217 results = ba.GameResults() 218 for team in self.teams: 219 results.set_team_score(team, team.flags_held) 220 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.
263 def handlemessage(self, msg: Any) -> Any: 264 if isinstance(msg, ba.PlayerDiedMessage): 265 # Augment standard behavior. 266 super().handlemessage(msg) 267 268 # Respawn only if this team has a flag. 269 player = msg.getplayer(Player) 270 if player.team.flags_held > 0: 271 self.respawn_player(player) 272 else: 273 player.respawn_timer = None 274 275 else: 276 super().handlemessage(msg)
General message handling; can be passed any message object.
278 def spawn_player(self, player: Player) -> ba.Actor: 279 # We spawn players at different places based on what flags are held. 280 return self.spawn_player_spaz(player, 281 self._get_player_spawn_position(player))
Spawn something for the provided ba.Player.
The default implementation simply calls spawn_player_spaz().
Inherited Members
- ba._activity.Activity
- slow_motion
- 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._gameactivity.GameActivity
- default_music
- tips
- scoreconfig
- 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
- 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._teamgame.TeamGameActivity
- on_transition_in
- spawn_player_spaz
- end
- ba._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps