mirror of
https://github.com/bombsquad-community/plugin-manager.git
synced 2025-10-08 14:54:36 +00:00
Create tnt_respawn_text
This commit is contained in:
parent
5bcacf251a
commit
101b95175a
1 changed files with 212 additions and 0 deletions
212
plugins/utilities/tnt_respawn_text
Normal file
212
plugins/utilities/tnt_respawn_text
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
# ba_meta require api 7
|
||||
|
||||
"""
|
||||
TNT Respawn Text by TheMikirog
|
||||
Version 1
|
||||
|
||||
Shows when a TNT box is about to respawn with non-intrusive text.
|
||||
|
||||
Heavily commented for easy modding learning!
|
||||
|
||||
No Rights Reserved
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
# Let's import everything we need and nothing more.
|
||||
import ba
|
||||
import bastd
|
||||
import math
|
||||
import random
|
||||
from bastd.actor.bomb import Bomb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
"""
|
||||
Turns out TNT respawning got changed during the 1.5 update.
|
||||
At first I planned to make an accurate timer text that just counts down seconds to respawn.
|
||||
However, to prevent timer stacking, Eric decided to update the timer every 1.1s seconds instead of the standard 1.0s.
|
||||
This makes it a pain to mod, so instead I had to make some compromises and go for a percentage charge instead.
|
||||
|
||||
The goal here is to make it easier to intuit when the box respawns, so you can still play around it.
|
||||
Percentage until respawn is still more helpful than absolutely nothing.
|
||||
I wanted to keep the original TNT box's respawn design here, so I didn't touch the timer.
|
||||
I prefer adding onto existing behavior than editing existing code.
|
||||
This mod is supposed to be a quality of life thing after all.
|
||||
"""
|
||||
|
||||
|
||||
# ba_meta export plugin
|
||||
class TNTRespawnText(ba.Plugin):
|
||||
|
||||
# This clamping function will make sure a certain value can't go above or below a certain threshold.
|
||||
# We're gonna need this functionality in just a bit.
|
||||
def clamp(num, min_value, max_value):
|
||||
num = max(min(num, max_value), min_value)
|
||||
return num
|
||||
|
||||
# This function gets called every time the TNT dies. Doesn't matter how.
|
||||
# Explosions, getting thrown out of bounds, stuff.
|
||||
# I want the text appearing animation to start as soon as the TNT box blows up.
|
||||
def on_tnt_exploded(self):
|
||||
self.tnt_has_callback = False
|
||||
self._respawn_text.color = (1.0, 1.0, 1.0)
|
||||
ba.animate(
|
||||
self._respawn_text,
|
||||
'opacity',
|
||||
{
|
||||
0: 0.0,
|
||||
self._respawn_time * 0.5: 0.175,
|
||||
self._respawn_time: 0.4
|
||||
},
|
||||
)
|
||||
|
||||
# We're gonna use the magic of decorators to expand the original code with new stuff.
|
||||
# This even works with other mods too! Don't replace functions, use decorators!
|
||||
# Anyway we're gonna access the TNTSpawner class' init function.
|
||||
def new_init(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
# The update function is not only called by a timer, but also manually
|
||||
# during the original init function's execution.
|
||||
# This means the code expects a variable that doesn't exist.
|
||||
# Let's make it prematurely.
|
||||
# args[0] is "self" in the original game code.
|
||||
args[0]._respawn_text = None
|
||||
|
||||
# This is where the original game's code is executed.
|
||||
func(*args, **kwargs)
|
||||
|
||||
# For each TNT we make we want to add a callback.
|
||||
# It's basically a flag that tells the TNT to call a function.
|
||||
# We don't want to add several of the same flag at once.
|
||||
# We set this to True every time we add a callback.
|
||||
# We check for this variable before adding a new one.
|
||||
args[0].tnt_has_callback = True
|
||||
|
||||
# Let's make the text.
|
||||
# We tap into the spawner position in order to decide where the text should be.
|
||||
respawn_text_position = (args[0]._position[0],
|
||||
args[0]._position[1] - 0.4,
|
||||
args[0]._position[2])
|
||||
args[0]._respawn_text = ba.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
'text': "", # we'll set the text later
|
||||
'in_world': True,
|
||||
'position': respawn_text_position,
|
||||
'shadow': 1.0,
|
||||
'flatness': 1.0,
|
||||
'color': (1.0, 1.0, 1.0),
|
||||
'opacity': 0.0,
|
||||
'scale': 0.0225,
|
||||
'h_align': 'center',
|
||||
'v_align': 'center',
|
||||
},
|
||||
)
|
||||
# Here we add our callback.
|
||||
# Timers don't like calling functions that are outside of the game's "universe".
|
||||
# If we call the function directly, we get a PyCallable error.
|
||||
# We make a dummy function to avoid this.
|
||||
def tnt_callback():
|
||||
TNTRespawnText.on_tnt_exploded(args[0])
|
||||
|
||||
# One disadvantage of the documentation is that it doesn't tell you all functions related to the node system.
|
||||
# To learn about all possible atttributes and functions you just gotta explore the code and experiment.
|
||||
# This add_death_action function of the node system is used in the original game
|
||||
# to let the player know if the node got removed.
|
||||
# For bombs that would be explosions or when they go out of bounds.
|
||||
# This is used to increase the owner's bomb count by one.
|
||||
# Here however we'll use this function to manipulate our text logic.
|
||||
# We want to animate our text the moment the TNT box dies.
|
||||
args[0]._tnt.node.add_death_action(tnt_callback)
|
||||
return wrapper
|
||||
# Let's replace the original init function with our modified version.
|
||||
bastd.actor.bomb.TNTSpawner.__init__ = new_init(bastd.actor.bomb.TNTSpawner.__init__)
|
||||
|
||||
# Our modified update function.
|
||||
# This gets called every 1.1s. Check the TNTSpawner class in the game's code for details.
|
||||
def new_update(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
|
||||
# Check if our TNT box is still kickin'.
|
||||
tnt_alive = args[0]._tnt is not None and args[0]._tnt.node
|
||||
|
||||
func(*args, **kwargs) # original code
|
||||
|
||||
# The first time this code executes, nothing happens.
|
||||
# However once our text node is created properly, let's do some work.
|
||||
if args[0]._respawn_text:
|
||||
|
||||
# Let's make a value that will represent percentage.
|
||||
# 0 means timer started and 100 means ready.
|
||||
value = args[0]._wait_time / args[0]._respawn_time
|
||||
|
||||
# It's annoying when the number jumps from 99% to 100% and it's delayed.
|
||||
# Let's make sure this happens less often.
|
||||
# I turned a linear curve into an exponential one.
|
||||
value = math.pow(value - 0.001, 2)
|
||||
|
||||
# Let's turn the value into a percentage.
|
||||
value = math.floor(value * 100)
|
||||
|
||||
# Let's make sure it's actually between 0 and 100.
|
||||
value = TNTRespawnText.clamp(value, 0, 100)
|
||||
|
||||
# Let's finish it off with a percentage symbol and preso!
|
||||
args[0]._respawn_text.text = str(value)+"%"
|
||||
|
||||
# When the timer ticks, we do different things depending on the time and the state of our TNT box.
|
||||
if not tnt_alive:
|
||||
# Code goes here if we don't have a TNT box and we reached 100%.
|
||||
if args[0]._tnt is None or args[0]._wait_time >= args[0]._respawn_time and args[0]._respawn_text:
|
||||
# Animate the text "bounce" to draw attention
|
||||
ba.animate(
|
||||
args[0]._respawn_text,
|
||||
'scale',
|
||||
{
|
||||
0: args[0]._respawn_text.scale * 1.2,
|
||||
0.3: args[0]._respawn_text.scale * 1.05,
|
||||
0.6: args[0]._respawn_text.scale * 1.025,
|
||||
1.1: args[0]._respawn_text.scale
|
||||
},
|
||||
)
|
||||
# Fade the text away
|
||||
ba.animate(
|
||||
args[0]._respawn_text,
|
||||
'opacity',
|
||||
{
|
||||
0: args[0]._respawn_text.opacity,
|
||||
1.1: 0.0
|
||||
},
|
||||
)
|
||||
# Make sure it says 100%, because our value we calculated earlier might not be accurate at that point.
|
||||
args[0]._respawn_text.text = "100%"
|
||||
|
||||
# Make our text orange.
|
||||
args[0]._respawn_text.color = (1.0, 0.75, 0.5)
|
||||
|
||||
# Make some sparks to draw the eye.
|
||||
ba.emitfx(
|
||||
position=args[0]._position,
|
||||
count=int(5.0 + random.random() * 10),
|
||||
scale=0.8,
|
||||
spread=1.25,
|
||||
chunk_type='spark',
|
||||
)
|
||||
# What if we still have our TNT box?
|
||||
else:
|
||||
# If the TNT box is fresly spawned spawned earlier in the function, chances are it doesn't have a callback.
|
||||
# If it has, ignore. Otherwise let's add it.
|
||||
# Cloning code that already exists in init is not very clean, but that'll do.
|
||||
if args[0].tnt_has_callback: return
|
||||
def tnt_callback():
|
||||
TNTRespawnText.on_tnt_exploded(args[0])
|
||||
args[0]._tnt.node.add_death_action(tnt_callback)
|
||||
return wrapper
|
||||
|
||||
# Let's replace the original update function with our modified version.
|
||||
bastd.actor.bomb.TNTSpawner._update = new_update(bastd.actor.bomb.TNTSpawner._update)
|
||||
Loading…
Add table
Add a link
Reference in a new issue