mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2025-10-08 14:54:36 +00:00
Utility minigames
This commit is contained in:
parent
d5bb3c369a
commit
1fb3293358
3 changed files with 328 additions and 0 deletions
85
plugins/utilities/bomb_radius_visualizer.py
Normal file
85
plugins/utilities/bomb_radius_visualizer.py
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
# ba_meta require api 7
|
||||||
|
|
||||||
|
"""
|
||||||
|
Bomb Radius Visualizer by TheMikirog
|
||||||
|
|
||||||
|
With this cutting edge technology, you precisely know
|
||||||
|
how close to the bomb you can tread. Supports modified blast radius values!
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
# Let's import everything we need and nothing more.
|
||||||
|
import ba
|
||||||
|
import bastd
|
||||||
|
from bastd.actor.bomb import Bomb
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ba_meta export plugin
|
||||||
|
class BombRadiusVisualizer(ba.Plugin):
|
||||||
|
|
||||||
|
# We use a decorator to add extra code to existing code, increasing mod compatibility.
|
||||||
|
# Here I'm defining a new bomb init function that'll be replaced.
|
||||||
|
def new_bomb_init(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" in our original bomb init 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):
|
||||||
|
# Here's where we execute the original game's code, so it's not lost.
|
||||||
|
# We want to add our code at the end of the existing code, so our code goes under that.
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
# Let's make a new node that's just a circle. It's the some one used in the Target Practice minigame.
|
||||||
|
# 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.
|
||||||
|
args[0].radius_visualizer = ba.newnode('locator',
|
||||||
|
owner=args[0].node, # Remove itself when the bomb node dies.
|
||||||
|
attrs={
|
||||||
|
'shape': 'circle',
|
||||||
|
'color': (1, 0, 0),
|
||||||
|
'opacity':0.05,
|
||||||
|
'draw_beauty': False,
|
||||||
|
'additive': False
|
||||||
|
})
|
||||||
|
# Let's connect our circle to the bomb.
|
||||||
|
args[0].node.connectattr('position', args[0].radius_visualizer, 'position')
|
||||||
|
|
||||||
|
# Let's do a fancy animation of that red circle growing into shape like a cartoon.
|
||||||
|
# We're gonna read our bomb's blast radius and use it to decide the size of our circle.
|
||||||
|
ba.animate_array(args[0].radius_visualizer, 'size', 1, {
|
||||||
|
0.0: [0.0],
|
||||||
|
0.2: [args[0].blast_radius * 2.2],
|
||||||
|
0.25: [args[0].blast_radius * 2.0]
|
||||||
|
})
|
||||||
|
|
||||||
|
# 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',
|
||||||
|
owner=args[0].node, # Remove itself when the bomb node dies.
|
||||||
|
attrs={
|
||||||
|
'shape': 'circleOutline',
|
||||||
|
'size':[args[0].blast_radius * 2.0], # Here's that bomb's blast radius value again!
|
||||||
|
'color': (1, 1, 0),
|
||||||
|
'draw_beauty': False,
|
||||||
|
'additive': True
|
||||||
|
})
|
||||||
|
# Attach the circle to the bomb.
|
||||||
|
args[0].node.connectattr('position', args[0].radius_visualizer_circle, 'position')
|
||||||
|
|
||||||
|
# Let's animate that circle too, but this time let's do the opacity.
|
||||||
|
ba.animate(
|
||||||
|
args[0].radius_visualizer_circle, 'opacity', {
|
||||||
|
0: 0.0,
|
||||||
|
0.4: 0.1
|
||||||
|
})
|
||||||
|
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.bomb.Bomb.__init__ = new_bomb_init(bastd.actor.bomb.Bomb.__init__)
|
||||||
|
|
||||||
133
plugins/utilities/quickturn.py
Normal file
133
plugins/utilities/quickturn.py
Normal file
|
|
@ -0,0 +1,133 @@
|
||||||
|
"""
|
||||||
|
Quickturn by TheMikirog
|
||||||
|
|
||||||
|
Super Smash Bros Melee's wavedash mechanic ported and tuned for BombSquad.
|
||||||
|
|
||||||
|
Sharp turns while running (releasing run button, changing direction, holding run again) are much faster with this mod, allowing for more dynamic, aggressive play.
|
||||||
|
Version 3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ba_meta require api 7
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import ba
|
||||||
|
import math
|
||||||
|
import bastd
|
||||||
|
from bastd.actor.spaz import Spaz
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# ba_meta export plugin
|
||||||
|
class Quickturn(ba.Plugin):
|
||||||
|
|
||||||
|
class FootConnectMessage:
|
||||||
|
"""Spaz started touching the ground"""
|
||||||
|
|
||||||
|
class FootDisconnectMessage:
|
||||||
|
"""Spaz stopped touching the ground"""
|
||||||
|
|
||||||
|
def wavedash(self) -> None:
|
||||||
|
if not self.node:
|
||||||
|
return
|
||||||
|
|
||||||
|
isMoving = abs(self.node.move_up_down) >= 0.5 or abs(self.node.move_left_right) >= 0.5
|
||||||
|
|
||||||
|
if self._dead or not self.grounded or not isMoving:
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.node.knockout > 0.0 or self.frozen or self.node.hold_node:
|
||||||
|
return
|
||||||
|
|
||||||
|
t_ms = ba.time(timeformat=ba.TimeFormat.MILLISECONDS)
|
||||||
|
assert isinstance(t_ms, int)
|
||||||
|
|
||||||
|
if t_ms - self.last_wavedash_time_ms >= self._wavedash_cooldown:
|
||||||
|
|
||||||
|
move = [self.node.move_left_right, -self.node.move_up_down]
|
||||||
|
vel = [self.node.velocity[0], self.node.velocity[2]]
|
||||||
|
|
||||||
|
move_length = math.hypot(move[0], move[1])
|
||||||
|
vel_length = math.hypot(vel[0], vel[1])
|
||||||
|
if vel_length < 0.6: return
|
||||||
|
move_norm = [m/move_length for m in move]
|
||||||
|
vel_norm = [v/vel_length for v in vel]
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
# https://easings.net/#easeInOutQuart
|
||||||
|
if turn_power < 0.55:
|
||||||
|
turn_power = 8 * turn_power * turn_power * turn_power * turn_power
|
||||||
|
else:
|
||||||
|
turn_power = 0.55 - pow(-2 * turn_power + 2, 4) / 2
|
||||||
|
if turn_power < 0.1: return
|
||||||
|
|
||||||
|
boost_power = math.sqrt(math.pow(vel[0],2) + math.pow(vel[1],2)) * 8
|
||||||
|
boost_power = min(pow(boost_power,8),160)
|
||||||
|
|
||||||
|
self.last_wavedash_time_ms = t_ms
|
||||||
|
|
||||||
|
# FX
|
||||||
|
ba.emitfx(position=self.node.position,
|
||||||
|
velocity=(vel[0]*0.5,-1,vel[1]*0.5),
|
||||||
|
chunk_type='spark',
|
||||||
|
count=5,
|
||||||
|
scale=boost_power / 160 * turn_power,
|
||||||
|
spread=0.25);
|
||||||
|
|
||||||
|
# Boost itself
|
||||||
|
pos = self.node.position
|
||||||
|
for i in range(6):
|
||||||
|
self.node.handlemessage('impulse',pos[0],0.2+pos[1]+i*0.1,pos[2],
|
||||||
|
0,0,0,
|
||||||
|
boost_power * turn_power,
|
||||||
|
boost_power * turn_power,0,0,
|
||||||
|
move[0],0,move[1])
|
||||||
|
|
||||||
|
def new_spaz_init(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
# args[0] = self
|
||||||
|
args[0]._wavedash_cooldown = 170
|
||||||
|
args[0].last_wavedash_time_ms = -9999
|
||||||
|
args[0].grounded = 0
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
bastd.actor.spaz.Spaz.__init__ = new_spaz_init(bastd.actor.spaz.Spaz.__init__)
|
||||||
|
|
||||||
|
def new_factory(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
func(*args, **kwargs)
|
||||||
|
|
||||||
|
args[0].roller_material.add_actions(
|
||||||
|
conditions=('they_have_material', bastd.gameutils.SharedObjects.get().footing_material),
|
||||||
|
actions=(('message', 'our_node', 'at_connect', Quickturn.FootConnectMessage),
|
||||||
|
('message', 'our_node', 'at_disconnect', Quickturn.FootDisconnectMessage)))
|
||||||
|
return wrapper
|
||||||
|
bastd.actor.spazfactory.SpazFactory.__init__ = new_factory(bastd.actor.spazfactory.SpazFactory.__init__)
|
||||||
|
|
||||||
|
def new_handlemessage(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if args[1] == Quickturn.FootConnectMessage:
|
||||||
|
args[0].grounded += 1
|
||||||
|
elif args[1] == Quickturn.FootDisconnectMessage:
|
||||||
|
if args[0].grounded > 0: args[0].grounded -= 1
|
||||||
|
|
||||||
|
func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
bastd.actor.spaz.Spaz.handlemessage = new_handlemessage(bastd.actor.spaz.Spaz.handlemessage)
|
||||||
|
|
||||||
|
def new_on_run(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if args[0]._last_run_value < args[1] and args[1] > 0.8:
|
||||||
|
Quickturn.wavedash(args[0])
|
||||||
|
func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
bastd.actor.spaz.Spaz.on_run = new_on_run(bastd.actor.spaz.Spaz.on_run)
|
||||||
110
plugins/utilities/ragdoll-b-gone.py
Normal file
110
plugins/utilities/ragdoll-b-gone.py
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
# 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)
|
||||||
Loading…
Add table
Add a link
Reference in a new issue