extends Node var tracker: Node var config: AggroConfig var last_damage_time: Dictionary = {} func process(delta: float) -> void: _update_combat_timers(delta) for enemy in tracker.aggro_tables.keys(): if not is_instance_valid(enemy): tracker.aggro_tables.erase(enemy) tracker.players_in_range.erase(enemy) continue _decay_aggro(enemy, delta) tracker.update_target(enemy) func _update_combat_timers(delta: float) -> void: for player in last_damage_time.keys(): if not is_instance_valid(player): last_damage_time.erase(player) else: last_damage_time[player] += delta func _decay_aggro(enemy: Node, delta: float) -> void: var table: Dictionary = tracker.aggro_tables[enemy] var data_source: Node = tracker._get_data_source(enemy) var aggro_decay: float = 1.0 if data_source: var base: EnemyStats = data_source.get_base(enemy) if base: aggro_decay = base.aggro_decay for player in table.keys(): if is_in_combat(player): continue var time_since_combat: float = last_damage_time.get(player, config.combat_timeout) - config.combat_timeout var decay: float = aggro_decay * delta decay += _exponential_decay(table[player], time_since_combat, delta) table[player] -= decay if table[player] <= 0: table.erase(player) func reset_combat_timer(player: Node) -> void: last_damage_time[player] = 0.0 func is_in_combat(player: Node) -> bool: if tracker.is_player_in_any_range(player): return true return last_damage_time.get(player, config.combat_timeout + 1.0) < config.combat_timeout func _exponential_decay(aggro: float, time_outside: float, delta: float) -> float: if time_outside <= 0: return 0.0 return aggro * config.exponential_decay_factor * pow(2, time_outside) * delta func spread_aggro(source: Node, attacker: Node, amount: float) -> void: if not is_instance_valid(source): return var radius: float = tracker.get_alert_radius(source) for enemy in tracker.get_enemies_in_radius(source, radius): tracker.add_aggro(enemy, attacker, amount) func alert_nearby(enemy: Node, target: Node) -> void: var radius: float = tracker.get_alert_radius(enemy) for other in tracker.get_enemies_in_radius(enemy, radius): var data_source: Node = tracker._get_data_source(other) if data_source and data_source.get_stat(other, "state") == 0: tracker.add_aggro(other, target, 1.0) data_source.set_stat(other, "target", target) data_source.set_stat(other, "state", 1) EventBus.enemy_engaged.emit(other, target) func erase_entity(entity: Node) -> void: last_damage_time.erase(entity)