extends Node var active_buffs: Dictionary = {} func _ready() -> void: EventBus.effect_requested.connect(_on_effect_requested) EventBus.role_changed.connect(_on_role_changed) EventBus.entity_died.connect(_on_entity_died) func _process(delta: float) -> void: for entity in active_buffs.keys(): if not is_instance_valid(entity): active_buffs.erase(entity) continue var entries: Array = active_buffs[entity] var i: int = entries.size() - 1 while i >= 0: var entry: Dictionary = entries[i] var effect: Effect = entry["effect"] if effect.duration > 0: entry["remaining"] -= delta if entry["remaining"] <= 0: var is_aura_buff: bool = entry.get("is_aura_buff", false) entries.remove_at(i) if not is_aura_buff: EventBus.effect_expired.emit(entity, effect) _recalc(entity) i -= 1 continue if effect.tick_interval > 0: entry["tick_timer"] -= delta if entry["tick_timer"] <= 0: entry["tick_timer"] += effect.tick_interval if not effect.is_multiplier and effect.type == Effect.Type.BUFF: var source: Node = entry["source"] if not is_instance_valid(source): source = entity EventBus.heal_requested.emit(source, entity, effect.value) i -= 1 func apply(target: Node, effect: Effect, source: Node) -> void: if effect.type != Effect.Type.BUFF and effect.type != Effect.Type.AURA: return if not active_buffs.has(target): active_buffs[target] = [] var replaced := false var entries: Array = active_buffs[target] for i in range(entries.size()): if entries[i]["effect"].effect_name == effect.effect_name: entries[i]["effect"] = effect entries[i]["source"] = source entries[i]["remaining"] = effect.duration entries[i]["tick_timer"] = effect.tick_interval replaced = true break if not replaced: entries.append({ "effect": effect, "source": source, "remaining": effect.duration, "tick_timer": effect.tick_interval, }) EventBus.effect_applied.emit(target, effect) if effect.is_multiplier: _recalc(target) func apply_aura_buff(target: Node, effect: Effect, source: Node) -> void: if not active_buffs.has(target): active_buffs[target] = [] var entry := { "effect": effect, "source": source, "remaining": effect.duration, "tick_timer": effect.tick_interval, "aura_source": source, "is_aura_buff": true, } active_buffs[target].append(entry) if effect.is_multiplier: _recalc(target) func has_aura_buff(target: Node, aura_name: String, source: Node) -> bool: if not active_buffs.has(target): return false for entry in active_buffs[target]: if entry["effect"].effect_name == aura_name and entry.get("aura_source") == source: return true return false func refresh_aura_buff(target: Node, aura_name: String, source: Node, duration: float) -> void: if not active_buffs.has(target): return for entry in active_buffs[target]: if entry["effect"].effect_name == aura_name and entry.get("aura_source") == source: entry["remaining"] = duration return func clear(entity: Node) -> void: active_buffs.erase(entity) _recalc(entity) func _on_effect_requested(target: Node, effect: Effect, source: Node) -> void: if effect.type == Effect.Type.BUFF or effect.type == Effect.Type.AURA: apply(target, effect, source) func _on_entity_died(entity: Node) -> void: active_buffs.erase(entity) _recalc(entity) func _on_role_changed(player: Node, _role_type: int) -> void: _remove_permanent(player) func _remove_permanent(entity: Node) -> void: if not active_buffs.has(entity): return var entries: Array = active_buffs[entity] var i: int = entries.size() - 1 while i >= 0: if entries[i]["effect"].duration < 0: EventBus.effect_expired.emit(entity, entries[i]["effect"]) entries.remove_at(i) i -= 1 _recalc(entity) func _recalc(entity: Node) -> void: var mults := { "damage": 1.0, "heal": 1.0, "shield": 1.0 } if active_buffs.has(entity): for entry in active_buffs[entity]: var effect: Effect = entry["effect"] if effect.is_multiplier and effect.stat in mults: mults[effect.stat] += effect.value var player: Node = get_tree().get_first_node_in_group("player") if entity == player: PlayerData.buff_damage = mults["damage"] PlayerData.buff_heal = mults["heal"] PlayerData.buff_shield = mults["shield"] if PlayerData.base: var new_max: float = PlayerData.base.max_shield * mults["shield"] PlayerData.max_shield = new_max PlayerData.shield = min(PlayerData.shield, new_max) PlayerData.set_shield(PlayerData.shield) EventBus.buff_changed.emit(entity, "damage", mults["damage"])