Utility minigames

This commit is contained in:
TheMikirog 2022-11-13 19:41:14 +01:00 committed by GitHub
parent d5bb3c369a
commit 1fb3293358
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 328 additions and 0 deletions

View 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__)

View 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)

View 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)