extends Node var _active: Dictionary = {} var _passive_auras: Dictionary = {} func _ready() -> void: EventBus.entity_died.connect(_on_died) EventBus.entity_deregistered.connect(_on_dereg) if multiplayer.multiplayer_peer != null and not multiplayer.is_server() and not (multiplayer.multiplayer_peer is OfflineMultiplayerPeer): set_physics_process(false) else: set_physics_process(true) var _heavy_accum: float = 0.0 func _physics_process(delta: float) -> void: if not multiplayer.is_server() and multiplayer.multiplayer_peer != null: return _tick_durations(delta) _heavy_accum += delta if _heavy_accum < 0.20: return _heavy_accum = 0.0 _propagate_auras() _recompute_buffs() func apply(target: Node, effect: Effect, source: Node = null) -> void: if not is_instance_valid(target): return if not target in _active: _active[target] = [] for entry in _active[target]: if entry.effect.effect_name == effect.effect_name and entry.source == source: entry.remaining = effect.duration entry.tick_timer = effect.tick_interval EventBus.effect_applied.emit(target, effect, source) return _active[target].append({ "effect": effect, "source": source, "remaining": effect.duration, "tick_timer": effect.tick_interval }) EventBus.effect_applied.emit(target, effect, source) func remove_by_source(target: Node, source: Node) -> void: if not target in _active: return var keep: Array = [] for entry in _active[target]: if entry.source == source: EventBus.effect_expired.emit(target, entry.effect) else: keep.append(entry) _active[target] = keep func apply_passive_aura(player: Node, ability: Ability) -> void: if ability == null or ability.passive_stat == &"": return _passive_auras[player] = ability for target in get_tree().get_nodes_in_group("player"): remove_by_source(target, player) func _propagate_auras() -> void: for source in _passive_auras.keys(): if not is_instance_valid(source): _passive_auras.erase(source) continue var ability: Ability = _passive_auras[source] var effect := Effect.new() effect.effect_name = StringName("aura_%s_%s" % [ability.passive_stat, source.name]) effect.type = Effect.Type.AURA effect.stat = ability.passive_stat effect.value = ability.passive_value effect.is_multiplier = true effect.duration = 0.5 effect.aura_radius = ability.passive_radius var src_pos: Vector3 = (source as Node3D).global_position for target in get_tree().get_nodes_in_group("player"): if not Stats.has(target): continue var d: float = (target as Node3D).global_position.distance_to(src_pos) if d <= ability.passive_radius: apply(target, effect, source) func _tick_durations(delta: float) -> void: for target in _active.keys(): if not is_instance_valid(target): _active.erase(target) continue var keep: Array = [] for entry in _active[target]: if entry.effect.duration > 0.0: entry.remaining -= delta if entry.remaining <= 0.0: EventBus.effect_expired.emit(target, entry.effect) continue if entry.effect.tick_interval > 0.0: entry.tick_timer -= delta if entry.tick_timer <= 0.0: entry.tick_timer = entry.effect.tick_interval if entry.effect.type == Effect.Type.DOT: EventBus.damage_requested.emit(entry.source, target, entry.effect.value, entry.effect.element) elif entry.effect.type == Effect.Type.HOT: EventBus.heal_requested.emit(entry.source, target, entry.effect.value) keep.append(entry) _active[target] = keep func _recompute_buffs() -> void: for target in _active.keys(): if not is_instance_valid(target) or not Stats.has(target): continue var bonus: Dictionary = {} var mult: Dictionary = {} for entry in _active[target]: var e: Effect = entry.effect if e.stat == &"": continue if e.is_multiplier: mult[e.stat] = mult.get(e.stat, 0.0) + e.value else: bonus[e.stat] = bonus.get(e.stat, 0.0) + e.value for stat in [&"buff_damage", &"buff_heal", &"buff_shield"]: var base: float = 1.0 var v: float = base + mult.get(stat, 0.0) + bonus.get(stat, 0.0) Stats.set_stat(target, stat, v) EventBus.buff_changed.emit(target, stat, v) func _on_died(entity: Node) -> void: if entity in _active: for entry in _active[entity]: EventBus.effect_expired.emit(entity, entry.effect) _active.erase(entity) _passive_auras.erase(entity) func _on_dereg(entity: Node) -> void: _active.erase(entity) _passive_auras.erase(entity)