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