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 11 12from bascenev1lib.actor.playerspaz import PlayerSpaz 13from bascenev1lib.actor.bomb import TNTSpawner 14from bascenev1lib.actor.scoreboard import Scoreboard 15from bascenev1lib.actor.powerupbox import PowerupBoxFactory, PowerupBox 16from bascenev1lib.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) 32import bascenev1 as bs 33 34if TYPE_CHECKING: 35 from typing import Any, Sequence 36 from bascenev1lib.actor.spazbot import SpazBot 37 38 39@dataclass 40class SpawnInfo: 41 """Spawning info for a particular bot type.""" 42 43 spawnrate: float 44 increase: float 45 dincrease: float 46 47 48class Player(bs.Player['Team']): 49 """Our player type for this game.""" 50 51 52class Team(bs.Team[Player]): 53 """Our team type for this game.""" 54 55 56class TheLastStandGame(bs.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 = bs.MusicType.EPIC 73 74 def __init__(self, settings: dict): 75 settings['map'] = 'Rampage' 76 super().__init__(settings) 77 self._new_wave_sound = bs.getsound('scoreHit01') 78 self._winsound = bs.getsound('score') 79 self._cashregistersound = bs.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 = bs.getsound('dingSmall') 90 self._dingsoundhigh = bs.getsound('dingSmallHigh') 91 self._tntspawner: TNTSpawner | None = None 92 self._bot_update_interval: float | None = None 93 self._bot_update_timer: bs.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 } 111 112 def on_transition_in(self) -> None: 113 super().on_transition_in() 114 bs.timer(1.3, self._new_wave_sound.play) 115 self._scoreboard = Scoreboard( 116 label=bs.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 bs.timer(2.0, bs.WeakCall(self._start_powerup_drops)) 125 bs.timer(0.001, bs.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) -> bs.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 = bs.Timer( 149 self._bot_update_interval, bs.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 = bs.Timer( 164 3.0, bs.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 bascenev1lib.actor import powerupbox 172 173 if standard_points: 174 pts = self.map.powerup_spawn_points 175 for i in range(len(pts)): 176 bs.timer( 177 1.0 + i * 0.5, 178 bs.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 = bs.Timer( 221 self._bot_update_interval, bs.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 logging.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, bs.PlayerDiedMessage): 295 player = msg.getplayer(Player) 296 self.stats.player_was_killed(player) 297 bs.timer(0.1, self._checkroundover) 298 299 elif isinstance(msg, bs.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 diesound = ( 318 self._dingsound if importance == 1 else self._dingsoundhigh 319 ) 320 diesound.play(volume=0.6) 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 bs.setmusic(None) 334 bs.pushcall(bs.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()
40@dataclass 41class SpawnInfo: 42 """Spawning info for a particular bot type.""" 43 44 spawnrate: float 45 increase: float 46 dincrease: float
Spawning info for a particular bot type.
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
Our team type for this game.
Inherited Members
- bascenev1._team.Team
- players
- id
- name
- color
- manual_init
- customdata
- on_expire
- sessionteam
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 def on_transition_in(self) -> None: 114 super().on_transition_in() 115 bs.timer(1.3, self._new_wave_sound.play) 116 self._scoreboard = Scoreboard( 117 label=bs.Lstr(resource='scoreText'), score_split=0.5 118 ) 119 120 def on_begin(self) -> None: 121 super().on_begin() 122 123 # Spit out a few powerups and start dropping more shortly. 124 self._drop_powerups(standard_points=True) 125 bs.timer(2.0, bs.WeakCall(self._start_powerup_drops)) 126 bs.timer(0.001, bs.WeakCall(self._start_bot_updates)) 127 self.setup_low_life_warning_sound() 128 self._update_scores() 129 self._tntspawner = TNTSpawner( 130 position=self._tntspawnpos, respawn_time=10.0 131 ) 132 133 def spawn_player(self, player: Player) -> bs.Actor: 134 pos = ( 135 self._spawn_center[0] + random.uniform(-1.5, 1.5), 136 self._spawn_center[1], 137 self._spawn_center[2] + random.uniform(-1.5, 1.5), 138 ) 139 return self.spawn_player_spaz(player, position=pos) 140 141 def _start_bot_updates(self) -> None: 142 self._bot_update_interval = 3.3 - 0.3 * (len(self.players)) 143 self._update_bots() 144 self._update_bots() 145 if len(self.players) > 2: 146 self._update_bots() 147 if len(self.players) > 3: 148 self._update_bots() 149 self._bot_update_timer = bs.Timer( 150 self._bot_update_interval, bs.WeakCall(self._update_bots) 151 ) 152 153 def _drop_powerup(self, index: int, poweruptype: str | None = None) -> None: 154 if poweruptype is None: 155 poweruptype = PowerupBoxFactory.get().get_random_powerup_type( 156 excludetypes=self._excludepowerups 157 ) 158 PowerupBox( 159 position=self.map.powerup_spawn_points[index], 160 poweruptype=poweruptype, 161 ).autoretain() 162 163 def _start_powerup_drops(self) -> None: 164 self._powerup_drop_timer = bs.Timer( 165 3.0, bs.WeakCall(self._drop_powerups), repeat=True 166 ) 167 168 def _drop_powerups( 169 self, standard_points: bool = False, force_first: str | None = None 170 ) -> None: 171 """Generic powerup drop.""" 172 from bascenev1lib.actor import powerupbox 173 174 if standard_points: 175 pts = self.map.powerup_spawn_points 176 for i in range(len(pts)): 177 bs.timer( 178 1.0 + i * 0.5, 179 bs.WeakCall( 180 self._drop_powerup, i, force_first if i == 0 else None 181 ), 182 ) 183 else: 184 drop_pt = ( 185 self._powerup_center[0] 186 + random.uniform( 187 -1.0 * self._powerup_spread[0], 188 1.0 * self._powerup_spread[0], 189 ), 190 self._powerup_center[1], 191 self._powerup_center[2] 192 + random.uniform( 193 -self._powerup_spread[1], self._powerup_spread[1] 194 ), 195 ) 196 197 # Drop one random one somewhere. 198 powerupbox.PowerupBox( 199 position=drop_pt, 200 poweruptype=PowerupBoxFactory.get().get_random_powerup_type( 201 excludetypes=self._excludepowerups 202 ), 203 ).autoretain() 204 205 def do_end(self, outcome: str) -> None: 206 """End the game.""" 207 if outcome == 'defeat': 208 self.fade_to_red() 209 self.end( 210 delay=2.0, 211 results={ 212 'outcome': outcome, 213 'score': self._score, 214 'playerinfos': self.initialplayerinfos, 215 }, 216 ) 217 218 def _update_bots(self) -> None: 219 assert self._bot_update_interval is not None 220 self._bot_update_interval = max(0.5, self._bot_update_interval * 0.98) 221 self._bot_update_timer = bs.Timer( 222 self._bot_update_interval, bs.WeakCall(self._update_bots) 223 ) 224 botspawnpts: list[Sequence[float]] = [ 225 [-5.0, 5.5, -4.14], 226 [0.0, 5.5, -4.14], 227 [5.0, 5.5, -4.14], 228 ] 229 dists = [0.0, 0.0, 0.0] 230 playerpts: list[Sequence[float]] = [] 231 for player in self.players: 232 try: 233 if player.is_alive(): 234 assert isinstance(player.actor, PlayerSpaz) 235 assert player.actor.node 236 playerpts.append(player.actor.node.position) 237 except Exception: 238 logging.exception('Error updating bots.') 239 for i in range(3): 240 for playerpt in playerpts: 241 dists[i] += abs(playerpt[0] - botspawnpts[i][0]) 242 dists[i] += random.random() * 5.0 # Minor random variation. 243 if dists[0] > dists[1] and dists[0] > dists[2]: 244 spawnpt = botspawnpts[0] 245 elif dists[1] > dists[2]: 246 spawnpt = botspawnpts[1] 247 else: 248 spawnpt = botspawnpts[2] 249 250 spawnpt = ( 251 spawnpt[0] + 3.0 * (random.random() - 0.5), 252 spawnpt[1], 253 2.0 * (random.random() - 0.5) + spawnpt[2], 254 ) 255 256 # Normalize our bot type total and find a random number within that. 257 total = 0.0 258 for spawninfo in self._bot_spawn_types.values(): 259 total += spawninfo.spawnrate 260 randval = random.random() * total 261 262 # Now go back through and see where this value falls. 263 total = 0 264 bottype: type[SpazBot] | None = None 265 for spawntype, spawninfo in self._bot_spawn_types.items(): 266 total += spawninfo.spawnrate 267 if randval <= total: 268 bottype = spawntype 269 break 270 spawn_time = 1.0 271 assert bottype is not None 272 self._bots.spawn_bot(bottype, pos=spawnpt, spawn_time=spawn_time) 273 274 # After every spawn we adjust our ratios slightly to get more 275 # difficult. 276 for spawninfo in self._bot_spawn_types.values(): 277 spawninfo.spawnrate += spawninfo.increase 278 spawninfo.increase += spawninfo.dincrease 279 280 def _update_scores(self) -> None: 281 score = self._score 282 283 # Achievements apply to the default preset only. 284 if self._preset == 'default': 285 if score >= 250: 286 self._award_achievement('Last Stand Master') 287 if score >= 500: 288 self._award_achievement('Last Stand Wizard') 289 if score >= 1000: 290 self._award_achievement('Last Stand God') 291 assert self._scoreboard is not None 292 self._scoreboard.set_team_value(self.teams[0], score, max_score=None) 293 294 def handlemessage(self, msg: Any) -> Any: 295 if isinstance(msg, bs.PlayerDiedMessage): 296 player = msg.getplayer(Player) 297 self.stats.player_was_killed(player) 298 bs.timer(0.1, self._checkroundover) 299 300 elif isinstance(msg, bs.PlayerScoredMessage): 301 self._score += msg.score 302 self._update_scores() 303 304 elif isinstance(msg, SpazBotDiedMessage): 305 pts, importance = msg.spazbot.get_death_points(msg.how) 306 target: Sequence[float] | None 307 if msg.killerplayer: 308 assert msg.spazbot.node 309 target = msg.spazbot.node.position 310 self.stats.player_scored( 311 msg.killerplayer, 312 pts, 313 target=target, 314 kill=True, 315 screenmessage=False, 316 importance=importance, 317 ) 318 diesound = ( 319 self._dingsound if importance == 1 else self._dingsoundhigh 320 ) 321 diesound.play(volume=0.6) 322 323 # Normally we pull scores from the score-set, but if there's no 324 # player lets be explicit. 325 else: 326 self._score += pts 327 self._update_scores() 328 else: 329 super().handlemessage(msg) 330 331 def end_game(self) -> None: 332 # Tell our bots to celebrate just to rub it in. 333 self._bots.final_celebrate() 334 bs.setmusic(None) 335 bs.pushcall(bs.WeakCall(self.do_end, 'defeat')) 336 337 def _checkroundover(self) -> None: 338 """End the round if conditions are met.""" 339 if not any(player.is_alive() for player in self.teams[0].players): 340 self.end_game()
Slow motion how-long-can-you-last game.
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 }
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.
113 def on_transition_in(self) -> None: 114 super().on_transition_in() 115 bs.timer(1.3, self._new_wave_sound.play) 116 self._scoreboard = Scoreboard( 117 label=bs.Lstr(resource='scoreText'), score_split=0.5 118 )
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.
120 def on_begin(self) -> None: 121 super().on_begin() 122 123 # Spit out a few powerups and start dropping more shortly. 124 self._drop_powerups(standard_points=True) 125 bs.timer(2.0, bs.WeakCall(self._start_powerup_drops)) 126 bs.timer(0.001, bs.WeakCall(self._start_bot_updates)) 127 self.setup_low_life_warning_sound() 128 self._update_scores() 129 self._tntspawner = TNTSpawner( 130 position=self._tntspawnpos, respawn_time=10.0 131 )
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.
133 def spawn_player(self, player: Player) -> bs.Actor: 134 pos = ( 135 self._spawn_center[0] + random.uniform(-1.5, 1.5), 136 self._spawn_center[1], 137 self._spawn_center[2] + random.uniform(-1.5, 1.5), 138 ) 139 return self.spawn_player_spaz(player, position=pos)
Spawn something for the provided bascenev1.Player.
The default implementation simply calls spawn_player_spaz().
205 def do_end(self, outcome: str) -> None: 206 """End the game.""" 207 if outcome == 'defeat': 208 self.fade_to_red() 209 self.end( 210 delay=2.0, 211 results={ 212 'outcome': outcome, 213 'score': self._score, 214 'playerinfos': self.initialplayerinfos, 215 }, 216 )
End the game.
294 def handlemessage(self, msg: Any) -> Any: 295 if isinstance(msg, bs.PlayerDiedMessage): 296 player = msg.getplayer(Player) 297 self.stats.player_was_killed(player) 298 bs.timer(0.1, self._checkroundover) 299 300 elif isinstance(msg, bs.PlayerScoredMessage): 301 self._score += msg.score 302 self._update_scores() 303 304 elif isinstance(msg, SpazBotDiedMessage): 305 pts, importance = msg.spazbot.get_death_points(msg.how) 306 target: Sequence[float] | None 307 if msg.killerplayer: 308 assert msg.spazbot.node 309 target = msg.spazbot.node.position 310 self.stats.player_scored( 311 msg.killerplayer, 312 pts, 313 target=target, 314 kill=True, 315 screenmessage=False, 316 importance=importance, 317 ) 318 diesound = ( 319 self._dingsound if importance == 1 else self._dingsoundhigh 320 ) 321 diesound.play(volume=0.6) 322 323 # Normally we pull scores from the score-set, but if there's no 324 # player lets be explicit. 325 else: 326 self._score += pts 327 self._update_scores() 328 else: 329 super().handlemessage(msg)
General message handling; can be passed any message object.
331 def end_game(self) -> None: 332 # Tell our bots to celebrate just to rub it in. 333 self._bots.final_celebrate() 334 bs.setmusic(None) 335 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.
Inherited Members
- bascenev1._coopgame.CoopGameActivity
- session
- supports_session_type
- get_score_type
- celebrate
- spawn_player_spaz
- fade_to_red
- setup_low_life_warning_sound
- bascenev1._gameactivity.GameActivity
- available_settings
- scoreconfig
- allow_pausing
- allow_kick_idle_players
- show_kill_points
- create_settings_ui
- getscoreconfig
- getname
- get_display_string
- get_team_display_string
- get_description
- get_description_display_string
- get_available_settings
- get_supported_maps
- get_settings_display_string
- initialplayerinfos
- 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
- bascenev1._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
- paused_text
- preloads
- lobby
- context
- 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
- bascenev1._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps