[ci] auto-format

This commit is contained in:
TheMikirog 2022-11-13 20:06:02 +00:00 committed by github-actions[bot]
parent 02cb7871ee
commit 05d2f1a4f7
4 changed files with 340 additions and 304 deletions

View file

@ -70,6 +70,8 @@ class PlayerState(Enum):
# To make the game easier to parse, I added Elimination style icons to the bottom of the screen. # To make the game easier to parse, I added Elimination style icons to the bottom of the screen.
# Here's the behavior of each icon. # Here's the behavior of each icon.
class Icon(ba.Actor): class Icon(ba.Actor):
"""Creates in in-game icon on screen.""" """Creates in in-game icon on screen."""
@ -194,10 +196,10 @@ class Icon(ba.Actor):
# Animate text and icon # Animate text and icon
animation_end_time = 1.5 if bool(self.activity.settings['Epic Mode']) else 3.0 animation_end_time = 1.5 if bool(self.activity.settings['Epic Mode']) else 3.0
ba.animate(self._marked_icon,'opacity', { ba.animate(self._marked_icon, 'opacity', {
0: 1.0, 0: 1.0,
animation_end_time: 0.0}) animation_end_time: 0.0})
ba.animate(self._marked_text,'opacity', { ba.animate(self._marked_text, 'opacity', {
0: 1.0, 0: 1.0,
animation_end_time: 0.0}) animation_end_time: 0.0})
@ -223,44 +225,46 @@ class Icon(ba.Actor):
# This gamemode heavily relies on edited player behavior. # This gamemode heavily relies on edited player behavior.
# We need that amount of control, so we're gonna create our own class and use the original PlayerSpaz as our blueprint. # We need that amount of control, so we're gonna create our own class and use the original PlayerSpaz as our blueprint.
class PotatoPlayerSpaz(PlayerSpaz): class PotatoPlayerSpaz(PlayerSpaz):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) # unchanged Spaz __init__ code goes here super().__init__(*args, **kwargs) # unchanged Spaz __init__ code goes here
self.dropped_bombs = [] # we use this to track bombs thrown by the player self.dropped_bombs = [] # we use this to track bombs thrown by the player
# Define a marked light # Define a marked light
self.marked_light = ba.newnode('light', self.marked_light = ba.newnode('light',
owner=self.node, owner=self.node,
attrs={'position':self.node.position, attrs={'position': self.node.position,
'radius':0.15, 'radius': 0.15,
'intensity':0.0, 'intensity': 0.0,
'height_attenuated':False, 'height_attenuated': False,
'color': (1.0, 0.0, 0.0)}) 'color': (1.0, 0.0, 0.0)})
# Pulsing red light when the player is Marked # Pulsing red light when the player is Marked
ba.animate(self.marked_light,'radius',{ ba.animate(self.marked_light, 'radius', {
0: 0.1, 0: 0.1,
0.3: 0.15, 0.3: 0.15,
0.6: 0.1}, 0.6: 0.1},
loop = True) loop=True)
self.node.connectattr('position_center',self.marked_light,'position') self.node.connectattr('position_center', self.marked_light, 'position')
# Marked timer. It should be above our head, so we attach the text to the offset that's attached to the player. # Marked timer. It should be above our head, so we attach the text to the offset that's attached to the player.
self.marked_timer_offset = ba.newnode('math', owner = self.node, attrs = { self.marked_timer_offset = ba.newnode('math', owner=self.node, attrs={
'input1': (0, 1.2, 0), 'input1': (0, 1.2, 0),
'operation': 'add'}) 'operation': 'add'})
self.node.connectattr('torso_position', self.marked_timer_offset, 'input2') self.node.connectattr('torso_position', self.marked_timer_offset, 'input2')
self.marked_timer_text = ba.newnode('text', owner = self.node, attrs = { self.marked_timer_text = ba.newnode('text', owner=self.node, attrs={
'text': '', 'text': '',
'in_world': True, 'in_world': True,
'shadow': 0.4, 'shadow': 0.4,
'color': (RED_COLOR[0], RED_COLOR[1], RED_COLOR[2], 0.0), 'color': (RED_COLOR[0], RED_COLOR[1], RED_COLOR[2], 0.0),
'flatness': 0, 'flatness': 0,
'scale': 0.02, 'scale': 0.02,
'h_align': 'center'}) 'h_align': 'center'})
self.marked_timer_offset.connectattr('output', self.marked_timer_text, 'position') self.marked_timer_offset.connectattr('output', self.marked_timer_text, 'position')
# Modified behavior when dropping bombs # Modified behavior when dropping bombs
@ -275,23 +279,23 @@ class PotatoPlayerSpaz(PlayerSpaz):
self.dropped_bombs.append(bomb) self.dropped_bombs.append(bomb)
# Bring a light # Bring a light
bomb.bomb_marked_light = ba.newnode('light', bomb.bomb_marked_light = ba.newnode('light',
owner=bomb.node, owner=bomb.node,
attrs={'position':bomb.node.position, attrs={'position': bomb.node.position,
'radius':0.04, 'radius': 0.04,
'intensity':0.0, 'intensity': 0.0,
'height_attenuated':False, 'height_attenuated': False,
'color': (1.0, 0.0, 0.0)}) 'color': (1.0, 0.0, 0.0)})
# Attach the light to the bomb # Attach the light to the bomb
bomb.node.connectattr('position',bomb.bomb_marked_light,'position') bomb.node.connectattr('position', bomb.bomb_marked_light, 'position')
# Let's adjust all lights for all bombs that we own. # Let's adjust all lights for all bombs that we own.
self.set_bombs_marked() self.set_bombs_marked()
# When the bomb physics node dies, call a function. # When the bomb physics node dies, call a function.
bomb.node.add_death_action( bomb.node.add_death_action(
ba.WeakCall(self.bomb_died, bomb)) ba.WeakCall(self.bomb_died, bomb))
# Here's the function that gets called when one of the player's bombs dies. # Here's the function that gets called when one of the player's bombs dies.
# We reference the player's dropped_bombs list and remove the bomb that died. # We reference the player's dropped_bombs list and remove the bomb that died.
def bomb_died(self, bomb): def bomb_died(self, bomb):
self.dropped_bombs.remove(bomb) self.dropped_bombs.remove(bomb)
@ -317,7 +321,8 @@ class PotatoPlayerSpaz(PlayerSpaz):
self.activity.pass_mark(msg._source_player, self._player) self.activity.pass_mark(msg._source_player, self._player)
# When stun timer runs out, we explode. Let's make sure our own explosion does throw us around. # When stun timer runs out, we explode. Let's make sure our own explosion does throw us around.
if msg.hit_type == 'stun_blast' and msg._source_player == self.source_player: return True if msg.hit_type == 'stun_blast' and msg._source_player == self.source_player:
return True
# If the attacker is healthy and we're stunned, do a flash and play a sound, then ignore the rest of the code. # If the attacker is healthy and we're stunned, do a flash and play a sound, then ignore the rest of the code.
if self.source_player.state == PlayerState.STUNNED and msg._source_player != PlayerState.MARKED: if self.source_player.state == PlayerState.STUNNED and msg._source_player != PlayerState.MARKED:
self.node.handlemessage('flash') self.node.handlemessage('flash')
@ -341,7 +346,7 @@ class PotatoPlayerSpaz(PlayerSpaz):
velocity_mag, msg.radius, 0, msg.force_direction[0], velocity_mag, msg.radius, 0, msg.force_direction[0],
msg.force_direction[1], msg.force_direction[2]) msg.force_direction[1], msg.force_direction[2])
damage = int(damage_scale * self.node.damage) damage = int(damage_scale * self.node.damage)
self.node.handlemessage('hurt_sound') # That's how we play spaz node's hurt sound self.node.handlemessage('hurt_sound') # That's how we play spaz node's hurt sound
# Play punch impact sounds based on damage if it was a punch. # Play punch impact sounds based on damage if it was a punch.
# We don't show damage percentages, because it's irrelevant. # We don't show damage percentages, because it's irrelevant.
@ -465,16 +470,19 @@ class PotatoPlayerSpaz(PlayerSpaz):
self.marked_timer_text.color[1], self.marked_timer_text.color[1],
self.marked_timer_text.color[2], self.marked_timer_text.color[2],
0.0) 0.0)
ba.animate(self.marked_light,'intensity',{ ba.animate(self.marked_light, 'intensity', {
0: self.marked_light.intensity, 0: self.marked_light.intensity,
0.5: 0.0}) 0.5: 0.0})
# Continue with the rest of the behavior. # Continue with the rest of the behavior.
super().handlemessage(msg) super().handlemessage(msg)
# If a message is something we haven't modified yet, let's pass it along to the original. # If a message is something we haven't modified yet, let's pass it along to the original.
else: super().handlemessage(msg) else:
super().handlemessage(msg)
# A concept of a player is very useful to reference if we don't have a player character present (maybe they died). # A concept of a player is very useful to reference if we don't have a player character present (maybe they died).
class Player(ba.Player['Team']): class Player(ba.Player['Team']):
"""Our player type for this game.""" """Our player type for this game."""
@ -498,13 +506,15 @@ class Player(ba.Player['Team']):
# When stun time is up, call this function. # When stun time is up, call this function.
def stun_remove(self) -> None: def stun_remove(self) -> None:
# Let's proceed only if we're stunned # Let's proceed only if we're stunned
if self.state != PlayerState.STUNNED: return if self.state != PlayerState.STUNNED:
return
# Do an explosion where we're standing. Normally it would throw us around, but we dealt # Do an explosion where we're standing. Normally it would throw us around, but we dealt
# with this issue in PlayerSpaz's edited HitMessage in line 312. # with this issue in PlayerSpaz's edited HitMessage in line 312.
Blast(position=self.actor.node.position, Blast(position=self.actor.node.position,
velocity=self.actor.node.velocity, velocity=self.actor.node.velocity,
blast_radius=2.5, blast_radius=2.5,
hit_type='stun_blast', # This hit type allows us to ignore our own stun blast explosions. # This hit type allows us to ignore our own stun blast explosions.
hit_type='stun_blast',
source_player=self).autoretain() source_player=self).autoretain()
# Let's switch our state back to healthy. # Let's switch our state back to healthy.
self.set_state(PlayerState.REGULAR) self.set_state(PlayerState.REGULAR)
@ -516,18 +526,21 @@ class Player(ba.Player['Team']):
# If we just became stunned, do all of this: # If we just became stunned, do all of this:
if old_state != PlayerState.STUNNED and state == PlayerState.STUNNED: if old_state != PlayerState.STUNNED and state == PlayerState.STUNNED:
self.actor.disconnect_controls_from_player() # Disallow all movement and actions self.actor.disconnect_controls_from_player() # Disallow all movement and actions
# Let's set our stun time based on the amount of times we fell out of the map. # Let's set our stun time based on the amount of times we fell out of the map.
if self.fall_times < len(FALL_PENALTIES): if self.fall_times < len(FALL_PENALTIES):
stun_time = FALL_PENALTIES[self.fall_times] stun_time = FALL_PENALTIES[self.fall_times]
else: else:
stun_time = FALL_PENALTIES[len(FALL_PENALTIES) - 1] stun_time = FALL_PENALTIES[len(FALL_PENALTIES) - 1]
self.stunned_time_remaining = stun_time # Set our stun time remaining self.stunned_time_remaining = stun_time # Set our stun time remaining
self.stunned_timer = ba.Timer(stun_time + 0.1, ba.Call(self.stun_remove)) # Remove our stun once the time is up # Remove our stun once the time is up
self.stunned_update_timer = ba.Timer(0.1, ba.Call(self.stunned_timer_tick), repeat = True) # Call a function every 0.1 seconds self.stunned_timer = ba.Timer(stun_time + 0.1, ba.Call(self.stun_remove))
self.fall_times += 1 # Increase the amount of times we fell by one self.stunned_update_timer = ba.Timer(0.1, ba.Call(
self.actor.marked_timer_text.text = str(stun_time) # Change the text above the Spaz's head to total stun time self.stunned_timer_tick), repeat=True) # Call a function every 0.1 seconds
self.fall_times += 1 # Increase the amount of times we fell by one
# Change the text above the Spaz's head to total stun time
self.actor.marked_timer_text.text = str(stun_time)
# If we were stunned, but now we're not, let's reconnect our controls. # If we were stunned, but now we're not, let's reconnect our controls.
# CODING CHALLENGE: to punch or bomb immediately after the stun ends, you need to # CODING CHALLENGE: to punch or bomb immediately after the stun ends, you need to
@ -562,8 +575,8 @@ class Player(ba.Player['Team']):
self.actor.marked_timer_text.text = '' self.actor.marked_timer_text.text = ''
self.state = state self.state = state
self.actor.set_bombs_marked() # Light our bombs red if we're Marked, removes the light otherwise self.actor.set_bombs_marked() # Light our bombs red if we're Marked, removes the light otherwise
self.icon.set_marked_icon(state) # Update our icon self.icon.set_marked_icon(state) # Update our icon
# ba_meta export game # ba_meta export game
@ -602,10 +615,10 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# Let's define some settings the user can mess around with to fit their needs. # Let's define some settings the user can mess around with to fit their needs.
available_settings = [ available_settings = [
ba.IntSetting('Elimination Timer', ba.IntSetting('Elimination Timer',
min_value=5, min_value=5,
default=15, default=15,
increment=1, increment=1,
), ),
ba.BoolSetting('Marked Players use Impact Bombs', default=False), ba.BoolSetting('Marked Players use Impact Bombs', default=False),
ba.BoolSetting('Epic Mode', default=False), ba.BoolSetting('Epic Mode', default=False),
] ]
@ -683,7 +696,7 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
chunk_type='spark', chunk_type='spark',
count=int(20.0+random.random()*20), count=int(20.0+random.random()*20),
scale=1.0, scale=1.0,
spread=1.0); spread=1.0)
if bool(self.settings['Marked Players use Impact Bombs']): if bool(self.settings['Marked Players use Impact Bombs']):
target.actor.bomb_type = 'impact' target.actor.bomb_type = 'impact'
target.actor.marked_timer_text.text = str(self.elimination_timer_display) target.actor.marked_timer_text.text = str(self.elimination_timer_display)
@ -703,7 +716,8 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# more control over the mark spreading mechanic. # more control over the mark spreading mechanic.
def pass_mark(self, marked_player: Player, hit_player: Player) -> None: def pass_mark(self, marked_player: Player, hit_player: Player) -> None:
# Make sure both players meet the requirements # Make sure both players meet the requirements
if not marked_player or not hit_player: return if not marked_player or not hit_player:
return
if marked_player.state == PlayerState.MARKED and hit_player.state != PlayerState.MARKED: if marked_player.state == PlayerState.MARKED and hit_player.state != PlayerState.MARKED:
self.mark(hit_player) self.mark(hit_player)
self.remove_mark(marked_player) self.remove_mark(marked_player)
@ -718,7 +732,7 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
if len(self.get_marked_players()) == 0: if len(self.get_marked_players()) == 0:
raise Exception("no marked players!") raise Exception("no marked players!")
self.elimination_timer_display -= 1 # Decrease our timer by one second. self.elimination_timer_display -= 1 # Decrease our timer by one second.
if self.elimination_timer_display > 1: if self.elimination_timer_display > 1:
sound_volume = 1.0 / marked_player_amount sound_volume = 1.0 / marked_player_amount
@ -769,7 +783,8 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# Is there only one player remaining? Or none at all? Let's end the gamemode # Is there only one player remaining? Or none at all? Let's end the gamemode
if len(alive_players) < 2: if len(alive_players) < 2:
if len(alive_players) == 1: if len(alive_players) == 1:
self.match_placement.append(alive_players[0].team) # Let's add our lone survivor to the match placement list. # Let's add our lone survivor to the match placement list.
self.match_placement.append(alive_players[0].team)
# Wait a while to let this sink in before we announce our victor. # Wait a while to let this sink in before we announce our victor.
self._end_game_timer = ba.Timer(1.25, ba.Call(self.end_game)) self._end_game_timer = ba.Timer(1.25, ba.Call(self.end_game))
else: else:
@ -780,7 +795,8 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
def get_alive_players(self) -> Sequence[ba.Player]: def get_alive_players(self) -> Sequence[ba.Player]:
alive_players = [] alive_players = []
for player in self.players: for player in self.players:
if player.state == PlayerState.ELIMINATED: continue # Ignore players who have been eliminated if player.state == PlayerState.ELIMINATED:
continue # Ignore players who have been eliminated
if player.is_alive(): if player.is_alive():
alive_players.append(player) alive_players.append(player)
return alive_players return alive_players
@ -808,8 +824,10 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# Pick one victim at random. # Pick one victim at random.
all_victims = [random.choice(possible_targets)] all_victims = [random.choice(possible_targets)]
self.elimination_timer_display = self.settings['Elimination Timer'] # Set time until marked players explode # Set time until marked players explode
self.marked_tick_timer = ba.Timer(1.0, ba.Call(self._eliminate_tick), repeat=True) # Set a timer that calls _eliminate_tick every second self.elimination_timer_display = self.settings['Elimination Timer']
# Set a timer that calls _eliminate_tick every second
self.marked_tick_timer = ba.Timer(1.0, ba.Call(self._eliminate_tick), repeat=True)
# Mark all chosen victims and play a sound # Mark all chosen victims and play a sound
for new_victim in all_victims: for new_victim in all_victims:
# _marked_sounds is an array. # _marked_sounds is an array.
@ -821,7 +839,7 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# This function is called when the gamemode first loads. # This function is called when the gamemode first loads.
def on_begin(self) -> None: def on_begin(self) -> None:
super().on_begin() # Do standard gamemode on_begin behavior super().on_begin() # Do standard gamemode on_begin behavior
self.elimination_timer_display = 0 self.elimination_timer_display = 0
self.match_placement = [] self.match_placement = []
@ -834,7 +852,7 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# Pick random player(s) to get marked # Pick random player(s) to get marked
self.new_mark_timer = ba.Timer(2.0 if self.slow_motion else 5.2, ba.Call(self.new_mark)) self.new_mark_timer = ba.Timer(2.0 if self.slow_motion else 5.2, ba.Call(self.new_mark))
self._update_icons() # Create player state icons self._update_icons() # Create player state icons
# This function creates and positions player state icons # This function creates and positions player state icons
def _update_icons(self): def _update_icons(self):
@ -882,61 +900,61 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
t_offs = -350.0 t_offs = -350.0
height_offs = 100.0 height_offs = 100.0
tnode = ba.newnode('text', tnode = ba.newnode('text',
attrs={ attrs={
'text': tip_lstr, 'text': tip_lstr,
'scale': tip_scale, 'scale': tip_scale,
'maxwidth': 900, 'maxwidth': 900,
'position': (base_position[0] + t_offs, 'position': (base_position[0] + t_offs,
base_position[1] + height_offs), base_position[1] + height_offs),
'h_align': 'left', 'h_align': 'left',
'vr_depth': 300, 'vr_depth': 300,
'shadow': 1.0 if vrmode else 0.5, 'shadow': 1.0 if vrmode else 0.5,
'flatness': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5,
'v_align': 'center', 'v_align': 'center',
'v_attach': 'bottom' 'v_attach': 'bottom'
}) })
t2pos = (base_position[0] + t_offs - (20 if icon is None else 82), t2pos = (base_position[0] + t_offs - (20 if icon is None else 82),
base_position[1] + 2 + height_offs) base_position[1] + 2 + height_offs)
t2node = ba.newnode('text', t2node = ba.newnode('text',
owner=tnode, owner=tnode,
attrs={ attrs={
'text': tip_title, 'text': tip_title,
'scale': tip_title_scale, 'scale': tip_title_scale,
'position': t2pos, 'position': t2pos,
'h_align': 'right', 'h_align': 'right',
'vr_depth': 300, 'vr_depth': 300,
'shadow': 1.0 if vrmode else 0.5, 'shadow': 1.0 if vrmode else 0.5,
'flatness': 1.0 if vrmode else 0.5, 'flatness': 1.0 if vrmode else 0.5,
'maxwidth': 140, 'maxwidth': 140,
'v_align': 'center', 'v_align': 'center',
'v_attach': 'bottom' 'v_attach': 'bottom'
}) })
if icon is not None: if icon is not None:
ipos = (base_position[0] + t_offs - 40, base_position[1] + 1 + height_offs) ipos = (base_position[0] + t_offs - 40, base_position[1] + 1 + height_offs)
img = ba.newnode('image', img = ba.newnode('image',
attrs={ attrs={
'texture': icon, 'texture': icon,
'position': ipos, 'position': ipos,
'scale': (50, 50), 'scale': (50, 50),
'opacity': 1.0, 'opacity': 1.0,
'vr_depth': 315, 'vr_depth': 315,
'color': (1, 1, 1), 'color': (1, 1, 1),
'absolute_scale': True, 'absolute_scale': True,
'attach': 'bottomCenter' 'attach': 'bottomCenter'
}) })
animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) animate(img, 'opacity', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
ba.timer(5.0, img.delete) ba.timer(5.0, img.delete)
if sound is not None: if sound is not None:
ba.playsound(sound) ba.playsound(sound)
combine = ba.newnode('combine', combine = ba.newnode('combine',
owner=tnode, owner=tnode,
attrs={ attrs={
'input0': 1.0, 'input0': 1.0,
'input1': 0.8, 'input1': 0.8,
'input2': 1.0, 'input2': 1.0,
'size': 4 'size': 4
}) })
combine.connectattr('output', tnode, 'color') combine.connectattr('output', tnode, 'color')
combine.connectattr('output', t2node, 'color') combine.connectattr('output', t2node, 'color')
animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0}) animate(combine, 'input3', {0: 0, 1.0: 1, 4.0: 1, 5.0: 0})
@ -968,7 +986,7 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
def spawn_player(self, player: Player) -> ba.Actor: def spawn_player(self, player: Player) -> ba.Actor:
position = self.map.get_ffa_start_position(self.players) position = self.map.get_ffa_start_position(self.players)
position = (position[0], position = (position[0],
position[1] - 0.3, # Move the spawn a bit lower position[1] - 0.3, # Move the spawn a bit lower
position[2]) position[2])
name = player.getname() name = player.getname()
@ -981,8 +999,8 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
highlight=player.highlight, highlight=player.highlight,
character=player.character, character=player.character,
player=player) player=player)
spaz.node.invincible = False # Immediately turn off invincibility spaz.node.invincible = False # Immediately turn off invincibility
player.actor = spaz # Assign player character to the owner player.actor = spaz # Assign player character to the owner
spaz.node.name = name spaz.node.name = name
spaz.node.name_color = display_color spaz.node.name_color = display_color
@ -1003,13 +1021,14 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
def handlemessage(self, msg: Any) -> Any: def handlemessage(self, msg: Any) -> Any:
# This is called if the player dies. # This is called if the player dies.
if isinstance(msg, ba.PlayerDiedMessage): if isinstance(msg, ba.PlayerDiedMessage):
super().handlemessage(msg) # super().handlemessage(msg)
player = msg.getplayer(Player) player = msg.getplayer(Player)
# If a player gets eliminated, don't respawn # If a player gets eliminated, don't respawn
if msg.how == 'marked_elimination': return if msg.how == 'marked_elimination':
return
self.spawn_player(player) # Spawn a new player character self.spawn_player(player) # Spawn a new player character
# If a REGULAR player dies, they respawn STUNNED. # If a REGULAR player dies, they respawn STUNNED.
# If a STUNNED player dies, reapply all visual effects. # If a STUNNED player dies, reapply all visual effects.
@ -1035,4 +1054,4 @@ class HotPotato(ba.TeamGameActivity[Player, ba.Team]):
# Use each player's index in the array for our scoring # Use each player's index in the array for our scoring
# 0 is the first index, so we add 1 to the score. # 0 is the first index, so we add 1 to the score.
results.set_team_score(team, self.match_placement.index(team) + 1) results.set_team_score(team, self.match_placement.index(team) + 1)
self.end(results=results) # Standard game ending behavior self.end(results=results) # Standard game ending behavior

