Files
mmo/systems/aggro_system.gd
Marek Le 2d4002bd3f refactor
2026-05-09 23:37:26 +02:00

111 lines
3.4 KiB
GDScript

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)