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