View file

@ -24,6 +24,8 @@ if TYPE_CHECKING:
pass pass
# ba_meta export plugin # ba_meta export plugin
class BombRadiusVisualizer(ba.Plugin): class BombRadiusVisualizer(ba.Plugin):
# We use a decorator to add extra code to existing code, increasing mod compatibility. # We use a decorator to add extra code to existing code, increasing mod compatibility.
@ -43,14 +45,15 @@ class BombRadiusVisualizer(ba.Plugin):
# This is going to make a slightly opaque red circle, signifying damaging area. # This is going to make a slightly opaque red circle, signifying damaging area.
# We aren't defining the size, because we're gonna animate it shortly after. # We aren't defining the size, because we're gonna animate it shortly after.
args[0].radius_visualizer = ba.newnode('locator', args[0].radius_visualizer = ba.newnode('locator',
owner=args[0].node, # Remove itself when the bomb node dies. # Remove itself when the bomb node dies.
attrs={ owner=args[0].node,
'shape': 'circle', attrs={
'color': (1, 0, 0), 'shape': 'circle',
'opacity':0.05, 'color': (1, 0, 0),
'draw_beauty': False, 'opacity': 0.05,
'additive': False 'draw_beauty': False,
}) 'additive': False
})
# Let's connect our circle to the bomb. # Let's connect our circle to the bomb.
args[0].node.connectattr('position', args[0].radius_visualizer, 'position') args[0].node.connectattr('position', args[0].radius_visualizer, 'position')
@ -64,14 +67,16 @@ class BombRadiusVisualizer(ba.Plugin):
# Let's do a second circle, this time just the outline to where the damaging area ends. # Let's do a second circle, this time just the outline to where the damaging area ends.
args[0].radius_visualizer_circle = ba.newnode('locator', args[0].radius_visualizer_circle = ba.newnode('locator',
owner=args[0].node, # Remove itself when the bomb node dies. # Remove itself when the bomb node dies.
attrs={ owner=args[0].node,
'shape': 'circleOutline', attrs={
'size':[args[0].blast_radius * 2.0], # Here's that bomb's blast radius value again! 'shape': 'circleOutline',
'color': (1, 1, 0), # Here's that bomb's blast radius value again!
'draw_beauty': False, 'size': [args[0].blast_radius * 2.0],
'additive': True 'color': (1, 1, 0),
}) 'draw_beauty': False,
'additive': True
})
# Attach the circle to the bomb. # Attach the circle to the bomb.
args[0].node.connectattr('position', args[0].radius_visualizer_circle, 'position') args[0].node.connectattr('position', args[0].radius_visualizer_circle, 'position')
@ -86,4 +91,3 @@ class BombRadiusVisualizer(ba.Plugin):
# Finally we """travel through the game files""" to replace the function we want with our own version. # Finally we """travel through the game files""" to replace the function we want with our own version.
# We transplant the old function's arguments into our version. # We transplant the old function's arguments into our version.
bastd.actor.bomb.Bomb.__init__ = new_bomb_init(bastd.actor.bomb.Bomb.__init__) bastd.actor.bomb.Bomb.__init__ = new_bomb_init(bastd.actor.bomb.Bomb.__init__)

