Files
2026-07-06 22:59:47 -07:00

214 lines
6.2 KiB
GDScript

class_name EffectContainer
extends Node
signal effect_added(effect_id: StringName)
signal effect_removed(effect_id: StringName)
const EffectInstanceScript := preload("res://resources/effects/effect_instance.gd")
@export var beats_per_measure := 4
@export var initial_effects: Array[Resource] = []
var effects: Array[RefCounted] = []
func _ready() -> void:
var bus := _event_bus_or_null()
if bus != null and bus.has_signal("beat_ticked") and not bus.is_connected("beat_ticked", _on_beat_ticked):
bus.connect("beat_ticked", _on_beat_ticked)
for definition: Resource in initial_effects:
add_effect(definition, &"loadout")
func _exit_tree() -> void:
var bus := _event_bus_or_null()
if bus != null and bus.has_signal("beat_ticked") and bus.is_connected("beat_ticked", _on_beat_ticked):
bus.disconnect("beat_ticked", _on_beat_ticked)
func _process(delta: float) -> void:
tick_time(delta)
func add_effect(definition: Resource, source: Variant = &"-") -> void:
if definition == null:
return
var effect_id := StringName(str(definition.get("id")))
var max_stacks := int(definition.get("max_stacks"))
for effect: RefCounted in effects:
if StringName(str(effect.definition.get("id"))) == effect_id:
effect.stacks = mini(effect.stacks + 1, max(1, max_stacks))
effect.remaining = float(definition.get("duration"))
effect.source = source
effect_added.emit(effect_id)
return
effects.append(EffectInstanceScript.create(definition, source))
effect_added.emit(effect_id)
func set_effect_stacks(effect_id: StringName, stacks: int) -> void:
if stacks <= 0:
remove_effect(effect_id)
return
for effect: RefCounted in effects:
if StringName(str(effect.definition.get("id"))) == effect_id:
var max_stacks := maxi(1, int(effect.definition.get("max_stacks")))
effect.stacks = mini(stacks, max_stacks)
return
func effect_stacks(effect_id: StringName) -> int:
for effect: RefCounted in effects:
if StringName(str(effect.definition.get("id"))) == effect_id:
return int(effect.stacks)
return 0
func remove_effect(effect_id: StringName) -> void:
for index: int in range(effects.size() - 1, -1, -1):
var effect: RefCounted = effects[index]
if StringName(str(effect.definition.get("id"))) == effect_id:
effects.remove_at(index)
effect_removed.emit(effect_id)
func tick_time(delta: float) -> void:
for effect: RefCounted in effects:
effect.tick_time(delta)
_prune_expired()
func tick_beats(beats: float) -> void:
for effect: RefCounted in effects:
effect.tick_beats(beats)
_prune_expired()
func active_count() -> int:
return effects.size()
func active_effect_ids() -> Array[StringName]:
var ids: Array[StringName] = []
for effect: RefCounted in effects:
ids.append(StringName(str(effect.definition.get("id"))))
return ids
func active_effect_summaries() -> Array[Dictionary]:
var summaries: Array[Dictionary] = []
for effect: RefCounted in effects:
summaries.append({
"id": StringName(str(effect.definition.get("id"))),
"duration_type": StringName(str(effect.definition.get("duration_type"))),
"remaining": float(effect.remaining),
"stacks": int(effect.stacks),
"source": _source_label(effect.source),
})
return summaries
func dispatch_event(event_name: StringName, context: Dictionary = {}) -> void:
var triggered: Array[Resource] = []
for effect: RefCounted in effects:
if StringName(str(effect.definition.get("trigger_event"))) != event_name:
continue
if effect.has_method("matches_event_context") and not bool(effect.call("matches_event_context", context)):
continue
var next_effects = effect.definition.get("trigger_effects")
if not next_effects is Array:
continue
for definition: Resource in next_effects:
if definition != null:
triggered.append(definition)
for effect: RefCounted in effects:
if effect.has_method("tick_event"):
effect.call("tick_event", event_name, context)
_prune_expired()
for definition: Resource in triggered:
add_effect(definition)
func damage_mult(action: Resource = null) -> float:
return stat_multiplier(&"damage_mult", action)
func cost_mult(action: Resource = null) -> float:
return stat_multiplier(&"cost_mult", action)
func move_mult(action: Resource = null) -> float:
return stat_multiplier(&"move_mult", action)
func defense_modifiers() -> Array[Resource]:
return _active_modifiers("defense_modifiers")
func action_rule_modifiers() -> Array[Resource]:
return _active_modifiers("action_rule_modifiers")
func stat_multiplier(stat: StringName, _action: Resource = null) -> float:
var multiplier := 1.0
for effect: RefCounted in effects:
for modifier: Resource in _stat_modifiers(effect):
if StringName(str(modifier.get("stat"))) != stat:
continue
if str(modifier.get("operation")) == "add":
multiplier += float(modifier.get("value")) * maxi(1, int(effect.stacks))
else:
multiplier *= float(modifier.get("value"))
return multiplier
func _stat_modifiers(effect: RefCounted) -> Array:
var modifiers = effect.definition.get("stat_modifiers")
return modifiers if modifiers is Array else []
func _active_modifiers(property_name: String) -> Array[Resource]:
var result: Array[Resource] = []
for effect: RefCounted in effects:
var modifiers = effect.definition.get(property_name)
if not modifiers is Array:
continue
for modifier: Resource in modifiers:
if modifier != null:
result.append(modifier)
return result
func _source_label(source: Variant) -> StringName:
if source == null:
return &"-"
if source is StringName:
return source
if source is String:
return StringName(source)
if source is Node:
return StringName((source as Node).name)
return StringName(str(source))
func _prune_expired() -> void:
for index: int in range(effects.size() - 1, -1, -1):
var effect: RefCounted = effects[index]
if effect.is_expired():
var effect_id := StringName(str(effect.definition.get("id")))
effects.remove_at(index)
effect_removed.emit(effect_id)
func _on_beat_ticked(beat_index: int) -> void:
tick_beats(1.0)
if beats_per_measure > 0 and beat_index % beats_per_measure == 0:
dispatch_event(&"on_measure_start", {"beat_index": beat_index})
dispatch_event(&"on_beat", {"beat_index": beat_index})
func _event_bus_or_null() -> Node:
if not is_inside_tree():
return null
return get_tree().root.get_node_or_null("EventBus")