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