View file

@ -24,6 +24,8 @@ if TYPE_CHECKING:
pass pass
# ba_meta export plugin # ba_meta export plugin
class Quickturn(ba.Plugin): class Quickturn(ba.Plugin):
class FootConnectMessage: class FootConnectMessage:
@ -54,41 +56,42 @@ class Quickturn(ba.Plugin):
move_length = math.hypot(move[0], move[1]) move_length = math.hypot(move[0], move[1])
vel_length = math.hypot(vel[0], vel[1]) vel_length = math.hypot(vel[0], vel[1])
if vel_length < 0.6: return if vel_length < 0.6:
return
move_norm = [m/move_length for m in move] move_norm = [m/move_length for m in move]
vel_norm = [v/vel_length for v in vel] vel_norm = [v/vel_length for v in vel]
dot = sum(x*y for x,y in zip(move_norm,vel_norm)) dot = sum(x*y for x, y in zip(move_norm, vel_norm))
turn_power = min(round(math.acos(dot) / math.pi,2)*1.3,1) turn_power = min(round(math.acos(dot) / math.pi, 2)*1.3, 1)
# https://easings.net/#easeInOutQuart # https://easings.net/#easeInOutQuart
if turn_power < 0.55: if turn_power < 0.55:
turn_power = 8 * turn_power * turn_power * turn_power * turn_power turn_power = 8 * turn_power * turn_power * turn_power * turn_power
else: else:
turn_power = 0.55 - pow(-2 * turn_power + 2, 4) / 2 turn_power = 0.55 - pow(-2 * turn_power + 2, 4) / 2
if turn_power < 0.1: return if turn_power < 0.1:
return
boost_power = math.sqrt(math.pow(vel[0],2) + math.pow(vel[1],2)) * 8 boost_power = math.sqrt(math.pow(vel[0], 2) + math.pow(vel[1], 2)) * 8
boost_power = min(pow(boost_power,8),160) boost_power = min(pow(boost_power, 8), 160)
self.last_wavedash_time_ms = t_ms self.last_wavedash_time_ms = t_ms
# FX # FX
ba.emitfx(position=self.node.position, ba.emitfx(position=self.node.position,
velocity=(vel[0]*0.5,-1,vel[1]*0.5), velocity=(vel[0]*0.5, -1, vel[1]*0.5),
chunk_type='spark', chunk_type='spark',
count=5, count=5,
scale=boost_power / 160 * turn_power, scale=boost_power / 160 * turn_power,
spread=0.25); spread=0.25)
# Boost itself # Boost itself
pos = self.node.position pos = self.node.position
for i in range(6): for i in range(6):
self.node.handlemessage('impulse',pos[0],0.2+pos[1]+i*0.1,pos[2], self.node.handlemessage('impulse', pos[0], 0.2+pos[1]+i*0.1, pos[2],
0,0,0, 0, 0, 0,
boost_power * turn_power, boost_power * turn_power,
boost_power * turn_power,0,0, boost_power * turn_power, 0, 0,
move[0],0,move[1]) move[0], 0, move[1])
def new_spaz_init(func): def new_spaz_init(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@ -108,18 +111,21 @@ class Quickturn(ba.Plugin):
func(*args, **kwargs) func(*args, **kwargs)
args[0].roller_material.add_actions( args[0].roller_material.add_actions(
conditions=('they_have_material', bastd.gameutils.SharedObjects.get().footing_material), conditions=('they_have_material',
actions=(('message', 'our_node', 'at_connect', Quickturn.FootConnectMessage), bastd.gameutils.SharedObjects.get().footing_material),
('message', 'our_node', 'at_disconnect', Quickturn.FootDisconnectMessage))) actions=(('message', 'our_node', 'at_connect', Quickturn.FootConnectMessage),
('message', 'our_node', 'at_disconnect', Quickturn.FootDisconnectMessage)))
return wrapper return wrapper
bastd.actor.spazfactory.SpazFactory.__init__ = new_factory(bastd.actor.spazfactory.SpazFactory.__init__) bastd.actor.spazfactory.SpazFactory.__init__ = new_factory(
bastd.actor.spazfactory.SpazFactory.__init__)
def new_handlemessage(func): def new_handlemessage(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
if args[1] == Quickturn.FootConnectMessage: if args[1] == Quickturn.FootConnectMessage:
args[0].grounded += 1 args[0].grounded += 1
elif args[1] == Quickturn.FootDisconnectMessage: elif args[1] == Quickturn.FootDisconnectMessage:
if args[0].grounded > 0: args[0].grounded -= 1 if args[0].grounded > 0:
args[0].grounded -= 1
func(*args, **kwargs) func(*args, **kwargs)
return wrapper return wrapper

View file

@ -28,6 +28,8 @@ if TYPE_CHECKING:
pass pass
# ba_meta export plugin # ba_meta export plugin
class RagdollBGone(ba.Plugin): class RagdollBGone(ba.Plugin):
# We use a decorator to add extra code to existing code, increasing mod compatibility. # We use a decorator to add extra code to existing code, increasing mod compatibility.
@ -40,7 +42,7 @@ class RagdollBGone(ba.Plugin):
# We're working kind of blindly here, so it's good to have the original function # We're working kind of blindly here, so it's good to have the original function
# open in a second window for argument reference. # open in a second window for argument reference.
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
if isinstance(args[1], ba.DieMessage): # Replace Spaz death behavior if isinstance(args[1], ba.DieMessage): # Replace Spaz death behavior
# Here we play the gamey death noise in Co-op. # Here we play the gamey death noise in Co-op.
if not args[1].immediate: if not args[1].immediate:
@ -50,10 +52,10 @@ class RagdollBGone(ba.Plugin):
# If our Spaz dies by falling out of the map, we want to keep the ragdoll. # If our Spaz dies by falling out of the map, we want to keep the ragdoll.
# Ragdolls don't impact gameplay if Spaz dies this way, so it's fine if we leave the behavior as is. # Ragdolls don't impact gameplay if Spaz dies this way, so it's fine if we leave the behavior as is.
if args[1].how == ba.DeathType.FALL: if args[1].how == ba.DeathType.FALL:
# The next two properties are all built-in, so their behavior can't be edited directly without touching the C++ layer. # The next two properties are all built-in, so their behavior can't be edited directly without touching the C++ layer.
# We can change their values though! # We can change their values though!
# "hurt" property is basically the health bar above the player and the blinking when low on health. # "hurt" property is basically the health bar above the player and the blinking when low on health.
# 1.0 means empty health bar and the fastest blinking in the west. # 1.0 means empty health bar and the fastest blinking in the west.
args[0].node.hurt = 1.0 args[0].node.hurt = 1.0
# Make our Spaz close their eyes permanently and then make their body disintegrate. # Make our Spaz close their eyes permanently and then make their body disintegrate.
# Again, this behavior is built in. We can only trigger it by setting "dead" to True. # Again, this behavior is built in. We can only trigger it by setting "dead" to True.
@ -78,18 +80,22 @@ class RagdollBGone(ba.Plugin):
args[0].node.position[2]) args[0].node.position[2])
# This function allows us to spawn particles like sparks and bomb shrapnel. # This function allows us to spawn particles like sparks and bomb shrapnel.
# We're gonna use sparks here. # We're gonna use sparks here.
ba.emitfx(position = pos, # Here we place our edited position. ba.emitfx(position=pos, # Here we place our edited position.
velocity=args[0].node.velocity, velocity=args[0].node.velocity,
count=random.randrange(2, 5), # Random amount of sparks between 2 and 5 # Random amount of sparks between 2 and 5
scale=3.0, count=random.randrange(2, 5),
spread=0.2, scale=3.0,
chunk_type='spark') spread=0.2,
chunk_type='spark')
# Make a Spaz death noise if we're not gibbed. # Make a Spaz death noise if we're not gibbed.
if not args[0].shattered: if not args[0].shattered:
death_sounds = args[0].node.death_sounds # Get our Spaz's death noises, these change depending on character skins # Get our Spaz's death noises, these change depending on character skins
sound = death_sounds[random.randrange(len(death_sounds))] # Pick a random death noise death_sounds = args[0].node.death_sounds
ba.playsound(sound, position=args[0].node.position) # Play the sound where our Spaz is # Pick a random death noise
sound = death_sounds[random.randrange(len(death_sounds))]
# Play the sound where our Spaz is
ba.playsound(sound, position=args[0].node.position)
# Delete our Spaz node immediately. # Delete our Spaz node immediately.
# Removing stuff is weird and prone to errors, so we're gonna delay it. # Removing stuff is weird and prone to errors, so we're gonna delay it.
ba.timer(0.001, args[0].node.delete) ba.timer(0.001, args[0].node.delete)
@ -98,7 +104,8 @@ class RagdollBGone(ba.Plugin):
# Notice how we're targeting the Spaz and not it's node. # Notice how we're targeting the Spaz and not it's node.
# "self.node" is a visual representation of the character while "self" is his game logic. # "self.node" is a visual representation of the character while "self" is his game logic.
args[0]._dead = True args[0]._dead = True
args[0].hitpoints = 0 # Set his health to zero. This value is independent from the health bar above his head. # Set his health to zero. This value is independent from the health bar above his head.
args[0].hitpoints = 0
return return
# Worry no longer! We're not gonna remove all the base game code! # Worry no longer! We're not gonna remove all the base game code!