extends Node const COMBAT_TIMEOUT: float = 5.0 const TANK_MULT: float = 2.0 const HEAL_MULT: float = 0.5 const SPREAD: float = 0.5 const DECAY_PER_SEC: float = 1.0 var aggro: Dictionary = {} var combat_timers: Dictionary = {} func _ready() -> void: EventBus.damage_dealt.connect(_on_damage_dealt) EventBus.heal_requested.connect(_on_heal_requested) EventBus.entity_died.connect(_on_died) EventBus.entity_deregistered.connect(_on_dereg) EventBus.enemy_detected.connect(_on_enemy_detected) 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 _accum: float = 0.0 func _physics_process(delta: float) -> void: if not multiplayer.is_server() and multiplayer.multiplayer_peer != null: return _accum += delta if _accum < 0.20: return var dt: float = _accum _accum = 0.0 for enemy in aggro.keys(): if not is_instance_valid(enemy): continue var t: float = combat_timers.get(enemy, 0.0) if t > 0.0: combat_timers[enemy] = max(0.0, t - dt) else: var table: Dictionary = aggro[enemy] var to_remove: Array = [] for player in table.keys(): table[player] = max(0.0, table[player] - DECAY_PER_SEC * dt) if table[player] <= 0.0: to_remove.append(player) for p in to_remove: table.erase(p) if not aggro[enemy].is_empty(): EventBus.enemy_engaged.emit(enemy, _top_target(enemy)) func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void: if not is_instance_valid(target) or not is_instance_valid(attacker): return if target.is_in_group("enemies"): var role: int = int(Stats.get_stat(attacker, "role", GameState.ROLE_DAMAGE)) var mult: float = TANK_MULT if role == GameState.ROLE_TANK else 1.0 _add(target, attacker, amount * mult) _spread(target, attacker, amount * SPREAD) combat_timers[target] = COMBAT_TIMEOUT func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void: if not is_instance_valid(healer): return for enemy in aggro.keys(): if not is_instance_valid(enemy): continue if healer in aggro[enemy]: _add(enemy, healer, amount * HEAL_MULT) func _on_enemy_detected(enemy: Node, player: Node) -> void: _add(enemy, player, 1.0) combat_timers[enemy] = COMBAT_TIMEOUT func _on_died(entity: Node) -> void: aggro.erase(entity) combat_timers.erase(entity) for enemy in aggro.keys(): if entity in aggro[enemy]: aggro[enemy].erase(entity) func _on_dereg(entity: Node) -> void: _on_died(entity) func _add(enemy: Node, player: Node, amount: float) -> void: if not enemy in aggro: aggro[enemy] = {} aggro[enemy][player] = aggro[enemy].get(player, 0.0) + amount func _spread(enemy: Node, player: Node, amount: float) -> void: for other in aggro.keys(): if other == enemy or not is_instance_valid(other): continue if (other as Node3D).global_position.distance_to((enemy as Node3D).global_position) <= 10.0: _add(other, player, amount) func _top_target(enemy: Node) -> Node: if not enemy in aggro: return null var best: Node = null var best_v: float = -1.0 for p in aggro[enemy].keys(): if not is_instance_valid(p): continue if aggro[enemy][p] > best_v: best_v = aggro[enemy][p] best = p return best func target_for(enemy: Node) -> Node: return _top_target(enemy)