bastd.game.thelaststand
Defines the last stand minigame.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Defines the last stand minigame.""" 4 5from __future__ import annotations 6 7import random 8from dataclasses import dataclass 9from typing import TYPE_CHECKING 10 11import ba 12from bastd.actor.playerspaz import PlayerSpaz 13from bastd.actor.bomb import TNTSpawner 14from bastd.actor.scoreboard import Scoreboard 15from bastd.actor.powerupbox import PowerupBoxFactory, PowerupBox 16from bastd.actor.spazbot import ( 17 SpazBotSet, 18 SpazBotDiedMessage, 19 BomberBot, 20 BomberBotPro, 21 BomberBotProShielded, 22 BrawlerBot, 23 BrawlerBotPro, 24 BrawlerBotProShielded, 25 TriggerBot, 26 TriggerBotPro, 27 TriggerBotProShielded, 28 ChargerBot, 29 StickyBot, 30 ExplodeyBot, 31) 32 33if TYPE_CHECKING: 34 from typing import Any, Sequence 35 from bastd.actor.spazbot import SpazBot 36 37 38@dataclass 39class SpawnInfo: 40 """Spawning info for a particular bot type.""" 41 42 spawnrate: float 43 increase: float 44 dincrease: float 45 46 47class Player(ba.Player['Team']): 48 """Our player type for this game.""" 49 50 51class Team(ba.Team[Player]): 52 """Our team type for this game.""" 53 54 55class TheLastStandGame(ba.CoopGameActivity[Player, Team]): 56 """Slow motion how-long-can-you-last game.""" 57 58 name = 'The Last Stand' 59 description = 'Final glorious epic slow motion battle to the death.' 60 tips = [ 61 'This level never ends, but a high score here\n' 62 'will earn you eternal respect throughout the world.' 63 ] 64 65 # Show messages when players die since it matters here. 66 announce_player_deaths = True 67 68 # And of course the most important part. 69 slow_motion = True 70 71 default_music = ba.MusicType.EPIC 72 73 def __init__(self, settings: dict): 74 settings['map'] = 'Rampage' 75 super().__init__(settings) 76 self._new_wave_sound = ba.getsound('scoreHit01') 77 self._winsound = ba.getsound('score') 78 self._cashregistersound = ba.getsound('cashRegister') 79 self._spawn_center = (0, 5.5, -4.14) 80 self._tntspawnpos = (0, 5.5, -6) 81 self._powerup_center = (0, 7, -4.14) 82 self._powerup_spread = (7, 2) 83 self._preset = str(settings.get('preset', 'default')) 84 self._excludepowerups: list[str] = [] 85 self._scoreboard: Scoreboard | None = None 86 self._score = 0 87 self._bots = SpazBotSet() 88 self._dingsound = ba.getsound('dingSmall') 89 self._dingsoundhigh = ba.getsound('dingSmallHigh') 90 self._tntspawner: TNTSpawner | None = None 91 self._bot_update_interval: float | None = None 92 self._bot_update_timer: ba.Timer | None = None 93 self._powerup_drop_timer = None 94 95 # For each bot type: [spawnrate, increase, d_increase] 96 self._bot_spawn_types = { 97 BomberBot: SpawnInfo(1.00, 0.00, 0.000), 98 BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), 99 BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 100 BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), 101 BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), 102 BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 103 TriggerBot: SpawnInfo(0.30, 0.00, 0.000), 104 TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), 105 TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 106 ChargerBot: SpawnInfo(0.30, 0.05, 0.000), 107 StickyBot: SpawnInfo(0.10, 0.03, 0.001), 108 ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002), 109 } # yapf: disable 110 111 def on_transition_in(self) -> None: 112 super().on_transition_in() 113 ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) 114 self._scoreboard = Scoreboard( 115 label=ba.Lstr(resource='scoreText'), score_split=0.5 116 ) 117 118 def on_begin(self) -> None: 119 super().on_begin() 120 121 # Spit out a few powerups and start dropping more shortly. 122 self._drop_powerups(standard_points=True) 123 ba.timer(2.0, ba.WeakCall(self._start_powerup_drops)) 124 ba.timer(0.001, ba.WeakCall(self._start_bot_updates)) 125 self.setup_low_life_warning_sound() 126 self._update_scores() 127 self._tntspawner = TNTSpawner( 128 position=self._tntspawnpos, respawn_time=10.0 129 ) 130 131 def spawn_player(self, player: Player) -> ba.Actor: 132 pos = ( 133 self._spawn_center[0] + random.uniform(-1.5, 1.5), 134 self._spawn_center[1], 135 self._spawn_center[2] + random.uniform(-1.5, 1.5), 136 ) 137 return self.spawn_player_spaz(player, position=pos) 138 139 def _start_bot_updates(self) -> None: 140 self._bot_update_interval = 3.3 - 0.3 * (len(self.players)) 141 self._update_bots() 142 self._update_bots() 143 if len(self.players) > 2: 144 self._update_bots() 145 if len(self.players) > 3: 146 self._update_bots() 147 self._bot_update_timer = ba.Timer( 148 self._bot_update_interval, ba.WeakCall(self._update_bots) 149 ) 150 151 def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: 152 if poweruptype is None: 153 poweruptype = PowerupBoxFactory.get().get_random_powerup_type( 154 excludetypes=self._excludepowerups 155 ) 156 PowerupBox( 157 position=self.map.powerup_spawn_points[index], 158 poweruptype=poweruptype, 159 ).autoretain() 160 161 def _start_powerup_drops(self) -> None: 162 self._powerup_drop_timer = ba.Timer( 163 3.0, ba.WeakCall(self._drop_powerups), repeat=True 164 ) 165 166 def _drop_powerups( 167 self, standard_points: bool = False, force_first: str | None = None 168 ) -> None: 169 """Generic powerup drop.""" 170 from bastd.actor import powerupbox 171 172 if standard_points: 173 pts = self.map.powerup_spawn_points 174 for i in range(len(pts)): 175 ba.timer( 176 1.0 + i * 0.5, 177 ba.WeakCall( 178 self._drop_powerup, i, force_first if i == 0 else None 179 ), 180 ) 181 else: 182 drop_pt = ( 183 self._powerup_center[0] 184 + random.uniform( 185 -1.0 * self._powerup_spread[0], 186 1.0 * self._powerup_spread[0], 187 ), 188 self._powerup_center[1], 189 self._powerup_center[2] 190 + random.uniform( 191 -self._powerup_spread[1], self._powerup_spread[1] 192 ), 193 ) 194 195 # Drop one random one somewhere. 196 powerupbox.PowerupBox( 197 position=drop_pt, 198 poweruptype=PowerupBoxFactory.get().get_random_powerup_type( 199 excludetypes=self._excludepowerups 200 ), 201 ).autoretain() 202 203 def do_end(self, outcome: str) -> None: 204 """End the game.""" 205 if outcome == 'defeat': 206 self.fade_to_red() 207 self.end( 208 delay=2.0, 209 results={ 210 'outcome': outcome, 211 'score': self._score, 212 'playerinfos': self.initialplayerinfos, 213 }, 214 ) 215 216 def _update_bots(self) -> None: 217 assert self._bot_update_interval is not None 218 self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98) 219 self._bot_update_timer = ba.Timer( 220 self._bot_update_interval, ba.WeakCall(self._update_bots) 221 ) 222 botspawnpts: list[Sequence[float]] = [ 223 [-5.0, 5.5, -4.14], 224 [0.0, 5.5, -4.14], 225 [5.0, 5.5, -4.14], 226 ] 227 dists = [0.0, 0.0, 0.0] 228 playerpts: list[Sequence[float]] = [] 229 for player in self.players: 230 try: 231 if player.is_alive(): 232 assert isinstance(player.actor, PlayerSpaz) 233 assert player.actor.node 234 playerpts.append(player.actor.node.position) 235 except Exception: 236 ba.print_exception('Error updating bots.') 237 for i in range(3): 238 for playerpt in playerpts: 239 dists[i] += abs(playerpt[0] - botspawnpts[i][0]) 240 dists[i] += random.random() * 5.0 # Minor random variation. 241 if dists[0] > dists[1] and dists[0] > dists[2]: 242 spawnpt = botspawnpts[0] 243 elif dists[1] > dists[2]: 244 spawnpt = botspawnpts[1] 245 else: 246 spawnpt = botspawnpts[2] 247 248 spawnpt = ( 249 spawnpt[0] + 3.0 * (random.random() - 0.5), 250 spawnpt[1], 251 2.0 * (random.random() - 0.5) + spawnpt[2], 252 ) 253 254 # Normalize our bot type total and find a random number within that. 255 total = 0.0 256 for spawninfo in self._bot_spawn_types.values(): 257 total += spawninfo.spawnrate 258 randval = random.random() * total 259 260 # Now go back through and see where this value falls. 261 total = 0 262 bottype: type[SpazBot] | None = None 263 for spawntype, spawninfo in self._bot_spawn_types.items(): 264 total += spawninfo.spawnrate 265 if randval <= total: 266 bottype = spawntype 267 break 268 spawn_time = 1.0 269 assert bottype is not None 270 self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time) 271 272 # After every spawn we adjust our ratios slightly to get more 273 # difficult. 274 for spawninfo in self._bot_spawn_types.values(): 275 spawninfo.spawnrate += spawninfo.increase 276 spawninfo.increase += spawninfo.dincrease 277 278 def _update_scores(self) -> None: 279 score = self._score 280 281 # Achievements apply to the default preset only. 282 if self._preset == 'default': 283 if score >= 250: 284 self._award_achievement('Last Stand Master') 285 if score >= 500: 286 self._award_achievement('Last Stand Wizard') 287 if score >= 1000: 288 self._award_achievement('Last Stand God') 289 assert self._scoreboard is not None 290 self._scoreboard.set_team_value(self.teams[0], score, max_score=None) 291 292 def handlemessage(self, msg: Any) -> Any: 293 if isinstance(msg, ba.PlayerDiedMessage): 294 player = msg.getplayer(Player) 295 self.stats.player_was_killed(player) 296 ba.timer(0.1, self._checkroundover) 297 298 elif isinstance(msg, ba.PlayerScoredMessage): 299 self._score += msg.score 300 self._update_scores() 301 302 elif isinstance(msg, SpazBotDiedMessage): 303 pts, importance = msg.spazbot.get_death_points(msg.how) 304 target: Sequence[float] | None 305 if msg.killerplayer: 306 assert msg.spazbot.node 307 target = msg.spazbot.node.position 308 self.stats.player_scored( 309 msg.killerplayer, 310 pts, 311 target=target, 312 kill=True, 313 screenmessage=False, 314 importance=importance, 315 ) 316 ba.playsound( 317 self._dingsound if importance == 1 else self._dingsoundhigh, 318 volume=0.6, 319 ) 320 321 # Normally we pull scores from the score-set, but if there's no 322 # player lets be explicit. 323 else: 324 self._score += pts 325 self._update_scores() 326 else: 327 super().handlemessage(msg) 328 329 def end_game(self) -> None: 330 # Tell our bots to celebrate just to rub it in. 331 self._bots.final_celebrate() 332 ba.setmusic(None) 333 ba.pushcall(ba.WeakCall(self.do_end, 'defeat')) 334 335 def _checkroundover(self) -> None: 336 """End the round if conditions are met.""" 337 if not any(player.is_alive() for player in self.teams[0].players): 338 self.end_game()
39@dataclass 40class SpawnInfo: 41 """Spawning info for a particular bot type.""" 42 43 spawnrate: float 44 increase: float 45 dincrease: float
Spawning info for a particular bot type.
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
Our team type for this game.
Inherited Members
- ba._team.Team
- manual_init
- customdata
- on_expire
- sessionteam
56class TheLastStandGame(ba.CoopGameActivity[Player, Team]): 57 """Slow motion how-long-can-you-last game.""" 58 59 name = 'The Last Stand' 60 description = 'Final glorious epic slow motion battle to the death.' 61 tips = [ 62 'This level never ends, but a high score here\n' 63 'will earn you eternal respect throughout the world.' 64 ] 65 66 # Show messages when players die since it matters here. 67 announce_player_deaths = True 68 69 # And of course the most important part. 70 slow_motion = True 71 72 default_music = ba.MusicType.EPIC 73 74 def __init__(self, settings: dict): 75 settings['map'] = 'Rampage' 76 super().__init__(settings) 77 self._new_wave_sound = ba.getsound('scoreHit01') 78 self._winsound = ba.getsound('score') 79 self._cashregistersound = ba.getsound('cashRegister') 80 self._spawn_center = (0, 5.5, -4.14) 81 self._tntspawnpos = (0, 5.5, -6) 82 self._powerup_center = (0, 7, -4.14) 83 self._powerup_spread = (7, 2) 84 self._preset = str(settings.get('preset', 'default')) 85 self._excludepowerups: list[str] = [] 86 self._scoreboard: Scoreboard | None = None 87 self._score = 0 88 self._bots = SpazBotSet() 89 self._dingsound = ba.getsound('dingSmall') 90 self._dingsoundhigh = ba.getsound('dingSmallHigh') 91 self._tntspawner: TNTSpawner | None = None 92 self._bot_update_interval: float | None = None 93 self._bot_update_timer: ba.Timer | None = None 94 self._powerup_drop_timer = None 95 96 # For each bot type: [spawnrate, increase, d_increase] 97 self._bot_spawn_types = { 98 BomberBot: SpawnInfo(1.00, 0.00, 0.000), 99 BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), 100 BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 101 BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), 102 BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), 103 BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 104 TriggerBot: SpawnInfo(0.30, 0.00, 0.000), 105 TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), 106 TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 107 ChargerBot: SpawnInfo(0.30, 0.05, 0.000), 108 StickyBot: SpawnInfo(0.10, 0.03, 0.001), 109 ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002), 110 } # yapf: disable 111 112 def on_transition_in(self) -> None: 113 super().on_transition_in() 114 ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) 115 self._scoreboard = Scoreboard( 116 label=ba.Lstr(resource='scoreText'), score_split=0.5 117 ) 118 119 def on_begin(self) -> None: 120 super().on_begin() 121 122 # Spit out a few powerups and start dropping more shortly. 123 self._drop_powerups(standard_points=True) 124 ba.timer(2.0, ba.WeakCall(self._start_powerup_drops)) 125 ba.timer(0.001, ba.WeakCall(self._start_bot_updates)) 126 self.setup_low_life_warning_sound() 127 self._update_scores() 128 self._tntspawner = TNTSpawner( 129 position=self._tntspawnpos, respawn_time=10.0 130 ) 131 132 def spawn_player(self, player: Player) -> ba.Actor: 133 pos = ( 134 self._spawn_center[0] + random.uniform(-1.5, 1.5), 135 self._spawn_center[1], 136 self._spawn_center[2] + random.uniform(-1.5, 1.5), 137 ) 138 return self.spawn_player_spaz(player, position=pos) 139 140 def _start_bot_updates(self) -> None: 141 self._bot_update_interval = 3.3 - 0.3 * (len(self.players)) 142 self._update_bots() 143 self._update_bots() 144 if len(self.players) > 2: 145 self._update_bots() 146 if len(self.players) > 3: 147 self._update_bots() 148 self._bot_update_timer = ba.Timer( 149 self._bot_update_interval, ba.WeakCall(self._update_bots) 150 ) 151 152 def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: 153 if poweruptype is None: 154 poweruptype = PowerupBoxFactory.get().get_random_powerup_type( 155 excludetypes=self._excludepowerups 156 ) 157 PowerupBox( 158 position=self.map.powerup_spawn_points[index], 159 poweruptype=poweruptype, 160 ).autoretain() 161 162 def _start_powerup_drops(self) -> None: 163 self._powerup_drop_timer = ba.Timer( 164 3.0, ba.WeakCall(self._drop_powerups), repeat=True 165 ) 166 167 def _drop_powerups( 168 self, standard_points: bool = False, force_first: str | None = None 169 ) -> None: 170 """Generic powerup drop.""" 171 from bastd.actor import powerupbox 172 173 if standard_points: 174 pts = self.map.powerup_spawn_points 175 for i in range(len(pts)): 176 ba.timer( 177 1.0 + i * 0.5, 178 ba.WeakCall( 179 self._drop_powerup, i, force_first if i == 0 else None 180 ), 181 ) 182 else: 183 drop_pt = ( 184 self._powerup_center[0] 185 + random.uniform( 186 -1.0 * self._powerup_spread[0], 187 1.0 * self._powerup_spread[0], 188 ), 189 self._powerup_center[1], 190 self._powerup_center[2] 191 + random.uniform( 192 -self._powerup_spread[1], self._powerup_spread[1] 193 ), 194 ) 195 196 # Drop one random one somewhere. 197 powerupbox.PowerupBox( 198 position=drop_pt, 199 poweruptype=PowerupBoxFactory.get().get_random_powerup_type( 200 excludetypes=self._excludepowerups 201 ), 202 ).autoretain() 203 204 def do_end(self, outcome: str) -> None: 205 """End the game.""" 206 if outcome == 'defeat': 207 self.fade_to_red() 208 self.end( 209 delay=2.0, 210 results={ 211 'outcome': outcome, 212 'score': self._score, 213 'playerinfos': self.initialplayerinfos, 214 }, 215 ) 216 217 def _update_bots(self) -> None: 218 assert self._bot_update_interval is not None 219 self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98) 220 self._bot_update_timer = ba.Timer( 221 self._bot_update_interval, ba.WeakCall(self._update_bots) 222 ) 223 botspawnpts: list[Sequence[float]] = [ 224 [-5.0, 5.5, -4.14], 225 [0.0, 5.5, -4.14], 226 [5.0, 5.5, -4.14], 227 ] 228 dists = [0.0, 0.0, 0.0] 229 playerpts: list[Sequence[float]] = [] 230 for player in self.players: 231 try: 232 if player.is_alive(): 233 assert isinstance(player.actor, PlayerSpaz) 234 assert player.actor.node 235 playerpts.append(player.actor.node.position) 236 except Exception: 237 ba.print_exception('Error updating bots.') 238 for i in range(3): 239 for playerpt in playerpts: 240 dists[i] += abs(playerpt[0] - botspawnpts[i][0]) 241 dists[i] += random.random() * 5.0 # Minor random variation. 242 if dists[0] > dists[1] and dists[0] > dists[2]: 243 spawnpt = botspawnpts[0] 244 elif dists[1] > dists[2]: 245 spawnpt = botspawnpts[1] 246 else: 247 spawnpt = botspawnpts[2] 248 249 spawnpt = ( 250 spawnpt[0] + 3.0 * (random.random() - 0.5), 251 spawnpt[1], 252 2.0 * (random.random() - 0.5) + spawnpt[2], 253 ) 254 255 # Normalize our bot type total and find a random number within that. 256 total = 0.0 257 for spawninfo in self._bot_spawn_types.values(): 258 total += spawninfo.spawnrate 259 randval = random.random() * total 260 261 # Now go back through and see where this value falls. 262 total = 0 263 bottype: type[SpazBot] | None = None 264 for spawntype, spawninfo in self._bot_spawn_types.items(): 265 total += spawninfo.spawnrate 266 if randval <= total: 267 bottype = spawntype 268 break 269 spawn_time = 1.0 270 assert bottype is not None 271 self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time) 272 273 # After every spawn we adjust our ratios slightly to get more 274 # difficult. 275 for spawninfo in self._bot_spawn_types.values(): 276 spawninfo.spawnrate += spawninfo.increase 277 spawninfo.increase += spawninfo.dincrease 278 279 def _update_scores(self) -> None: 280 score = self._score 281 282 # Achievements apply to the default preset only. 283 if self._preset == 'default': 284 if score >= 250: 285 self._award_achievement('Last Stand Master') 286 if score >= 500: 287 self._award_achievement('Last Stand Wizard') 288 if score >= 1000: 289 self._award_achievement('Last Stand God') 290 assert self._scoreboard is not None 291 self._scoreboard.set_team_value(self.teams[0], score, max_score=None) 292 293 def handlemessage(self, msg: Any) -> Any: 294 if isinstance(msg, ba.PlayerDiedMessage): 295 player = msg.getplayer(Player) 296 self.stats.player_was_killed(player) 297 ba.timer(0.1, self._checkroundover) 298 299 elif isinstance(msg, ba.PlayerScoredMessage): 300 self._score += msg.score 301 self._update_scores() 302 303 elif isinstance(msg, SpazBotDiedMessage): 304 pts, importance = msg.spazbot.get_death_points(msg.how) 305 target: Sequence[float] | None 306 if msg.killerplayer: 307 assert msg.spazbot.node 308 target = msg.spazbot.node.position 309 self.stats.player_scored( 310 msg.killerplayer, 311 pts, 312 target=target, 313 kill=True, 314 screenmessage=False, 315 importance=importance, 316 ) 317 ba.playsound( 318 self._dingsound if importance == 1 else self._dingsoundhigh, 319 volume=0.6, 320 ) 321 322 # Normally we pull scores from the score-set, but if there's no 323 # player lets be explicit. 324 else: 325 self._score += pts 326 self._update_scores() 327 else: 328 super().handlemessage(msg) 329 330 def end_game(self) -> None: 331 # Tell our bots to celebrate just to rub it in. 332 self._bots.final_celebrate() 333 ba.setmusic(None) 334 ba.pushcall(ba.WeakCall(self.do_end, 'defeat')) 335 336 def _checkroundover(self) -> None: 337 """End the round if conditions are met.""" 338 if not any(player.is_alive() for player in self.teams[0].players): 339 self.end_game()
Slow motion how-long-can-you-last game.
74 def __init__(self, settings: dict): 75 settings['map'] = 'Rampage' 76 super().__init__(settings) 77 self._new_wave_sound = ba.getsound('scoreHit01') 78 self._winsound = ba.getsound('score') 79 self._cashregistersound = ba.getsound('cashRegister') 80 self._spawn_center = (0, 5.5, -4.14) 81 self._tntspawnpos = (0, 5.5, -6) 82 self._powerup_center = (0, 7, -4.14) 83 self._powerup_spread = (7, 2) 84 self._preset = str(settings.get('preset', 'default')) 85 self._excludepowerups: list[str] = [] 86 self._scoreboard: Scoreboard | None = None 87 self._score = 0 88 self._bots = SpazBotSet() 89 self._dingsound = ba.getsound('dingSmall') 90 self._dingsoundhigh = ba.getsound('dingSmallHigh') 91 self._tntspawner: TNTSpawner | None = None 92 self._bot_update_interval: float | None = None 93 self._bot_update_timer: ba.Timer | None = None 94 self._powerup_drop_timer = None 95 96 # For each bot type: [spawnrate, increase, d_increase] 97 self._bot_spawn_types = { 98 BomberBot: SpawnInfo(1.00, 0.00, 0.000), 99 BomberBotPro: SpawnInfo(0.00, 0.05, 0.001), 100 BomberBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 101 BrawlerBot: SpawnInfo(1.00, 0.00, 0.000), 102 BrawlerBotPro: SpawnInfo(0.00, 0.05, 0.001), 103 BrawlerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 104 TriggerBot: SpawnInfo(0.30, 0.00, 0.000), 105 TriggerBotPro: SpawnInfo(0.00, 0.05, 0.001), 106 TriggerBotProShielded: SpawnInfo(0.00, 0.02, 0.002), 107 ChargerBot: SpawnInfo(0.30, 0.05, 0.000), 108 StickyBot: SpawnInfo(0.10, 0.03, 0.001), 109 ExplodeyBot: SpawnInfo(0.05, 0.02, 0.002), 110 } # yapf: disable
Instantiate the Activity.
Whether to print every time a player dies. This can be pertinent in games such as Death-Match but can be annoying in games where it doesn't matter.
112 def on_transition_in(self) -> None: 113 super().on_transition_in() 114 ba.timer(1.3, ba.Call(ba.playsound, self._new_wave_sound)) 115 self._scoreboard = Scoreboard( 116 label=ba.Lstr(resource='scoreText'), score_split=0.5 117 )
Called when the Activity is first becoming visible.
Upon this call, the Activity should fade in backgrounds, start playing music, etc. It does not yet have access to players or teams, however. They remain owned by the previous Activity up until ba.Activity.on_begin() is called.
119 def on_begin(self) -> None: 120 super().on_begin() 121 122 # Spit out a few powerups and start dropping more shortly. 123 self._drop_powerups(standard_points=True) 124 ba.timer(2.0, ba.WeakCall(self._start_powerup_drops)) 125 ba.timer(0.001, ba.WeakCall(self._start_bot_updates)) 126 self.setup_low_life_warning_sound() 127 self._update_scores() 128 self._tntspawner = TNTSpawner( 129 position=self._tntspawnpos, respawn_time=10.0 130 )
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.
132 def spawn_player(self, player: Player) -> ba.Actor: 133 pos = ( 134 self._spawn_center[0] + random.uniform(-1.5, 1.5), 135 self._spawn_center[1], 136 self._spawn_center[2] + random.uniform(-1.5, 1.5), 137 ) 138 return self.spawn_player_spaz(player, position=pos)
Spawn something for the provided ba.Player.
The default implementation simply calls spawn_player_spaz().
204 def do_end(self, outcome: str) -> None: 205 """End the game.""" 206 if outcome == 'defeat': 207 self.fade_to_red() 208 self.end( 209 delay=2.0, 210 results={ 211 'outcome': outcome, 212 'score': self._score, 213 'playerinfos': self.initialplayerinfos, 214 }, 215 )
End the game.
293 def handlemessage(self, msg: Any) -> Any: 294 if isinstance(msg, ba.PlayerDiedMessage): 295 player = msg.getplayer(Player) 296 self.stats.player_was_killed(player) 297 ba.timer(0.1, self._checkroundover) 298 299 elif isinstance(msg, ba.PlayerScoredMessage): 300 self._score += msg.score 301 self._update_scores() 302 303 elif isinstance(msg, SpazBotDiedMessage): 304 pts, importance = msg.spazbot.get_death_points(msg.how) 305 target: Sequence[float] | None 306 if msg.killerplayer: 307 assert msg.spazbot.node 308 target = msg.spazbot.node.position 309 self.stats.player_scored( 310 msg.killerplayer, 311 pts, 312 target=target, 313 kill=True, 314 screenmessage=False, 315 importance=importance, 316 ) 317 ba.playsound( 318 self._dingsound if importance == 1 else self._dingsoundhigh, 319 volume=0.6, 320 ) 321 322 # Normally we pull scores from the score-set, but if there's no 323 # player lets be explicit. 324 else: 325 self._score += pts 326 self._update_scores() 327 else: 328 super().handlemessage(msg)
General message handling; can be passed any message object.
330 def end_game(self) -> None: 331 # Tell our bots to celebrate just to rub it in. 332 self._bots.final_celebrate() 333 ba.setmusic(None) 334 ba.pushcall(ba.WeakCall(self.do_end, 'defeat'))
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.
Inherited Members
- ba._coopgame.CoopGameActivity
- session
- supports_session_type
- get_score_type
- celebrate
- spawn_player_spaz
- fade_to_red
- setup_low_life_warning_sound
- ba._gameactivity.GameActivity
- allow_pausing
- allow_kick_idle_players
- create_settings_ui
- getscoreconfig
- getname
- get_display_string
- get_team_display_string
- get_description
- get_description_display_string
- get_available_settings
- get_supported_maps
- get_settings_display_string
- map
- get_instance_display_string
- get_instance_scoreboard_display_string
- get_instance_description
- get_instance_description_short
- on_continue
- is_waiting_for_continue
- continue_or_end_game
- on_player_join
- end
- respawn_player
- spawn_player_if_exists
- setup_standard_powerup_drops
- setup_standard_time_limit
- show_zoom_message
- ba._activity.Activity
- settings_raw
- teams
- players
- 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
- on_player_leave
- on_team_join
- on_team_leave
- on_transition_out
- has_transitioned_in
- has_begun
- has_ended
- is_transitioning_out
- transition_out
- create_player
- create_team
- ba._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps