214 lines
6.2 KiB
GDScript
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")
|