diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py index 4478b4d2..1f5b4455 100644 --- a/assets/src/ba_data/python/ba/__init__.py +++ b/assets/src/ba_data/python/ba/__init__.py @@ -78,7 +78,8 @@ from ba._messages import (OutOfBoundsMessage, DeathType, DieMessage, StandMessage, PickUpMessage, DropMessage, PickedUpMessage, DroppedMessage, ShouldShatterMessage, ImpactDamageMessage, - FreezeMessage, ThawMessage, HitMessage) + FreezeMessage, ThawMessage, HitMessage, + CelebrateMessage) from ba._music import setmusic, MusicPlayer, MusicType, MusicPlayMode from ba._powerup import PowerupMessage, PowerupAcceptMessage from ba._teambasesession import TeamBaseSession diff --git a/assets/src/ba_data/python/ba/_actor.py b/assets/src/ba_data/python/ba/_actor.py index b0efa0ce..42a9d611 100644 --- a/assets/src/ba_data/python/ba/_actor.py +++ b/assets/src/ba_data/python/ba/_actor.py @@ -25,6 +25,9 @@ from __future__ import annotations import weakref from typing import TYPE_CHECKING, TypeVar +from ba._messages import DieMessage, DeathType, OutOfBoundsMessage +from ba import _error + import _ba if TYPE_CHECKING: @@ -83,9 +86,11 @@ class Actor: def __init__(self) -> None: """Instantiates an Actor in the current ba.Activity.""" - # FIXME: Actor should not be require to have a 'node' attr. - self.node: Optional[ba.Node] = None + # FIXME: Actor should not be assumed to have a 'node' attr. + # self.node: Optional[ba.Node] = None + if __debug__: + self._root_actor_init_called = True activity = _ba.getactivity() self._activity = weakref.ref(activity) activity.add_actor_weak_ref(self) @@ -96,27 +101,20 @@ class Actor: # That way we can treat DieMessage handling as the single # point-of-action for death. if not self.is_expired(): - from ba import _messages - self.handlemessage(_messages.DieMessage()) + self.handlemessage(DieMessage()) except Exception: - from ba import _error _error.print_exception('exception in ba.Actor.__del__() for', self) def handlemessage(self, msg: Any) -> Any: - """General message handling; can be passed any message object. + """General message handling; can be passed any message object.""" + if __debug__: + self._handlemessage_sanity_check() - The default implementation will handle ba.DieMessages by - calling self.node.delete() if self contains a 'node' attribute. - """ - from ba._error import UNHANDLED - del msg # Unused. - return UNHANDLED + # By default, actors going out-of-bounds simply kill themselves. + if isinstance(msg, OutOfBoundsMessage): + return self.handlemessage(DieMessage(how=DeathType.OUT_OF_BOUNDS)) - def _handlemessage_sanity_check(self) -> None: - if self.is_expired(): - from ba import _error - _error.print_error( - f'handlemessage called on expired actor: {self}') + return _error.UNHANDLED def autoretain(self: T) -> T: """Keep this Actor alive without needing to hold a reference to it. @@ -131,8 +129,7 @@ class Actor: """ activity = self._activity() if activity is None: - from ba._error import ActivityNotFoundError - raise ActivityNotFoundError() + raise _error.ActivityNotFoundError() activity.retain_actor(self) return self @@ -192,6 +189,21 @@ class Actor: """ return True + def _handlemessage_sanity_check(self) -> None: + """Make sure things are kosher in handlemessage(). + + Place this in an 'if __debug__:' clause at the top of handlemessage() + overrides. This will will complain if anything is sending the Actor + messages after the activity has ended, which should be explicitly + avoided. + """ + assert __debug__, "This should only be called in __debug__ mode." + if not getattr(self, '_root_actor_init_called', False): + _error.print_error('Root Actor __init__() not called.') + if self.is_expired(): + _error.print_error( + f'handlemessage() called on expired actor: {self}') + @property def activity(self) -> ba.Activity: """The Activity this Actor was created in. @@ -200,8 +212,7 @@ class Actor: """ activity = self._activity() if activity is None: - from ba._error import ActivityNotFoundError - raise ActivityNotFoundError() + raise _error.ActivityNotFoundError() return activity def getactivity(self, doraise: bool = True) -> Optional[ba.Activity]: @@ -212,6 +223,5 @@ class Actor: """ activity = self._activity() if activity is None and doraise: - from ba._error import ActivityNotFoundError - raise ActivityNotFoundError() + raise _error.ActivityNotFoundError() return activity diff --git a/assets/src/ba_data/python/ba/_apputils.py b/assets/src/ba_data/python/ba/_apputils.py index 95a7fd3f..2b974b87 100644 --- a/assets/src/ba_data/python/ba/_apputils.py +++ b/assets/src/ba_data/python/ba/_apputils.py @@ -188,7 +188,6 @@ def print_live_object_warnings(when: Any, ignore_session: ba.Session = None, ignore_activity: ba.Activity = None) -> None: """Print warnings for remaining objects in the current context.""" - # pylint: disable=too-many-branches # pylint: disable=cyclic-import import gc from ba import _session as bs_session @@ -252,13 +251,14 @@ def print_live_object_warnings(when: Any, for actor in actors: _ba.app.printed_live_object_warning = True print('ERROR: Actor found', when, ':', actor) - if isinstance(actor, bs_actor.Actor): - try: - if actor.node: - print(' - contains node:', actor.node.getnodetype(), ';', - actor.node.get_name()) - except Exception as exc: - print(' - exception checking actor node:', exc) + # if isinstance(actor, bs_actor.Actor): + # try: + # if actor.node: + # print(' - contains node:', + # actor.node.getnodetype(), ';', + # actor.node.get_name()) + # except Exception as exc: + # print(' - exception checking actor node:', exc) # refs = list(gc.get_referrers(actor)) # i = 1 # for ref in refs: diff --git a/assets/src/ba_data/python/ba/_coopgame.py b/assets/src/ba_data/python/ba/_coopgame.py index 4d43aace..1b183f4d 100644 --- a/assets/src/ba_data/python/ba/_coopgame.py +++ b/assets/src/ba_data/python/ba/_coopgame.py @@ -135,10 +135,10 @@ class CoopGameActivity(GameActivity): a wave. duration is given in seconds. """ + from ba._messages import CelebrateMessage for player in self.players: - if player.actor is not None and player.actor.node: - player.actor.node.handlemessage('celebrate', - int(duration * 1000)) + if player.actor: + player.actor.handlemessage(CelebrateMessage(duration)) def _preload_achievements(self) -> None: from ba import _achievement diff --git a/assets/src/ba_data/python/ba/_dependency.py b/assets/src/ba_data/python/ba/_dependency.py index 8ac1c652..b7ba86cc 100644 --- a/assets/src/ba_data/python/ba/_dependency.py +++ b/assets/src/ba_data/python/ba/_dependency.py @@ -315,7 +315,6 @@ class AssetPackage(DependencyComponent): def __init__(self) -> None: super().__init__() - # pylint: disable=no-member # This is used internally by the get_package_xxx calls. self.context = _ba.Context('current') diff --git a/assets/src/ba_data/python/ba/_gameactivity.py b/assets/src/ba_data/python/ba/_gameactivity.py index 67999626..de6ec230 100644 --- a/assets/src/ba_data/python/ba/_gameactivity.py +++ b/assets/src/ba_data/python/ba/_gameactivity.py @@ -337,13 +337,13 @@ class GameActivity(Activity): self._game_scoreboard_description_text: Optional[ba.Actor] = None self._standard_time_limit_time: Optional[int] = None self._standard_time_limit_timer: Optional[ba.Timer] = None - self._standard_time_limit_text: Optional[ba.Actor] = None - self._standard_time_limit_text_input: Optional[ba.Actor] = None + self._standard_time_limit_text: Optional[ba.NodeActor] = None + self._standard_time_limit_text_input: Optional[ba.NodeActor] = None self._tournament_time_limit: Optional[int] = None self._tournament_time_limit_timer: Optional[ba.Timer] = None - self._tournament_time_limit_title_text: Optional[ba.Actor] = None - self._tournament_time_limit_text: Optional[ba.Actor] = None - self._tournament_time_limit_text_input: Optional[ba.Actor] = None + self._tournament_time_limit_title_text: Optional[ba.NodeActor] = None + self._tournament_time_limit_text: Optional[ba.NodeActor] = None + self._tournament_time_limit_text_input: Optional[ba.NodeActor] = None self._zoom_message_times: Dict[int, float] = {} self._is_waiting_for_continue = False @@ -1042,17 +1042,18 @@ class GameActivity(Activity): from ba import _messages from ba._gameutils import animate from ba._coopsession import CoopSession - from bastd.actor import playerspaz + from bastd.actor.playerspaz import PlayerSpaz name = player.get_name() color = player.color highlight = player.highlight light_color = _math.normalized_color(color) display_color = _ba.safecolor(color, target_intensity=0.75) - spaz = playerspaz.PlayerSpaz(color=color, - highlight=highlight, - character=player.character, - player=player) + spaz = PlayerSpaz(color=color, + highlight=highlight, + character=player.character, + player=player) + player.set_actor(spaz) assert spaz.node diff --git a/assets/src/ba_data/python/ba/_map.py b/assets/src/ba_data/python/ba/_map.py index 921c39b0..2bfdf8cb 100644 --- a/assets/src/ba_data/python/ba/_map.py +++ b/assets/src/ba_data/python/ba/_map.py @@ -211,7 +211,7 @@ class Map(Actor): # This is expected to always be a ba.Node object (whether valid or not) # should be set to something meaningful by child classes. - self.node = None + self.node: Optional[_ba.Node] = None # Make our class' preload-data available to us # (and instruct the user if we weren't preloaded properly). @@ -365,9 +365,8 @@ class Map(Actor): player_pts = [] for player in players: try: - if player.actor is not None and player.actor.is_alive(): - assert player.actor.node - pnt = _ba.Vec3(player.actor.node.position) + if player.node: + pnt = _ba.Vec3(player.node.position) player_pts.append(pnt) except Exception as exc: print('EXC in get_ffa_start_position:', exc) @@ -412,12 +411,17 @@ class Map(Actor): return self.flag_points_default[:3] return self.flag_points[team_index % len(self.flag_points)][:3] + def exists(self) -> bool: + return bool(self.node) + def handlemessage(self, msg: Any) -> Any: from ba import _messages if isinstance(msg, _messages.DieMessage): if self.node: self.node.delete() - super().handlemessage(msg) + else: + return super().handlemessage(msg) + return None def register_map(maptype: Type[Map]) -> None: diff --git a/assets/src/ba_data/python/ba/_messages.py b/assets/src/ba_data/python/ba/_messages.py index a82bd481..9ac55859 100644 --- a/assets/src/ba_data/python/ba/_messages.py +++ b/assets/src/ba_data/python/ba/_messages.py @@ -47,6 +47,7 @@ class DeathType(Enum): Category: Enums """ GENERIC = 'generic' + OUT_OF_BOUNDS = 'out_of_bounds' IMPACT = 'impact' FALL = 'fall' REACHED_GOAL = 'reached_goal' @@ -187,6 +188,20 @@ class ThawMessage: """ +@dataclass +class CelebrateMessage: + """Tells an object to celebrate. + + Category: Message Classes + + Attributes: + + duration + Amount of time to celebrate in seconds. + """ + duration: float = 10.0 + + @dataclass(init=False) class HitMessage: """Tells an object it has been hit in some way. diff --git a/assets/src/ba_data/python/ba/_nodeactor.py b/assets/src/ba_data/python/ba/_nodeactor.py index 6a1305be..68f4dcf9 100644 --- a/assets/src/ba_data/python/ba/_nodeactor.py +++ b/assets/src/ba_data/python/ba/_nodeactor.py @@ -51,3 +51,6 @@ class NodeActor(Actor): self.node.delete() return None return super().handlemessage(msg) + + def exists(self) -> bool: + return bool(self.node) diff --git a/assets/src/ba_data/python/ba/_team.py b/assets/src/ba_data/python/ba/_team.py index c9735dff..23f53726 100644 --- a/assets/src/ba_data/python/ba/_team.py +++ b/assets/src/ba_data/python/ba/_team.py @@ -98,21 +98,6 @@ class Team: """Returns the numeric team ID.""" return self._team_id - def celebrate(self, duration: float = 10.0) -> None: - """Tells all players on the team to celebrate. - - duration is given in seconds. - """ - for player in self.players: - try: - if player.actor is not None and player.actor.node: - # Internal node-message is in milliseconds. - player.actor.node.handlemessage('celebrate', - int(duration * 1000)) - except Exception: - from ba import _error - _error.print_exception('Error on celebrate') - def reset(self) -> None: """(internal)""" self.reset_gamedata() diff --git a/assets/src/ba_data/python/ba/_teambasesession.py b/assets/src/ba_data/python/ba/_teambasesession.py index 063c24ac..ffd93f36 100644 --- a/assets/src/ba_data/python/ba/_teambasesession.py +++ b/assets/src/ba_data/python/ba/_teambasesession.py @@ -254,22 +254,23 @@ class TeamBaseSession(Session): announcement of the same. """ # pylint: disable=cyclic-import + # pylint: disable=too-many-locals from ba import _math from ba import _general from ba._gameutils import cameraflash from ba import _lang from ba._freeforallsession import FreeForAllSession + from ba._messages import CelebrateMessage _ba.timer(delay, _general.Call(_ba.playsound, _ba.getsound("boxingBell"))) if announce_winning_team: winning_team = results.get_winning_team() if winning_team is not None: # Have all players celebrate. + celebrate_msg = CelebrateMessage(duration=10.0) for player in winning_team.players: - if player.actor is not None and player.actor.node: - # Note: celebrate message takes milliseconds - # for historical reasons. - player.actor.node.handlemessage('celebrate', 10000) + if player.actor: + player.actor.handlemessage(celebrate_msg) cameraflash() # Some languages say "FOO WINS" different for teams vs players. diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py index a600f3a9..1034c17b 100644 --- a/assets/src/ba_data/python/bastd/actor/playerspaz.py +++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py @@ -25,7 +25,7 @@ from __future__ import annotations from typing import TYPE_CHECKING import ba -from bastd.actor import spaz as basespaz +from bastd.actor.spaz import Spaz if TYPE_CHECKING: from typing import Any, Optional, Sequence, Tuple @@ -77,7 +77,7 @@ class PlayerSpazHurtMessage: self.spaz = spaz -class PlayerSpaz(basespaz.Spaz): +class PlayerSpaz(Spaz): """A ba.Spaz subclass meant to be controlled by a ba.Player. category: Gameplay Classes @@ -102,13 +102,12 @@ class PlayerSpaz(basespaz.Spaz): you must call connect_controls_to_player() to do so. """ - basespaz.Spaz.__init__(self, - color=color, - highlight=highlight, - character=character, - source_player=player, - start_invincible=True, - powerups_expire=powerups_expire) + super().__init__(color=color, + highlight=highlight, + character=character, + source_player=player, + start_invincible=True, + powerups_expire=powerups_expire) self.last_player_attacked_by: Optional[ba.Player] = None self.last_attacked_time = 0.0 self.last_attacked_type: Optional[Tuple[str, str]] = None diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index 1e38890b..d344ee36 100644 --- a/assets/src/ba_data/python/bastd/actor/spaz.py +++ b/assets/src/ba_data/python/bastd/actor/spaz.py @@ -155,7 +155,7 @@ class Spaz(ba.Actor): media = factory.get_media(character) punchmats = (factory.punch_material, ba.sharedobj('attack_material')) pickupmats = (factory.pickup_material, ba.sharedobj('pickup_material')) - self.node = ba.newnode( + self.node: ba.Node = ba.newnode( type="spaz", delegate=self, attrs={ @@ -256,10 +256,13 @@ class Spaz(ba.Actor): self.punch_callback: Optional[Callable[[Spaz], Any]] = None self.pick_up_powerup_callback: Optional[Callable[[Spaz], Any]] = None + def exists(self) -> bool: + return bool(self.node) + def on_expire(self) -> None: super().on_expire() - # release callbacks/refs so we don't wind up with dependency loops.. + # Release callbacks/refs so we don't wind up with dependency loops. self._dropped_bomb_callbacks = [] self.punch_callback = None self.pick_up_powerup_callback = None @@ -296,8 +299,8 @@ class Spaz(ba.Actor): assert isinstance(t_ms, int) t_bucket = int(t_ms / 1000) if t_bucket == self._turbo_filter_time_bucket: - # add only once per timestep (filter out buttons triggering - # multiple actions) + # Add only once per timestep (filter out buttons triggering + # multiple actions). if t_ms != self._turbo_filter_times.get(source, 0): self._turbo_filter_counts[source] = ( self._turbo_filter_counts.get(source, 0) + 1) @@ -306,11 +309,11 @@ class Spaz(ba.Actor): # ba.screenmessage( str(source) + " " # + str(self._turbo_filter_counts[source])) if self._turbo_filter_counts[source] == 15: - # knock 'em out. That'll learn 'em. + # Knock 'em out. That'll learn 'em. assert self.node self.node.handlemessage("knockout", 500.0) - # also issue periodic notices about who is turbo-ing + # Also issue periodic notices about who is turbo-ing. now = ba.time(ba.TimeType.REAL) if now > ba.app.last_spaz_turbo_warn_time + 30.0: ba.app.last_spaz_turbo_warn_time = now @@ -1226,6 +1229,10 @@ class Spaz(ba.Actor): # hold_body needs to be set before hold_node. self.node.hold_body = opposing_body self.node.hold_node = opposing_node + elif isinstance(msg, ba.CelebrateMessage): + if self.node: + self.node.handlemessage('celebrate', int(msg.duration * 1000)) + else: return super().handlemessage(msg) return None diff --git a/assets/src/ba_data/python/bastd/actor/spazbot.py b/assets/src/ba_data/python/bastd/actor/spazbot.py index a5eee8b9..c3e7e639 100644 --- a/assets/src/ba_data/python/bastd/actor/spazbot.py +++ b/assets/src/ba_data/python/bastd/actor/spazbot.py @@ -190,7 +190,7 @@ class SpazBot(basespaz.Spaz): closest_dist = None closest_vel = None closest = None - assert self._player_pts + assert self._player_pts is not None for plpt, plvel in self._player_pts: dist = (plpt - botpt).length() @@ -983,6 +983,7 @@ class BotSet: for player in ba.getactivity().players: try: if player.is_alive(): + assert isinstance(player.actor, basespaz.Spaz) assert player.actor is not None and player.actor.node player_pts.append((ba.Vec3(player.actor.node.position), ba.Vec3(player.actor.node.velocity))) @@ -1006,16 +1007,6 @@ class BotSet: bot.handlemessage(ba.DieMessage(immediate=True)) self._bot_lists[i] = [] - def celebrate(self, duration: float) -> None: - """Tell all living bots in the set to celebrate momentarily. - - Duration is given in seconds. - """ - for botlist in self._bot_lists: - for bot in botlist: - if bot.node: - bot.node.handlemessage('celebrate', int(duration * 1000)) - def start_moving(self) -> None: """Start processing bot AI updates so they start doing their thing.""" self._bot_update_timer = ba.Timer(0.05, @@ -1035,6 +1026,17 @@ class BotSet: bot.node.move_left_right = 0 bot.node.move_up_down = 0 + def celebrate(self, duration: float) -> None: + """Tell all living bots in the set to celebrate momentarily. + + Duration is given in seconds. + """ + msg = ba.CelebrateMessage(duration=duration) + for botlist in self._bot_lists: + for bot in botlist: + if bot: + bot.handlemessage(msg) + def final_celebrate(self) -> None: """Tell all bots in the set to stop what they were doing and celebrate. @@ -1045,12 +1047,12 @@ class BotSet: # At this point stop doing anything but jumping and celebrating. for botlist in self._bot_lists: for bot in botlist: - if bot.node: + if bot: + assert bot.node # (should exist if 'if bot' was True) bot.node.move_left_right = 0 bot.node.move_up_down = 0 - ba.timer( - 0.5 * random.random(), - ba.Call(bot.node.handlemessage, 'celebrate', 10.0)) + ba.timer(0.5 * random.random(), + ba.Call(bot.handlemessage, ba.CelebrateMessage())) jump_duration = random.randrange(400, 500) j = random.randrange(0, 200) for _i in range(10): diff --git a/assets/src/ba_data/python/bastd/game/assault.py b/assets/src/ba_data/python/bastd/game/assault.py index 4a6be7c4..2a3d06f6 100644 --- a/assets/src/ba_data/python/bastd/game/assault.py +++ b/assets/src/ba_data/python/bastd/game/assault.py @@ -225,12 +225,8 @@ class AssaultGame(ba.TeamGameActivity): # Have teammates celebrate. for player in player_team.players: - try: - # Note: celebrate message is milliseconds - # for historical reasons. - player.actor.node.handlemessage('celebrate', 2000) - except Exception: - pass + if player.actor: + player.actor.handlemessage(ba.CelebrateMessage(2.0)) player_team.gamedata['score'] += 1 self._update_scoreboard() diff --git a/assets/src/ba_data/python/bastd/game/capturetheflag.py b/assets/src/ba_data/python/bastd/game/capturetheflag.py index 2d98ee98..8227854b 100644 --- a/assets/src/ba_data/python/bastd/game/capturetheflag.py +++ b/assets/src/ba_data/python/bastd/game/capturetheflag.py @@ -306,10 +306,8 @@ class CaptureTheFlagGame(ba.TeamGameActivity): # Have teammates celebrate for player in team.players: - if player.actor is not None and player.actor.node: - # Note: celebrate message is milliseconds - # for historical reasons. - player.actor.node.handlemessage('celebrate', 2000) + if player.actor: + player.actor.handlemessage(ba.CelebrateMessage(2.0)) # Reset all flags/state. for reset_team in self.teams: diff --git a/assets/src/ba_data/python/bastd/game/chosenone.py b/assets/src/ba_data/python/bastd/game/chosenone.py index 119ab31c..07e81941 100644 --- a/assets/src/ba_data/python/bastd/game/chosenone.py +++ b/assets/src/ba_data/python/bastd/game/chosenone.py @@ -279,7 +279,7 @@ class ChosenOneGame(ba.TeamGameActivity): self._flag = None self._chosen_one_player = player - if player.actor.node: + if player.actor: if self.settings['Chosen One Gets Shield']: player.actor.handlemessage( ba.PowerupMessage('shield')) @@ -311,8 +311,10 @@ class ChosenOneGame(ba.TeamGameActivity): 0.4: 1.0 }, loop=True) + assert isinstance(player.actor, playerspaz.PlayerSpaz) player.actor.node.connectattr('position', light.node, 'position') + except Exception: ba.print_exception('EXC in _set_chosen_one_player') diff --git a/assets/src/ba_data/python/bastd/game/easteregghunt.py b/assets/src/ba_data/python/bastd/game/easteregghunt.py index 1bc75279..9f1ce390 100644 --- a/assets/src/ba_data/python/bastd/game/easteregghunt.py +++ b/assets/src/ba_data/python/bastd/game/easteregghunt.py @@ -143,7 +143,9 @@ class EasterEggHuntGame(ba.TeamGameActivity): 'source_node', 'opposing_node') if egg_node is not None and playernode is not None: egg = egg_node.getdelegate() + assert isinstance(egg, Egg) spaz = playernode.getdelegate() + assert isinstance(spaz, playerspaz.PlayerSpaz) player = (spaz.getplayer() if hasattr(spaz, 'getplayer') else None) if player and egg: @@ -187,13 +189,8 @@ class EasterEggHuntGame(ba.TeamGameActivity): ypos = random.uniform(3.5, 3.5) zpos = random.uniform(-8.2, 3.7) - def _is_exists(egg: Egg) -> bool: - if egg.node is None: - return False - return egg.node.exists() - # Prune dead eggs from our list. - self._eggs = [e for e in self._eggs if _is_exists(e)] + self._eggs = [e for e in self._eggs if e] # Spawn more eggs if we've got space. if len(self._eggs) < int(self._max_eggs): @@ -283,12 +280,13 @@ class Egg(ba.Actor): 'materials': mats }) + def exists(self) -> bool: + return bool(self.node) + def handlemessage(self, msg: Any) -> Any: if isinstance(msg, ba.DieMessage): if self.node: self.node.delete() - elif isinstance(msg, ba.OutOfBoundsMessage): - self.handlemessage(ba.DieMessage()) elif isinstance(msg, ba.HitMessage): if self.node: assert msg.force_direction is not None diff --git a/assets/src/ba_data/python/bastd/game/elimination.py b/assets/src/ba_data/python/bastd/game/elimination.py index 6e53f8f7..f17a116c 100644 --- a/assets/src/ba_data/python/bastd/game/elimination.py +++ b/assets/src/ba_data/python/bastd/game/elimination.py @@ -386,8 +386,8 @@ class EliminationGame(ba.TeamGameActivity): for team in self.teams: for tplayer in team.players: if tplayer.is_alive(): - assert tplayer.actor is not None and tplayer.actor.node - ppos = tplayer.actor.node.position + assert tplayer.node + ppos = tplayer.node.position living_player = tplayer living_player_pos = ppos break @@ -416,20 +416,16 @@ class EliminationGame(ba.TeamGameActivity): def _print_lives(self, player: ba.Player) -> None: from bastd.actor import popuptext - if not player or not player.is_alive(): - return - try: - assert player.actor is not None and player.actor.node - pos = player.actor.node.position - except Exception as exc: - print('EXC getting player pos in bs_elim', exc) + assert player # Shouldn't be passing invalid refs around. + if not player or not player.is_alive() or not player.node: return + popuptext.PopupText('x' + str(player.gamedata['lives'] - 1), color=(1, 1, 0, 1), offset=(0, -0.8, 0), random_offset=0.0, scale=1.8, - position=pos).autoretain() + position=player.node.position).autoretain() def on_player_leave(self, player: ba.Player) -> None: ba.TeamGameActivity.on_player_leave(self, player) diff --git a/assets/src/ba_data/python/bastd/game/football.py b/assets/src/ba_data/python/bastd/game/football.py index 3f814cb9..d74fd9e1 100644 --- a/assets/src/ba_data/python/bastd/game/football.py +++ b/assets/src/ba_data/python/bastd/game/football.py @@ -128,10 +128,10 @@ class FootballTeamGame(ba.TeamGameActivity): True), ("modify_part_collision", "physical", False), ("call", "at_connect", self._handle_score))) self._flag_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: List[ba.Actor] = [] + self._score_regions: List[ba.NodeActor] = [] self._flag: Optional[FootballFlag] = None self._flag_respawn_timer: Optional[ba.Timer] = None - self._flag_respawn_light: Optional[ba.Actor] = None + self._flag_respawn_light: Optional[ba.NodeActor] = None def get_instance_description(self) -> Union[str, Sequence]: touchdowns = self.settings['Score to Win'] / 7 @@ -203,13 +203,8 @@ class FootballTeamGame(ba.TeamGameActivity): # Tell all players to celebrate. for player in team.players: - if player.actor is not None and player.actor.node: - try: - # Note: celebrate message is milliseconds - # (for historical reasons). - player.actor.node.handlemessage('celebrate', 2000) - except Exception: - ba.print_exception('Error on celebrate') + if player.actor: + player.actor.handlemessage(ba.CelebrateMessage(2.0)) # If someone on this team was last to touch it, # give them points. @@ -376,7 +371,7 @@ class FootballCoopGame(ba.CoopGameActivity): self._player_has_punched = False self._scoreboard: Optional[Scoreboard] = None self._flag_spawn_pos: Optional[Sequence[float]] = None - self.score_regions: List[ba.Actor] = [] + self.score_regions: List[ba.NodeActor] = [] self._exclude_powerups: List[str] = [] self._have_tnt = False self._bot_types_initial: Optional[List[Type[spazbot.SpazBot]]] = None @@ -384,8 +379,8 @@ class FootballCoopGame(ba.CoopGameActivity): self._bot_types_14: Optional[List[Type[spazbot.SpazBot]]] = None self._bot_team: Optional[ba.Team] = None self._starttime_ms: Optional[int] = None - self._time_text: Optional[ba.Actor] = None - self._time_text_input: Optional[ba.Actor] = None + self._time_text: Optional[ba.NodeActor] = None + self._time_text_input: Optional[ba.NodeActor] = None self._tntspawner: Optional[stdbomb.TNTSpawner] = None self._bots = spazbot.BotSet() self._bot_spawn_timer: Optional[ba.Timer] = None @@ -580,13 +575,11 @@ class FootballCoopGame(ba.CoopGameActivity): assert self._flag is not None if self._flag.node: for player in self.players: - try: - assert player.actor is not None and player.actor.node + if player.actor: + assert isinstance(player.actor, playerspaz.PlayerSpaz) if (player.actor.is_alive() and player.actor.node.hold_node == self._flag.node): return - except Exception: - ba.print_exception("exception checking hold node") flagpos = ba.Vec3(self._flag.node.position) closest_bot = None @@ -651,7 +644,6 @@ class FootballCoopGame(ba.CoopGameActivity): """ a point has been scored """ # FIXME tidy this up # pylint: disable=too-many-branches - # pylint: disable=too-many-nested-blocks # Our flag might stick around for a second or two; # we don't want it to be able to score again. @@ -674,16 +666,11 @@ class FootballCoopGame(ba.CoopGameActivity): # Tell all players (or bots) to celebrate. if i == 0: for player in team.players: - try: - # Note: celebrate message is milliseconds - # (for historical reasons). - if player.actor is not None and player.actor.node: - player.actor.node.handlemessage( - 'celebrate', 2000) - except Exception: - ba.print_exception() + if player.actor: + player.actor.handlemessage( + ba.CelebrateMessage(2.0)) else: - self._bots.celebrate(2000) + self._bots.celebrate(2.0) # If the good guys scored, add more enemies. if i == 0: diff --git a/assets/src/ba_data/python/bastd/game/hockey.py b/assets/src/ba_data/python/bastd/game/hockey.py index f8e394da..3a778a33 100644 --- a/assets/src/ba_data/python/bastd/game/hockey.py +++ b/assets/src/ba_data/python/bastd/game/hockey.py @@ -193,7 +193,7 @@ class HockeyGame(ba.TeamGameActivity): True), ("modify_part_collision", "physical", False), ("call", "at_connect", self._handle_score))) self._puck_spawn_pos: Optional[Sequence[float]] = None - self._score_regions: Optional[List[ba.Actor]] = None + self._score_regions: Optional[List[ba.NodeActor]] = None self._puck: Optional[Puck] = None def get_instance_description(self) -> Union[str, Sequence]: @@ -284,10 +284,8 @@ class HockeyGame(ba.TeamGameActivity): # Tell all players to celebrate. for player in team.players: - if player.actor is not None and player.actor.node: - # Note: celebrate message takes milliseconds - # (for historical reasons). - player.actor.node.handlemessage('celebrate', 2000) + if player.actor: + player.actor.handlemessage(ba.CelebrateMessage(2.0)) # If we've got the player from the scoring team that last # touched us, give them points. diff --git a/assets/src/ba_data/python/bastd/game/keepaway.py b/assets/src/ba_data/python/bastd/game/keepaway.py index 882ba803..f39667eb 100644 --- a/assets/src/ba_data/python/bastd/game/keepaway.py +++ b/assets/src/ba_data/python/bastd/game/keepaway.py @@ -194,7 +194,7 @@ class KeepAwayGame(ba.TeamGameActivity): for player in self.players: holding_flag = False try: - assert player.actor is not None + assert isinstance(player.actor, playerspaz.PlayerSpaz) if (player.actor.is_alive() and player.actor.node and player.actor.node.hold_node): holding_flag = ( diff --git a/assets/src/ba_data/python/bastd/game/ninjafight.py b/assets/src/ba_data/python/bastd/game/ninjafight.py index d967cfa1..d80bf190 100644 --- a/assets/src/ba_data/python/bastd/game/ninjafight.py +++ b/assets/src/ba_data/python/bastd/game/ninjafight.py @@ -188,7 +188,9 @@ class NinjaFightGame(ba.TeamGameActivity): ba.cameraflash() ba.playsound(self._winsound) for team in self.teams: - team.celebrate() # Woooo! par-tay! + for player in team.players: + if player.actor: + player.actor.handlemessage(ba.CelebrateMessage()) results.set_team_score(team, elapsed_time_ms) # Ends the activity. diff --git a/assets/src/ba_data/python/bastd/game/onslaught.py b/assets/src/ba_data/python/bastd/game/onslaught.py index 94c53c3e..e3eecfdf 100644 --- a/assets/src/ba_data/python/bastd/game/onslaught.py +++ b/assets/src/ba_data/python/bastd/game/onslaught.py @@ -105,7 +105,7 @@ class OnslaughtGame(ba.CoopGameActivity): self._can_end_wave = True self._score = 0 self._time_bonus = 0 - self._spawn_info_text: Optional[ba.Actor] = None + self._spawn_info_text: Optional[ba.NodeActor] = None self._dingsound = ba.getsound('dingSmall') self._dingsoundhigh = ba.getsound('dingSmallHigh') self._have_tnt = False @@ -115,9 +115,9 @@ class OnslaughtGame(ba.CoopGameActivity): self._bots: Optional[spazbot.BotSet] = None self._powerup_drop_timer: Optional[ba.Timer] = None self._time_bonus_timer: Optional[ba.Timer] = None - self._time_bonus_text: Optional[ba.Actor] = None + self._time_bonus_text: Optional[ba.NodeActor] = None self._flawless_bonus: Optional[int] = None - self._wave_text: Optional[ba.Actor] = None + self._wave_text: Optional[ba.NodeActor] = None self._wave_update_timer: Optional[ba.Timer] = None self._throw_off_kills = 0 self._land_mine_kills = 0 @@ -873,7 +873,7 @@ class OnslaughtGame(ba.CoopGameActivity): if self._game_over: return - # respawn applicable players + # Respawn applicable players. if self._wave > 1 and not self.is_waiting_for_continue(): for player in self.players: if (not player.is_alive() diff --git a/assets/src/ba_data/python/bastd/game/race.py b/assets/src/ba_data/python/bastd/game/race.py index 3ea58f71..f4d1de6f 100644 --- a/assets/src/ba_data/python/bastd/game/race.py +++ b/assets/src/ba_data/python/bastd/game/race.py @@ -189,6 +189,7 @@ class RaceGame(ba.TeamGameActivity): self._regions.append(RaceRegion(rpt, len(self._regions))) def _flash_player(self, player: ba.Player, scale: float) -> None: + assert isinstance(player.actor, PlayerSpaz) assert player.actor is not None and player.actor.node pos = player.actor.node.position light = ba.newnode('light', @@ -523,6 +524,7 @@ class RaceGame(ba.TeamGameActivity): for player in self.players: pos: Optional[ba.Vec3] try: + assert isinstance(player.actor, PlayerSpaz) assert player.actor is not None and player.actor.node pos = ba.Vec3(player.actor.node.position) except Exception: diff --git a/assets/src/ba_data/python/bastd/game/runaround.py b/assets/src/ba_data/python/bastd/game/runaround.py index 88601355..32ba00e5 100644 --- a/assets/src/ba_data/python/bastd/game/runaround.py +++ b/assets/src/ba_data/python/bastd/game/runaround.py @@ -117,15 +117,15 @@ class RunaroundGame(ba.CoopGameActivity): self._waves: Optional[List[Dict[str, Any]]] = None self._bots = spazbot.BotSet() self._tntspawner: Optional[TNTSpawner] = None - self._lives_bg: Optional[ba.Actor] = None + self._lives_bg: Optional[ba.NodeActor] = None self._start_lives = 10 self._lives = self._start_lives - self._lives_text: Optional[ba.Actor] = None + self._lives_text: Optional[ba.NodeActor] = None self._flawless = True self._time_bonus_timer: Optional[ba.Timer] = None - self._time_bonus_text: Optional[ba.Actor] = None + self._time_bonus_text: Optional[ba.NodeActor] = None self._time_bonus_mult: Optional[float] = None - self._wave_text: Optional[ba.Actor] = None + self._wave_text: Optional[ba.NodeActor] = None self._flawless_bonus: Optional[int] = None self._wave_update_timer: Optional[ba.Timer] = None @@ -1050,10 +1050,12 @@ class RunaroundGame(ba.CoopGameActivity): def _update_bot(self, bot: spazbot.SpazBot) -> bool: # Yup; that's a lot of return statements right there. # pylint: disable=too-many-return-statements - assert bot.node - if not bot.is_alive() or not bot.node.exists(): + + if not bool(bot): return True + assert bot.node + # FIXME: Do this in a type safe way. r_walk_speed: float = bot.r_walk_speed # type: ignore r_walk_row: int = bot.r_walk_row # type: ignore diff --git a/assets/src/ba_data/python/bastd/game/thelaststand.py b/assets/src/ba_data/python/bastd/game/thelaststand.py index 340d8c33..08f87f32 100644 --- a/assets/src/ba_data/python/bastd/game/thelaststand.py +++ b/assets/src/ba_data/python/bastd/game/thelaststand.py @@ -197,6 +197,7 @@ class TheLastStandGame(ba.CoopGameActivity): for player in self.players: try: if player.is_alive(): + assert isinstance(player.actor, playerspaz.PlayerSpaz) assert player.actor is not None and player.actor.node playerpts.append(player.actor.node.position) except Exception as exc: diff --git a/assets/src/ba_data/python/bastd/mainmenu.py b/assets/src/ba_data/python/bastd/mainmenu.py index f6df76bb..ae5c4816 100644 --- a/assets/src/ba_data/python/bastd/mainmenu.py +++ b/assets/src/ba_data/python/bastd/mainmenu.py @@ -262,7 +262,7 @@ class MainMenuActivity(ba.Activity): self._valid = True self._message_duration = 10.0 self._message_spacing = 2.0 - self._text: Optional[ba.Actor] = None + self._text: Optional[ba.NodeActor] = None self._activity = weakref.ref(activity) # If we're signed in, fetch news immediately. @@ -290,8 +290,8 @@ class MainMenuActivity(ba.Activity): # If our news is way out of date, lets re-request it; # otherwise, rotate our phrase. - assert app.main_menu_last_news_fetch_time is not None - if time.time() - app.main_menu_last_news_fetch_time > 600.0: + assert ba.app.main_menu_last_news_fetch_time is not None + if time.time() - ba.app.main_menu_last_news_fetch_time > 600.0: self._fetch_news() self._text = None else: diff --git a/assets/src/ba_data/python/bastd/maps.py b/assets/src/ba_data/python/bastd/maps.py index 4f74678a..f55fbee2 100644 --- a/assets/src/ba_data/python/bastd/maps.py +++ b/assets/src/ba_data/python/bastd/maps.py @@ -1169,8 +1169,8 @@ class TowerD(ba.Map): running: bool = False) -> bool: # see if we're within edge_box boxes = self.defs.boxes - box_position = boxes['edge_box1'][0:3] - box_scale = boxes['edge_box1'][6:9] + box_position = boxes['edge_box'][0:3] + box_scale = boxes['edge_box'][6:9] box_position2 = boxes['edge_box2'][0:3] box_scale2 = boxes['edge_box2'][6:9] xpos = (point.x - box_position[0]) / box_scale[0] diff --git a/config/toolconfigsrc/projectile b/config/toolconfigsrc/projectile index 1f9c0e47..e7af40de 100644 --- a/config/toolconfigsrc/projectile +++ b/config/toolconfigsrc/projectile @@ -1,4 +1,2 @@ +/tools -+/src/ballistica -+/src/generated_src +/assets/src/ba_data/python diff --git a/config/toolconfigsrc/pylintrc b/config/toolconfigsrc/pylintrc index 8662f2fa..3300f2f1 100644 --- a/config/toolconfigsrc/pylintrc +++ b/config/toolconfigsrc/pylintrc @@ -80,6 +80,12 @@ good-names=i, U, _ +[TYPECHECK] +# Mypy does most all the type checking we need, and in some cases +# Pylint has conflicting views about what types it thinks things are, +# so let's just flip stuff off here. +disable=no-member + [MISCELLANEOUS] # We've got various TODO and FIXME notes in scripts, but don't want # lint to trigger over that fact. diff --git a/docs/ba_module.md b/docs/ba_module.md index 8760c93a..18cb3bcb 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -1,5 +1,5 @@ -

last updated on 2020-04-06 for Ballistica version 1.5.0 build 20001

+

last updated on 2020-04-07 for Ballistica version 1.5.0 build 20001

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!


@@ -112,6 +112,7 @@

Message Classes