bastd.game.keepaway
Defines a keep-away game type.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Defines a keep-away game type.""" 4 5# ba_meta require api 7 6# (see https://ballistica.net/wiki/meta-tag-system) 7 8from __future__ import annotations 9 10from enum import Enum 11from typing import TYPE_CHECKING 12 13import ba 14from bastd.actor.playerspaz import PlayerSpaz 15from bastd.actor.scoreboard import Scoreboard 16from bastd.actor.flag import (Flag, FlagDroppedMessage, FlagDiedMessage, 17 FlagPickedUpMessage) 18 19if TYPE_CHECKING: 20 from typing import Any, Sequence 21 22 23class FlagState(Enum): 24 """States our single flag can be in.""" 25 NEW = 0 26 UNCONTESTED = 1 27 CONTESTED = 2 28 HELD = 3 29 30 31class Player(ba.Player['Team']): 32 """Our player type for this game.""" 33 34 35class Team(ba.Team[Player]): 36 """Our team type for this game.""" 37 38 def __init__(self, timeremaining: int) -> None: 39 self.timeremaining = timeremaining 40 self.holdingflag = False 41 42 43# ba_meta export game 44class KeepAwayGame(ba.TeamGameActivity[Player, Team]): 45 """Game where you try to keep the flag away from your enemies.""" 46 47 name = 'Keep Away' 48 description = 'Carry the flag for a set length of time.' 49 available_settings = [ 50 ba.IntSetting( 51 'Hold Time', 52 min_value=10, 53 default=30, 54 increment=10, 55 ), 56 ba.IntChoiceSetting( 57 'Time Limit', 58 choices=[ 59 ('None', 0), 60 ('1 Minute', 60), 61 ('2 Minutes', 120), 62 ('5 Minutes', 300), 63 ('10 Minutes', 600), 64 ('20 Minutes', 1200), 65 ], 66 default=0, 67 ), 68 ba.FloatChoiceSetting( 69 'Respawn Times', 70 choices=[ 71 ('Shorter', 0.25), 72 ('Short', 0.5), 73 ('Normal', 1.0), 74 ('Long', 2.0), 75 ('Longer', 4.0), 76 ], 77 default=1.0, 78 ), 79 ] 80 scoreconfig = ba.ScoreConfig(label='Time Held') 81 default_music = ba.MusicType.KEEP_AWAY 82 83 @classmethod 84 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 85 return (issubclass(sessiontype, ba.DualTeamSession) 86 or issubclass(sessiontype, ba.FreeForAllSession)) 87 88 @classmethod 89 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 90 return ba.getmaps('keep_away') 91 92 def __init__(self, settings: dict): 93 super().__init__(settings) 94 self._scoreboard = Scoreboard() 95 self._swipsound = ba.getsound('swip') 96 self._tick_sound = ba.getsound('tick') 97 self._countdownsounds = { 98 10: ba.getsound('announceTen'), 99 9: ba.getsound('announceNine'), 100 8: ba.getsound('announceEight'), 101 7: ba.getsound('announceSeven'), 102 6: ba.getsound('announceSix'), 103 5: ba.getsound('announceFive'), 104 4: ba.getsound('announceFour'), 105 3: ba.getsound('announceThree'), 106 2: ba.getsound('announceTwo'), 107 1: ba.getsound('announceOne') 108 } 109 self._flag_spawn_pos: Sequence[float] | None = None 110 self._update_timer: ba.Timer | None = None 111 self._holding_players: list[Player] = [] 112 self._flag_state: FlagState | None = None 113 self._flag_light: ba.Node | None = None 114 self._scoring_team: Team | None = None 115 self._flag: Flag | None = None 116 self._hold_time = int(settings['Hold Time']) 117 self._time_limit = float(settings['Time Limit']) 118 119 def get_instance_description(self) -> str | Sequence: 120 return 'Carry the flag for ${ARG1} seconds.', self._hold_time 121 122 def get_instance_description_short(self) -> str | Sequence: 123 return 'carry the flag for ${ARG1} seconds', self._hold_time 124 125 def create_team(self, sessionteam: ba.SessionTeam) -> Team: 126 return Team(timeremaining=self._hold_time) 127 128 def on_team_join(self, team: Team) -> None: 129 self._update_scoreboard() 130 131 def on_begin(self) -> None: 132 super().on_begin() 133 self.setup_standard_time_limit(self._time_limit) 134 self.setup_standard_powerup_drops() 135 self._flag_spawn_pos = self.map.get_flag_position(None) 136 self._spawn_flag() 137 self._update_timer = ba.Timer(1.0, call=self._tick, repeat=True) 138 self._update_flag_state() 139 Flag.project_stand(self._flag_spawn_pos) 140 141 def _tick(self) -> None: 142 self._update_flag_state() 143 144 # Award points to all living players holding the flag. 145 for player in self._holding_players: 146 if player: 147 assert self.stats 148 self.stats.player_scored(player, 149 3, 150 screenmessage=False, 151 display=False) 152 153 scoreteam = self._scoring_team 154 155 if scoreteam is not None: 156 157 if scoreteam.timeremaining > 0: 158 ba.playsound(self._tick_sound) 159 160 scoreteam.timeremaining = max(0, scoreteam.timeremaining - 1) 161 self._update_scoreboard() 162 if scoreteam.timeremaining > 0: 163 assert self._flag is not None 164 self._flag.set_score_text(str(scoreteam.timeremaining)) 165 166 # Announce numbers we have sounds for. 167 if scoreteam.timeremaining in self._countdownsounds: 168 ba.playsound(self._countdownsounds[scoreteam.timeremaining]) 169 170 # Winner. 171 if scoreteam.timeremaining <= 0: 172 self.end_game() 173 174 def end_game(self) -> None: 175 results = ba.GameResults() 176 for team in self.teams: 177 results.set_team_score(team, self._hold_time - team.timeremaining) 178 self.end(results=results, announce_delay=0) 179 180 def _update_flag_state(self) -> None: 181 for team in self.teams: 182 team.holdingflag = False 183 self._holding_players = [] 184 for player in self.players: 185 holdingflag = False 186 try: 187 assert isinstance(player.actor, (PlayerSpaz, type(None))) 188 if (player.actor and player.actor.node 189 and player.actor.node.hold_node): 190 holdingflag = ( 191 player.actor.node.hold_node.getnodetype() == 'flag') 192 except Exception: 193 ba.print_exception('Error checking hold flag.') 194 if holdingflag: 195 self._holding_players.append(player) 196 player.team.holdingflag = True 197 198 holdingteams = set(t for t in self.teams if t.holdingflag) 199 prevstate = self._flag_state 200 assert self._flag is not None 201 assert self._flag_light 202 assert self._flag.node 203 if len(holdingteams) > 1: 204 self._flag_state = FlagState.CONTESTED 205 self._scoring_team = None 206 self._flag_light.color = (0.6, 0.6, 0.1) 207 self._flag.node.color = (1.0, 1.0, 0.4) 208 elif len(holdingteams) == 1: 209 holdingteam = list(holdingteams)[0] 210 self._flag_state = FlagState.HELD 211 self._scoring_team = holdingteam 212 self._flag_light.color = ba.normalized_color(holdingteam.color) 213 self._flag.node.color = holdingteam.color 214 else: 215 self._flag_state = FlagState.UNCONTESTED 216 self._scoring_team = None 217 self._flag_light.color = (0.2, 0.2, 0.2) 218 self._flag.node.color = (1, 1, 1) 219 220 if self._flag_state != prevstate: 221 ba.playsound(self._swipsound) 222 223 def _spawn_flag(self) -> None: 224 ba.playsound(self._swipsound) 225 self._flash_flag_spawn() 226 assert self._flag_spawn_pos is not None 227 self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos) 228 self._flag_state = FlagState.NEW 229 self._flag_light = ba.newnode('light', 230 owner=self._flag.node, 231 attrs={ 232 'intensity': 0.2, 233 'radius': 0.3, 234 'color': (0.2, 0.2, 0.2) 235 }) 236 assert self._flag.node 237 self._flag.node.connectattr('position', self._flag_light, 'position') 238 self._update_flag_state() 239 240 def _flash_flag_spawn(self) -> None: 241 light = ba.newnode('light', 242 attrs={ 243 'position': self._flag_spawn_pos, 244 'color': (1, 1, 1), 245 'radius': 0.3, 246 'height_attenuated': False 247 }) 248 ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True) 249 ba.timer(1.0, light.delete) 250 251 def _update_scoreboard(self) -> None: 252 for team in self.teams: 253 self._scoreboard.set_team_value(team, 254 team.timeremaining, 255 self._hold_time, 256 countdown=True) 257 258 def handlemessage(self, msg: Any) -> Any: 259 if isinstance(msg, ba.PlayerDiedMessage): 260 # Augment standard behavior. 261 super().handlemessage(msg) 262 self.respawn_player(msg.getplayer(Player)) 263 elif isinstance(msg, FlagDiedMessage): 264 self._spawn_flag() 265 elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)): 266 self._update_flag_state() 267 else: 268 super().handlemessage(msg)
24class FlagState(Enum): 25 """States our single flag can be in.""" 26 NEW = 0 27 UNCONTESTED = 1 28 CONTESTED = 2 29 HELD = 3
States our single flag can be in.
Inherited Members
- enum.Enum
- name
- value
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
36class Team(ba.Team[Player]): 37 """Our team type for this game.""" 38 39 def __init__(self, timeremaining: int) -> None: 40 self.timeremaining = timeremaining 41 self.holdingflag = False
Our team type for this game.
Inherited Members
45class KeepAwayGame(ba.TeamGameActivity[Player, Team]): 46 """Game where you try to keep the flag away from your enemies.""" 47 48 name = 'Keep Away' 49 description = 'Carry the flag for a set length of time.' 50 available_settings = [ 51 ba.IntSetting( 52 'Hold Time', 53 min_value=10, 54 default=30, 55 increment=10, 56 ), 57 ba.IntChoiceSetting( 58 'Time Limit', 59 choices=[ 60 ('None', 0), 61 ('1 Minute', 60), 62 ('2 Minutes', 120), 63 ('5 Minutes', 300), 64 ('10 Minutes', 600), 65 ('20 Minutes', 1200), 66 ], 67 default=0, 68 ), 69 ba.FloatChoiceSetting( 70 'Respawn Times', 71 choices=[ 72 ('Shorter', 0.25), 73 ('Short', 0.5), 74 ('Normal', 1.0), 75 ('Long', 2.0), 76 ('Longer', 4.0), 77 ], 78 default=1.0, 79 ), 80 ] 81 scoreconfig = ba.ScoreConfig(label='Time Held') 82 default_music = ba.MusicType.KEEP_AWAY 83 84 @classmethod 85 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 86 return (issubclass(sessiontype, ba.DualTeamSession) 87 or issubclass(sessiontype, ba.FreeForAllSession)) 88 89 @classmethod 90 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 91 return ba.getmaps('keep_away') 92 93 def __init__(self, settings: dict): 94 super().__init__(settings) 95 self._scoreboard = Scoreboard() 96 self._swipsound = ba.getsound('swip') 97 self._tick_sound = ba.getsound('tick') 98 self._countdownsounds = { 99 10: ba.getsound('announceTen'), 100 9: ba.getsound('announceNine'), 101 8: ba.getsound('announceEight'), 102 7: ba.getsound('announceSeven'), 103 6: ba.getsound('announceSix'), 104 5: ba.getsound('announceFive'), 105 4: ba.getsound('announceFour'), 106 3: ba.getsound('announceThree'), 107 2: ba.getsound('announceTwo'), 108 1: ba.getsound('announceOne') 109 } 110 self._flag_spawn_pos: Sequence[float] | None = None 111 self._update_timer: ba.Timer | None = None 112 self._holding_players: list[Player] = [] 113 self._flag_state: FlagState | None = None 114 self._flag_light: ba.Node | None = None 115 self._scoring_team: Team | None = None 116 self._flag: Flag | None = None 117 self._hold_time = int(settings['Hold Time']) 118 self._time_limit = float(settings['Time Limit']) 119 120 def get_instance_description(self) -> str | Sequence: 121 return 'Carry the flag for ${ARG1} seconds.', self._hold_time 122 123 def get_instance_description_short(self) -> str | Sequence: 124 return 'carry the flag for ${ARG1} seconds', self._hold_time 125 126 def create_team(self, sessionteam: ba.SessionTeam) -> Team: 127 return Team(timeremaining=self._hold_time) 128 129 def on_team_join(self, team: Team) -> None: 130 self._update_scoreboard() 131 132 def on_begin(self) -> None: 133 super().on_begin() 134 self.setup_standard_time_limit(self._time_limit) 135 self.setup_standard_powerup_drops() 136 self._flag_spawn_pos = self.map.get_flag_position(None) 137 self._spawn_flag() 138 self._update_timer = ba.Timer(1.0, call=self._tick, repeat=True) 139 self._update_flag_state() 140 Flag.project_stand(self._flag_spawn_pos) 141 142 def _tick(self) -> None: 143 self._update_flag_state() 144 145 # Award points to all living players holding the flag. 146 for player in self._holding_players: 147 if player: 148 assert self.stats 149 self.stats.player_scored(player, 150 3, 151 screenmessage=False, 152 display=False) 153 154 scoreteam = self._scoring_team 155 156 if scoreteam is not None: 157 158 if scoreteam.timeremaining > 0: 159 ba.playsound(self._tick_sound) 160 161 scoreteam.timeremaining = max(0, scoreteam.timeremaining - 1) 162 self._update_scoreboard() 163 if scoreteam.timeremaining > 0: 164 assert self._flag is not None 165 self._flag.set_score_text(str(scoreteam.timeremaining)) 166 167 # Announce numbers we have sounds for. 168 if scoreteam.timeremaining in self._countdownsounds: 169 ba.playsound(self._countdownsounds[scoreteam.timeremaining]) 170 171 # Winner. 172 if scoreteam.timeremaining <= 0: 173 self.end_game() 174 175 def end_game(self) -> None: 176 results = ba.GameResults() 177 for team in self.teams: 178 results.set_team_score(team, self._hold_time - team.timeremaining) 179 self.end(results=results, announce_delay=0) 180 181 def _update_flag_state(self) -> None: 182 for team in self.teams: 183 team.holdingflag = False 184 self._holding_players = [] 185 for player in self.players: 186 holdingflag = False 187 try: 188 assert isinstance(player.actor, (PlayerSpaz, type(None))) 189 if (player.actor and player.actor.node 190 and player.actor.node.hold_node): 191 holdingflag = ( 192 player.actor.node.hold_node.getnodetype() == 'flag') 193 except Exception: 194 ba.print_exception('Error checking hold flag.') 195 if holdingflag: 196 self._holding_players.append(player) 197 player.team.holdingflag = True 198 199 holdingteams = set(t for t in self.teams if t.holdingflag) 200 prevstate = self._flag_state 201 assert self._flag is not None 202 assert self._flag_light 203 assert self._flag.node 204 if len(holdingteams) > 1: 205 self._flag_state = FlagState.CONTESTED 206 self._scoring_team = None 207 self._flag_light.color = (0.6, 0.6, 0.1) 208 self._flag.node.color = (1.0, 1.0, 0.4) 209 elif len(holdingteams) == 1: 210 holdingteam = list(holdingteams)[0] 211 self._flag_state = FlagState.HELD 212 self._scoring_team = holdingteam 213 self._flag_light.color = ba.normalized_color(holdingteam.color) 214 self._flag.node.color = holdingteam.color 215 else: 216 self._flag_state = FlagState.UNCONTESTED 217 self._scoring_team = None 218 self._flag_light.color = (0.2, 0.2, 0.2) 219 self._flag.node.color = (1, 1, 1) 220 221 if self._flag_state != prevstate: 222 ba.playsound(self._swipsound) 223 224 def _spawn_flag(self) -> None: 225 ba.playsound(self._swipsound) 226 self._flash_flag_spawn() 227 assert self._flag_spawn_pos is not None 228 self._flag = Flag(dropped_timeout=20, position=self._flag_spawn_pos) 229 self._flag_state = FlagState.NEW 230 self._flag_light = ba.newnode('light', 231 owner=self._flag.node, 232 attrs={ 233 'intensity': 0.2, 234 'radius': 0.3, 235 'color': (0.2, 0.2, 0.2) 236 }) 237 assert self._flag.node 238 self._flag.node.connectattr('position', self._flag_light, 'position') 239 self._update_flag_state() 240 241 def _flash_flag_spawn(self) -> None: 242 light = ba.newnode('light', 243 attrs={ 244 'position': self._flag_spawn_pos, 245 'color': (1, 1, 1), 246 'radius': 0.3, 247 'height_attenuated': False 248 }) 249 ba.animate(light, 'intensity', {0.0: 0, 0.25: 0.5, 0.5: 0}, loop=True) 250 ba.timer(1.0, light.delete) 251 252 def _update_scoreboard(self) -> None: 253 for team in self.teams: 254 self._scoreboard.set_team_value(team, 255 team.timeremaining, 256 self._hold_time, 257 countdown=True) 258 259 def handlemessage(self, msg: Any) -> Any: 260 if isinstance(msg, ba.PlayerDiedMessage): 261 # Augment standard behavior. 262 super().handlemessage(msg) 263 self.respawn_player(msg.getplayer(Player)) 264 elif isinstance(msg, FlagDiedMessage): 265 self._spawn_flag() 266 elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)): 267 self._update_flag_state() 268 else: 269 super().handlemessage(msg)
Game where you try to keep the flag away from your enemies.
93 def __init__(self, settings: dict): 94 super().__init__(settings) 95 self._scoreboard = Scoreboard() 96 self._swipsound = ba.getsound('swip') 97 self._tick_sound = ba.getsound('tick') 98 self._countdownsounds = { 99 10: ba.getsound('announceTen'), 100 9: ba.getsound('announceNine'), 101 8: ba.getsound('announceEight'), 102 7: ba.getsound('announceSeven'), 103 6: ba.getsound('announceSix'), 104 5: ba.getsound('announceFive'), 105 4: ba.getsound('announceFour'), 106 3: ba.getsound('announceThree'), 107 2: ba.getsound('announceTwo'), 108 1: ba.getsound('announceOne') 109 } 110 self._flag_spawn_pos: Sequence[float] | None = None 111 self._update_timer: ba.Timer | None = None 112 self._holding_players: list[Player] = [] 113 self._flag_state: FlagState | None = None 114 self._flag_light: ba.Node | None = None 115 self._scoring_team: Team | None = None 116 self._flag: Flag | None = None 117 self._hold_time = int(settings['Hold Time']) 118 self._time_limit = float(settings['Time Limit'])
Instantiate the Activity.
84 @classmethod 85 def supports_session_type(cls, sessiontype: type[ba.Session]) -> bool: 86 return (issubclass(sessiontype, ba.DualTeamSession) 87 or issubclass(sessiontype, ba.FreeForAllSession))
Class method override; returns True for ba.DualTeamSessions and ba.FreeForAllSessions; False otherwise.
89 @classmethod 90 def get_supported_maps(cls, sessiontype: type[ba.Session]) -> list[str]: 91 return ba.getmaps('keep_away')
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.
120 def get_instance_description(self) -> str | Sequence: 121 return 'Carry the flag for ${ARG1} seconds.', self._hold_time
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.
123 def get_instance_description_short(self) -> str | Sequence: 124 return 'carry the flag for ${ARG1} seconds', self._hold_time
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.
126 def create_team(self, sessionteam: ba.SessionTeam) -> Team: 127 return Team(timeremaining=self._hold_time)
Create the Team instance for this Activity.
Subclasses can override this if the activity's team class requires a custom constructor; otherwise it will be called with no args. Note that the team object should not be used at this point as it is not yet fully wired up; wait for on_team_join() for that.
Called when a new ba.Team joins the Activity.
(including the initial set of Teams)
132 def on_begin(self) -> None: 133 super().on_begin() 134 self.setup_standard_time_limit(self._time_limit) 135 self.setup_standard_powerup_drops() 136 self._flag_spawn_pos = self.map.get_flag_position(None) 137 self._spawn_flag() 138 self._update_timer = ba.Timer(1.0, call=self._tick, repeat=True) 139 self._update_flag_state() 140 Flag.project_stand(self._flag_spawn_pos)
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.
175 def end_game(self) -> None: 176 results = ba.GameResults() 177 for team in self.teams: 178 results.set_team_score(team, self._hold_time - team.timeremaining) 179 self.end(results=results, announce_delay=0)
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.
259 def handlemessage(self, msg: Any) -> Any: 260 if isinstance(msg, ba.PlayerDiedMessage): 261 # Augment standard behavior. 262 super().handlemessage(msg) 263 self.respawn_player(msg.getplayer(Player)) 264 elif isinstance(msg, FlagDiedMessage): 265 self._spawn_flag() 266 elif isinstance(msg, (FlagDroppedMessage, FlagPickedUpMessage)): 267 self._update_flag_state() 268 else: 269 super().handlemessage(msg)
General message handling; can be passed any message object.
Inherited Members
- ba._teamgame.TeamGameActivity
- on_transition_in
- spawn_player_spaz
- end
- ba._gameactivity.GameActivity
- tips
- 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_settings_display_string
- map
- get_instance_display_string
- get_instance_scoreboard_display_string
- on_continue
- is_waiting_for_continue
- continue_or_end_game
- on_player_join
- respawn_player
- spawn_player_if_exists
- spawn_player
- setup_standard_powerup_drops
- setup_standard_time_limit
- show_zoom_message
- ba._activity.Activity
- settings_raw
- teams
- players
- announce_player_deaths
- is_joining_activity
- use_fixed_vr_overlay
- slow_motion
- inherits_slow_motion
- inherits_music
- inherits_vr_camera_offset
- inherits_vr_overlay_center
- inherits_tint
- allow_mid_activity_joins
- transition_time
- can_show_ad_on_death
- globalsnode
- stats
- on_expire
- customdata
- expired
- playertype
- teamtype
- retain_actor
- add_actor_weak_ref
- session
- on_player_leave
- on_team_leave
- on_transition_out
- has_transitioned_in
- has_begun
- has_ended
- is_transitioning_out
- transition_out
- create_player
- ba._dependency.DependencyComponent
- dep_is_present
- get_dynamic_deps