bascenev1lib.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 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.flag import Flag 14from bascenev1lib.actor.scoreboard import Scoreboard 15from bascenev1lib.actor.playerspaz import PlayerSpaz 16from bascenev1lib.gameutils import SharedObjects 17import bascenev1 as bs 18 19if TYPE_CHECKING: 20 from typing import Any, Sequence 21 22 from bascenev1lib.actor.respawnicon import RespawnIcon 23 24 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: bs.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 42 43 44class Player(bs.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) -> bs.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: bs.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 66 67 68class Team(bs.Team[Player]): 69 """Our team type for this game.""" 70 71 def __init__(self) -> None: 72 self.flags_held = 0 73 74 75# ba_meta export bascenev1.GameActivity 76class ConquestGame(bs.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 bs.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 bs.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 bs.BoolSetting('Epic Mode', default=False), 106 ] 107 108 @classmethod 109 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 110 return issubclass(sessiontype, bs.DualTeamSession) 111 112 @classmethod 113 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 114 assert bs.app.classic is not None 115 return bs.app.classic.getmaps('conquest') 116 117 def __init__(self, settings: dict): 118 super().__init__(settings) 119 shared = SharedObjects.get() 120 self._scoreboard = Scoreboard() 121 self._score_sound = bs.getsound('score') 122 self._swipsound = bs.getsound('swip') 123 self._extraflagmat = bs.Material() 124 self._flags: list[ConquestFlag] = [] 125 self._epic_mode = bool(settings['Epic Mode']) 126 self._time_limit = float(settings['Time Limit']) 127 128 # Base class overrides. 129 self.slow_motion = self._epic_mode 130 self.default_music = ( 131 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP 132 ) 133 134 # We want flags to tell us they've been hit but not react physically. 135 self._extraflagmat.add_actions( 136 conditions=('they_have_material', shared.player_material), 137 actions=( 138 ('modify_part_collision', 'collide', True), 139 ('call', 'at_connect', self._handle_flag_player_collide), 140 ), 141 ) 142 143 def get_instance_description(self) -> str | Sequence: 144 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 145 146 def get_instance_description_short(self) -> str | Sequence: 147 return 'secure all ${ARG1} flags', len(self.map.flag_points) 148 149 def on_team_join(self, team: Team) -> None: 150 if self.has_begun(): 151 self._update_scores() 152 153 def on_player_join(self, player: Player) -> None: 154 player.respawn_timer = None 155 156 # Only spawn if this player's team has a flag currently. 157 if player.team.flags_held > 0: 158 self.spawn_player(player) 159 160 def on_begin(self) -> None: 161 super().on_begin() 162 self.setup_standard_time_limit(self._time_limit) 163 self.setup_standard_powerup_drops() 164 165 # Set up flags with marker lights. 166 for i, flag_point in enumerate(self.map.flag_points): 167 point = flag_point 168 flag = ConquestFlag( 169 position=point, touchable=False, materials=[self._extraflagmat] 170 ) 171 self._flags.append(flag) 172 Flag.project_stand(point) 173 flag.light = bs.newnode( 174 'light', 175 owner=flag.node, 176 attrs={ 177 'position': point, 178 'intensity': 0.25, 179 'height_attenuated': False, 180 'radius': 0.3, 181 'color': (1, 1, 1), 182 }, 183 ) 184 185 # Give teams a flag to start with. 186 for i, team in enumerate(self.teams): 187 self._flags[i].team = team 188 light = self._flags[i].light 189 assert light 190 node = self._flags[i].node 191 assert node 192 light.color = team.color 193 node.color = team.color 194 195 self._update_scores() 196 197 # Initial joiners didn't spawn due to no flags being owned yet; 198 # spawn them now. 199 for player in self.players: 200 self.spawn_player(player) 201 202 def _update_scores(self) -> None: 203 for team in self.teams: 204 team.flags_held = 0 205 for flag in self._flags: 206 if flag.team is not None: 207 flag.team.flags_held += 1 208 for team in self.teams: 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 = bs.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 = bs.newnode( 231 'light', 232 attrs={ 233 'position': flag.node.position, 234 'height_attenuated': False, 235 'color': flag.light.color, 236 }, 237 ) 238 bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 239 bs.timer(length, light.delete) 240 241 def _handle_flag_player_collide(self) -> None: 242 collision = bs.getcollision() 243 try: 244 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 245 player = collision.opposingnode.getdelegate( 246 PlayerSpaz, True 247 ).getplayer(Player, True) 248 except bs.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 self._swipsound.play() 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, bs.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) -> bs.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 # 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 = bs.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 = bs.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
26class ConquestFlag(Flag): 27 """A custom flag for use with Conquest games.""" 28 29 def __init__(self, *args: Any, **keywds: Any): 30 super().__init__(*args, **keywds) 31 self._team: Team | None = None 32 self.light: bs.Node | None = None 33 34 @property 35 def team(self) -> Team | None: 36 """The team that owns this flag.""" 37 return self._team 38 39 @team.setter 40 def team(self, team: Team) -> None: 41 """Set the team that owns this flag.""" 42 self._team = team
A custom flag for use with Conquest games.
29 def __init__(self, *args: Any, **keywds: Any): 30 super().__init__(*args, **keywds) 31 self._team: Team | None = None 32 self.light: bs.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 bs.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
- bascenev1._actor.Actor
- autoretain
- on_expire
- expired
- exists
- is_alive
- activity
- getactivity
45class Player(bs.Player['Team']): 46 """Our player type for this game.""" 47 48 # FIXME: We shouldn't be using customdata here 49 # (but need to update respawn funcs accordingly first). 50 @property 51 def respawn_timer(self) -> bs.Timer | None: 52 """Type safe access to standard respawn timer.""" 53 return self.customdata.get('respawn_timer', None) 54 55 @respawn_timer.setter 56 def respawn_timer(self, value: bs.Timer | None) -> None: 57 self.customdata['respawn_timer'] = value 58 59 @property 60 def respawn_icon(self) -> RespawnIcon | None: 61 """Type safe access to standard respawn icon.""" 62 return self.customdata.get('respawn_icon', None) 63 64 @respawn_icon.setter 65 def respawn_icon(self, value: RespawnIcon | None) -> None: 66 self.customdata['respawn_icon'] = value
Our player type for this game.
Type safe access to standard respawn icon.
Inherited Members
- bascenev1._player.Player
- character
- actor
- color
- highlight
- on_expire
- team
- customdata
- sessionplayer
- node
- position
- exists
- getname
- is_alive
- get_icon
- assigninput
- resetinput
69class Team(bs.Team[Player]): 70 """Our team type for this game.""" 71 72 def __init__(self) -> None: 73 self.flags_held = 0
Our team type for this game.
Inherited Members
- bascenev1._team.Team
- players
- id
- name
- color
- manual_init
- customdata
- on_expire
- sessionteam
77class ConquestGame(bs.TeamGameActivity[Player, Team]): 78 """A game where teams try to claim all flags on the map.""" 79 80 name = 'Conquest' 81 description = 'Secure all flags on the map to win.' 82 available_settings = [ 83 bs.IntChoiceSetting( 84 'Time Limit', 85 choices=[ 86 ('None', 0), 87 ('1 Minute', 60), 88 ('2 Minutes', 120), 89 ('5 Minutes', 300), 90 ('10 Minutes', 600), 91 ('20 Minutes', 1200), 92 ], 93 default=0, 94 ), 95 bs.FloatChoiceSetting( 96 'Respawn Times', 97 choices=[ 98 ('Shorter', 0.25), 99 ('Short', 0.5), 100 ('Normal', 1.0), 101 ('Long', 2.0), 102 ('Longer', 4.0), 103 ], 104 default=1.0, 105 ), 106 bs.BoolSetting('Epic Mode', default=False), 107 ] 108 109 @classmethod 110 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 111 return issubclass(sessiontype, bs.DualTeamSession) 112 113 @classmethod 114 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 115 assert bs.app.classic is not None 116 return bs.app.classic.getmaps('conquest') 117 118 def __init__(self, settings: dict): 119 super().__init__(settings) 120 shared = SharedObjects.get() 121 self._scoreboard = Scoreboard() 122 self._score_sound = bs.getsound('score') 123 self._swipsound = bs.getsound('swip') 124 self._extraflagmat = bs.Material() 125 self._flags: list[ConquestFlag] = [] 126 self._epic_mode = bool(settings['Epic Mode']) 127 self._time_limit = float(settings['Time Limit']) 128 129 # Base class overrides. 130 self.slow_motion = self._epic_mode 131 self.default_music = ( 132 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP 133 ) 134 135 # We want flags to tell us they've been hit but not react physically. 136 self._extraflagmat.add_actions( 137 conditions=('they_have_material', shared.player_material), 138 actions=( 139 ('modify_part_collision', 'collide', True), 140 ('call', 'at_connect', self._handle_flag_player_collide), 141 ), 142 ) 143 144 def get_instance_description(self) -> str | Sequence: 145 return 'Secure all ${ARG1} flags.', len(self.map.flag_points) 146 147 def get_instance_description_short(self) -> str | Sequence: 148 return 'secure all ${ARG1} flags', len(self.map.flag_points) 149 150 def on_team_join(self, team: Team) -> None: 151 if self.has_begun(): 152 self._update_scores() 153 154 def on_player_join(self, player: Player) -> None: 155 player.respawn_timer = None 156 157 # Only spawn if this player's team has a flag currently. 158 if player.team.flags_held > 0: 159 self.spawn_player(player) 160 161 def on_begin(self) -> None: 162 super().on_begin() 163 self.setup_standard_time_limit(self._time_limit) 164 self.setup_standard_powerup_drops() 165 166 # Set up flags with marker lights. 167 for i, flag_point in enumerate(self.map.flag_points): 168 point = flag_point 169 flag = ConquestFlag( 170 position=point, touchable=False, materials=[self._extraflagmat] 171 ) 172 self._flags.append(flag) 173 Flag.project_stand(point) 174 flag.light = bs.newnode( 175 'light', 176 owner=flag.node, 177 attrs={ 178 'position': point, 179 'intensity': 0.25, 180 'height_attenuated': False, 181 'radius': 0.3, 182 'color': (1, 1, 1), 183 }, 184 ) 185 186 # Give teams a flag to start with. 187 for i, team in enumerate(self.teams): 188 self._flags[i].team = team 189 light = self._flags[i].light 190 assert light 191 node = self._flags[i].node 192 assert node 193 light.color = team.color 194 node.color = team.color 195 196 self._update_scores() 197 198 # Initial joiners didn't spawn due to no flags being owned yet; 199 # spawn them now. 200 for player in self.players: 201 self.spawn_player(player) 202 203 def _update_scores(self) -> None: 204 for team in self.teams: 205 team.flags_held = 0 206 for flag in self._flags: 207 if flag.team is not None: 208 flag.team.flags_held += 1 209 for team in self.teams: 210 # If a team finds themselves with no flags, cancel all 211 # outstanding spawn-timers. 212 if team.flags_held == 0: 213 for player in team.players: 214 player.respawn_timer = None 215 player.respawn_icon = None 216 if team.flags_held == len(self._flags): 217 self.end_game() 218 self._scoreboard.set_team_value( 219 team, team.flags_held, len(self._flags) 220 ) 221 222 def end_game(self) -> None: 223 results = bs.GameResults() 224 for team in self.teams: 225 results.set_team_score(team, team.flags_held) 226 self.end(results=results) 227 228 def _flash_flag(self, flag: ConquestFlag, length: float = 1.0) -> None: 229 assert flag.node 230 assert flag.light 231 light = bs.newnode( 232 'light', 233 attrs={ 234 'position': flag.node.position, 235 'height_attenuated': False, 236 'color': flag.light.color, 237 }, 238 ) 239 bs.animate(light, 'intensity', {0: 0, 0.25: 1, 0.5: 0}, loop=True) 240 bs.timer(length, light.delete) 241 242 def _handle_flag_player_collide(self) -> None: 243 collision = bs.getcollision() 244 try: 245 flag = collision.sourcenode.getdelegate(ConquestFlag, True) 246 player = collision.opposingnode.getdelegate( 247 PlayerSpaz, True 248 ).getplayer(Player, True) 249 except bs.NotFoundError: 250 return 251 assert flag.light 252 253 if flag.team is not player.team: 254 flag.team = player.team 255 flag.light.color = player.team.color 256 flag.node.color = player.team.color 257 self.stats.player_scored(player, 10, screenmessage=False) 258 self._swipsound.play() 259 self._flash_flag(flag) 260 self._update_scores() 261 262 # Respawn any players on this team that were in limbo due to the 263 # lack of a flag for their team. 264 for otherplayer in self.players: 265 if ( 266 otherplayer.team is flag.team 267 and otherplayer.actor is not None 268 and not otherplayer.is_alive() 269 and otherplayer.respawn_timer is None 270 ): 271 self.spawn_player(otherplayer) 272 273 def handlemessage(self, msg: Any) -> Any: 274 if isinstance(msg, bs.PlayerDiedMessage): 275 # Augment standard behavior. 276 super().handlemessage(msg) 277 278 # Respawn only if this team has a flag. 279 player = msg.getplayer(Player) 280 if player.team.flags_held > 0: 281 self.respawn_player(player) 282 else: 283 player.respawn_timer = None 284 285 else: 286 super().handlemessage(msg) 287 288 def spawn_player(self, player: Player) -> bs.Actor: 289 # We spawn players at different places based on what flags are held. 290 return self.spawn_player_spaz( 291 player, self._get_player_spawn_position(player) 292 ) 293 294 def _get_player_spawn_position(self, player: Player) -> Sequence[float]: 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 = bs.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 = bs.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.
118 def __init__(self, settings: dict): 119 super().__init__(settings) 120 shared = SharedObjects.get() 121 self._scoreboard = Scoreboard() 122 self._score_sound = bs.getsound('score') 123 self._swipsound = bs.getsound('swip') 124 self._extraflagmat = bs.Material() 125 self._flags: list[ConquestFlag] = [] 126 self._epic_mode = bool(settings['Epic Mode']) 127 self._time_limit = float(settings['Time Limit']) 128 129 # Base class overrides. 130 self.slow_motion = self._epic_mode 131 self.default_music = ( 132 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.GRAND_ROMP 133 ) 134 135 # We want flags to tell us they've been hit but not react physically. 136 self._extraflagmat.add_actions( 137 conditions=('they_have_material', shared.player_material), 138 actions=( 139 ('modify_part_collision', 'collide', True), 140 ('call', 'at_connect', self._handle_flag_player_collide), 141 ), 142 )
Instantiate the Activity.
109 @classmethod 110 def supports_session_type(cls, sessiontype: type[bs.Session]) -> bool: 111 return issubclass(sessiontype, bs.DualTeamSession)
Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.
113 @classmethod 114 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 115 assert bs.app.classic is not None 116 return bs.app.classic.getmaps('conquest')
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.
144 def get_instance_description(self) -> str | Sequence: 145 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.
147 def get_instance_description_short(self) -> str | Sequence: 148 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 bascenev1.Team joins the Activity.
(including the initial set of Teams)
154 def on_player_join(self, player: Player) -> None: 155 player.respawn_timer = None 156 157 # Only spawn if this player's team has a flag currently. 158 if player.team.flags_held > 0: 159 self.spawn_player(player)
Called when a new bascenev1.Player has joined the Activity.
(including the initial set of Players)
161 def on_begin(self) -> None: 162 super().on_begin() 163 self.setup_standard_time_limit(self._time_limit) 164 self.setup_standard_powerup_drops() 165 166 # Set up flags with marker lights. 167 for i, flag_point in enumerate(self.map.flag_points): 168 point = flag_point 169 flag = ConquestFlag( 170 position=point, touchable=False, materials=[self._extraflagmat] 171 ) 172 self._flags.append(flag) 173 Flag.project_stand(point) 174 flag.light = bs.newnode( 175 'light', 176 owner=flag.node, 177 attrs={ 178 'position': point, 179 'intensity': 0.25, 180 'height_attenuated': False, 181 'radius': 0.3, 182 'color': (1, 1, 1), 183 }, 184 ) 185 186 # Give teams a flag to start with. 187 for i, team in enumerate(self.teams): 188 self._flags[i].team = team 189 light = self._flags[i].light 190 assert light 191 node = self._flags[i].node 192 assert node 193 light.color = team.color 194 node.color = team.color 195 196 self._update_scores() 197 198 # Initial joiners didn't spawn due to no flags being owned yet; 199 # spawn them now. 200 for player in self.players: 201 self.spawn_player(player)
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.
222 def end_game(self) -> None: 223 results = bs.GameResults() 224 for team in self.teams: 225 results.set_team_score(team, team.flags_held) 226 self.end(results=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.
273 def handlemessage(self, msg: Any) -> Any: 274 if isinstance(msg, bs.PlayerDiedMessage): 275 # Augment standard behavior. 276 super().handlemessage(msg) 277 278 # Respawn only if this team has a flag. 279 player = msg.getplayer(Player) 280 if player.team.flags_held > 0: 281 self.respawn_player(player) 282 else: 283 player.respawn_timer = None 284 285 else: 286 super().handlemessage(msg)
General message handling; can be passed any message object.
288 def spawn_player(self, player: Player) -> bs.Actor: 289 # We spawn players at different places based on what flags are held. 290 return self.spawn_player_spaz( 291 player, self._get_player_spawn_position(player) 292 )
Spawn something for the provided bascenev1.Player.
The default implementation simply calls spawn_player_spaz().
Inherited Members
- bascenev1._teamgame.TeamGameActivity
- on_transition_in
- spawn_player_spaz
- end
- bascenev1._gameactivity.GameActivity
- 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
- initialplayerinfos
- 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
- bascenev1._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
- 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_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