bascenev1lib.game.chosenone
Provides the chosen-one mini-game.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides the chosen-one 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 logging 11from typing import TYPE_CHECKING 12 13from bascenev1lib.actor.flag import Flag 14from bascenev1lib.actor.playerspaz import PlayerSpaz 15from bascenev1lib.actor.scoreboard import Scoreboard 16from bascenev1lib.gameutils import SharedObjects 17import bascenev1 as bs 18 19if TYPE_CHECKING: 20 from typing import Any, Sequence 21 22 23class Player(bs.Player['Team']): 24 """Our player type for this game.""" 25 26 def __init__(self) -> None: 27 self.chosen_light: bs.NodeActor | None = None 28 29 30class Team(bs.Team[Player]): 31 """Our team type for this game.""" 32 33 def __init__(self, time_remaining: int) -> None: 34 self.time_remaining = time_remaining 35 36 37# ba_meta export bascenev1.GameActivity 38class ChosenOneGame(bs.TeamGameActivity[Player, Team]): 39 """ 40 Game involving trying to remain the one 'chosen one' 41 for a set length of time while everyone else tries to 42 kill you and become the chosen one themselves. 43 """ 44 45 name = 'Chosen One' 46 description = ( 47 'Be the chosen one for a length of time to win.\n' 48 'Kill the chosen one to become it.' 49 ) 50 available_settings = [ 51 bs.IntSetting( 52 'Chosen One Time', 53 min_value=10, 54 default=30, 55 increment=10, 56 ), 57 bs.BoolSetting('Chosen One Gets Gloves', default=True), 58 bs.BoolSetting('Chosen One Gets Shield', default=False), 59 bs.IntChoiceSetting( 60 'Time Limit', 61 choices=[ 62 ('None', 0), 63 ('1 Minute', 60), 64 ('2 Minutes', 120), 65 ('5 Minutes', 300), 66 ('10 Minutes', 600), 67 ('20 Minutes', 1200), 68 ], 69 default=0, 70 ), 71 bs.FloatChoiceSetting( 72 'Respawn Times', 73 choices=[ 74 ('Shorter', 0.25), 75 ('Short', 0.5), 76 ('Normal', 1.0), 77 ('Long', 2.0), 78 ('Longer', 4.0), 79 ], 80 default=1.0, 81 ), 82 bs.BoolSetting('Epic Mode', default=False), 83 ] 84 scoreconfig = bs.ScoreConfig(label='Time Held') 85 86 @classmethod 87 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 88 assert bs.app.classic is not None 89 return bs.app.classic.getmaps('keep_away') 90 91 def __init__(self, settings: dict): 92 super().__init__(settings) 93 self._scoreboard = Scoreboard() 94 self._chosen_one_player: Player | None = None 95 self._swipsound = bs.getsound('swip') 96 self._countdownsounds: dict[int, bs.Sound] = { 97 10: bs.getsound('announceTen'), 98 9: bs.getsound('announceNine'), 99 8: bs.getsound('announceEight'), 100 7: bs.getsound('announceSeven'), 101 6: bs.getsound('announceSix'), 102 5: bs.getsound('announceFive'), 103 4: bs.getsound('announceFour'), 104 3: bs.getsound('announceThree'), 105 2: bs.getsound('announceTwo'), 106 1: bs.getsound('announceOne'), 107 } 108 self._flag_spawn_pos: Sequence[float] | None = None 109 self._reset_region_material: bs.Material | None = None 110 self._flag: Flag | None = None 111 self._reset_region: bs.Node | None = None 112 self._epic_mode = bool(settings['Epic Mode']) 113 self._chosen_one_time = int(settings['Chosen One Time']) 114 self._time_limit = float(settings['Time Limit']) 115 self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield']) 116 self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves']) 117 118 # Base class overrides 119 self.slow_motion = self._epic_mode 120 self.default_music = ( 121 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.CHOSEN_ONE 122 ) 123 124 def get_instance_description(self) -> str | Sequence: 125 return 'There can be only one.' 126 127 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 128 return Team(time_remaining=self._chosen_one_time) 129 130 def on_team_join(self, team: Team) -> None: 131 self._update_scoreboard() 132 133 def on_player_leave(self, player: Player) -> None: 134 super().on_player_leave(player) 135 if self._get_chosen_one_player() is player: 136 self._set_chosen_one_player(None) 137 138 def on_begin(self) -> None: 139 super().on_begin() 140 shared = SharedObjects.get() 141 self.setup_standard_time_limit(self._time_limit) 142 self.setup_standard_powerup_drops() 143 self._flag_spawn_pos = self.map.get_flag_position(None) 144 Flag.project_stand(self._flag_spawn_pos) 145 bs.timer(1.0, call=self._tick, repeat=True) 146 147 mat = self._reset_region_material = bs.Material() 148 mat.add_actions( 149 conditions=( 150 'they_have_material', 151 shared.player_material, 152 ), 153 actions=( 154 ('modify_part_collision', 'collide', True), 155 ('modify_part_collision', 'physical', False), 156 ('call', 'at_connect', bs.WeakCall(self._handle_reset_collide)), 157 ), 158 ) 159 160 self._set_chosen_one_player(None) 161 162 def _create_reset_region(self) -> None: 163 assert self._reset_region_material is not None 164 assert self._flag_spawn_pos is not None 165 pos = self._flag_spawn_pos 166 self._reset_region = bs.newnode( 167 'region', 168 attrs={ 169 'position': (pos[0], pos[1] + 0.75, pos[2]), 170 'scale': (0.5, 0.5, 0.5), 171 'type': 'sphere', 172 'materials': [self._reset_region_material], 173 }, 174 ) 175 176 def _get_chosen_one_player(self) -> Player | None: 177 # Should never return invalid references; return None in that case. 178 if self._chosen_one_player: 179 return self._chosen_one_player 180 return None 181 182 def _handle_reset_collide(self) -> None: 183 # If we have a chosen one, ignore these. 184 if self._get_chosen_one_player() is not None: 185 return 186 187 # Attempt to get a Actor that we hit. 188 try: 189 spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True) 190 player = spaz.getplayer(Player, True) 191 except bs.NotFoundError: 192 return 193 194 if spaz.is_alive(): 195 self._set_chosen_one_player(player) 196 197 def _flash_flag_spawn(self) -> None: 198 light = bs.newnode( 199 'light', 200 attrs={ 201 'position': self._flag_spawn_pos, 202 'color': (1, 1, 1), 203 'radius': 0.3, 204 'height_attenuated': False, 205 }, 206 ) 207 bs.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True) 208 bs.timer(1.0, light.delete) 209 210 def _tick(self) -> None: 211 # Give the chosen one points. 212 player = self._get_chosen_one_player() 213 if player is not None: 214 # This shouldn't happen, but just in case. 215 if not player.is_alive(): 216 logging.error('got dead player as chosen one in _tick') 217 self._set_chosen_one_player(None) 218 else: 219 scoring_team = player.team 220 self.stats.player_scored( 221 player, 3, screenmessage=False, display=False 222 ) 223 224 scoring_team.time_remaining = max( 225 0, scoring_team.time_remaining - 1 226 ) 227 228 # Show the count over their head 229 if scoring_team.time_remaining > 0: 230 if isinstance(player.actor, PlayerSpaz) and player.actor: 231 player.actor.set_score_text( 232 str(scoring_team.time_remaining) 233 ) 234 235 self._update_scoreboard() 236 237 # announce numbers we have sounds for 238 if scoring_team.time_remaining in self._countdownsounds: 239 self._countdownsounds[scoring_team.time_remaining].play() 240 241 # Winner! 242 if scoring_team.time_remaining <= 0: 243 self.end_game() 244 245 else: 246 # (player is None) 247 # This shouldn't happen, but just in case. 248 # (Chosen-one player ceasing to exist should 249 # trigger on_player_leave which resets chosen-one) 250 if self._chosen_one_player is not None: 251 logging.error('got nonexistent player as chosen one in _tick') 252 self._set_chosen_one_player(None) 253 254 def end_game(self) -> None: 255 results = bs.GameResults() 256 for team in self.teams: 257 results.set_team_score( 258 team, self._chosen_one_time - team.time_remaining 259 ) 260 self.end(results=results, announce_delay=0) 261 262 def _set_chosen_one_player(self, player: Player | None) -> None: 263 existing = self._get_chosen_one_player() 264 if existing: 265 existing.chosen_light = None 266 self._swipsound.play() 267 if not player: 268 assert self._flag_spawn_pos is not None 269 self._flag = Flag( 270 color=(1, 0.9, 0.2), 271 position=self._flag_spawn_pos, 272 touchable=False, 273 ) 274 self._chosen_one_player = None 275 276 # Create a light to highlight the flag; 277 # this will go away when the flag dies. 278 bs.newnode( 279 'light', 280 owner=self._flag.node, 281 attrs={ 282 'position': self._flag_spawn_pos, 283 'intensity': 0.6, 284 'height_attenuated': False, 285 'volume_intensity_scale': 0.1, 286 'radius': 0.1, 287 'color': (1.2, 1.2, 0.4), 288 }, 289 ) 290 291 # Also an extra momentary flash. 292 self._flash_flag_spawn() 293 294 # Re-create our flag region in case if someone is waiting for 295 # flag right there: 296 self._create_reset_region() 297 else: 298 if player.actor: 299 self._flag = None 300 self._chosen_one_player = player 301 302 if self._chosen_one_gets_shield: 303 player.actor.handlemessage(bs.PowerupMessage('shield')) 304 if self._chosen_one_gets_gloves: 305 player.actor.handlemessage(bs.PowerupMessage('punch')) 306 307 # Use a color that's partway between their team color 308 # and white. 309 color = [ 310 0.3 + c * 0.7 311 for c in bs.normalized_color(player.team.color) 312 ] 313 light = player.chosen_light = bs.NodeActor( 314 bs.newnode( 315 'light', 316 attrs={ 317 'intensity': 0.6, 318 'height_attenuated': False, 319 'volume_intensity_scale': 0.1, 320 'radius': 0.13, 321 'color': color, 322 }, 323 ) 324 ) 325 326 assert light.node 327 bs.animate( 328 light.node, 329 'intensity', 330 {0: 1.0, 0.2: 0.4, 0.4: 1.0}, 331 loop=True, 332 ) 333 assert isinstance(player.actor, PlayerSpaz) 334 player.actor.node.connectattr( 335 'position', light.node, 'position' 336 ) 337 338 def handlemessage(self, msg: Any) -> Any: 339 if isinstance(msg, bs.PlayerDiedMessage): 340 # Augment standard behavior. 341 super().handlemessage(msg) 342 player = msg.getplayer(Player) 343 if player is self._get_chosen_one_player(): 344 killerplayer = msg.getkillerplayer(Player) 345 self._set_chosen_one_player( 346 None 347 if ( 348 killerplayer is None 349 or killerplayer is player 350 or not killerplayer.is_alive() 351 ) 352 else killerplayer 353 ) 354 self.respawn_player(player) 355 else: 356 super().handlemessage(msg) 357 358 def _update_scoreboard(self) -> None: 359 for team in self.teams: 360 self._scoreboard.set_team_value( 361 team, team.time_remaining, self._chosen_one_time, countdown=True 362 )
24class Player(bs.Player['Team']): 25 """Our player type for this game.""" 26 27 def __init__(self) -> None: 28 self.chosen_light: bs.NodeActor | None = None
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
31class Team(bs.Team[Player]): 32 """Our team type for this game.""" 33 34 def __init__(self, time_remaining: int) -> None: 35 self.time_remaining = time_remaining
Our team type for this game.
Inherited Members
- bascenev1._team.Team
- players
- id
- name
- color
- manual_init
- customdata
- on_expire
- sessionteam
39class ChosenOneGame(bs.TeamGameActivity[Player, Team]): 40 """ 41 Game involving trying to remain the one 'chosen one' 42 for a set length of time while everyone else tries to 43 kill you and become the chosen one themselves. 44 """ 45 46 name = 'Chosen One' 47 description = ( 48 'Be the chosen one for a length of time to win.\n' 49 'Kill the chosen one to become it.' 50 ) 51 available_settings = [ 52 bs.IntSetting( 53 'Chosen One Time', 54 min_value=10, 55 default=30, 56 increment=10, 57 ), 58 bs.BoolSetting('Chosen One Gets Gloves', default=True), 59 bs.BoolSetting('Chosen One Gets Shield', default=False), 60 bs.IntChoiceSetting( 61 'Time Limit', 62 choices=[ 63 ('None', 0), 64 ('1 Minute', 60), 65 ('2 Minutes', 120), 66 ('5 Minutes', 300), 67 ('10 Minutes', 600), 68 ('20 Minutes', 1200), 69 ], 70 default=0, 71 ), 72 bs.FloatChoiceSetting( 73 'Respawn Times', 74 choices=[ 75 ('Shorter', 0.25), 76 ('Short', 0.5), 77 ('Normal', 1.0), 78 ('Long', 2.0), 79 ('Longer', 4.0), 80 ], 81 default=1.0, 82 ), 83 bs.BoolSetting('Epic Mode', default=False), 84 ] 85 scoreconfig = bs.ScoreConfig(label='Time Held') 86 87 @classmethod 88 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 89 assert bs.app.classic is not None 90 return bs.app.classic.getmaps('keep_away') 91 92 def __init__(self, settings: dict): 93 super().__init__(settings) 94 self._scoreboard = Scoreboard() 95 self._chosen_one_player: Player | None = None 96 self._swipsound = bs.getsound('swip') 97 self._countdownsounds: dict[int, bs.Sound] = { 98 10: bs.getsound('announceTen'), 99 9: bs.getsound('announceNine'), 100 8: bs.getsound('announceEight'), 101 7: bs.getsound('announceSeven'), 102 6: bs.getsound('announceSix'), 103 5: bs.getsound('announceFive'), 104 4: bs.getsound('announceFour'), 105 3: bs.getsound('announceThree'), 106 2: bs.getsound('announceTwo'), 107 1: bs.getsound('announceOne'), 108 } 109 self._flag_spawn_pos: Sequence[float] | None = None 110 self._reset_region_material: bs.Material | None = None 111 self._flag: Flag | None = None 112 self._reset_region: bs.Node | None = None 113 self._epic_mode = bool(settings['Epic Mode']) 114 self._chosen_one_time = int(settings['Chosen One Time']) 115 self._time_limit = float(settings['Time Limit']) 116 self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield']) 117 self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves']) 118 119 # Base class overrides 120 self.slow_motion = self._epic_mode 121 self.default_music = ( 122 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.CHOSEN_ONE 123 ) 124 125 def get_instance_description(self) -> str | Sequence: 126 return 'There can be only one.' 127 128 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 129 return Team(time_remaining=self._chosen_one_time) 130 131 def on_team_join(self, team: Team) -> None: 132 self._update_scoreboard() 133 134 def on_player_leave(self, player: Player) -> None: 135 super().on_player_leave(player) 136 if self._get_chosen_one_player() is player: 137 self._set_chosen_one_player(None) 138 139 def on_begin(self) -> None: 140 super().on_begin() 141 shared = SharedObjects.get() 142 self.setup_standard_time_limit(self._time_limit) 143 self.setup_standard_powerup_drops() 144 self._flag_spawn_pos = self.map.get_flag_position(None) 145 Flag.project_stand(self._flag_spawn_pos) 146 bs.timer(1.0, call=self._tick, repeat=True) 147 148 mat = self._reset_region_material = bs.Material() 149 mat.add_actions( 150 conditions=( 151 'they_have_material', 152 shared.player_material, 153 ), 154 actions=( 155 ('modify_part_collision', 'collide', True), 156 ('modify_part_collision', 'physical', False), 157 ('call', 'at_connect', bs.WeakCall(self._handle_reset_collide)), 158 ), 159 ) 160 161 self._set_chosen_one_player(None) 162 163 def _create_reset_region(self) -> None: 164 assert self._reset_region_material is not None 165 assert self._flag_spawn_pos is not None 166 pos = self._flag_spawn_pos 167 self._reset_region = bs.newnode( 168 'region', 169 attrs={ 170 'position': (pos[0], pos[1] + 0.75, pos[2]), 171 'scale': (0.5, 0.5, 0.5), 172 'type': 'sphere', 173 'materials': [self._reset_region_material], 174 }, 175 ) 176 177 def _get_chosen_one_player(self) -> Player | None: 178 # Should never return invalid references; return None in that case. 179 if self._chosen_one_player: 180 return self._chosen_one_player 181 return None 182 183 def _handle_reset_collide(self) -> None: 184 # If we have a chosen one, ignore these. 185 if self._get_chosen_one_player() is not None: 186 return 187 188 # Attempt to get a Actor that we hit. 189 try: 190 spaz = bs.getcollision().opposingnode.getdelegate(PlayerSpaz, True) 191 player = spaz.getplayer(Player, True) 192 except bs.NotFoundError: 193 return 194 195 if spaz.is_alive(): 196 self._set_chosen_one_player(player) 197 198 def _flash_flag_spawn(self) -> None: 199 light = bs.newnode( 200 'light', 201 attrs={ 202 'position': self._flag_spawn_pos, 203 'color': (1, 1, 1), 204 'radius': 0.3, 205 'height_attenuated': False, 206 }, 207 ) 208 bs.animate(light, 'intensity', {0: 0, 0.25: 0.5, 0.5: 0}, loop=True) 209 bs.timer(1.0, light.delete) 210 211 def _tick(self) -> None: 212 # Give the chosen one points. 213 player = self._get_chosen_one_player() 214 if player is not None: 215 # This shouldn't happen, but just in case. 216 if not player.is_alive(): 217 logging.error('got dead player as chosen one in _tick') 218 self._set_chosen_one_player(None) 219 else: 220 scoring_team = player.team 221 self.stats.player_scored( 222 player, 3, screenmessage=False, display=False 223 ) 224 225 scoring_team.time_remaining = max( 226 0, scoring_team.time_remaining - 1 227 ) 228 229 # Show the count over their head 230 if scoring_team.time_remaining > 0: 231 if isinstance(player.actor, PlayerSpaz) and player.actor: 232 player.actor.set_score_text( 233 str(scoring_team.time_remaining) 234 ) 235 236 self._update_scoreboard() 237 238 # announce numbers we have sounds for 239 if scoring_team.time_remaining in self._countdownsounds: 240 self._countdownsounds[scoring_team.time_remaining].play() 241 242 # Winner! 243 if scoring_team.time_remaining <= 0: 244 self.end_game() 245 246 else: 247 # (player is None) 248 # This shouldn't happen, but just in case. 249 # (Chosen-one player ceasing to exist should 250 # trigger on_player_leave which resets chosen-one) 251 if self._chosen_one_player is not None: 252 logging.error('got nonexistent player as chosen one in _tick') 253 self._set_chosen_one_player(None) 254 255 def end_game(self) -> None: 256 results = bs.GameResults() 257 for team in self.teams: 258 results.set_team_score( 259 team, self._chosen_one_time - team.time_remaining 260 ) 261 self.end(results=results, announce_delay=0) 262 263 def _set_chosen_one_player(self, player: Player | None) -> None: 264 existing = self._get_chosen_one_player() 265 if existing: 266 existing.chosen_light = None 267 self._swipsound.play() 268 if not player: 269 assert self._flag_spawn_pos is not None 270 self._flag = Flag( 271 color=(1, 0.9, 0.2), 272 position=self._flag_spawn_pos, 273 touchable=False, 274 ) 275 self._chosen_one_player = None 276 277 # Create a light to highlight the flag; 278 # this will go away when the flag dies. 279 bs.newnode( 280 'light', 281 owner=self._flag.node, 282 attrs={ 283 'position': self._flag_spawn_pos, 284 'intensity': 0.6, 285 'height_attenuated': False, 286 'volume_intensity_scale': 0.1, 287 'radius': 0.1, 288 'color': (1.2, 1.2, 0.4), 289 }, 290 ) 291 292 # Also an extra momentary flash. 293 self._flash_flag_spawn() 294 295 # Re-create our flag region in case if someone is waiting for 296 # flag right there: 297 self._create_reset_region() 298 else: 299 if player.actor: 300 self._flag = None 301 self._chosen_one_player = player 302 303 if self._chosen_one_gets_shield: 304 player.actor.handlemessage(bs.PowerupMessage('shield')) 305 if self._chosen_one_gets_gloves: 306 player.actor.handlemessage(bs.PowerupMessage('punch')) 307 308 # Use a color that's partway between their team color 309 # and white. 310 color = [ 311 0.3 + c * 0.7 312 for c in bs.normalized_color(player.team.color) 313 ] 314 light = player.chosen_light = bs.NodeActor( 315 bs.newnode( 316 'light', 317 attrs={ 318 'intensity': 0.6, 319 'height_attenuated': False, 320 'volume_intensity_scale': 0.1, 321 'radius': 0.13, 322 'color': color, 323 }, 324 ) 325 ) 326 327 assert light.node 328 bs.animate( 329 light.node, 330 'intensity', 331 {0: 1.0, 0.2: 0.4, 0.4: 1.0}, 332 loop=True, 333 ) 334 assert isinstance(player.actor, PlayerSpaz) 335 player.actor.node.connectattr( 336 'position', light.node, 'position' 337 ) 338 339 def handlemessage(self, msg: Any) -> Any: 340 if isinstance(msg, bs.PlayerDiedMessage): 341 # Augment standard behavior. 342 super().handlemessage(msg) 343 player = msg.getplayer(Player) 344 if player is self._get_chosen_one_player(): 345 killerplayer = msg.getkillerplayer(Player) 346 self._set_chosen_one_player( 347 None 348 if ( 349 killerplayer is None 350 or killerplayer is player 351 or not killerplayer.is_alive() 352 ) 353 else killerplayer 354 ) 355 self.respawn_player(player) 356 else: 357 super().handlemessage(msg) 358 359 def _update_scoreboard(self) -> None: 360 for team in self.teams: 361 self._scoreboard.set_team_value( 362 team, team.time_remaining, self._chosen_one_time, countdown=True 363 )
Game involving trying to remain the one 'chosen one' for a set length of time while everyone else tries to kill you and become the chosen one themselves.
92 def __init__(self, settings: dict): 93 super().__init__(settings) 94 self._scoreboard = Scoreboard() 95 self._chosen_one_player: Player | None = None 96 self._swipsound = bs.getsound('swip') 97 self._countdownsounds: dict[int, bs.Sound] = { 98 10: bs.getsound('announceTen'), 99 9: bs.getsound('announceNine'), 100 8: bs.getsound('announceEight'), 101 7: bs.getsound('announceSeven'), 102 6: bs.getsound('announceSix'), 103 5: bs.getsound('announceFive'), 104 4: bs.getsound('announceFour'), 105 3: bs.getsound('announceThree'), 106 2: bs.getsound('announceTwo'), 107 1: bs.getsound('announceOne'), 108 } 109 self._flag_spawn_pos: Sequence[float] | None = None 110 self._reset_region_material: bs.Material | None = None 111 self._flag: Flag | None = None 112 self._reset_region: bs.Node | None = None 113 self._epic_mode = bool(settings['Epic Mode']) 114 self._chosen_one_time = int(settings['Chosen One Time']) 115 self._time_limit = float(settings['Time Limit']) 116 self._chosen_one_gets_shield = bool(settings['Chosen One Gets Shield']) 117 self._chosen_one_gets_gloves = bool(settings['Chosen One Gets Gloves']) 118 119 # Base class overrides 120 self.slow_motion = self._epic_mode 121 self.default_music = ( 122 bs.MusicType.EPIC if self._epic_mode else bs.MusicType.CHOSEN_ONE 123 )
Instantiate the Activity.
87 @classmethod 88 def get_supported_maps(cls, sessiontype: type[bs.Session]) -> list[str]: 89 assert bs.app.classic is not None 90 return bs.app.classic.getmaps('keep_away')
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.
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.
128 def create_team(self, sessionteam: bs.SessionTeam) -> Team: 129 return Team(time_remaining=self._chosen_one_time)
Create the Team instance for this Activity.
Subclasses can override this if the activity's team class requires a custom constructor; otherwise it will be called with no args. Note that the team object should not be used at this point as it is not yet fully wired up; wait for on_team_join() for that.
Called when a new bascenev1.Team joins the Activity.
(including the initial set of Teams)
134 def on_player_leave(self, player: Player) -> None: 135 super().on_player_leave(player) 136 if self._get_chosen_one_player() is player: 137 self._set_chosen_one_player(None)
Called when a bascenev1.Player is leaving the Activity.
139 def on_begin(self) -> None: 140 super().on_begin() 141 shared = SharedObjects.get() 142 self.setup_standard_time_limit(self._time_limit) 143 self.setup_standard_powerup_drops() 144 self._flag_spawn_pos = self.map.get_flag_position(None) 145 Flag.project_stand(self._flag_spawn_pos) 146 bs.timer(1.0, call=self._tick, repeat=True) 147 148 mat = self._reset_region_material = bs.Material() 149 mat.add_actions( 150 conditions=( 151 'they_have_material', 152 shared.player_material, 153 ), 154 actions=( 155 ('modify_part_collision', 'collide', True), 156 ('modify_part_collision', 'physical', False), 157 ('call', 'at_connect', bs.WeakCall(self._handle_reset_collide)), 158 ), 159 ) 160 161 self._set_chosen_one_player(None)
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.
255 def end_game(self) -> None: 256 results = bs.GameResults() 257 for team in self.teams: 258 results.set_team_score( 259 team, self._chosen_one_time - team.time_remaining 260 ) 261 self.end(results=results, announce_delay=0)
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.
339 def handlemessage(self, msg: Any) -> Any: 340 if isinstance(msg, bs.PlayerDiedMessage): 341 # Augment standard behavior. 342 super().handlemessage(msg) 343 player = msg.getplayer(Player) 344 if player is self._get_chosen_one_player(): 345 killerplayer = msg.getkillerplayer(Player) 346 self._set_chosen_one_player( 347 None 348 if ( 349 killerplayer is None 350 or killerplayer is player 351 or not killerplayer.is_alive() 352 ) 353 else killerplayer 354 ) 355 self.respawn_player(player) 356 else: 357 super().handlemessage(msg)
General message handling; can be passed any message object.
Inherited Members
- bascenev1._teamgame.TeamGameActivity
- supports_session_type
- on_transition_in
- spawn_player_spaz
- end
- bascenev1._gameactivity.GameActivity
- tips
- 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_short
- on_continue
- is_waiting_for_continue
- continue_or_end_game
- on_player_join
- respawn_player
- spawn_player_if_exists
- spawn_player
- 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_team_leave
- on_transition_out
- has_transitioned_in
- has_begun
- has_ended
- is_transitioning_out
- transition_out
- create_player
- bascenev1._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps