extends Node var aggro_tables: Dictionary = {} var seconds_outside: Dictionary = {} func _ready() -> void: EventBus.damage_dealt.connect(_on_damage_dealt) EventBus.heal_requested.connect(_on_heal_requested) EventBus.entity_died.connect(_on_entity_died) EventBus.enemy_detected.connect(_on_enemy_detected) func _process(delta: float) -> void: for enemy in aggro_tables.keys(): if not is_instance_valid(enemy): aggro_tables.erase(enemy) seconds_outside.erase(enemy) continue _decay_aggro(enemy, delta) _update_target(enemy) func _decay_aggro(enemy: Node, delta: float) -> void: var table: Dictionary = aggro_tables[enemy] var base: BaseStats = Stats.get_base(enemy) var portal_radius: float = base.portal_radius if base is EnemyStats else 10.0 var aggro_decay: float = base.aggro_decay if base is EnemyStats else 1.0 var outside_portal := false if "portal" in enemy and enemy.portal and is_instance_valid(enemy.portal): var dist: float = enemy.global_position.distance_to(enemy.portal.global_position) if dist > portal_radius: outside_portal = true seconds_outside[enemy] = seconds_outside.get(enemy, 0.0) + delta else: seconds_outside[enemy] = 0.0 for player in table.keys(): var decay: float = aggro_decay * delta if outside_portal: var bonus: float = table[player] * 0.01 * pow(2, seconds_outside.get(enemy, 0.0)) * delta decay += bonus table[player] -= decay if not outside_portal and "portal" in enemy and enemy.portal and is_instance_valid(player): var player_dist: float = player.global_position.distance_to(enemy.portal.global_position) if player_dist <= portal_radius and table[player] < 1.0: table[player] = 1.0 if table[player] <= 0: table.erase(player) func _update_target(enemy: Node) -> void: if not "state" in enemy: return var table: Dictionary = aggro_tables[enemy] var top: Node = _get_top_target(table) if top and top != enemy.target: enemy.target = top if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN: enemy.state = enemy.State.CHASE elif not top and enemy.state != enemy.State.IDLE and enemy.state != enemy.State.RETURN: enemy.target = null enemy.state = enemy.State.RETURN func _add_aggro(enemy: Node, player: Node, amount: float) -> void: if enemy not in aggro_tables: aggro_tables[enemy] = {} if player in aggro_tables[enemy]: aggro_tables[enemy][player] += amount else: aggro_tables[enemy][player] = amount func _get_top_target(table: Dictionary) -> Node: var top: Node = null var top_val := 0.0 for player in table: if is_instance_valid(player) and table[player] > top_val: top_val = table[player] top = player return top func _alert_nearby(enemy: Node, target: Node) -> void: var base: BaseStats = Stats.get_base(enemy) var alert_radius: float = base.alert_radius if base is EnemyStats else 3.0 var enemies := enemy.get_tree().get_nodes_in_group("enemies") for other in enemies: if other != enemy and is_instance_valid(other) and "state" in other: if other.state == other.State.IDLE: var dist: float = enemy.global_position.distance_to(other.global_position) if dist <= alert_radius: _add_aggro(other, target, 1.0) other.target = target other.state = other.State.CHASE EventBus.enemy_engaged.emit(other, target) func _on_enemy_detected(enemy: Node, player: Node) -> void: if not enemy.is_in_group("enemies"): return if "state" in enemy: if enemy.state == enemy.State.CHASE or enemy.state == enemy.State.ATTACK: return _add_aggro(enemy, player, 1.0) if "state" in enemy: enemy.target = player enemy.state = enemy.State.CHASE EventBus.enemy_engaged.emit(enemy, player) _alert_nearby(enemy, player) func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void: if not target.is_in_group("enemies") and not target.is_in_group("portals"): return var multiplier := 1.0 var role: Node = attacker.get_node_or_null("Role") if role and role.current_role == 0: multiplier = 2.0 _add_aggro(target, attacker, amount * multiplier) func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void: if not healer.is_in_group("player"): return for enemy in aggro_tables: if is_instance_valid(enemy) and healer in aggro_tables[enemy]: _add_aggro(enemy, healer, amount * 0.5) func _on_entity_died(entity: Node) -> void: aggro_tables.erase(entity) seconds_outside.erase(entity) for enemy in aggro_tables: if is_instance_valid(enemy): aggro_tables[enemy].erase(entity) if "target" in enemy and entity == enemy.target: enemy.target = null enemy.state = enemy.State.RETURN