bastd.game.elimination
Elimination mini-game.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Elimination mini-game.""" 4 5# ba_meta require api 7 6# (see https://ballistica.net/wiki/meta-tag-system) 7 8from __future__ import annotations 9 10from typing import TYPE_CHECKING 11 12import ba 13from bastd.actor.spazfactory import SpazFactory 14from bastd.actor.scoreboard import Scoreboard 15 16if TYPE_CHECKING: 17 from typing import Any, Sequence 18 19 20class Icon(ba.Actor): 21 """Creates in in-game icon on screen.""" 22 23 def __init__(self, 24 player: Player, 25 position: tuple[float, float], 26 scale: float, 27 show_lives: bool = True, 28 show_death: bool = True, 29 name_scale: float = 1.0, 30 name_maxwidth: float = 115.0, 31 flatness: float = 1.0, 32 shadow: float = 1.0): 33 super().__init__() 34 35 self._player = player 36 self._show_lives = show_lives 37 self._show_death = show_death 38 self._name_scale = name_scale 39 self._outline_tex = ba.gettexture('characterIconMask') 40 41 icon = player.get_icon() 42 self.node = ba.newnode('image', 43 delegate=self, 44 attrs={ 45 'texture': icon['texture'], 46 'tint_texture': icon['tint_texture'], 47 'tint_color': icon['tint_color'], 48 'vr_depth': 400, 49 'tint2_color': icon['tint2_color'], 50 'mask_texture': self._outline_tex, 51 'opacity': 1.0, 52 'absolute_scale': True, 53 'attach': 'bottomCenter' 54 }) 55 self._name_text = ba.newnode( 56 'text', 57 owner=self.node, 58 attrs={ 59 'text': ba.Lstr(value=player.getname()), 60 'color': ba.safecolor(player.team.color), 61 'h_align': 'center', 62 'v_align': 'center', 63 'vr_depth': 410, 64 'maxwidth': name_maxwidth, 65 'shadow': shadow, 66 'flatness': flatness, 67 'h_attach': 'center', 68 'v_attach': 'bottom' 69 }) 70 if self._show_lives: 71 self._lives_text = ba.newnode('text', 72 owner=self.node, 73 attrs={ 74 'text': 'x0', 75 'color': (1, 1, 0.5), 76 'h_align': 'left', 77 'vr_depth': 430, 78 'shadow': 1.0, 79 'flatness': 1.0, 80 'h_attach': 'center', 81 'v_attach': 'bottom' 82 }) 83 self.set_position_and_scale(position, scale) 84 85 def set_position_and_scale(self, position: tuple[float, float], 86 scale: float) -> None: 87 """(Re)position the icon.""" 88 assert self.node 89 self.node.position = position 90 self.node.scale = [70.0 * scale] 91 self._name_text.position = (position[0], position[1] + scale * 52.0) 92 self._name_text.scale = 1.0 * scale * self._name_scale 93 if self._show_lives: 94 self._lives_text.position = (position[0] + scale * 10.0, 95 position[1] - scale * 43.0) 96 self._lives_text.scale = 1.0 * scale 97 98 def update_for_lives(self) -> None: 99 """Update for the target player's current lives.""" 100 if self._player: 101 lives = self._player.lives 102 else: 103 lives = 0 104 if self._show_lives: 105 if lives > 0: 106 self._lives_text.text = 'x' + str(lives - 1) 107 else: 108 self._lives_text.text = '' 109 if lives == 0: 110 self._name_text.opacity = 0.2 111 assert self.node 112 self.node.color = (0.7, 0.3, 0.3) 113 self.node.opacity = 0.2 114 115 def handle_player_spawned(self) -> None: 116 """Our player spawned; hooray!""" 117 if not self.node: 118 return 119 self.node.opacity = 1.0 120 self.update_for_lives() 121 122 def handle_player_died(self) -> None: 123 """Well poo; our player died.""" 124 if not self.node: 125 return 126 if self._show_death: 127 ba.animate( 128 self.node, 'opacity', { 129 0.00: 1.0, 130 0.05: 0.0, 131 0.10: 1.0, 132 0.15: 0.0, 133 0.20: 1.0, 134 0.25: 0.0, 135 0.30: 1.0, 136 0.35: 0.0, 137 0.40: 1.0, 138 0.45: 0.0, 139 0.50: 1.0, 140 0.55: 0.2 141 }) 142 lives = self._player.lives 143 if lives == 0: 144 ba.timer(0.6, self.update_for_lives) 145 146 def handlemessage(self, msg: Any) -> Any: 147 if isinstance(msg, ba.DieMessage): 148 self.node.delete() 149 return None 150 return super().handlemessage(msg) 151 152 153class Player(ba.Player['Team']): 154 """Our player type for this game.""" 155 156 def __init__(self) -> None: 157 self.lives = 0 158 self.icons: list[Icon] = [] 159 160 161class Team(ba.Team[Player]): 162 """Our team type for this game.""" 163 164 def __init__(self) -> None: 165 self.survival_seconds: int | None = None 166 self.spawn_order: list[Player] = [] 167 168 169# ba_meta export game 170class EliminationGame(ba.TeamGameActivity[Player, Team]): 171 """Game type where last player(s) left alive win.""" 172 173 name = 'Elimination' 174 description = 'Last remaining alive wins.' 175 scoreconfig = ba.ScoreConfig(label='Survived', 176 scoretype=ba.ScoreType.SECONDS, 177 none_is_winner=True) 178 # Show messages when players die since it's meaningful here. 179 announce_player_deaths = True 180 181 allow_mid_activity_joins = False 182 183 @classmethod 184 def get_available_settings( 185 cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: 186 settings = [ 187 ba.IntSetting( 188 'Lives Per Player', 189 default=1, 190 min_value=1, 191 max_value=10, 192 increment=1, 193 ), 194 ba.IntChoiceSetting( 195 'Time Limit', 196 choices=[ 197 ('None', 0), 198 ('1 Minute', 60), 199 ('2 Minutes', 120), 200 ('5 Minutes', 300), 201 ('10 Minutes', 600), 202 ('20 Minutes', 1200), 203 ], 204 default=0, 205 ), 206 ba.FloatChoiceSetting( 207 'Respawn Times', 208 choices=[ 209 ('Shorter', 0.25), 210 ('Short', 0.5), 211 ('Normal', 1.0), 212 ('Long', 2.0), 213 ('Longer', 4.0), 214 ], 215 default=1.0, 216 ), 217 ba.BoolSetting('Epic Mode', default=False), 218 ] 219 if issubclass(sessiontype, ba.DualTeamSession): 220 settings.append(ba.BoolSetting('Solo Mode', default=False)) 221 settings.append( 222 ba.BoolSetting('Balance Total Lives', default=False)) 223 return settings 224 225 @classmethod 226 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 227 return (issubclass(sessiontype, ba.DualTeamSession) 228 or issubclass(sessiontype, ba.FreeForAllSession)) 229 230 @classmethod 231 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 232 return ba.getmaps('melee') 233 234 def __init__(self, settings: dict): 235 super().__init__(settings) 236 self._scoreboard = Scoreboard() 237 self._start_time: float | None = None 238 self._vs_text: ba.Actor | None = None 239 self._round_end_timer: ba.Timer | None = None 240 self._epic_mode = bool(settings['Epic Mode']) 241 self._lives_per_player = int(settings['Lives Per Player']) 242 self._time_limit = float(settings['Time Limit']) 243 self._balance_total_lives = bool( 244 settings.get('Balance Total Lives', False)) 245 self._solo_mode = bool(settings.get('Solo Mode', False)) 246 247 # Base class overrides: 248 self.slow_motion = self._epic_mode 249 self.default_music = (ba.MusicType.EPIC 250 if self._epic_mode else ba.MusicType.SURVIVAL) 251 252 def get_instance_description(self) -> str | Sequence: 253 return 'Last team standing wins.' if isinstance( 254 self.session, ba.DualTeamSession) else 'Last one standing wins.' 255 256 def get_instance_description_short(self) -> str | Sequence: 257 return 'last team standing wins' if isinstance( 258 self.session, ba.DualTeamSession) else 'last one standing wins' 259 260 def on_player_join(self, player: Player) -> None: 261 player.lives = self._lives_per_player 262 263 if self._solo_mode: 264 player.team.spawn_order.append(player) 265 self._update_solo_mode() 266 else: 267 # Create our icon and spawn. 268 player.icons = [Icon(player, position=(0, 50), scale=0.8)] 269 if player.lives > 0: 270 self.spawn_player(player) 271 272 # Don't waste time doing this until begin. 273 if self.has_begun(): 274 self._update_icons() 275 276 def on_begin(self) -> None: 277 super().on_begin() 278 self._start_time = ba.time() 279 self.setup_standard_time_limit(self._time_limit) 280 self.setup_standard_powerup_drops() 281 if self._solo_mode: 282 self._vs_text = ba.NodeActor( 283 ba.newnode('text', 284 attrs={ 285 'position': (0, 105), 286 'h_attach': 'center', 287 'h_align': 'center', 288 'maxwidth': 200, 289 'shadow': 0.5, 290 'vr_depth': 390, 291 'scale': 0.6, 292 'v_attach': 'bottom', 293 'color': (0.8, 0.8, 0.3, 1.0), 294 'text': ba.Lstr(resource='vsText') 295 })) 296 297 # If balance-team-lives is on, add lives to the smaller team until 298 # total lives match. 299 if (isinstance(self.session, ba.DualTeamSession) 300 and self._balance_total_lives and self.teams[0].players 301 and self.teams[1].players): 302 if self._get_total_team_lives( 303 self.teams[0]) < self._get_total_team_lives(self.teams[1]): 304 lesser_team = self.teams[0] 305 greater_team = self.teams[1] 306 else: 307 lesser_team = self.teams[1] 308 greater_team = self.teams[0] 309 add_index = 0 310 while (self._get_total_team_lives(lesser_team) < 311 self._get_total_team_lives(greater_team)): 312 lesser_team.players[add_index].lives += 1 313 add_index = (add_index + 1) % len(lesser_team.players) 314 315 self._update_icons() 316 317 # We could check game-over conditions at explicit trigger points, 318 # but lets just do the simple thing and poll it. 319 ba.timer(1.0, self._update, repeat=True) 320 321 def _update_solo_mode(self) -> None: 322 # For both teams, find the first player on the spawn order list with 323 # lives remaining and spawn them if they're not alive. 324 for team in self.teams: 325 # Prune dead players from the spawn order. 326 team.spawn_order = [p for p in team.spawn_order if p] 327 for player in team.spawn_order: 328 assert isinstance(player, Player) 329 if player.lives > 0: 330 if not player.is_alive(): 331 self.spawn_player(player) 332 break 333 334 def _update_icons(self) -> None: 335 # pylint: disable=too-many-branches 336 337 # In free-for-all mode, everyone is just lined up along the bottom. 338 if isinstance(self.session, ba.FreeForAllSession): 339 count = len(self.teams) 340 x_offs = 85 341 xval = x_offs * (count - 1) * -0.5 342 for team in self.teams: 343 if len(team.players) == 1: 344 player = team.players[0] 345 for icon in player.icons: 346 icon.set_position_and_scale((xval, 30), 0.7) 347 icon.update_for_lives() 348 xval += x_offs 349 350 # In teams mode we split up teams. 351 else: 352 if self._solo_mode: 353 # First off, clear out all icons. 354 for player in self.players: 355 player.icons = [] 356 357 # Now for each team, cycle through our available players 358 # adding icons. 359 for team in self.teams: 360 if team.id == 0: 361 xval = -60 362 x_offs = -78 363 else: 364 xval = 60 365 x_offs = 78 366 is_first = True 367 test_lives = 1 368 while True: 369 players_with_lives = [ 370 p for p in team.spawn_order 371 if p and p.lives >= test_lives 372 ] 373 if not players_with_lives: 374 break 375 for player in players_with_lives: 376 player.icons.append( 377 Icon(player, 378 position=(xval, (40 if is_first else 25)), 379 scale=1.0 if is_first else 0.5, 380 name_maxwidth=130 if is_first else 75, 381 name_scale=0.8 if is_first else 1.0, 382 flatness=0.0 if is_first else 1.0, 383 shadow=0.5 if is_first else 1.0, 384 show_death=is_first, 385 show_lives=False)) 386 xval += x_offs * (0.8 if is_first else 0.56) 387 is_first = False 388 test_lives += 1 389 # Non-solo mode. 390 else: 391 for team in self.teams: 392 if team.id == 0: 393 xval = -50 394 x_offs = -85 395 else: 396 xval = 50 397 x_offs = 85 398 for player in team.players: 399 for icon in player.icons: 400 icon.set_position_and_scale((xval, 30), 0.7) 401 icon.update_for_lives() 402 xval += x_offs 403 404 def _get_spawn_point(self, player: Player) -> ba.Vec3 | None: 405 del player # Unused. 406 407 # In solo-mode, if there's an existing live player on the map, spawn at 408 # whichever spot is farthest from them (keeps the action spread out). 409 if self._solo_mode: 410 living_player = None 411 living_player_pos = None 412 for team in self.teams: 413 for tplayer in team.players: 414 if tplayer.is_alive(): 415 assert tplayer.node 416 ppos = tplayer.node.position 417 living_player = tplayer 418 living_player_pos = ppos 419 break 420 if living_player: 421 assert living_player_pos is not None 422 player_pos = ba.Vec3(living_player_pos) 423 points: list[tuple[float, ba.Vec3]] = [] 424 for team in self.teams: 425 start_pos = ba.Vec3(self.map.get_start_position(team.id)) 426 points.append( 427 ((start_pos - player_pos).length(), start_pos)) 428 # Hmm.. we need to sorting vectors too? 429 points.sort(key=lambda x: x[0]) 430 return points[-1][1] 431 return None 432 433 def spawn_player(self, player: Player) -> ba.Actor: 434 actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) 435 if not self._solo_mode: 436 ba.timer(0.3, ba.Call(self._print_lives, player)) 437 438 # If we have any icons, update their state. 439 for icon in player.icons: 440 icon.handle_player_spawned() 441 return actor 442 443 def _print_lives(self, player: Player) -> None: 444 from bastd.actor import popuptext 445 446 # We get called in a timer so it's possible our player has left/etc. 447 if not player or not player.is_alive() or not player.node: 448 return 449 450 popuptext.PopupText('x' + str(player.lives - 1), 451 color=(1, 1, 0, 1), 452 offset=(0, -0.8, 0), 453 random_offset=0.0, 454 scale=1.8, 455 position=player.node.position).autoretain() 456 457 def on_player_leave(self, player: Player) -> None: 458 super().on_player_leave(player) 459 player.icons = [] 460 461 # Remove us from spawn-order. 462 if self._solo_mode: 463 if player in player.team.spawn_order: 464 player.team.spawn_order.remove(player) 465 466 # Update icons in a moment since our team will be gone from the 467 # list then. 468 ba.timer(0, self._update_icons) 469 470 # If the player to leave was the last in spawn order and had 471 # their final turn currently in-progress, mark the survival time 472 # for their team. 473 if self._get_total_team_lives(player.team) == 0: 474 assert self._start_time is not None 475 player.team.survival_seconds = int(ba.time() - self._start_time) 476 477 def _get_total_team_lives(self, team: Team) -> int: 478 return sum(player.lives for player in team.players) 479 480 def handlemessage(self, msg: Any) -> Any: 481 if isinstance(msg, ba.PlayerDiedMessage): 482 483 # Augment standard behavior. 484 super().handlemessage(msg) 485 player: Player = msg.getplayer(Player) 486 487 player.lives -= 1 488 if player.lives < 0: 489 ba.print_error( 490 "Got lives < 0 in Elim; this shouldn't happen. solo:" + 491 str(self._solo_mode)) 492 player.lives = 0 493 494 # If we have any icons, update their state. 495 for icon in player.icons: 496 icon.handle_player_died() 497 498 # Play big death sound on our last death 499 # or for every one in solo mode. 500 if self._solo_mode or player.lives == 0: 501 ba.playsound(SpazFactory.get().single_player_death_sound) 502 503 # If we hit zero lives, we're dead (and our team might be too). 504 if player.lives == 0: 505 # If the whole team is now dead, mark their survival time. 506 if self._get_total_team_lives(player.team) == 0: 507 assert self._start_time is not None 508 player.team.survival_seconds = int(ba.time() - 509 self._start_time) 510 else: 511 # Otherwise, in regular mode, respawn. 512 if not self._solo_mode: 513 self.respawn_player(player) 514 515 # In solo, put ourself at the back of the spawn order. 516 if self._solo_mode: 517 player.team.spawn_order.remove(player) 518 player.team.spawn_order.append(player) 519 520 def _update(self) -> None: 521 if self._solo_mode: 522 # For both teams, find the first player on the spawn order 523 # list with lives remaining and spawn them if they're not alive. 524 for team in self.teams: 525 # Prune dead players from the spawn order. 526 team.spawn_order = [p for p in team.spawn_order if p] 527 for player in team.spawn_order: 528 assert isinstance(player, Player) 529 if player.lives > 0: 530 if not player.is_alive(): 531 self.spawn_player(player) 532 self._update_icons() 533 break 534 535 # If we're down to 1 or fewer living teams, start a timer to end 536 # the game (allows the dust to settle and draws to occur if deaths 537 # are close enough). 538 if len(self._get_living_teams()) < 2: 539 self._round_end_timer = ba.Timer(0.5, self.end_game) 540 541 def _get_living_teams(self) -> list[Team]: 542 return [ 543 team for team in self.teams 544 if len(team.players) > 0 and any(player.lives > 0 545 for player in team.players) 546 ] 547 548 def end_game(self) -> None: 549 if self.has_ended(): 550 return 551 results = ba.GameResults() 552 self._vs_text = None # Kill our 'vs' if its there. 553 for team in self.teams: 554 results.set_team_score(team, team.survival_seconds) 555 self.end(results=results)
21class Icon(ba.Actor): 22 """Creates in in-game icon on screen.""" 23 24 def __init__(self, 25 player: Player, 26 position: tuple[float, float], 27 scale: float, 28 show_lives: bool = True, 29 show_death: bool = True, 30 name_scale: float = 1.0, 31 name_maxwidth: float = 115.0, 32 flatness: float = 1.0, 33 shadow: float = 1.0): 34 super().__init__() 35 36 self._player = player 37 self._show_lives = show_lives 38 self._show_death = show_death 39 self._name_scale = name_scale 40 self._outline_tex = ba.gettexture('characterIconMask') 41 42 icon = player.get_icon() 43 self.node = ba.newnode('image', 44 delegate=self, 45 attrs={ 46 'texture': icon['texture'], 47 'tint_texture': icon['tint_texture'], 48 'tint_color': icon['tint_color'], 49 'vr_depth': 400, 50 'tint2_color': icon['tint2_color'], 51 'mask_texture': self._outline_tex, 52 'opacity': 1.0, 53 'absolute_scale': True, 54 'attach': 'bottomCenter' 55 }) 56 self._name_text = ba.newnode( 57 'text', 58 owner=self.node, 59 attrs={ 60 'text': ba.Lstr(value=player.getname()), 61 'color': ba.safecolor(player.team.color), 62 'h_align': 'center', 63 'v_align': 'center', 64 'vr_depth': 410, 65 'maxwidth': name_maxwidth, 66 'shadow': shadow, 67 'flatness': flatness, 68 'h_attach': 'center', 69 'v_attach': 'bottom' 70 }) 71 if self._show_lives: 72 self._lives_text = ba.newnode('text', 73 owner=self.node, 74 attrs={ 75 'text': 'x0', 76 'color': (1, 1, 0.5), 77 'h_align': 'left', 78 'vr_depth': 430, 79 'shadow': 1.0, 80 'flatness': 1.0, 81 'h_attach': 'center', 82 'v_attach': 'bottom' 83 }) 84 self.set_position_and_scale(position, scale) 85 86 def set_position_and_scale(self, position: tuple[float, float], 87 scale: float) -> None: 88 """(Re)position the icon.""" 89 assert self.node 90 self.node.position = position 91 self.node.scale = [70.0 * scale] 92 self._name_text.position = (position[0], position[1] + scale * 52.0) 93 self._name_text.scale = 1.0 * scale * self._name_scale 94 if self._show_lives: 95 self._lives_text.position = (position[0] + scale * 10.0, 96 position[1] - scale * 43.0) 97 self._lives_text.scale = 1.0 * scale 98 99 def update_for_lives(self) -> None: 100 """Update for the target player's current lives.""" 101 if self._player: 102 lives = self._player.lives 103 else: 104 lives = 0 105 if self._show_lives: 106 if lives > 0: 107 self._lives_text.text = 'x' + str(lives - 1) 108 else: 109 self._lives_text.text = '' 110 if lives == 0: 111 self._name_text.opacity = 0.2 112 assert self.node 113 self.node.color = (0.7, 0.3, 0.3) 114 self.node.opacity = 0.2 115 116 def handle_player_spawned(self) -> None: 117 """Our player spawned; hooray!""" 118 if not self.node: 119 return 120 self.node.opacity = 1.0 121 self.update_for_lives() 122 123 def handle_player_died(self) -> None: 124 """Well poo; our player died.""" 125 if not self.node: 126 return 127 if self._show_death: 128 ba.animate( 129 self.node, 'opacity', { 130 0.00: 1.0, 131 0.05: 0.0, 132 0.10: 1.0, 133 0.15: 0.0, 134 0.20: 1.0, 135 0.25: 0.0, 136 0.30: 1.0, 137 0.35: 0.0, 138 0.40: 1.0, 139 0.45: 0.0, 140 0.50: 1.0, 141 0.55: 0.2 142 }) 143 lives = self._player.lives 144 if lives == 0: 145 ba.timer(0.6, self.update_for_lives) 146 147 def handlemessage(self, msg: Any) -> Any: 148 if isinstance(msg, ba.DieMessage): 149 self.node.delete() 150 return None 151 return super().handlemessage(msg)
Creates in in-game icon on screen.
24 def __init__(self, 25 player: Player, 26 position: tuple[float, float], 27 scale: float, 28 show_lives: bool = True, 29 show_death: bool = True, 30 name_scale: float = 1.0, 31 name_maxwidth: float = 115.0, 32 flatness: float = 1.0, 33 shadow: float = 1.0): 34 super().__init__() 35 36 self._player = player 37 self._show_lives = show_lives 38 self._show_death = show_death 39 self._name_scale = name_scale 40 self._outline_tex = ba.gettexture('characterIconMask') 41 42 icon = player.get_icon() 43 self.node = ba.newnode('image', 44 delegate=self, 45 attrs={ 46 'texture': icon['texture'], 47 'tint_texture': icon['tint_texture'], 48 'tint_color': icon['tint_color'], 49 'vr_depth': 400, 50 'tint2_color': icon['tint2_color'], 51 'mask_texture': self._outline_tex, 52 'opacity': 1.0, 53 'absolute_scale': True, 54 'attach': 'bottomCenter' 55 }) 56 self._name_text = ba.newnode( 57 'text', 58 owner=self.node, 59 attrs={ 60 'text': ba.Lstr(value=player.getname()), 61 'color': ba.safecolor(player.team.color), 62 'h_align': 'center', 63 'v_align': 'center', 64 'vr_depth': 410, 65 'maxwidth': name_maxwidth, 66 'shadow': shadow, 67 'flatness': flatness, 68 'h_attach': 'center', 69 'v_attach': 'bottom' 70 }) 71 if self._show_lives: 72 self._lives_text = ba.newnode('text', 73 owner=self.node, 74 attrs={ 75 'text': 'x0', 76 'color': (1, 1, 0.5), 77 'h_align': 'left', 78 'vr_depth': 430, 79 'shadow': 1.0, 80 'flatness': 1.0, 81 'h_attach': 'center', 82 'v_attach': 'bottom' 83 }) 84 self.set_position_and_scale(position, scale)
Instantiates an Actor in the current ba.Activity.
86 def set_position_and_scale(self, position: tuple[float, float], 87 scale: float) -> None: 88 """(Re)position the icon.""" 89 assert self.node 90 self.node.position = position 91 self.node.scale = [70.0 * scale] 92 self._name_text.position = (position[0], position[1] + scale * 52.0) 93 self._name_text.scale = 1.0 * scale * self._name_scale 94 if self._show_lives: 95 self._lives_text.position = (position[0] + scale * 10.0, 96 position[1] - scale * 43.0) 97 self._lives_text.scale = 1.0 * scale
(Re)position the icon.
99 def update_for_lives(self) -> None: 100 """Update for the target player's current lives.""" 101 if self._player: 102 lives = self._player.lives 103 else: 104 lives = 0 105 if self._show_lives: 106 if lives > 0: 107 self._lives_text.text = 'x' + str(lives - 1) 108 else: 109 self._lives_text.text = '' 110 if lives == 0: 111 self._name_text.opacity = 0.2 112 assert self.node 113 self.node.color = (0.7, 0.3, 0.3) 114 self.node.opacity = 0.2
Update for the target player's current lives.
116 def handle_player_spawned(self) -> None: 117 """Our player spawned; hooray!""" 118 if not self.node: 119 return 120 self.node.opacity = 1.0 121 self.update_for_lives()
Our player spawned; hooray!
123 def handle_player_died(self) -> None: 124 """Well poo; our player died.""" 125 if not self.node: 126 return 127 if self._show_death: 128 ba.animate( 129 self.node, 'opacity', { 130 0.00: 1.0, 131 0.05: 0.0, 132 0.10: 1.0, 133 0.15: 0.0, 134 0.20: 1.0, 135 0.25: 0.0, 136 0.30: 1.0, 137 0.35: 0.0, 138 0.40: 1.0, 139 0.45: 0.0, 140 0.50: 1.0, 141 0.55: 0.2 142 }) 143 lives = self._player.lives 144 if lives == 0: 145 ba.timer(0.6, self.update_for_lives)
Well poo; our player died.
147 def handlemessage(self, msg: Any) -> Any: 148 if isinstance(msg, ba.DieMessage): 149 self.node.delete() 150 return None 151 return super().handlemessage(msg)
General message handling; can be passed any message object.
Inherited Members
- ba._actor.Actor
- autoretain
- on_expire
- expired
- exists
- is_alive
- activity
- getactivity
154class Player(ba.Player['Team']): 155 """Our player type for this game.""" 156 157 def __init__(self) -> None: 158 self.lives = 0 159 self.icons: list[Icon] = []
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
162class Team(ba.Team[Player]): 163 """Our team type for this game.""" 164 165 def __init__(self) -> None: 166 self.survival_seconds: int | None = None 167 self.spawn_order: list[Player] = []
Our team type for this game.
Inherited Members
171class EliminationGame(ba.TeamGameActivity[Player, Team]): 172 """Game type where last player(s) left alive win.""" 173 174 name = 'Elimination' 175 description = 'Last remaining alive wins.' 176 scoreconfig = ba.ScoreConfig(label='Survived', 177 scoretype=ba.ScoreType.SECONDS, 178 none_is_winner=True) 179 # Show messages when players die since it's meaningful here. 180 announce_player_deaths = True 181 182 allow_mid_activity_joins = False 183 184 @classmethod 185 def get_available_settings( 186 cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: 187 settings = [ 188 ba.IntSetting( 189 'Lives Per Player', 190 default=1, 191 min_value=1, 192 max_value=10, 193 increment=1, 194 ), 195 ba.IntChoiceSetting( 196 'Time Limit', 197 choices=[ 198 ('None', 0), 199 ('1 Minute', 60), 200 ('2 Minutes', 120), 201 ('5 Minutes', 300), 202 ('10 Minutes', 600), 203 ('20 Minutes', 1200), 204 ], 205 default=0, 206 ), 207 ba.FloatChoiceSetting( 208 'Respawn Times', 209 choices=[ 210 ('Shorter', 0.25), 211 ('Short', 0.5), 212 ('Normal', 1.0), 213 ('Long', 2.0), 214 ('Longer', 4.0), 215 ], 216 default=1.0, 217 ), 218 ba.BoolSetting('Epic Mode', default=False), 219 ] 220 if issubclass(sessiontype, ba.DualTeamSession): 221 settings.append(ba.BoolSetting('Solo Mode', default=False)) 222 settings.append( 223 ba.BoolSetting('Balance Total Lives', default=False)) 224 return settings 225 226 @classmethod 227 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 228 return (issubclass(sessiontype, ba.DualTeamSession) 229 or issubclass(sessiontype, ba.FreeForAllSession)) 230 231 @classmethod 232 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 233 return ba.getmaps('melee') 234 235 def __init__(self, settings: dict): 236 super().__init__(settings) 237 self._scoreboard = Scoreboard() 238 self._start_time: float | None = None 239 self._vs_text: ba.Actor | None = None 240 self._round_end_timer: ba.Timer | None = None 241 self._epic_mode = bool(settings['Epic Mode']) 242 self._lives_per_player = int(settings['Lives Per Player']) 243 self._time_limit = float(settings['Time Limit']) 244 self._balance_total_lives = bool( 245 settings.get('Balance Total Lives', False)) 246 self._solo_mode = bool(settings.get('Solo Mode', False)) 247 248 # Base class overrides: 249 self.slow_motion = self._epic_mode 250 self.default_music = (ba.MusicType.EPIC 251 if self._epic_mode else ba.MusicType.SURVIVAL) 252 253 def get_instance_description(self) -> str | Sequence: 254 return 'Last team standing wins.' if isinstance( 255 self.session, ba.DualTeamSession) else 'Last one standing wins.' 256 257 def get_instance_description_short(self) -> str | Sequence: 258 return 'last team standing wins' if isinstance( 259 self.session, ba.DualTeamSession) else 'last one standing wins' 260 261 def on_player_join(self, player: Player) -> None: 262 player.lives = self._lives_per_player 263 264 if self._solo_mode: 265 player.team.spawn_order.append(player) 266 self._update_solo_mode() 267 else: 268 # Create our icon and spawn. 269 player.icons = [Icon(player, position=(0, 50), scale=0.8)] 270 if player.lives > 0: 271 self.spawn_player(player) 272 273 # Don't waste time doing this until begin. 274 if self.has_begun(): 275 self._update_icons() 276 277 def on_begin(self) -> None: 278 super().on_begin() 279 self._start_time = ba.time() 280 self.setup_standard_time_limit(self._time_limit) 281 self.setup_standard_powerup_drops() 282 if self._solo_mode: 283 self._vs_text = ba.NodeActor( 284 ba.newnode('text', 285 attrs={ 286 'position': (0, 105), 287 'h_attach': 'center', 288 'h_align': 'center', 289 'maxwidth': 200, 290 'shadow': 0.5, 291 'vr_depth': 390, 292 'scale': 0.6, 293 'v_attach': 'bottom', 294 'color': (0.8, 0.8, 0.3, 1.0), 295 'text': ba.Lstr(resource='vsText') 296 })) 297 298 # If balance-team-lives is on, add lives to the smaller team until 299 # total lives match. 300 if (isinstance(self.session, ba.DualTeamSession) 301 and self._balance_total_lives and self.teams[0].players 302 and self.teams[1].players): 303 if self._get_total_team_lives( 304 self.teams[0]) < self._get_total_team_lives(self.teams[1]): 305 lesser_team = self.teams[0] 306 greater_team = self.teams[1] 307 else: 308 lesser_team = self.teams[1] 309 greater_team = self.teams[0] 310 add_index = 0 311 while (self._get_total_team_lives(lesser_team) < 312 self._get_total_team_lives(greater_team)): 313 lesser_team.players[add_index].lives += 1 314 add_index = (add_index + 1) % len(lesser_team.players) 315 316 self._update_icons() 317 318 # We could check game-over conditions at explicit trigger points, 319 # but lets just do the simple thing and poll it. 320 ba.timer(1.0, self._update, repeat=True) 321 322 def _update_solo_mode(self) -> None: 323 # For both teams, find the first player on the spawn order list with 324 # lives remaining and spawn them if they're not alive. 325 for team in self.teams: 326 # Prune dead players from the spawn order. 327 team.spawn_order = [p for p in team.spawn_order if p] 328 for player in team.spawn_order: 329 assert isinstance(player, Player) 330 if player.lives > 0: 331 if not player.is_alive(): 332 self.spawn_player(player) 333 break 334 335 def _update_icons(self) -> None: 336 # pylint: disable=too-many-branches 337 338 # In free-for-all mode, everyone is just lined up along the bottom. 339 if isinstance(self.session, ba.FreeForAllSession): 340 count = len(self.teams) 341 x_offs = 85 342 xval = x_offs * (count - 1) * -0.5 343 for team in self.teams: 344 if len(team.players) == 1: 345 player = team.players[0] 346 for icon in player.icons: 347 icon.set_position_and_scale((xval, 30), 0.7) 348 icon.update_for_lives() 349 xval += x_offs 350 351 # In teams mode we split up teams. 352 else: 353 if self._solo_mode: 354 # First off, clear out all icons. 355 for player in self.players: 356 player.icons = [] 357 358 # Now for each team, cycle through our available players 359 # adding icons. 360 for team in self.teams: 361 if team.id == 0: 362 xval = -60 363 x_offs = -78 364 else: 365 xval = 60 366 x_offs = 78 367 is_first = True 368 test_lives = 1 369 while True: 370 players_with_lives = [ 371 p for p in team.spawn_order 372 if p and p.lives >= test_lives 373 ] 374 if not players_with_lives: 375 break 376 for player in players_with_lives: 377 player.icons.append( 378 Icon(player, 379 position=(xval, (40 if is_first else 25)), 380 scale=1.0 if is_first else 0.5, 381 name_maxwidth=130 if is_first else 75, 382 name_scale=0.8 if is_first else 1.0, 383 flatness=0.0 if is_first else 1.0, 384 shadow=0.5 if is_first else 1.0, 385 show_death=is_first, 386 show_lives=False)) 387 xval += x_offs * (0.8 if is_first else 0.56) 388 is_first = False 389 test_lives += 1 390 # Non-solo mode. 391 else: 392 for team in self.teams: 393 if team.id == 0: 394 xval = -50 395 x_offs = -85 396 else: 397 xval = 50 398 x_offs = 85 399 for player in team.players: 400 for icon in player.icons: 401 icon.set_position_and_scale((xval, 30), 0.7) 402 icon.update_for_lives() 403 xval += x_offs 404 405 def _get_spawn_point(self, player: Player) -> ba.Vec3 | None: 406 del player # Unused. 407 408 # In solo-mode, if there's an existing live player on the map, spawn at 409 # whichever spot is farthest from them (keeps the action spread out). 410 if self._solo_mode: 411 living_player = None 412 living_player_pos = None 413 for team in self.teams: 414 for tplayer in team.players: 415 if tplayer.is_alive(): 416 assert tplayer.node 417 ppos = tplayer.node.position 418 living_player = tplayer 419 living_player_pos = ppos 420 break 421 if living_player: 422 assert living_player_pos is not None 423 player_pos = ba.Vec3(living_player_pos) 424 points: list[tuple[float, ba.Vec3]] = [] 425 for team in self.teams: 426 start_pos = ba.Vec3(self.map.get_start_position(team.id)) 427 points.append( 428 ((start_pos - player_pos).length(), start_pos)) 429 # Hmm.. we need to sorting vectors too? 430 points.sort(key=lambda x: x[0]) 431 return points[-1][1] 432 return None 433 434 def spawn_player(self, player: Player) -> ba.Actor: 435 actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) 436 if not self._solo_mode: 437 ba.timer(0.3, ba.Call(self._print_lives, player)) 438 439 # If we have any icons, update their state. 440 for icon in player.icons: 441 icon.handle_player_spawned() 442 return actor 443 444 def _print_lives(self, player: Player) -> None: 445 from bastd.actor import popuptext 446 447 # We get called in a timer so it's possible our player has left/etc. 448 if not player or not player.is_alive() or not player.node: 449 return 450 451 popuptext.PopupText('x' + str(player.lives - 1), 452 color=(1, 1, 0, 1), 453 offset=(0, -0.8, 0), 454 random_offset=0.0, 455 scale=1.8, 456 position=player.node.position).autoretain() 457 458 def on_player_leave(self, player: Player) -> None: 459 super().on_player_leave(player) 460 player.icons = [] 461 462 # Remove us from spawn-order. 463 if self._solo_mode: 464 if player in player.team.spawn_order: 465 player.team.spawn_order.remove(player) 466 467 # Update icons in a moment since our team will be gone from the 468 # list then. 469 ba.timer(0, self._update_icons) 470 471 # If the player to leave was the last in spawn order and had 472 # their final turn currently in-progress, mark the survival time 473 # for their team. 474 if self._get_total_team_lives(player.team) == 0: 475 assert self._start_time is not None 476 player.team.survival_seconds = int(ba.time() - self._start_time) 477 478 def _get_total_team_lives(self, team: Team) -> int: 479 return sum(player.lives for player in team.players) 480 481 def handlemessage(self, msg: Any) -> Any: 482 if isinstance(msg, ba.PlayerDiedMessage): 483 484 # Augment standard behavior. 485 super().handlemessage(msg) 486 player: Player = msg.getplayer(Player) 487 488 player.lives -= 1 489 if player.lives < 0: 490 ba.print_error( 491 "Got lives < 0 in Elim; this shouldn't happen. solo:" + 492 str(self._solo_mode)) 493 player.lives = 0 494 495 # If we have any icons, update their state. 496 for icon in player.icons: 497 icon.handle_player_died() 498 499 # Play big death sound on our last death 500 # or for every one in solo mode. 501 if self._solo_mode or player.lives == 0: 502 ba.playsound(SpazFactory.get().single_player_death_sound) 503 504 # If we hit zero lives, we're dead (and our team might be too). 505 if player.lives == 0: 506 # If the whole team is now dead, mark their survival time. 507 if self._get_total_team_lives(player.team) == 0: 508 assert self._start_time is not None 509 player.team.survival_seconds = int(ba.time() - 510 self._start_time) 511 else: 512 # Otherwise, in regular mode, respawn. 513 if not self._solo_mode: 514 self.respawn_player(player) 515 516 # In solo, put ourself at the back of the spawn order. 517 if self._solo_mode: 518 player.team.spawn_order.remove(player) 519 player.team.spawn_order.append(player) 520 521 def _update(self) -> None: 522 if self._solo_mode: 523 # For both teams, find the first player on the spawn order 524 # list with lives remaining and spawn them if they're not alive. 525 for team in self.teams: 526 # Prune dead players from the spawn order. 527 team.spawn_order = [p for p in team.spawn_order if p] 528 for player in team.spawn_order: 529 assert isinstance(player, Player) 530 if player.lives > 0: 531 if not player.is_alive(): 532 self.spawn_player(player) 533 self._update_icons() 534 break 535 536 # If we're down to 1 or fewer living teams, start a timer to end 537 # the game (allows the dust to settle and draws to occur if deaths 538 # are close enough). 539 if len(self._get_living_teams()) < 2: 540 self._round_end_timer = ba.Timer(0.5, self.end_game) 541 542 def _get_living_teams(self) -> list[Team]: 543 return [ 544 team for team in self.teams 545 if len(team.players) > 0 and any(player.lives > 0 546 for player in team.players) 547 ] 548 549 def end_game(self) -> None: 550 if self.has_ended(): 551 return 552 results = ba.GameResults() 553 self._vs_text = None # Kill our 'vs' if its there. 554 for team in self.teams: 555 results.set_team_score(team, team.survival_seconds) 556 self.end(results=results)
Game type where last player(s) left alive win.
235 def __init__(self, settings: dict): 236 super().__init__(settings) 237 self._scoreboard = Scoreboard() 238 self._start_time: float | None = None 239 self._vs_text: ba.Actor | None = None 240 self._round_end_timer: ba.Timer | None = None 241 self._epic_mode = bool(settings['Epic Mode']) 242 self._lives_per_player = int(settings['Lives Per Player']) 243 self._time_limit = float(settings['Time Limit']) 244 self._balance_total_lives = bool( 245 settings.get('Balance Total Lives', False)) 246 self._solo_mode = bool(settings.get('Solo Mode', False)) 247 248 # Base class overrides: 249 self.slow_motion = self._epic_mode 250 self.default_music = (ba.MusicType.EPIC 251 if self._epic_mode else ba.MusicType.SURVIVAL)
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.
Whether players should be allowed to join in the middle of this activity. Note that Sessions may not allow mid-activity-joins even if the activity says its ok.
184 @classmethod 185 def get_available_settings( 186 cls, sessiontype: type[ba.Session]) -> list[ba.Setting]: 187 settings = [ 188 ba.IntSetting( 189 'Lives Per Player', 190 default=1, 191 min_value=1, 192 max_value=10, 193 increment=1, 194 ), 195 ba.IntChoiceSetting( 196 'Time Limit', 197 choices=[ 198 ('None', 0), 199 ('1 Minute', 60), 200 ('2 Minutes', 120), 201 ('5 Minutes', 300), 202 ('10 Minutes', 600), 203 ('20 Minutes', 1200), 204 ], 205 default=0, 206 ), 207 ba.FloatChoiceSetting( 208 'Respawn Times', 209 choices=[ 210 ('Shorter', 0.25), 211 ('Short', 0.5), 212 ('Normal', 1.0), 213 ('Long', 2.0), 214 ('Longer', 4.0), 215 ], 216 default=1.0, 217 ), 218 ba.BoolSetting('Epic Mode', default=False), 219 ] 220 if issubclass(sessiontype, ba.DualTeamSession): 221 settings.append(ba.BoolSetting('Solo Mode', default=False)) 222 settings.append( 223 ba.BoolSetting('Balance Total Lives', default=False)) 224 return settings
Return a list of settings relevant to this game type when running under the provided session type.
226 @classmethod 227 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 228 return (issubclass(sessiontype, ba.DualTeamSession) 229 or issubclass(sessiontype, ba.FreeForAllSession))
Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.
231 @classmethod 232 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 233 return ba.getmaps('melee')
Called by the default ba.GameActivity.create_settings_ui() implementation; should return a list of map names valid for this game-type for the given ba.Session type.
253 def get_instance_description(self) -> str | Sequence: 254 return 'Last team standing wins.' if isinstance( 255 self.session, ba.DualTeamSession) else 'Last one standing wins.'
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.
257 def get_instance_description_short(self) -> str | Sequence: 258 return 'last team standing wins' if isinstance( 259 self.session, ba.DualTeamSession) else 'last one standing wins'
Return a short description for this game instance in English.
This description is used above the game scoreboard in the corner of the screen, so it should be as concise as possible. It should be lowercase and should not contain periods or other punctuation.
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.
261 def on_player_join(self, player: Player) -> None: 262 player.lives = self._lives_per_player 263 264 if self._solo_mode: 265 player.team.spawn_order.append(player) 266 self._update_solo_mode() 267 else: 268 # Create our icon and spawn. 269 player.icons = [Icon(player, position=(0, 50), scale=0.8)] 270 if player.lives > 0: 271 self.spawn_player(player) 272 273 # Don't waste time doing this until begin. 274 if self.has_begun(): 275 self._update_icons()
Called when a new ba.Player has joined the Activity.
(including the initial set of Players)
277 def on_begin(self) -> None: 278 super().on_begin() 279 self._start_time = ba.time() 280 self.setup_standard_time_limit(self._time_limit) 281 self.setup_standard_powerup_drops() 282 if self._solo_mode: 283 self._vs_text = ba.NodeActor( 284 ba.newnode('text', 285 attrs={ 286 'position': (0, 105), 287 'h_attach': 'center', 288 'h_align': 'center', 289 'maxwidth': 200, 290 'shadow': 0.5, 291 'vr_depth': 390, 292 'scale': 0.6, 293 'v_attach': 'bottom', 294 'color': (0.8, 0.8, 0.3, 1.0), 295 'text': ba.Lstr(resource='vsText') 296 })) 297 298 # If balance-team-lives is on, add lives to the smaller team until 299 # total lives match. 300 if (isinstance(self.session, ba.DualTeamSession) 301 and self._balance_total_lives and self.teams[0].players 302 and self.teams[1].players): 303 if self._get_total_team_lives( 304 self.teams[0]) < self._get_total_team_lives(self.teams[1]): 305 lesser_team = self.teams[0] 306 greater_team = self.teams[1] 307 else: 308 lesser_team = self.teams[1] 309 greater_team = self.teams[0] 310 add_index = 0 311 while (self._get_total_team_lives(lesser_team) < 312 self._get_total_team_lives(greater_team)): 313 lesser_team.players[add_index].lives += 1 314 add_index = (add_index + 1) % len(lesser_team.players) 315 316 self._update_icons() 317 318 # We could check game-over conditions at explicit trigger points, 319 # but lets just do the simple thing and poll it. 320 ba.timer(1.0, self._update, repeat=True)
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.
434 def spawn_player(self, player: Player) -> ba.Actor: 435 actor = self.spawn_player_spaz(player, self._get_spawn_point(player)) 436 if not self._solo_mode: 437 ba.timer(0.3, ba.Call(self._print_lives, player)) 438 439 # If we have any icons, update their state. 440 for icon in player.icons: 441 icon.handle_player_spawned() 442 return actor
Spawn something for the provided ba.Player.
The default implementation simply calls spawn_player_spaz().
458 def on_player_leave(self, player: Player) -> None: 459 super().on_player_leave(player) 460 player.icons = [] 461 462 # Remove us from spawn-order. 463 if self._solo_mode: 464 if player in player.team.spawn_order: 465 player.team.spawn_order.remove(player) 466 467 # Update icons in a moment since our team will be gone from the 468 # list then. 469 ba.timer(0, self._update_icons) 470 471 # If the player to leave was the last in spawn order and had 472 # their final turn currently in-progress, mark the survival time 473 # for their team. 474 if self._get_total_team_lives(player.team) == 0: 475 assert self._start_time is not None 476 player.team.survival_seconds = int(ba.time() - self._start_time)
Called when a ba.Player is leaving the Activity.
481 def handlemessage(self, msg: Any) -> Any: 482 if isinstance(msg, ba.PlayerDiedMessage): 483 484 # Augment standard behavior. 485 super().handlemessage(msg) 486 player: Player = msg.getplayer(Player) 487 488 player.lives -= 1 489 if player.lives < 0: 490 ba.print_error( 491 "Got lives < 0 in Elim; this shouldn't happen. solo:" + 492 str(self._solo_mode)) 493 player.lives = 0 494 495 # If we have any icons, update their state. 496 for icon in player.icons: 497 icon.handle_player_died() 498 499 # Play big death sound on our last death 500 # or for every one in solo mode. 501 if self._solo_mode or player.lives == 0: 502 ba.playsound(SpazFactory.get().single_player_death_sound) 503 504 # If we hit zero lives, we're dead (and our team might be too). 505 if player.lives == 0: 506 # If the whole team is now dead, mark their survival time. 507 if self._get_total_team_lives(player.team) == 0: 508 assert self._start_time is not None 509 player.team.survival_seconds = int(ba.time() - 510 self._start_time) 511 else: 512 # Otherwise, in regular mode, respawn. 513 if not self._solo_mode: 514 self.respawn_player(player) 515 516 # In solo, put ourself at the back of the spawn order. 517 if self._solo_mode: 518 player.team.spawn_order.remove(player) 519 player.team.spawn_order.append(player)
General message handling; can be passed any message object.
549 def end_game(self) -> None: 550 if self.has_ended(): 551 return 552 results = ba.GameResults() 553 self._vs_text = None # Kill our 'vs' if its there. 554 for team in self.teams: 555 results.set_team_score(team, team.survival_seconds) 556 self.end(results=results)
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._activity.Activity
- slow_motion
- 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
- transition_time
- can_show_ad_on_death
- globalsnode
- stats
- on_expire
- customdata
- expired
- playertype
- teamtype
- retain_actor
- add_actor_weak_ref
- session
- 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._gameactivity.GameActivity
- default_music
- tips
- available_settings
- 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_settings_display_string
- map
- get_instance_display_string
- get_instance_scoreboard_display_string
- on_continue
- is_waiting_for_continue
- continue_or_end_game
- respawn_player
- spawn_player_if_exists
- setup_standard_powerup_drops
- setup_standard_time_limit
- show_zoom_message
- ba._teamgame.TeamGameActivity
- on_transition_in
- spawn_player_spaz
- end
- ba._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps