bascenev1lib.actor.playerspaz
Functionality related to player-controlled Spazzes.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Functionality related to player-controlled Spazzes.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING, TypeVar, overload, override 8 9import bascenev1 as bs 10 11from bascenev1lib.actor.spaz import Spaz 12 13if TYPE_CHECKING: 14 from typing import Any, Sequence, Literal 15 16PlayerT = TypeVar('PlayerT', bound=bs.Player) 17 18 19class PlayerSpazHurtMessage: 20 """A message saying a PlayerSpaz was hurt.""" 21 22 spaz: PlayerSpaz 23 """The PlayerSpaz that was hurt""" 24 25 def __init__(self, spaz: PlayerSpaz): 26 """Instantiate with the given bascenev1.Spaz value.""" 27 self.spaz = spaz 28 29 30class PlayerSpaz(Spaz): 31 """A Spaz subclass meant to be controlled by a bascenev1.Player. 32 33 When a PlayerSpaz dies, it delivers a bascenev1.PlayerDiedMessage 34 to the current bascenev1.Activity. (unless the death was the result 35 of the player leaving the game, in which case no message is sent) 36 37 When a PlayerSpaz is hurt, it delivers a PlayerSpazHurtMessage 38 to the current bascenev1.Activity. 39 """ 40 41 def __init__( 42 self, 43 player: bs.Player, 44 *, 45 color: Sequence[float] = (1.0, 1.0, 1.0), 46 highlight: Sequence[float] = (0.5, 0.5, 0.5), 47 character: str = 'Spaz', 48 powerups_expire: bool = True, 49 ): 50 """Create a spaz for the provided bascenev1.Player. 51 52 Note: this does not wire up any controls; 53 you must call connect_controls_to_player() to do so. 54 """ 55 56 super().__init__( 57 color=color, 58 highlight=highlight, 59 character=character, 60 source_player=player, 61 start_invincible=True, 62 powerups_expire=powerups_expire, 63 ) 64 self.last_player_attacked_by: bs.Player | None = None 65 self.last_attacked_time = 0.0 66 self.last_attacked_type: tuple[str, str] | None = None 67 self.held_count = 0 68 self.last_player_held_by: bs.Player | None = None 69 self._player = player 70 self._drive_player_position() 71 72 # Overloads to tell the type system our return type based on doraise val. 73 74 @overload 75 def getplayer( 76 self, playertype: type[PlayerT], doraise: Literal[False] = False 77 ) -> PlayerT | None: ... 78 79 @overload 80 def getplayer( 81 self, playertype: type[PlayerT], doraise: Literal[True] 82 ) -> PlayerT: ... 83 84 def getplayer( 85 self, playertype: type[PlayerT], doraise: bool = False 86 ) -> PlayerT | None: 87 """Get the bascenev1.Player associated with this Spaz. 88 89 By default this will return None if the Player no longer exists. 90 If you are logically certain that the Player still exists, pass 91 doraise=False to get a non-optional return type. 92 """ 93 player: Any = self._player 94 assert isinstance(player, playertype) 95 if not player.exists() and doraise: 96 raise bs.PlayerNotFoundError() 97 return player if player.exists() else None 98 99 def connect_controls_to_player( 100 self, 101 *, 102 enable_jump: bool = True, 103 enable_punch: bool = True, 104 enable_pickup: bool = True, 105 enable_bomb: bool = True, 106 enable_run: bool = True, 107 enable_fly: bool = True, 108 ) -> None: 109 """Wire this spaz up to the provided bascenev1.Player. 110 111 Full control of the character is given by default 112 but can be selectively limited by passing False 113 to specific arguments. 114 """ 115 player = self.getplayer(bs.Player) 116 assert player 117 118 # Reset any currently connected player and/or the player we're 119 # wiring up. 120 if self._connected_to_player: 121 if player != self._connected_to_player: 122 player.resetinput() 123 self.disconnect_controls_from_player() 124 else: 125 player.resetinput() 126 127 player.assigninput(bs.InputType.UP_DOWN, self.on_move_up_down) 128 player.assigninput(bs.InputType.LEFT_RIGHT, self.on_move_left_right) 129 player.assigninput( 130 bs.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press 131 ) 132 player.assigninput( 133 bs.InputType.HOLD_POSITION_RELEASE, 134 self.on_hold_position_release, 135 ) 136 intp = bs.InputType 137 if enable_jump: 138 player.assigninput(intp.JUMP_PRESS, self.on_jump_press) 139 player.assigninput(intp.JUMP_RELEASE, self.on_jump_release) 140 if enable_pickup: 141 player.assigninput(intp.PICK_UP_PRESS, self.on_pickup_press) 142 player.assigninput(intp.PICK_UP_RELEASE, self.on_pickup_release) 143 if enable_punch: 144 player.assigninput(intp.PUNCH_PRESS, self.on_punch_press) 145 player.assigninput(intp.PUNCH_RELEASE, self.on_punch_release) 146 if enable_bomb: 147 player.assigninput(intp.BOMB_PRESS, self.on_bomb_press) 148 player.assigninput(intp.BOMB_RELEASE, self.on_bomb_release) 149 if enable_run: 150 player.assigninput(intp.RUN, self.on_run) 151 if enable_fly: 152 player.assigninput(intp.FLY_PRESS, self.on_fly_press) 153 player.assigninput(intp.FLY_RELEASE, self.on_fly_release) 154 155 self._connected_to_player = player 156 157 def disconnect_controls_from_player(self) -> None: 158 """ 159 Completely sever any previously connected 160 bascenev1.Player from control of this spaz. 161 """ 162 if self._connected_to_player: 163 self._connected_to_player.resetinput() 164 self._connected_to_player = None 165 166 # Send releases for anything in case its held. 167 self.on_move_up_down(0) 168 self.on_move_left_right(0) 169 self.on_hold_position_release() 170 self.on_jump_release() 171 self.on_pickup_release() 172 self.on_punch_release() 173 self.on_bomb_release() 174 self.on_run(0.0) 175 self.on_fly_release() 176 else: 177 print( 178 'WARNING: disconnect_controls_from_player() called for' 179 ' non-connected player' 180 ) 181 182 @override 183 def handlemessage(self, msg: Any) -> Any: 184 # FIXME: Tidy this up. 185 # pylint: disable=too-many-branches 186 # pylint: disable=too-many-statements 187 # pylint: disable=too-many-nested-blocks 188 assert not self.expired 189 190 # Keep track of if we're being held and by who most recently. 191 if isinstance(msg, bs.PickedUpMessage): 192 # Augment standard behavior. 193 super().handlemessage(msg) 194 self.held_count += 1 195 picked_up_by = msg.node.source_player 196 if picked_up_by: 197 self.last_player_held_by = picked_up_by 198 elif isinstance(msg, bs.DroppedMessage): 199 # Augment standard behavior. 200 super().handlemessage(msg) 201 self.held_count -= 1 202 if self.held_count < 0: 203 print('ERROR: spaz held_count < 0') 204 205 # Let's count someone dropping us as an attack. 206 picked_up_by = msg.node.source_player 207 if picked_up_by: 208 self.last_player_attacked_by = picked_up_by 209 self.last_attacked_time = bs.time() 210 self.last_attacked_type = ('picked_up', 'default') 211 elif isinstance(msg, bs.StandMessage): 212 super().handlemessage(msg) # Augment standard behavior. 213 214 # Our Spaz was just moved somewhere. Explicitly update 215 # our associated player's position in case it is being used 216 # for logic (otherwise it will be out of date until next step) 217 self._drive_player_position() 218 219 elif isinstance(msg, bs.DieMessage): 220 # Report player deaths to the game. 221 if not self._dead: 222 # Was this player killed while being held? 223 was_held = self.held_count > 0 and self.last_player_held_by 224 # Was this player attacked before death? 225 was_attacked_recently = ( 226 self.last_player_attacked_by 227 and bs.time() - self.last_attacked_time < 4.0 228 ) 229 # Leaving the game doesn't count as a kill *unless* 230 # someone does it intentionally while being attacked. 231 left_game_cleanly = msg.how is bs.DeathType.LEFT_GAME and not ( 232 was_held or was_attacked_recently 233 ) 234 235 killed = not (msg.immediate or left_game_cleanly) 236 237 activity = self._activity() 238 239 player = self.getplayer(bs.Player, False) 240 if not killed: 241 killerplayer = None 242 else: 243 # If this player was being held at the time of death, 244 # the holder is the killer. 245 if was_held: 246 killerplayer = self.last_player_held_by 247 else: 248 # Otherwise, if they were attacked by someone in the 249 # last few seconds, that person is the killer. 250 # Otherwise it was a suicide. 251 # FIXME: Currently disabling suicides in Co-Op since 252 # all bot kills would register as suicides; need to 253 # change this from last_player_attacked_by to 254 # something like last_actor_attacked_by to fix that. 255 if was_attacked_recently: 256 killerplayer = self.last_player_attacked_by 257 else: 258 # ok, call it a suicide unless we're in co-op 259 if activity is not None and not isinstance( 260 activity.session, bs.CoopSession 261 ): 262 killerplayer = player 263 else: 264 killerplayer = None 265 266 # We should never wind up with a dead-reference here; 267 # we want to use None in that case. 268 assert killerplayer is None or killerplayer 269 270 # Only report if both the player and the activity still exist. 271 if killed and activity is not None and player: 272 activity.handlemessage( 273 bs.PlayerDiedMessage( 274 player, killed, killerplayer, msg.how 275 ) 276 ) 277 278 super().handlemessage(msg) # Augment standard behavior. 279 280 # Keep track of the player who last hit us for point rewarding. 281 elif isinstance(msg, bs.HitMessage): 282 source_player = msg.get_source_player(type(self._player)) 283 if source_player: 284 self.last_player_attacked_by = source_player 285 self.last_attacked_time = bs.time() 286 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 287 super().handlemessage(msg) # Augment standard behavior. 288 activity = self._activity() 289 if activity is not None and self._player.exists(): 290 activity.handlemessage(PlayerSpazHurtMessage(self)) 291 else: 292 return super().handlemessage(msg) 293 return None 294 295 def _drive_player_position(self) -> None: 296 """Drive our bascenev1.Player's official position 297 298 If our position is changed explicitly, this should be called again 299 to instantly update the player position (otherwise it would be out 300 of date until the next sim step) 301 """ 302 player = self._player 303 if player: 304 assert self.node 305 assert player.node 306 self.node.connectattr('torso_position', player.node, 'position')
20class PlayerSpazHurtMessage: 21 """A message saying a PlayerSpaz was hurt.""" 22 23 spaz: PlayerSpaz 24 """The PlayerSpaz that was hurt""" 25 26 def __init__(self, spaz: PlayerSpaz): 27 """Instantiate with the given bascenev1.Spaz value.""" 28 self.spaz = spaz
A message saying a PlayerSpaz was hurt.
26 def __init__(self, spaz: PlayerSpaz): 27 """Instantiate with the given bascenev1.Spaz value.""" 28 self.spaz = spaz
Instantiate with the given bascenev1.Spaz value.
31class PlayerSpaz(Spaz): 32 """A Spaz subclass meant to be controlled by a bascenev1.Player. 33 34 When a PlayerSpaz dies, it delivers a bascenev1.PlayerDiedMessage 35 to the current bascenev1.Activity. (unless the death was the result 36 of the player leaving the game, in which case no message is sent) 37 38 When a PlayerSpaz is hurt, it delivers a PlayerSpazHurtMessage 39 to the current bascenev1.Activity. 40 """ 41 42 def __init__( 43 self, 44 player: bs.Player, 45 *, 46 color: Sequence[float] = (1.0, 1.0, 1.0), 47 highlight: Sequence[float] = (0.5, 0.5, 0.5), 48 character: str = 'Spaz', 49 powerups_expire: bool = True, 50 ): 51 """Create a spaz for the provided bascenev1.Player. 52 53 Note: this does not wire up any controls; 54 you must call connect_controls_to_player() to do so. 55 """ 56 57 super().__init__( 58 color=color, 59 highlight=highlight, 60 character=character, 61 source_player=player, 62 start_invincible=True, 63 powerups_expire=powerups_expire, 64 ) 65 self.last_player_attacked_by: bs.Player | None = None 66 self.last_attacked_time = 0.0 67 self.last_attacked_type: tuple[str, str] | None = None 68 self.held_count = 0 69 self.last_player_held_by: bs.Player | None = None 70 self._player = player 71 self._drive_player_position() 72 73 # Overloads to tell the type system our return type based on doraise val. 74 75 @overload 76 def getplayer( 77 self, playertype: type[PlayerT], doraise: Literal[False] = False 78 ) -> PlayerT | None: ... 79 80 @overload 81 def getplayer( 82 self, playertype: type[PlayerT], doraise: Literal[True] 83 ) -> PlayerT: ... 84 85 def getplayer( 86 self, playertype: type[PlayerT], doraise: bool = False 87 ) -> PlayerT | None: 88 """Get the bascenev1.Player associated with this Spaz. 89 90 By default this will return None if the Player no longer exists. 91 If you are logically certain that the Player still exists, pass 92 doraise=False to get a non-optional return type. 93 """ 94 player: Any = self._player 95 assert isinstance(player, playertype) 96 if not player.exists() and doraise: 97 raise bs.PlayerNotFoundError() 98 return player if player.exists() else None 99 100 def connect_controls_to_player( 101 self, 102 *, 103 enable_jump: bool = True, 104 enable_punch: bool = True, 105 enable_pickup: bool = True, 106 enable_bomb: bool = True, 107 enable_run: bool = True, 108 enable_fly: bool = True, 109 ) -> None: 110 """Wire this spaz up to the provided bascenev1.Player. 111 112 Full control of the character is given by default 113 but can be selectively limited by passing False 114 to specific arguments. 115 """ 116 player = self.getplayer(bs.Player) 117 assert player 118 119 # Reset any currently connected player and/or the player we're 120 # wiring up. 121 if self._connected_to_player: 122 if player != self._connected_to_player: 123 player.resetinput() 124 self.disconnect_controls_from_player() 125 else: 126 player.resetinput() 127 128 player.assigninput(bs.InputType.UP_DOWN, self.on_move_up_down) 129 player.assigninput(bs.InputType.LEFT_RIGHT, self.on_move_left_right) 130 player.assigninput( 131 bs.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press 132 ) 133 player.assigninput( 134 bs.InputType.HOLD_POSITION_RELEASE, 135 self.on_hold_position_release, 136 ) 137 intp = bs.InputType 138 if enable_jump: 139 player.assigninput(intp.JUMP_PRESS, self.on_jump_press) 140 player.assigninput(intp.JUMP_RELEASE, self.on_jump_release) 141 if enable_pickup: 142 player.assigninput(intp.PICK_UP_PRESS, self.on_pickup_press) 143 player.assigninput(intp.PICK_UP_RELEASE, self.on_pickup_release) 144 if enable_punch: 145 player.assigninput(intp.PUNCH_PRESS, self.on_punch_press) 146 player.assigninput(intp.PUNCH_RELEASE, self.on_punch_release) 147 if enable_bomb: 148 player.assigninput(intp.BOMB_PRESS, self.on_bomb_press) 149 player.assigninput(intp.BOMB_RELEASE, self.on_bomb_release) 150 if enable_run: 151 player.assigninput(intp.RUN, self.on_run) 152 if enable_fly: 153 player.assigninput(intp.FLY_PRESS, self.on_fly_press) 154 player.assigninput(intp.FLY_RELEASE, self.on_fly_release) 155 156 self._connected_to_player = player 157 158 def disconnect_controls_from_player(self) -> None: 159 """ 160 Completely sever any previously connected 161 bascenev1.Player from control of this spaz. 162 """ 163 if self._connected_to_player: 164 self._connected_to_player.resetinput() 165 self._connected_to_player = None 166 167 # Send releases for anything in case its held. 168 self.on_move_up_down(0) 169 self.on_move_left_right(0) 170 self.on_hold_position_release() 171 self.on_jump_release() 172 self.on_pickup_release() 173 self.on_punch_release() 174 self.on_bomb_release() 175 self.on_run(0.0) 176 self.on_fly_release() 177 else: 178 print( 179 'WARNING: disconnect_controls_from_player() called for' 180 ' non-connected player' 181 ) 182 183 @override 184 def handlemessage(self, msg: Any) -> Any: 185 # FIXME: Tidy this up. 186 # pylint: disable=too-many-branches 187 # pylint: disable=too-many-statements 188 # pylint: disable=too-many-nested-blocks 189 assert not self.expired 190 191 # Keep track of if we're being held and by who most recently. 192 if isinstance(msg, bs.PickedUpMessage): 193 # Augment standard behavior. 194 super().handlemessage(msg) 195 self.held_count += 1 196 picked_up_by = msg.node.source_player 197 if picked_up_by: 198 self.last_player_held_by = picked_up_by 199 elif isinstance(msg, bs.DroppedMessage): 200 # Augment standard behavior. 201 super().handlemessage(msg) 202 self.held_count -= 1 203 if self.held_count < 0: 204 print('ERROR: spaz held_count < 0') 205 206 # Let's count someone dropping us as an attack. 207 picked_up_by = msg.node.source_player 208 if picked_up_by: 209 self.last_player_attacked_by = picked_up_by 210 self.last_attacked_time = bs.time() 211 self.last_attacked_type = ('picked_up', 'default') 212 elif isinstance(msg, bs.StandMessage): 213 super().handlemessage(msg) # Augment standard behavior. 214 215 # Our Spaz was just moved somewhere. Explicitly update 216 # our associated player's position in case it is being used 217 # for logic (otherwise it will be out of date until next step) 218 self._drive_player_position() 219 220 elif isinstance(msg, bs.DieMessage): 221 # Report player deaths to the game. 222 if not self._dead: 223 # Was this player killed while being held? 224 was_held = self.held_count > 0 and self.last_player_held_by 225 # Was this player attacked before death? 226 was_attacked_recently = ( 227 self.last_player_attacked_by 228 and bs.time() - self.last_attacked_time < 4.0 229 ) 230 # Leaving the game doesn't count as a kill *unless* 231 # someone does it intentionally while being attacked. 232 left_game_cleanly = msg.how is bs.DeathType.LEFT_GAME and not ( 233 was_held or was_attacked_recently 234 ) 235 236 killed = not (msg.immediate or left_game_cleanly) 237 238 activity = self._activity() 239 240 player = self.getplayer(bs.Player, False) 241 if not killed: 242 killerplayer = None 243 else: 244 # If this player was being held at the time of death, 245 # the holder is the killer. 246 if was_held: 247 killerplayer = self.last_player_held_by 248 else: 249 # Otherwise, if they were attacked by someone in the 250 # last few seconds, that person is the killer. 251 # Otherwise it was a suicide. 252 # FIXME: Currently disabling suicides in Co-Op since 253 # all bot kills would register as suicides; need to 254 # change this from last_player_attacked_by to 255 # something like last_actor_attacked_by to fix that. 256 if was_attacked_recently: 257 killerplayer = self.last_player_attacked_by 258 else: 259 # ok, call it a suicide unless we're in co-op 260 if activity is not None and not isinstance( 261 activity.session, bs.CoopSession 262 ): 263 killerplayer = player 264 else: 265 killerplayer = None 266 267 # We should never wind up with a dead-reference here; 268 # we want to use None in that case. 269 assert killerplayer is None or killerplayer 270 271 # Only report if both the player and the activity still exist. 272 if killed and activity is not None and player: 273 activity.handlemessage( 274 bs.PlayerDiedMessage( 275 player, killed, killerplayer, msg.how 276 ) 277 ) 278 279 super().handlemessage(msg) # Augment standard behavior. 280 281 # Keep track of the player who last hit us for point rewarding. 282 elif isinstance(msg, bs.HitMessage): 283 source_player = msg.get_source_player(type(self._player)) 284 if source_player: 285 self.last_player_attacked_by = source_player 286 self.last_attacked_time = bs.time() 287 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 288 super().handlemessage(msg) # Augment standard behavior. 289 activity = self._activity() 290 if activity is not None and self._player.exists(): 291 activity.handlemessage(PlayerSpazHurtMessage(self)) 292 else: 293 return super().handlemessage(msg) 294 return None 295 296 def _drive_player_position(self) -> None: 297 """Drive our bascenev1.Player's official position 298 299 If our position is changed explicitly, this should be called again 300 to instantly update the player position (otherwise it would be out 301 of date until the next sim step) 302 """ 303 player = self._player 304 if player: 305 assert self.node 306 assert player.node 307 self.node.connectattr('torso_position', player.node, 'position')
A Spaz subclass meant to be controlled by a bascenev1.Player.
When a PlayerSpaz dies, it delivers a bascenev1.PlayerDiedMessage to the current bascenev1.Activity. (unless the death was the result of the player leaving the game, in which case no message is sent)
When a PlayerSpaz is hurt, it delivers a PlayerSpazHurtMessage to the current bascenev1.Activity.
42 def __init__( 43 self, 44 player: bs.Player, 45 *, 46 color: Sequence[float] = (1.0, 1.0, 1.0), 47 highlight: Sequence[float] = (0.5, 0.5, 0.5), 48 character: str = 'Spaz', 49 powerups_expire: bool = True, 50 ): 51 """Create a spaz for the provided bascenev1.Player. 52 53 Note: this does not wire up any controls; 54 you must call connect_controls_to_player() to do so. 55 """ 56 57 super().__init__( 58 color=color, 59 highlight=highlight, 60 character=character, 61 source_player=player, 62 start_invincible=True, 63 powerups_expire=powerups_expire, 64 ) 65 self.last_player_attacked_by: bs.Player | None = None 66 self.last_attacked_time = 0.0 67 self.last_attacked_type: tuple[str, str] | None = None 68 self.held_count = 0 69 self.last_player_held_by: bs.Player | None = None 70 self._player = player 71 self._drive_player_position()
Create a spaz for the provided bascenev1.Player.
Note: this does not wire up any controls; you must call connect_controls_to_player() to do so.
85 def getplayer( 86 self, playertype: type[PlayerT], doraise: bool = False 87 ) -> PlayerT | None: 88 """Get the bascenev1.Player associated with this Spaz. 89 90 By default this will return None if the Player no longer exists. 91 If you are logically certain that the Player still exists, pass 92 doraise=False to get a non-optional return type. 93 """ 94 player: Any = self._player 95 assert isinstance(player, playertype) 96 if not player.exists() and doraise: 97 raise bs.PlayerNotFoundError() 98 return player if player.exists() else None
Get the bascenev1.Player associated with this Spaz.
By default this will return None if the Player no longer exists. If you are logically certain that the Player still exists, pass doraise=False to get a non-optional return type.
100 def connect_controls_to_player( 101 self, 102 *, 103 enable_jump: bool = True, 104 enable_punch: bool = True, 105 enable_pickup: bool = True, 106 enable_bomb: bool = True, 107 enable_run: bool = True, 108 enable_fly: bool = True, 109 ) -> None: 110 """Wire this spaz up to the provided bascenev1.Player. 111 112 Full control of the character is given by default 113 but can be selectively limited by passing False 114 to specific arguments. 115 """ 116 player = self.getplayer(bs.Player) 117 assert player 118 119 # Reset any currently connected player and/or the player we're 120 # wiring up. 121 if self._connected_to_player: 122 if player != self._connected_to_player: 123 player.resetinput() 124 self.disconnect_controls_from_player() 125 else: 126 player.resetinput() 127 128 player.assigninput(bs.InputType.UP_DOWN, self.on_move_up_down) 129 player.assigninput(bs.InputType.LEFT_RIGHT, self.on_move_left_right) 130 player.assigninput( 131 bs.InputType.HOLD_POSITION_PRESS, self.on_hold_position_press 132 ) 133 player.assigninput( 134 bs.InputType.HOLD_POSITION_RELEASE, 135 self.on_hold_position_release, 136 ) 137 intp = bs.InputType 138 if enable_jump: 139 player.assigninput(intp.JUMP_PRESS, self.on_jump_press) 140 player.assigninput(intp.JUMP_RELEASE, self.on_jump_release) 141 if enable_pickup: 142 player.assigninput(intp.PICK_UP_PRESS, self.on_pickup_press) 143 player.assigninput(intp.PICK_UP_RELEASE, self.on_pickup_release) 144 if enable_punch: 145 player.assigninput(intp.PUNCH_PRESS, self.on_punch_press) 146 player.assigninput(intp.PUNCH_RELEASE, self.on_punch_release) 147 if enable_bomb: 148 player.assigninput(intp.BOMB_PRESS, self.on_bomb_press) 149 player.assigninput(intp.BOMB_RELEASE, self.on_bomb_release) 150 if enable_run: 151 player.assigninput(intp.RUN, self.on_run) 152 if enable_fly: 153 player.assigninput(intp.FLY_PRESS, self.on_fly_press) 154 player.assigninput(intp.FLY_RELEASE, self.on_fly_release) 155 156 self._connected_to_player = player
Wire this spaz up to the provided bascenev1.Player.
Full control of the character is given by default but can be selectively limited by passing False to specific arguments.
158 def disconnect_controls_from_player(self) -> None: 159 """ 160 Completely sever any previously connected 161 bascenev1.Player from control of this spaz. 162 """ 163 if self._connected_to_player: 164 self._connected_to_player.resetinput() 165 self._connected_to_player = None 166 167 # Send releases for anything in case its held. 168 self.on_move_up_down(0) 169 self.on_move_left_right(0) 170 self.on_hold_position_release() 171 self.on_jump_release() 172 self.on_pickup_release() 173 self.on_punch_release() 174 self.on_bomb_release() 175 self.on_run(0.0) 176 self.on_fly_release() 177 else: 178 print( 179 'WARNING: disconnect_controls_from_player() called for' 180 ' non-connected player' 181 )
Completely sever any previously connected bascenev1.Player from control of this spaz.
183 @override 184 def handlemessage(self, msg: Any) -> Any: 185 # FIXME: Tidy this up. 186 # pylint: disable=too-many-branches 187 # pylint: disable=too-many-statements 188 # pylint: disable=too-many-nested-blocks 189 assert not self.expired 190 191 # Keep track of if we're being held and by who most recently. 192 if isinstance(msg, bs.PickedUpMessage): 193 # Augment standard behavior. 194 super().handlemessage(msg) 195 self.held_count += 1 196 picked_up_by = msg.node.source_player 197 if picked_up_by: 198 self.last_player_held_by = picked_up_by 199 elif isinstance(msg, bs.DroppedMessage): 200 # Augment standard behavior. 201 super().handlemessage(msg) 202 self.held_count -= 1 203 if self.held_count < 0: 204 print('ERROR: spaz held_count < 0') 205 206 # Let's count someone dropping us as an attack. 207 picked_up_by = msg.node.source_player 208 if picked_up_by: 209 self.last_player_attacked_by = picked_up_by 210 self.last_attacked_time = bs.time() 211 self.last_attacked_type = ('picked_up', 'default') 212 elif isinstance(msg, bs.StandMessage): 213 super().handlemessage(msg) # Augment standard behavior. 214 215 # Our Spaz was just moved somewhere. Explicitly update 216 # our associated player's position in case it is being used 217 # for logic (otherwise it will be out of date until next step) 218 self._drive_player_position() 219 220 elif isinstance(msg, bs.DieMessage): 221 # Report player deaths to the game. 222 if not self._dead: 223 # Was this player killed while being held? 224 was_held = self.held_count > 0 and self.last_player_held_by 225 # Was this player attacked before death? 226 was_attacked_recently = ( 227 self.last_player_attacked_by 228 and bs.time() - self.last_attacked_time < 4.0 229 ) 230 # Leaving the game doesn't count as a kill *unless* 231 # someone does it intentionally while being attacked. 232 left_game_cleanly = msg.how is bs.DeathType.LEFT_GAME and not ( 233 was_held or was_attacked_recently 234 ) 235 236 killed = not (msg.immediate or left_game_cleanly) 237 238 activity = self._activity() 239 240 player = self.getplayer(bs.Player, False) 241 if not killed: 242 killerplayer = None 243 else: 244 # If this player was being held at the time of death, 245 # the holder is the killer. 246 if was_held: 247 killerplayer = self.last_player_held_by 248 else: 249 # Otherwise, if they were attacked by someone in the 250 # last few seconds, that person is the killer. 251 # Otherwise it was a suicide. 252 # FIXME: Currently disabling suicides in Co-Op since 253 # all bot kills would register as suicides; need to 254 # change this from last_player_attacked_by to 255 # something like last_actor_attacked_by to fix that. 256 if was_attacked_recently: 257 killerplayer = self.last_player_attacked_by 258 else: 259 # ok, call it a suicide unless we're in co-op 260 if activity is not None and not isinstance( 261 activity.session, bs.CoopSession 262 ): 263 killerplayer = player 264 else: 265 killerplayer = None 266 267 # We should never wind up with a dead-reference here; 268 # we want to use None in that case. 269 assert killerplayer is None or killerplayer 270 271 # Only report if both the player and the activity still exist. 272 if killed and activity is not None and player: 273 activity.handlemessage( 274 bs.PlayerDiedMessage( 275 player, killed, killerplayer, msg.how 276 ) 277 ) 278 279 super().handlemessage(msg) # Augment standard behavior. 280 281 # Keep track of the player who last hit us for point rewarding. 282 elif isinstance(msg, bs.HitMessage): 283 source_player = msg.get_source_player(type(self._player)) 284 if source_player: 285 self.last_player_attacked_by = source_player 286 self.last_attacked_time = bs.time() 287 self.last_attacked_type = (msg.hit_type, msg.hit_subtype) 288 super().handlemessage(msg) # Augment standard behavior. 289 activity = self._activity() 290 if activity is not None and self._player.exists(): 291 activity.handlemessage(PlayerSpazHurtMessage(self)) 292 else: 293 return super().handlemessage(msg) 294 return None
General message handling; can be passed any message object.
Inherited Members
- bascenev1lib.actor.spaz.Spaz
- node
- points_mult
- curse_time
- default_bomb_count
- default_bomb_type
- default_boxing_gloves
- default_shields
- default_hitpoints
- play_big_death_sound
- impact_scale
- source_player
- fly
- shield
- hitpoints
- hitpoints_max
- shield_hitpoints
- shield_hitpoints_max
- shield_decay_rate
- shield_decay_timer
- bomb_count
- bomb_type_default
- bomb_type
- land_mine_count
- blast_radius
- powerups_expire
- last_punch_time_ms
- last_pickup_time_ms
- last_jump_time_ms
- last_run_time_ms
- last_bomb_time_ms
- frozen
- shattered
- punch_callback
- pick_up_powerup_callback
- exists
- on_expire
- add_dropped_bomb_callback
- is_alive
- set_score_text
- on_jump_press
- on_jump_release
- on_pickup_press
- on_pickup_release
- on_hold_position_press
- on_hold_position_release
- on_punch_press
- on_punch_release
- on_bomb_press
- on_bomb_release
- on_run
- on_fly_press
- on_fly_release
- on_move
- on_move_up_down
- on_move_left_right
- on_punched
- get_death_points
- curse
- equip_boxing_gloves
- equip_shields
- shield_decay
- drop_bomb
- set_land_mine_count
- curse_explode
- shatter
- set_bomb_count