From c98e645f74c0cdbb223d7a80bd0402206d5b5859 Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Fri, 29 May 2020 18:22:04 -0700 Subject: [PATCH] A few game logic bug fixes --- .efrocachemap | 24 ++++---- assets/src/ba_data/python/_ba.py | 2 +- assets/src/ba_data/python/ba/__init__.py | 3 +- assets/src/ba_data/python/ba/_activity.py | 58 ++++++++++--------- assets/src/ba_data/python/ba/_gameactivity.py | 27 ++++----- assets/src/ba_data/python/ba/_player.py | 13 ++--- assets/src/ba_data/python/ba/_session.py | 44 ++++++++++---- assets/src/ba_data/python/bastd/actor/bomb.py | 42 +++++++------- .../ba_data/python/bastd/actor/playerspaz.py | 19 +++--- .../ba_data/python/bastd/actor/scoreboard.py | 4 +- assets/src/ba_data/python/bastd/mainmenu.py | 4 +- docs/ba_module.md | 58 ++++--------------- 12 files changed, 142 insertions(+), 156 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 46f06447..ed3f1d9d 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4132,16 +4132,16 @@ "assets/build/windows/x64/python.exe": "https://files.ballistica.net/cache/ba1/25/a7/dc87c1be41605eb6fefd0145144c", "assets/build/windows/x64/python37.dll": "https://files.ballistica.net/cache/ba1/b9/e4/d912f56e42e9991bcbb4c804cfcb", "assets/build/windows/x64/pythonw.exe": "https://files.ballistica.net/cache/ba1/6c/bb/b6f52c306aa4e88061510e96cefe", - "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/8d/c2/9fd3ab19a28b05160f818f787115", - "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/67/25/34c42457f20c9d87d538f4f69320", - "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d9/3d/39326060f1a1df1b18070d75ade8", - "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/9f/b7/d63ab7e13f1b9d931b83ef652262", - "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/39/45/f4d0bbe6dfa0286b1faaad5d4c0e", - "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a8/2b/251dcadd37ae0d2c08a0f3bb683d", - "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3a/1a/b80f37d9802d40e625b2df3696b1", - "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/46/fd/6400f9eba88a419487f5d3732e54", - "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/7d/6a/3dc3c77c340471c0a7b79bf077a4", - "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/97/5d/824c5c71f61d871c904c61fabc56", - "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/91/92/1f309a3edf4b4aa595ea83472e7a", - "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d5/3c/fefdeed4e8048332724167fe1442" + "build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f0/6c/17cfdbdfeac96070b5134251a451", + "build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/52/25/297b9a00058301b85ddb02f6ee09", + "build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/94/a8/e1477ff611c1e1a5a625bffff60c", + "build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c6/d8/20917734baac0fdec68f72c41ed7", + "build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/2b/0f91bf6faf8efd08890a687d48d7", + "build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1b/cc/88ba8eb1dbd3cc30181d75b4d16e", + "build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/9e/df/8560a3f6a5394d6e1e4eff3c4225", + "build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/73/e2/8d816aa46639e1200a63c387ad5a", + "build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/85/9a/d64a09c882fe8ddd061d37547ea3", + "build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/d9/f8/ede540a4757ea9c246b4818abc93", + "build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/33/00/49adaf80c83df4d6d806691676c0", + "build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/5e/e3/4a2fd651c619449b0ac47580b442" } \ No newline at end of file diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py index 942dc9d2..4afc871e 100644 --- a/assets/src/ba_data/python/_ba.py +++ b/assets/src/ba_data/python/_ba.py @@ -34,7 +34,7 @@ NOTE: This file was autogenerated by gendummymodule; do not edit by hand. """ # (hash we can use to see if this file is out of date) -# SOURCES_HASH=214732868903831008614942145147141302125 +# SOURCES_HASH=34814525496434113316994893689868607574 # I'm sorry Pylint. I know this file saddens you. Be strong. # pylint: disable=useless-suppression diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 3576a2cd..3a2d949a 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -39,8 +39,7 @@ from _ba import (CollideModel, Context, ContextCall, Data, InputDevice, open_url, widget) from ba._activity import Activity from ba._actor import Actor -from ba._player import (PlayerInfo, Player, playercast, playercast_o, - StandLocation) +from ba._player import PlayerInfo, Player, StandLocation from ba._nodeactor import NodeActor from ba._app import App from ba._coopgame import CoopGameActivity diff --git a/assets/src/ba_data/python/ba/_activity.py b/assets/src/ba_data/python/ba/_activity.py index e46bb06d..fb02787c 100644 --- a/assets/src/ba_data/python/ba/_activity.py +++ b/assets/src/ba_data/python/ba/_activity.py @@ -30,7 +30,7 @@ from ba._error import (print_exception, SessionTeamNotFoundError, NodeNotFoundError) from ba._dependency import DependencyComponent from ba._general import Call, verify_object_death -from ba._messages import UNHANDLED +from ba._messages import UNHANDLED, DieMessage, DeathType import _ba if TYPE_CHECKING: @@ -224,7 +224,7 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): with _ba.Context('empty'): self._expire() - # Inform our owner that we're officially kicking the bucket. + # Inform our owner that we officially kicked the bucket. if self._transitioning_out: session = self._session() if session is not None: @@ -287,19 +287,19 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): """(internal)""" self._has_ended = val - def set_immediate_end(self, results: ba.TeamGameResults, delay: float, - force: bool) -> None: - """Set the activity to die immediately after beginning. + # def set_immediate_end(self, results: ba.TeamGameResults, delay: float, + # force: bool) -> None: + # """Set the activity to die immediately after beginning. - (internal) - """ - if self.has_begun(): - raise RuntimeError('This should only be called for Activities' - 'that have not yet begun.') - if not self._should_end_immediately or force: - self._should_end_immediately = True - self._should_end_immediately_results = results - self._should_end_immediately_delay = delay + # (internal) + # """ + # if self.has_begun(): + # raise RuntimeError('This should only be called for Activities' + # 'that have not yet begun.') + # if not self._should_end_immediately or force: + # self._should_end_immediately = True + # self._should_end_immediately_results = results + # self._should_end_immediately_delay = delay def destroy(self) -> None: """Begin the process of tearing down the activity. @@ -522,20 +522,14 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): for player in session.players: self.add_player(player) - # And finally tell the game to start. - with _ba.Context(self): - self._has_begun = True - self.on_begin() + self._has_begun = True - # If the whole session wants to die and was waiting on us, - # can kick off that process now. - if session.wants_to_end: - session.launch_end_session_activity() - else: - # Otherwise, if we've already been told to die, do so now. - if self._should_end_immediately: - self.end(self._should_end_immediately_results, - self._should_end_immediately_delay) + # Let the activity do its thing. + with _ba.Context(self): + # Note: do we want to catch errors here? + # Currently I believe we wind up canceling the + # activity launch; just wanna be sure that is intentional. + self.on_begin() def end(self, results: Any = None, @@ -636,6 +630,16 @@ class Activity(DependencyComponent, Generic[PlayerType, TeamType]): except Exception: print_exception(f'Error in on_player_leave for {self}') try: + # If they have an actor, kill it. + if player.actor: + player.actor.handlemessage( + DieMessage(how=DeathType.LEFT_GAME)) + player.actor = None + except Exception: + print_exception( + f'Error killing player actor on leave for {self}') + try: + player.reset() sessionplayer.reset() sessionplayer.set_node(None) sessionplayer.set_activity(None) diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index aa74790a..a46c5367 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -29,7 +29,7 @@ from typing import TYPE_CHECKING, TypeVar from ba._activity import Activity from ba._score import ScoreInfo from ba._lang import Lstr -from ba._messages import PlayerDiedMessage, StandMessage, DieMessage, DeathType +from ba._messages import PlayerDiedMessage, StandMessage from ba._error import NotFoundError, print_error, print_exception from ba._general import Call, WeakCall from ba._player import PlayerInfo @@ -549,18 +549,19 @@ class GameActivity(Activity[PlayerType, TeamType]): # By default, just spawn a dude. self.spawn_player(player) - def on_player_leave(self, player: PlayerType) -> None: - super().on_player_leave(player) + # def on_player_leave(self, player: PlayerType) -> None: + # super().on_player_leave(player) - # If the player has an actor, send it a deferred die message. - # This way the player will be completely gone from the game - # when the message goes through, making it less likely games - # will incorrectly try to respawn them, etc. - actor = player.actor - if actor is not None: - _ba.pushcall( - Call(actor.handlemessage, DieMessage(how=DeathType.LEFT_GAME))) - player.set_actor(None) + # # If the player has an actor, send it a deferred die message. + # # This way the player will be completely gone from the game + # # when the message goes through, making it less likely games + # # will incorrectly try to respawn them, etc. + # actor = player.actor + # if actor is not None: + # _ba.pushcall( + # Call(actor.handlemessage, + # DieMessage(how=DeathType.LEFT_GAME))) + # player.actor = None def handlemessage(self, msg: Any) -> Any: if isinstance(msg, PlayerDiedMessage): @@ -948,7 +949,7 @@ class GameActivity(Activity[PlayerType, TeamType]): character=player.character, player=player) - player.set_actor(spaz) + player.actor = spaz assert spaz.node # If this is co-op and we're on Courtyard or Runaround, add the diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index 9bea0bae..ecc27b4d 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -175,13 +175,6 @@ class Player(Generic[TeamType]): """ return self._sessionplayer.get_name(full=full, icon=icon) - def set_actor(self, actor: Optional[ba.Actor]) -> None: - """set_actor(actor: Optional[ba.Actor]) -> None - - Set the player's associated ba.Actor. - """ - self.actor = actor - def is_alive(self) -> bool: """is_alive() -> bool @@ -223,6 +216,12 @@ class Player(Generic[TeamType]): return self.exists() +# NOTE: It seems we might not need these playercast() calls; have gone +# the direction where things returning players generally take a type arg +# and do this themselves; that way the user is 'forced' to deal with types +# instead of requiring extra work by them. + + def playercast(totype: Type[PlayerType], player: ba.Player) -> PlayerType: """Cast a ba.Player to a specific ba.Player subclass. diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py index 913ee62e..12be8c93 100644 --- a/assets/src/ba_data/python/ba/_session.py +++ b/assets/src/ba_data/python/ba/_session.py @@ -175,8 +175,12 @@ class Session: self._activity_end_timer: Optional[ba.Timer] = None self._activity_weak = empty_weakref(Activity) self._next_activity: Optional[ba.Activity] = None - self.wants_to_end = False + self._wants_to_end = False self._ending = False + self._activity_should_end_immediately = False + self._activity_should_end_immediately_results: ( + Optional[ba.TeamGameResults]) = None + self._activity_should_end_immediately_delay = 0.0 # Create static teams if we're using them. if self.use_teams: @@ -324,11 +328,11 @@ class Session: Note that this happens asynchronously, allowing the session and its activities to shut down gracefully. """ - self.wants_to_end = True + self._wants_to_end = True if self._next_activity is None: - self.launch_end_session_activity() + self._launch_end_session_activity() - def launch_end_session_activity(self) -> None: + def _launch_end_session_activity(self) -> None: """(internal)""" from ba._activitytypes import EndSessionActivity from ba._enums import TimeType @@ -341,11 +345,11 @@ class Session: if since_last < 30.0: return print_error( - 'launch_end_session_activity called twice (since_last=' + + '_launch_end_session_activity called twice (since_last=' + str(since_last) + ')') self._launch_end_session_activity_time = curtime self.set_activity(_ba.new_activity(EndSessionActivity)) - self.wants_to_end = False + self._wants_to_end = False self._ending = True # Prevent further actions. def on_team_join(self, team: ba.SessionTeam) -> None: @@ -373,7 +377,11 @@ class Session: # If this activity hasn't begun yet, just set it up to end immediately # once it does. if not activity.has_begun(): - activity.set_immediate_end(results, delay, force) + # activity.set_immediate_end(results, delay, force) + if not self._activity_should_end_immediately or force: + self._activity_should_end_immediately = True + self._activity_should_end_immediately_results = results + self._activity_should_end_immediately_delay = delay # The activity has already begun; get ready to end it. else: @@ -506,7 +514,7 @@ class Session: with _ba.Context(self): result = self.on_player_request(sessionplayer) except Exception: - print_exception('error in on_player_request call for', self) + print_exception(f'Error in on_player_request for {self}') result = False # If the user said yes, add the player to the session list. @@ -540,11 +548,25 @@ class Session: self._activity_retained = self._next_activity self._activity_weak = weakref.ref(self._next_activity) self._next_activity = None + self._activity_should_end_immediately = False # Kick out anyone loitering in the lobby. self.lobby.remove_all_choosers_and_kick_players() + + # Kick off the activity. self._activity_retained.begin(self) + # If we want to go down, we're now free to kick off that process. + if self._wants_to_end: + self._launch_end_session_activity() + else: + # Otherwise, if the activity has already been told to end, + # do so now. + if self._activity_should_end_immediately: + self._activity_retained.end( + self._activity_should_end_immediately_results, + self._activity_should_end_immediately_delay) + def _on_player_ready(self, chooser: ba.Chooser) -> None: """Called when a ba.Player has checked themself ready.""" lobby = chooser.lobby @@ -607,13 +629,13 @@ class Session: # referencing the chooser which could inadvertently keep it alive. sessionplayer.reset_input() - # Pass it to the current activity if it has already begun + # We can pass it to the current activity if it has already begun # (otherwise it'll get passed once begin is called). pass_to_activity = (activity.has_begun() and not activity.is_joining_activity) - # If we're not allowing mid-game joins, don't pass; just announce - # the arrival and say they'll partake next round. + # However, if we're not allowing mid-game joins, don't actually pass; + # just announce the arrival and say they'll partake next round. if pass_to_activity: if not self.allow_mid_activity_joins: pass_to_activity = False diff --git a/assets/src/ba_data/python/bastd/actor/bomb.py b/assets/src/ba_data/python/bastd/actor/bomb.py index 6cf5477a..f5a4b6cd 100644 --- a/assets/src/ba_data/python/bastd/actor/bomb.py +++ b/assets/src/ba_data/python/bastd/actor/bomb.py @@ -150,6 +150,17 @@ class BombFactory: ba.Sound for a rolling bomb. """ + @staticmethod + def get() -> BombFactory: + """Get/create a shared bastd.actor.bomb.BombFactory object.""" + activity = ba.getactivity() + factory = getattr(activity, STORAGE_ATTR_NAME, None) + if factory is None: + factory = BombFactory() + setattr(activity, STORAGE_ATTR_NAME, factory) + assert isinstance(factory, BombFactory) + return factory + def random_explode_sound(self) -> ba.Sound: """Return a random explosion ba.Sound from the factory.""" return self.explode_sounds[random.randrange(len(self.explode_sounds))] @@ -301,17 +312,6 @@ class BombFactory: ) -def get_factory() -> BombFactory: - """Get/create a shared bastd.actor.bomb.BombFactory object.""" - activity = ba.getactivity() - factory = getattr(activity, STORAGE_ATTR_NAME, None) - if factory is None: - factory = BombFactory() - setattr(activity, STORAGE_ATTR_NAME, factory) - assert isinstance(factory, BombFactory) - return factory - - class SplatMessage: """Tells an object to make a splat noise.""" @@ -335,9 +335,6 @@ class WarnMessage: class ExplodeHitMessage: """Tell an object it was hit by an explosion.""" - def __init__(self) -> None: - pass - class Blast(ba.Actor): """An explosion, as generated by a bomb or some other object. @@ -362,7 +359,7 @@ class Blast(ba.Actor): super().__init__() shared = SharedObjects.get() - factory = get_factory() + factory = BombFactory.get() self.blast_type = blast_type self._source_player = source_player @@ -653,11 +650,14 @@ class Blast(ba.Actor): radius=self.radius, source_player=ba.existing(self._source_player))) if self.blast_type == 'ice': - ba.playsound(get_factory().freeze_sound, 10, position=nodepos) + ba.playsound(BombFactory.get().freeze_sound, + 10, + position=nodepos) node.handlemessage(ba.FreezeMessage()) else: - super().handlemessage(msg) + return super().handlemessage(msg) + return None class Bomb(ba.Actor): @@ -687,7 +687,7 @@ class Bomb(ba.Actor): super().__init__() shared = SharedObjects.get() - factory = get_factory() + factory = BombFactory.get() if bomb_type not in ('ice', 'impact', 'land_mine', 'normal', 'sticky', 'tnt'): @@ -906,7 +906,7 @@ class Bomb(ba.Actor): and ba.time() - self._last_sticky_sound_time > 1.0): self._last_sticky_sound_time = ba.time() assert self.node - ba.playsound(get_factory().sticky_impact_sound, + ba.playsound(BombFactory.get().sticky_impact_sound, 2.0, position=self.node.position) @@ -940,7 +940,7 @@ class Bomb(ba.Actor): def _handle_warn(self) -> None: if self.texture_sequence and self.node: self.texture_sequence.rate = 30 - ba.playsound(get_factory().warn_sound, + ba.playsound(BombFactory.get().warn_sound, 0.5, position=self.node.position) @@ -959,7 +959,7 @@ class Bomb(ba.Actor): """ if not self.node: return - factory = get_factory() + factory = BombFactory.get() intex: Sequence[ba.Texture] if self.bomb_type == 'land_mine': intex = (factory.land_mine_lit_tex, factory.land_mine_tex) diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py index 1c706cd0..0019b7d8 100644 --- a/assets/src/ba_data/python/bastd/actor/playerspaz.py +++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py @@ -201,26 +201,23 @@ class PlayerSpaz(Spaz): # Keep track of if we're being held and by who most recently. if isinstance(msg, ba.PickedUpMessage): - super().handlemessage(msg) # Augment standard behavior. + # Augment standard behavior. + super().handlemessage(msg) self.held_count += 1 - picked_up_by = ba.playercast_o(type(self._player), - msg.node.source_player) + picked_up_by = msg.node.source_player if picked_up_by: self.last_player_held_by = picked_up_by elif isinstance(msg, ba.DroppedMessage): - super().handlemessage(msg) # Augment standard behavior. + # Augment standard behavior. + super().handlemessage(msg) self.held_count -= 1 if self.held_count < 0: print('ERROR: spaz held_count < 0') # Let's count someone dropping us as an attack. - try: - picked_up_by_2 = ba.playercast_o(type(self._player), - msg.node.source_player) - except Exception: - picked_up_by_2 = None - if picked_up_by_2: - self.last_player_attacked_by = picked_up_by_2 + picked_up_by = msg.node.source_player + if picked_up_by: + self.last_player_attacked_by = picked_up_by self.last_attacked_time = ba.time() self.last_attacked_type = ('picked_up', 'default') elif isinstance(msg, ba.StandMessage): diff --git a/assets/src/ba_data/python/bastd/actor/scoreboard.py b/assets/src/ba_data/python/bastd/actor/scoreboard.py index 7ed874fc..1dd1659b 100644 --- a/assets/src/ba_data/python/bastd/actor/scoreboard.py +++ b/assets/src/ba_data/python/bastd/actor/scoreboard.py @@ -325,8 +325,10 @@ class _EntryProxy: scoreboard = self._scoreboard() # Remove our team from the scoreboard if its still around. + # (but deferred, in case we die in a sim step or something where + # its illegal to modify nodes) if scoreboard is not None: - scoreboard.remove_team(self._team_id) + ba.pushcall(ba.Call(scoreboard.remove_team, self._team_id)) class Scoreboard: diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index 61cfb0f2..9a70b78b 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -880,8 +880,8 @@ def _preload2() -> None: 'dripity', 'spawn', 'gong' ]: ba.getsound(sname) - from bastd.actor import bomb - bomb.get_factory() + from bastd.actor.bomb import BombFactory + BombFactory.get() ba.timer(0.1, _preload3) diff --git a/docs/ba_module.md b/docs/ba_module.md index ae318809..71e4cb0a 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-05-29 for Ballistica version 1.5.0 build 20034

