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