Using Modifiers as buffs and debuffs
All Projectiles comes with a built-in method to temporarily modify your Projectiles and Attacks.
Timed Modifiers are RefCouteds used to modify objects over a period of time. You can use them to apply buffs and debuffs to your Projectiles and Attacks.

Creating and using Modifiers
As you can see here, the TimedModifier constructor can take a wide range of arguments, the most important of which are:
name
: Modifier global identifier.duration
: Active time span of the modifier in seconds.on_enter
: Optional Callable to overwrite the set on_enter modifier property.on_exit
: Optional Callable to overwrite the set on_exit modifier property.
- Working with only these parameters, we can create simple Projectile buffs:
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_projectile_modifier(0, Projectile2DModifier.new("PROJ_BUFF", 1.0, on_projectile_buff_enter, on_projectile_buff_exit))
func on_projectile_buff_enter(_modifier: Projectile2DModifier, proj: Projectile2D) -> void:
proj.instances += 2
proj.proj_spread = Projectile2D.ProjectileSpread.ANGULAR
proj.angular_spread = 90
func on_projectile_buff_exit(_modifier: Projectile2DModifier, proj: Projectile2D) -> void:
proj.instances -= 2
proj.proj_spread = proj.resource.proj_spread
proj.angular_spread = proj.resource.angular_spread

- Attack buffs:
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_attack_modifier(0, Attack2DModifier.new("ATTACK_BUFF", 1.0, on_attack_buff_enter, on_attack_buff_exit))
func on_attack_buff_enter(_modifier: Attack2DModifier, attack: Attack2D) -> void:
var previous_state_duration: float = attack.get_current_state_duration()
attack.attack_charge_time *= 0.15
attack.attack_anticipate_time *= 0.15
attack.attack_duration_time *= 0.15
attack.attack_recovery_time *= 0.15
attack.current_state_lifetime += (attack.get_current_state_duration() - previous_state_duration)
func on_attack_buff_exit(_modifier: Attack2DModifier, attack: Attack2D) -> void:
attack.attack_charge_time = attack.resource.attack_charge_time
attack.attack_anticipate_time = attack.resource.attack_anticipate_time
attack.attack_duration_time = attack.resource.attack_duration_time
attack.attack_recovery_time = attack.resource.attack_recovery_time

- Or both:
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_projectile_modifier(0, Projectile2DModifier.new("PROJ_BUFF", 1.0, on_projectile_buff_enter, on_projectile_buff_exit))
projectile_manager.add_attack_modifier(0, Attack2DModifier.new("ATTACK_BUFF", 1.0, on_attack_buff_enter, on_attack_buff_exit))
...

Understanding Modifier verification
By default, all your modifiers will check if the object they are targeting already has a modifier with the same name. If that is the case, the modifier will not be applied or reapplied as long as the previous one is active. Fortunately, you can modify this behavior by assigning your own custom validation method.
Here is a collection of some useful examples of verification functions:
- Refreshable Buff
...
func _process(_delta: float) -> void:
if Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT):
var destination = get_global_mouse_position()
projectile_manager.request_execution(0, 0, position, destination)
if Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
projectile_manager.add_projectile_modifier(0, Projectile2DModifier.new("PROJ_BUFF", 1.0, on_projectile_buff_enter, on_projectile_buff_exit, Callable(), refresh_projectile_validation))
projectile_manager.add_attack_modifier(0, Attack2DModifier.new("ATTACK_BUFF", 1.0, on_attack_buff_enter, on_attack_buff_exit, Callable(), refresh_attack_validation))
...
func refresh_projectile_validation(_modifier: Projectile2DModifier, _proj: Projectile2D) -> bool:
if (_proj.projectile_modifiers.has(_modifier.name)):
_proj.projectile_modifiers[_modifier.name].lifetime = _modifier.duration
return false
return true
func refresh_attack_validation(_modifier: Attack2DModifier, _attack: Attack2D) -> bool:
if (_attack.attack_modifiers.has(_modifier.name)):
_attack.attack_modifiers[_modifier.name].lifetime = _modifier.duration
return false
return true

- Stackable Buff
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_projectile_modifier(0, Projectile2DModifier.new("PROJ_BUFF", 2.0, on_projectile_buff_enter, on_projectile_buff_exit, Callable(), stackable_projectile_validation))
projectile_manager.add_attack_modifier(0, Attack2DModifier.new("ATTACK_BUFF", 2.0, on_attack_buff_enter, on_attack_buff_exit, Callable(), stackable_attack_validation))
...
func stackable_projectile_validation(_modifier: Projectile2DModifier, _proj: Projectile2D) -> bool:
if !(_proj.projectile_modifiers.has(_modifier.name)):
return true
# I would recommend using (_modifier.stacks < _modifier.global_properties["MAX_STACKS"])
if (_modifier.stacks < 3):
return true
return false
func stackable_attack_validation(_modifier: Attack2DModifier, _attack: Attack2D) -> bool:
if !(_attack.attack_modifiers.has(_modifier.name)):
return true
# I would recommend using (_modifier.stacks < _modifier.global_properties["MAX_STACKS"])
if (_modifier.stacks < 3):
return true
return false

- Stackable and Refreshable Buff
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_projectile_modifier(0, Projectile2DModifier.new("PROJ_BUFF", 2.0, on_projectile_buff_enter, on_projectile_buff_exit, Callable(), stackable_and_refreshable_projectile_validation))
projectile_manager.add_attack_modifier(0, Attack2DModifier.new("ATTACK_BUFF", 2.0, on_attack_buff_enter, on_attack_buff_exit, Callable(), stackable_and_refreshable_attack_validation))
...
func stackable_and_refreshable_projectile_validation(_modifier: Projectile2DModifier, _proj: Projectile2D) -> bool:
if !(_proj.projectile_modifiers.has(_modifier.name)):
return true
# I would recommend using (_modifier.stacks < _modifier.global_properties["MAX_STACKS"])
if (_modifier.stacks < 3):
return true
for i: int in _modifier.copies.size():
_modifier.copies[i].lifetime = _modifier.copies[i].duration
return false
func stackable_and_refreshable_attack_validation(_modifier: Attack2DModifier, _attack: Attack2D) -> bool:
if !(_attack.attack_modifiers.has(_modifier.name)):
return true
# I would recommend using (_modifier.stacks < _modifier.global_properties["MAX_STACKS"])
if (_modifier.stacks < 3):
return true
for i: int in _modifier.copies.size():
_modifier.copies[i].lifetime = _modifier.copies[i].duration
return false
return false

Integration with the Global Database
Timed Modifiers are also fully incorporated into the Global Database.
...
func _ready() -> void:
APDatabase.set_modifier(Projectile2DModifier.new("PROJ_BUFF", 2.0, on_projectile_buff_enter, on_projectile_buff_exit, Callable(), stackable_and_refreshable_projectile_validation))
APDatabase.set_modifier(Attack2DModifier.new("ATTACK_BUFF", 2.0, on_attack_buff_enter, on_attack_buff_exit, Callable(), stackable_and_refreshable_attack_validation))
...
func _input(event):
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_RIGHT and event.pressed:
projectile_manager.add_projectile_modifier(0, APDatabase.get_projectile_2d_modifier("PROJ_BUFF"))
projectile_manager.add_attack_modifier(0, APDatabase.get_attack_2d_modifier("ATTACK_BUFF"))