+

last updated on 2020-05-29 for Ballistica version 1.5.0 build 20035

This page documents the Python classes and functions in the 'ba' module, which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!


@@ -57,8 +57,6 @@
  • ba.getnodes()
  • ba.getsession()
  • ba.newnode()
  • -
  • ba.playercast()
  • -
  • ba.playercast_o()
  • ba.playsound()
  • ba.printnodes()
  • ba.setmusic()
  • @@ -1597,7 +1595,7 @@ start_long_action(callback_when_done=ba.ContextC

    Methods Inherited:

    -
    add_actor_weak_ref(), add_player(), add_team(), begin(), continue_or_end_game(), create_player(), create_settings_ui(), create_team(), dep_is_present(), destroy(), end(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_dynamic_deps(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), is_waiting_for_continue(), on_continue(), on_expire(), on_player_join(), on_player_leave(), on_team_join(), on_team_leave(), on_transition_in(), on_transition_out(), remove_player(), remove_team(), respawn_player(), retain_actor(), set_has_ended(), set_immediate_end(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), transition_in(), transition_out()
    +
    add_actor_weak_ref(), add_player(), add_team(), begin(), continue_or_end_game(), create_player(), create_settings_ui(), create_team(), dep_is_present(), destroy(), end(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_dynamic_deps(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), is_waiting_for_continue(), on_continue(), on_expire(), on_player_join(), on_player_leave(), on_team_join(), on_team_leave(), on_transition_in(), on_transition_out(), remove_player(), remove_team(), respawn_player(), retain_actor(), set_has_ended(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), transition_in(), transition_out()

    Methods Defined or Overridden:

    <constructor>, celebrate(), fade_to_red(), get_score_type(), on_begin(), setup_low_life_warning_sound(), spawn_player_spaz(), supports_session_type()
    @@ -1687,7 +1685,7 @@ there is no associated Campaign.

    Methods Inherited:

    -
    begin_next_activity(), end(), end_activity(), getactivity(), handlemessage(), launch_end_session_activity(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()
    +
    begin_next_activity(), end(), end_activity(), getactivity(), handlemessage(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    <constructor>, get_current_game_instance(), get_custom_menu_entries(), on_activity_end(), on_player_leave(), restart()
    @@ -2038,7 +2036,7 @@ its time with lingering corpses, sound effects, etc.

    Methods Inherited:

    -
    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), launch_end_session_activity(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()
    +
    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    <constructor>

    @@ -2084,7 +2082,7 @@ its time with lingering corpses, sound effects, etc.

    Methods Inherited:

    -
    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), launch_end_session_activity(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()
    +
    announce_game_results(), begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), getactivity(), handlemessage(), on_activity_end(), on_player_leave(), on_player_request(), on_team_join(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    <constructor>, get_ffa_point_awards()
    @@ -2182,9 +2180,9 @@ its time with lingering corpses, sound effects, etc.

    Methods Inherited:

    -
    add_actor_weak_ref(), add_player(), add_team(), begin(), create_player(), create_team(), dep_is_present(), destroy(), get_dynamic_deps(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), on_expire(), on_team_join(), on_team_leave(), on_transition_out(), remove_player(), remove_team(), retain_actor(), set_has_ended(), set_immediate_end(), transition_in(), transition_out()
    +
    add_actor_weak_ref(), add_player(), add_team(), begin(), create_player(), create_team(), dep_is_present(), destroy(), get_dynamic_deps(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), on_expire(), on_player_leave(), on_team_join(), on_team_leave(), on_transition_out(), remove_player(), remove_team(), retain_actor(), set_has_ended(), transition_in(), transition_out()

    Methods Defined or Overridden:

    -
    <constructor>, continue_or_end_game(), create_settings_ui(), end(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), is_waiting_for_continue(), on_begin(), on_continue(), on_player_join(), on_player_leave(), on_transition_in(), respawn_player(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), spawn_player_spaz(), supports_session_type()
    +
    <constructor>, continue_or_end_game(), create_settings_ui(), end(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), is_waiting_for_continue(), on_begin(), on_continue(), on_player_join(), on_transition_in(), respawn_player(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), spawn_player_spaz(), supports_session_type()

    <constructor>

    ba.GameActivity(settings: Dict[str, Any])

    @@ -2479,12 +2477,6 @@ whatever is relevant to keep the game going.

    (including the initial set of Players)

    -
    -

    on_player_leave()

    -

    on_player_leave(self, player: PlayerType) -> None

    - -

    Called when a ba.Player is leaving the Activity.

    -

    on_transition_in()

    on_transition_in(self) -> None

    @@ -3375,7 +3367,7 @@ Use ba.getmodel() to instantiate one.

    Methods Inherited:

    -
    begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), getactivity(), handlemessage(), launch_end_session_activity(), on_player_leave(), on_player_request(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()
    +
    begin_next_activity(), end(), end_activity(), get_custom_menu_entries(), getactivity(), handlemessage(), on_player_leave(), on_player_request(), on_team_leave(), set_activity(), transitioning_out_activity_was_freed()

    Methods Defined or Overridden:

    <constructor>, announce_game_results(), get_ffa_series_length(), get_game_number(), get_max_players(), get_next_game_description(), get_series_length(), on_activity_end(), on_team_join()
    @@ -3882,7 +3874,7 @@ even if myactor is set to None.

    Methods:

    -
    assign_input_call(), exists(), get_icon(), get_name(), is_alive(), reset_input(), set_actor()
    +
    assign_input_call(), exists(), get_icon(), get_name(), is_alive(), reset_input()

    assign_input_call()

    assign_input_call(self, inputtype: Union[str, Tuple[str, ...]], call: Callable) -> None

    @@ -3943,14 +3935,6 @@ is_alive() method return True. False is returned otherwise.

    Clears out the player's assigned input actions.

    -
    -

    set_actor()

    -

    set_actor(self, actor: Optional[ba.Actor]) -> None

    - -

    set_actor(actor: Optional[ba.Actor]) -> None

    - -

    Set the player's associated ba.Actor.

    -

    @@ -5077,7 +5061,7 @@ of the session.

    Methods Inherited:

    -
    add_actor_weak_ref(), add_player(), add_team(), begin(), continue_or_end_game(), create_player(), create_settings_ui(), create_team(), dep_is_present(), destroy(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_dynamic_deps(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), is_waiting_for_continue(), on_continue(), on_expire(), on_player_join(), on_player_leave(), on_team_join(), on_team_leave(), on_transition_out(), remove_player(), remove_team(), respawn_player(), retain_actor(), set_has_ended(), set_immediate_end(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), transition_in(), transition_out()
    +
    add_actor_weak_ref(), add_player(), add_team(), begin(), continue_or_end_game(), create_player(), create_settings_ui(), create_team(), dep_is_present(), destroy(), end_game(), get_description(), get_description_display_string(), get_display_string(), get_dynamic_deps(), get_game_settings(), get_instance_description(), get_instance_description_short(), get_instance_display_string(), get_instance_scoreboard_display_string(), get_name(), get_score_info(), get_settings_display_string(), get_supported_maps(), get_team_display_string(), handlemessage(), has_begun(), has_ended(), has_transitioned_in(), is_transitioning_out(), is_waiting_for_continue(), on_continue(), on_expire(), on_player_join(), on_player_leave(), on_team_join(), on_team_leave(), on_transition_out(), remove_player(), remove_team(), respawn_player(), retain_actor(), set_has_ended(), setup_standard_powerup_drops(), setup_standard_time_limit(), show_zoom_message(), spawn_player(), spawn_player_if_exists(), transition_in(), transition_out()

    Methods Defined or Overridden:

    <constructor>, end(), on_begin(), on_transition_in(), spawn_player_spaz(), supports_session_type()
    @@ -6161,28 +6145,6 @@ object dies. 'owner' can be another node or a ba.Actor

    Open the provided url in a web-browser, or display the URL string in a window if that isn't possible.

    -
    -

    ba.playercast()

    -

    playercast(totype: Type[PlayerType], player: ba.Player) -> PlayerType

    - -

    Cast a ba.Player to a specific ba.Player subclass.

    - -

    Category: Gameplay Functions

    - -

    When writing type-checked code, sometimes code will deal with raw -ba.Player objects which need to be cast back to the game's actual -player type so that access can be properly type-checked. This function -is a safe way to do so. It ensures that Optional values are not cast -into Non-Optional, etc.

    - -
    -

    ba.playercast_o()

    -

    playercast_o(totype: Type[PlayerType], player: Optional[ba.Player]) -> Optional[PlayerType]

    - -

    A variant of ba.playercast() for use with optional ba.Player values.

    - -

    Category: Gameplay Functions

    -

    ba.playsound()

    playsound(sound: Sound, volume: float = 1.0,