# ba_meta require api 7 """ Ragdoll-B-Gone by TheMikirog Removes ragdolls. Thanos snaps those pesky feet-tripping body sacks out of existence. Literally that's it. Heavily commented for easy modding learning! """ from __future__ import annotations from typing import TYPE_CHECKING # Let's import everything we need and nothing more. import ba import bastd import random from bastd.actor.spaz import Spaz from bastd.actor.spazfactory import SpazFactory if TYPE_CHECKING: pass # ba_meta export plugin class RagdollBGone(ba.Plugin): # We use a decorator to add extra code to existing code, increasing mod compatibility. # Any gameplay altering mod should master the decorator! # Here I'm defining a new handlemessage function that'll be replaced. def new_handlemessage(func): # This function will return our wrapper function, which is going to take the original function's base arguments. # Yes, in Python functions are objects that can be passed as arguments. It's bonkers. # arg[0] is "self", args[1] is "msg" in our original handlemessage 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. def wrapper(*args, **kwargs): if isinstance(args[1], ba.DieMessage): # Replace Spaz death behavior # Here we play the gamey death noise in Co-op. if not args[1].immediate: if args[0].play_big_death_sound and not args[0]._dead: ba.playsound(SpazFactory.get().single_player_death_sound) # 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. 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. # We can change their values though! # "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. args[0].node.hurt = 1.0 # 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. args[0].node.dead = True # After the death animation ends (which is around 2 seconds) let's remove the Spaz our of existence. ba.timer(2.0, args[0].node.delete) else: # Here's our new behavior! # The idea is to remove the Spaz node and make some sparks for extra flair. # First we see if that node even exists, just in case. if args[0].node: # Make sure Spaz isn't dead, so we can perform the removal. if not args[0]._dead: # Run this next piece of code 4 times. # "i" will start at 0 and becomes higher each iteration until it reaches 3. for i in range(4): # XYZ position of our sparks, we'll take the Spaz position as a base. pos = (args[0].node.position[0], # Let's spread the sparks across the body, assuming Spaz is standing straight. # We're gonna change the Y axis position, which is height. args[0].node.position[1] + i * 0.2, args[0].node.position[2]) # This function allows us to spawn particles like sparks and bomb shrapnel. # We're gonna use sparks here. ba.emitfx(position = pos, # Here we place our edited position. velocity=args[0].node.velocity, count=random.randrange(2, 5), # Random amount of sparks between 2 and 5 scale=3.0, spread=0.2, chunk_type='spark') # Make a Spaz death noise if we're not gibbed. if not args[0].shattered: death_sounds = args[0].node.death_sounds # 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 ba.playsound(sound, position=args[0].node.position) # Play the sound where our Spaz is # Delete our Spaz node immediately. # Removing stuff is weird and prone to errors, so we're gonna delay it. ba.timer(0.001, args[0].node.delete) # Let's mark our Spaz as dead, so he can't die again. # 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. args[0]._dead = True args[0].hitpoints = 0 # Set his health to zero. This value is independent from the health bar above his head. return # Worry no longer! We're not gonna remove all the base game code! # Here's where we bring it all back. # If I wanted to add extra code at the end of the base game's behavior, I would just put that at the beginning of my function. func(*args, **kwargs) return wrapper # 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. bastd.actor.spaz.Spaz.handlemessage = new_handlemessage(bastd.actor.spaz.Spaz.handlemessage)