diff --git a/assets/src/ba_data/python/ba/_map.py b/assets/src/ba_data/python/ba/_map.py index 139b4d63..9af476a8 100644 --- a/assets/src/ba_data/python/ba/_map.py +++ b/assets/src/ba_data/python/ba/_map.py @@ -367,13 +367,8 @@ class Map(Actor): # Get positions for existing players. player_pts = [] for player in players: - try: - if player and player.node: - pnt = _ba.Vec3(player.node.position) - player_pts.append(pnt) - except Exception: - from ba import _error - _error.print_exception() + if player.is_alive(): + player_pts.append(player.position) def _getpt() -> Sequence[float]: point = self.ffa_spawn_points[self._next_ffa_start_index] diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py index be51c669..20615269 100644 --- a/assets/src/ba_data/python/ba/_player.py +++ b/assets/src/ba_data/python/ba/_player.py @@ -24,6 +24,8 @@ from __future__ import annotations from typing import TYPE_CHECKING, TypeVar, Generic +import _ba + if TYPE_CHECKING: from typing import (Type, Optional, Sequence, Dict, Any, Union, Tuple, Callable) @@ -66,7 +68,6 @@ class Player(Generic[TeamType]): (internal) """ from ba._nodeactor import NodeActor - import _ba # Sanity check; if a dataclass is created that inherits from us, # it will define an equality operator by default which will break @@ -92,6 +93,8 @@ class Player(Generic[TeamType]): self.gamedata = sessionplayer.gamedata # Create our player node in the current activity. + # Note: do we want to save a few cycles here by managing our player + # node manually instead of wrapping it in a NodeActor? node = _ba.newnode('player', attrs={'playerID': sessionplayer.id}) self._nodeactor = NodeActor(node) sessionplayer.set_node(node) @@ -118,6 +121,15 @@ class Player(Generic[TeamType]): raise _error.NodeNotFoundError return self._nodeactor.node + @property + def position(self) -> ba.Vec3: + """The position of the player, as defined by its current Actor. + + This value should not be used when the player has no Actor, as + it is undefined in that case. + """ + return _ba.Vec3(self.node.position) + def exists(self) -> bool: """Whether the underlying player still exists. diff --git a/assets/src/ba_data/python/bastd/actor/playerspaz.py b/assets/src/ba_data/python/bastd/actor/playerspaz.py index 1c620736..ea346360 100644 --- a/assets/src/ba_data/python/bastd/actor/playerspaz.py +++ b/assets/src/ba_data/python/bastd/actor/playerspaz.py @@ -87,14 +87,7 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): self.held_count = 0 self.last_player_held_by: Optional[PlayerType] = None self._player = player - self.playertype = type(player) - - # Grab the node for this player and wire it to follow our spaz - # (so players' controllers know where to draw their guides, etc). - if player: - assert self.node - assert player.node - self.node.connectattr('torso_position', player.node, 'position') + self._drive_player_position() @property def player(self) -> PlayerType: @@ -200,7 +193,7 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): if isinstance(msg, ba.PickedUpMessage): super().handlemessage(msg) # Augment standard behavior. self.held_count += 1 - picked_up_by = ba.playercast_o(self.playertype, + picked_up_by = ba.playercast_o(type(self._player), msg.node.source_player) if picked_up_by: self.last_player_held_by = picked_up_by @@ -212,7 +205,7 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): # Let's count someone dropping us as an attack. try: - picked_up_by_2 = ba.playercast_o(self.playertype, + picked_up_by_2 = ba.playercast_o(type(self._player), msg.node.source_player) except Exception: picked_up_by_2 = None @@ -220,6 +213,14 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): self.last_player_attacked_by = picked_up_by_2 self.last_attacked_time = ba.time() self.last_attacked_type = ('picked_up', 'default') + elif isinstance(msg, ba.StandMessage): + super().handlemessage(msg) # Augment standard behavior. + + # Our Spaz was just moved somewhere. Explicitly update + # our associated player's position in case it is being used + # for logic (otherwise it will be out of date until next step) + self._drive_player_position() + elif isinstance(msg, ba.DieMessage): # Report player deaths to the game. @@ -271,7 +272,7 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): # Keep track of the player who last hit us for point rewarding. elif isinstance(msg, ba.HitMessage): - source_player = msg.get_source_player(self.playertype) + source_player = msg.get_source_player(type(self._player)) if source_player: self.last_player_attacked_by = source_player self.last_attacked_time = ba.time() @@ -282,3 +283,16 @@ class PlayerSpaz(Spaz, Generic[PlayerType]): activity.handlemessage(PlayerSpazHurtMessage(self)) else: super().handlemessage(msg) + + def _drive_player_position(self) -> None: + """Drive our ba.Player's official position + + If our position is changed explicitly, this should be called again + to instantly update the player position (otherwise it would be out + of date until the next sim step) + """ + player = self._player + if player: + assert self.node + assert player.node + self.node.connectattr('torso_position', player.node, 'position') diff --git a/assets/src/ba_data/python/bastd/actor/spaz.py b/assets/src/ba_data/python/bastd/actor/spaz.py index 007ffc1d..b1d64372 100644 --- a/assets/src/ba_data/python/bastd/actor/spaz.py +++ b/assets/src/ba_data/python/bastd/actor/spaz.py @@ -1070,9 +1070,9 @@ class Spaz(ba.Actor): if self.hitpoints > 0: # It's kinda crappy to die from impacts, so lets reduce - # impact damage by a reasonable amount if it'll keep us alive + # impact damage by a reasonable amount *if* it'll keep us alive if msg.hit_type == 'impact' and damage > self.hitpoints: - # drop damage to whatever puts us at 10 hit points, + # Drop damage to whatever puts us at 10 hit points, # or 200 less than it used to be whichever is greater # (so it *can* still kill us if its high enough) newdamage = max(damage - 200, self.hitpoints - 10) @@ -1081,27 +1081,28 @@ class Spaz(ba.Actor): # If we're holding something, drop it. if damage > 0.0 and self.node.hold_node: - # self.node.hold_node = ba.Node(None) self.node.hold_node = None self.hitpoints -= damage self.node.hurt = 1.0 - float( self.hitpoints) / self.hitpoints_max + # If we're cursed, *any* damage blows us up. if self._cursed and damage > 0: ba.timer( 0.05, ba.WeakCall(self.curse_explode, msg.get_source_player(ba.Player))) - # if we're frozen, shatter.. otherwise die if we hit zero + + # If we're frozen, shatter.. otherwise die if we hit zero if self.frozen and (damage > 200 or self.hitpoints <= 0): self.shatter() elif self.hitpoints <= 0: self.node.handlemessage( ba.DieMessage(how=ba.DeathType.IMPACT)) - # if we're dead, take a look at the smoothed damage val + # If we're dead, take a look at the smoothed damage value # (which gives us a smoothed average of recent damage) and shatter - # us if its grown high enough + # us if its grown high enough. if self.hitpoints <= 0: damage_avg = self.node.damage_smoothed * damage_scale if damage_avg > 1000: @@ -1144,23 +1145,23 @@ class Spaz(ba.Actor): return None node = ba.get_collision_info('opposing_node') - # only allow one hit per node per punch + # Only allow one hit per node per punch. if node and (node not in self._punched_nodes): punch_momentum_angular = (self.node.punch_momentum_angular * self._punch_power_scale) punch_power = self.node.punch_power * self._punch_power_scale - # ok here's the deal: we pass along our base velocity for use + # Ok here's the deal: we pass along our base velocity for use # in the impulse damage calculations since that is a more # predictable value than our fist velocity, which is rather - # erratic. ...however we want to actually apply force in the - # direction our fist is moving so it looks better.. so we still - # pass that along as a direction ..perhaps a time-averaged - # fist-velocity would work too?.. should try that. + # erratic. However, we want to actually apply force in the + # direction our fist is moving so it looks better. So we still + # pass that along as a direction. Perhaps a time-averaged + # fist-velocity would work too?.. perhaps should try that. - # if its something besides another spaz, just do a muffled - # punch sound + # If its something besides another spaz, just do a muffled + # punch sound. if node.getnodetype() != 'spaz': sounds = get_factory().impact_sounds_medium sound = sounds[random.randrange(len(sounds))] @@ -1226,7 +1227,7 @@ class Spaz(ba.Actor): if held and held.getnodetype() == 'flag': return True - # hold_body needs to be set before hold_node. + # Note: 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): @@ -1280,7 +1281,8 @@ class Spaz(ba.Actor): def _pick_up(self, node: ba.Node) -> None: if self.node: - self.node.hold_body = 0 # needs to be set before hold_node + # Note: hold_body needs to be set before hold_node. + self.node.hold_body = 0 self.node.hold_node = node def set_land_mine_count(self, count: int) -> None: @@ -1317,7 +1319,7 @@ class Spaz(ba.Actor): self.shattered = True assert self.node if self.frozen: - # momentary flash of light + # Momentary flash of light. light = ba.newnode('light', attrs={ 'position': self.node.position, @@ -1333,7 +1335,8 @@ class Spaz(ba.Actor): 0.3: 0 }) ba.timer(0.3, light.delete) - # emit ice chunks.. + + # Emit ice chunks. ba.emitfx(position=self.node.position, velocity=self.node.velocity, count=int(random.random() * 10.0 + 10.0), @@ -1399,8 +1402,8 @@ class Spaz(ba.Actor): def set_bomb_count(self, count: int) -> None: """Sets the number of bombs this Spaz has.""" - # we cant just set bomb_count cuz some bombs may be laid currently - # so we have to do a relative diff based on max + # We can't just set bomb_count because some bombs may be laid currently + # so we have to do a relative diff based on max. diff = count - self._max_bomb_count self._max_bomb_count += diff self.bomb_count += diff @@ -1413,7 +1416,7 @@ class Spaz(ba.Actor): self.node.billboard_cross_out = True def _gloves_wear_off(self) -> None: - if self._demo_mode: # preserve old behavior + if self._demo_mode: # Preserve old behavior. self._punch_power_scale = 1.2 self._punch_cooldown = BASE_PUNCH_COOLDOWN else: diff --git a/docs/ba_module.md b/docs/ba_module.md index cfb7a5a6..1ae1ec10 100644 --- a/docs/ba_module.md +++ b/docs/ba_module.md @@ -3730,7 +3730,7 @@ even if myactor is set to None.
This node can be used to get a generic player position/etc.
+The position of the player, as defined by its current Actor.
+ +This value should not be used when the player has no Actor, as + it is undefined in that case.
+