This commit is contained in:
Marek Lenczewski
2026-04-02 18:33:53 +02:00
parent 47f4fe3d90
commit c7e6f8f4b5
25 changed files with 301 additions and 157 deletions

View File

@@ -30,10 +30,11 @@ Der User kommuniziert auf Deutsch. Code und Variablen auf Englisch. Kommentare n
- `dungeon/` — Dungeon (.tscn + .gd): dungeon, dungeon_manager
- `hud/` — HUD (.tscn + .gd): hud
- `world/` — Hauptszene (.tscn + .gd): world, portal_spawner
- `systems/`10 Systeme: health, shield, damage, ability, cooldown, aggro, enemy_ai, respawn, spawn, buff
- `systems/`9 Systeme (health, shield, damage, ability, cooldown, enemy_ai, respawn, spawn, buff)
- `aggro/` — AggroSystem (aggro_system, aggro_tracker, aggro_decay, aggro_events)
- `autoload/` — EventBus (event_bus), Stats (stats), GameState (game_state)
- `components/` — Shared Components: healthbar
- `resources/stats/` — Stats-Klassen (.gd) + Daten (.tres): base_stats, player_stats, enemy_stats, boss_stats, portal_stats
- `resources/stats/` — Stats-Klassen (.gd) + Daten (.tres): base_stats, player_stats, enemy_stats, boss_stats, portal_stats, aggro_config
- `resources/roles/` — Ability/AbilitySet-Klassen (.gd) + pro Rolle (damage, tank, healer):
- `{rolle}/set.tres` — AbilitySet der Rolle
- `{rolle}/abilities/` — Abilities (single, aoe, utility, ult, passive)

View File

@@ -37,6 +37,7 @@ signal buff_changed(entity, stat, value)
# Gegner
signal enemy_engaged(enemy, target)
signal enemy_lost(enemy, player)
# Portal
signal portal_spawn(portal, enemies)

View File

@@ -11,7 +11,10 @@
[ext_resource type="Script" path="res://systems/damage_system.gd" id="damage_system"]
[ext_resource type="Script" path="res://systems/ability_system.gd" id="ability_system"]
[ext_resource type="Script" path="res://systems/cooldown_system.gd" id="cooldown_system"]
[ext_resource type="Script" path="res://systems/aggro_system.gd" id="aggro_system"]
[ext_resource type="Script" path="res://systems/aggro/aggro_system.gd" id="aggro_system"]
[ext_resource type="Script" path="res://systems/aggro/aggro_tracker.gd" id="aggro_tracker"]
[ext_resource type="Script" path="res://systems/aggro/aggro_decay.gd" id="aggro_decay"]
[ext_resource type="Script" path="res://systems/aggro/aggro_events.gd" id="aggro_events"]
[ext_resource type="Script" path="res://systems/enemy_ai_system.gd" id="enemy_ai_system"]
[ext_resource type="Script" path="res://systems/respawn_system.gd" id="respawn_system"]
[ext_resource type="Script" path="res://systems/spawn_system.gd" id="spawn_system"]
@@ -69,6 +72,15 @@ script = ExtResource("cooldown_system")
[node name="AggroSystem" type="Node" parent="Systems"]
script = ExtResource("aggro_system")
[node name="AggroTracker" type="Node" parent="Systems/AggroSystem"]
script = ExtResource("aggro_tracker")
[node name="AggroDecay" type="Node" parent="Systems/AggroSystem"]
script = ExtResource("aggro_decay")
[node name="AggroEvents" type="Node" parent="Systems/AggroSystem"]
script = ExtResource("aggro_events")
[node name="EnemyAISystem" type="Node" parent="Systems"]
script = ExtResource("enemy_ai_system")

View File

@@ -32,5 +32,6 @@ func _on_detection_area_body_entered(body: Node3D) -> void:
if body is CharacterBody3D and body.name == "Player":
EventBus.enemy_detected.emit(self, body)
func _on_detection_area_body_exited(_body: Node3D) -> void:
pass
func _on_detection_area_body_exited(body: Node3D) -> void:
if body is CharacterBody3D and body.name == "Player":
EventBus.enemy_lost.emit(self, body)

View File

@@ -34,7 +34,7 @@ height = 1.0
material = SubResource("StandardMaterial3D_1")
[sub_resource type="SphereShape3D" id="SphereShape3D_1"]
radius = 8.0
radius = 10.0
[node name="Enemy" type="CharacterBody3D"]
script = ExtResource("1")

34
plan.md
View File

@@ -8,11 +8,12 @@ portal/ — Portal + Gate (.tscn + .gd)
dungeon/ — Dungeon (.tscn + .gd)
hud/ — HUD (.tscn + .gd)
world/ — Hauptszene (.tscn + .gd)
systems/ — 10 Gameplay-Systeme (.gd)
systems/ — 9 Gameplay-Systeme (.gd)
aggro/ — AggroSystem (aggro_system, aggro_tracker, aggro_decay, aggro_events)
autoload/ — EventBus, Stats, GameState (.gd)
components/ — Shared UI: healthbar (.gd)
resources/
stats/ — Stats-Klassen (.gd) + Daten (.tres)
stats/ — Stats-Klassen (.gd) + Daten (.tres) + AggroConfig
roles/ — Ability/AbilitySet-Klassen (.gd) + Rollen-Daten
damage/ — set.tres + abilities/
tank/ — set.tres + abilities/
@@ -137,9 +138,12 @@ resources/
- Event: cooldown_tick
### DamageSystem (damage_system.gd)
- Reserviert für spätere Schadensberechnung (aktuell leer)
### AggroSystem (aggro_system.gd)
- Aggro-Tabellen, Decay, Zielwahl, Nearby-Alerting
- Listener: damage_dealt, heal_requested, entity_died, enemy_detected
### AggroSystem (systems/aggro/)
- Systemweite Werte in AggroConfig Resource (resources/stats/aggro_config.tres)
- aggro_system.gd — Parent, Config halten, Children verdrahten
- aggro_tracker.gd — Aggro-Tabellen, Players-in-Range, Zielwahl, Radius-Helper
- aggro_decay.gd — Combat-Timer, Decay-Berechnung, Spread, Alert
- aggro_events.gd — Signal-Handler (damage_dealt, heal_requested, entity_died, enemy_detected, enemy_lost)
- Event: enemy_engaged
### EnemyAISystem (enemy_ai_system.gd)
- ATTACK-State: Range-Check, Timer, Schaden
@@ -193,13 +197,19 @@ resources/
- EnemyMovement (Node, enemy_movement.gd) — Empfängt Bewegungsbefehle
- Healthbar (Sprite3D + SubViewport, healthbar.gd) — liest HP/Shield von Stats
- enemy.gd — Registriert bei Stats mit EnemyStats Resource, Detection-Area Signal
- Aggro-Regeln:
- Schaden = Aggro (1:1)
- Heilung = Aggro (0.5x)
- Tank = Aggro-Multiplikator (2x)
- Aggro verfällt -1/s
- Spieler im Portal-Radius: Aggro bleibt bei mindestens 1
- Außerhalb Portal-Radius: Aggro verfällt exponentiell (1%, 2%, 4%, 8%, ...)
- Aggro-Regeln (Werte in AggroConfig Resource):
- Aufbau:
- Schaden = Aggro (1:1), Tank 2x Multiplikator
- Heilung = 0.5x Aggro auf alle Gegner die Heiler kennen
- Aggro-Spread: 50% des Aggro an Gegner im alert_radius (10m)
- Detection-Area (10m): +1 Aggro, Alert an Nachbarn im alert_radius
- Kampfstatus:
- Spieler in DetectionArea → immer im Kampf (kein Decay)
- Spieler verlässt DetectionArea → 5s Combat-Timeout, dann Decay
- Schaden verursachen setzt Combat-Timer zurück
- Abbau (nach Combat-Timeout):
- Basis: -aggro_decay/s (default 1.0)
- Exponentieller Decay basierend auf Zeit seit Kampfende (1%·2^sekunden)
- Ohne Aggro: Gegner kehrt zum Portal zurück, regeneriert
- Bei Spieler-Tod → Aggro auf 0

View File

@@ -19,8 +19,8 @@ func _on_entity_died(entity: Node) -> void:
return
var pos: Vector3 = global_position
var gate: Node3D = GATE_SCENE.instantiate()
gate.global_position = pos
get_parent().add_child(gate)
gate.global_position = pos
var enemies := get_tree().get_nodes_in_group("enemies")
for enemy in enemies:
if is_instance_valid(enemy):

View File

@@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://resources/roles/ability.gd" id="1_ability"]
[ext_resource type="Resource" uid="uid://dwvc8b3cmce8l" path="res://resources/roles/damage/abilities/single.tres" id="2"]
[ext_resource type="Resource" uid="uid://bpx3l13iuynfv" path="res://resources/roles/damage/abilities/aoe.tres" id="3"]
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/roles/damage/abilities/utility.tres" id="4"]
[ext_resource type="Resource" path="res://resources/roles/damage/abilities/utility.tres" id="4"]
[ext_resource type="Resource" uid="uid://s32wvlww2ls2" path="res://resources/roles/damage/abilities/ult.tres" id="5"]
[ext_resource type="Resource" uid="uid://dadpl32yujwhe" path="res://resources/roles/damage/abilities/passive.tres" id="6"]

View File

@@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://resources/roles/ability.gd" id="1_ability"]
[ext_resource type="Resource" path="res://resources/roles/healer/abilities/single.tres" id="2"]
[ext_resource type="Resource" path="res://resources/roles/healer/abilities/aoe.tres" id="3"]
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/roles/healer/abilities/utility.tres" id="4"]
[ext_resource type="Resource" path="res://resources/roles/healer/abilities/utility.tres" id="4"]
[ext_resource type="Resource" path="res://resources/roles/healer/abilities/ult.tres" id="5"]
[ext_resource type="Resource" path="res://resources/roles/healer/abilities/passive.tres" id="6"]

View File

@@ -4,7 +4,7 @@
[ext_resource type="Script" uid="uid://c03xbbf3yhfl3" path="res://resources/roles/ability.gd" id="1_ability"]
[ext_resource type="Resource" path="res://resources/roles/tank/abilities/single.tres" id="2"]
[ext_resource type="Resource" path="res://resources/roles/tank/abilities/aoe.tres" id="3"]
[ext_resource type="Resource" uid="uid://du0hyuuj26ea0" path="res://resources/roles/tank/abilities/utility.tres" id="4"]
[ext_resource type="Resource" path="res://resources/roles/tank/abilities/utility.tres" id="4"]
[ext_resource type="Resource" path="res://resources/roles/tank/abilities/ult.tres" id="5"]
[ext_resource type="Resource" path="res://resources/roles/tank/abilities/passive.tres" id="6"]

View File

@@ -0,0 +1,8 @@
extends Resource
class_name AggroConfig
@export var combat_timeout := 5.0
@export var tank_multiplier := 2.0
@export var heal_multiplier := 0.5
@export var spread_multiplier := 0.5
@export var exponential_decay_factor := 0.01

View File

@@ -0,0 +1 @@
uid://b3gwl1wweld2x

View File

@@ -0,0 +1,6 @@
[gd_resource type="Resource" script_class="AggroConfig" format=3]
[ext_resource type="Script" path="res://resources/stats/aggro_config.gd" id="1"]
[resource]
script = ExtResource("1")

View File

@@ -17,4 +17,4 @@ regen_fast = 0.1
regen_slow = 0.01
aggro_decay = 1.0
portal_radius = 10.0
alert_radius = 3.0
alert_radius = 10.0

View File

@@ -9,4 +9,4 @@ class_name EnemyStats
@export var regen_slow := 0.01
@export var aggro_decay := 1.0
@export var portal_radius := 10.0
@export var alert_radius := 3.0
@export var alert_radius := 10.0

View File

@@ -0,0 +1,68 @@
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 base: BaseStats = Stats.get_base(enemy)
var aggro_decay: float = base.aggro_decay if base is EnemyStats else 1.0
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):
if "state" in other and other.state == other.State.IDLE:
tracker.add_aggro(other, target, 1.0)
other.target = target
other.state = other.State.CHASE
EventBus.enemy_engaged.emit(other, target)
func erase_entity(entity: Node) -> void:
last_damage_time.erase(entity)

View File

@@ -0,0 +1 @@
uid://cysg30lud2ta2

View File

@@ -0,0 +1,52 @@
extends Node
var tracker: Node
var decay: Node
var config: AggroConfig
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)
EventBus.enemy_lost.connect(_on_enemy_lost)
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
tracker.add_player_in_range(enemy, player)
tracker.add_aggro(enemy, player, 1.0)
if "state" in enemy:
enemy.target = player
enemy.state = enemy.State.CHASE
EventBus.enemy_engaged.emit(enemy, player)
decay.alert_nearby(enemy, player)
func _on_enemy_lost(enemy: Node, player: Node) -> void:
tracker.remove_player_in_range(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
decay.reset_combat_timer(attacker)
var multiplier := 1.0
var role: Node = attacker.get_node_or_null("Role")
if role and role.current_role == 0:
multiplier = config.tank_multiplier
var aggro: float = amount * multiplier
tracker.add_aggro(target, attacker, aggro)
decay.spread_aggro(target, attacker, aggro * config.spread_multiplier)
func _on_heal_requested(healer: Node, _target: Node, amount: float) -> void:
if not healer.is_in_group("player"):
return
for enemy in tracker.aggro_tables:
if is_instance_valid(enemy) and healer in tracker.aggro_tables[enemy]:
tracker.add_aggro(enemy, healer, amount * config.heal_multiplier)
func _on_entity_died(entity: Node) -> void:
tracker.erase_entity(entity)
decay.erase_entity(entity)

View File

@@ -0,0 +1 @@
uid://cyffo1g4uhmwh

View File

@@ -0,0 +1,17 @@
extends Node
@export var config: AggroConfig = preload("res://resources/stats/aggro_config.tres")
@onready var tracker: Node = $AggroTracker
@onready var decay: Node = $AggroDecay
@onready var events: Node = $AggroEvents
func _ready() -> void:
decay.tracker = tracker
decay.config = config
events.tracker = tracker
events.decay = decay
events.config = config
func _process(delta: float) -> void:
decay.process(delta)

View File

@@ -0,0 +1,82 @@
extends Node
var aggro_tables: Dictionary = {}
var players_in_range: Dictionary = {}
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 remove_aggro(enemy: Node, player: Node, amount: float) -> void:
if enemy in aggro_tables and player in aggro_tables[enemy]:
aggro_tables[enemy][player] -= amount
if aggro_tables[enemy][player] <= 0:
aggro_tables[enemy].erase(player)
func add_player_in_range(enemy: Node, player: Node) -> void:
if enemy not in players_in_range:
players_in_range[enemy] = []
if player not in players_in_range[enemy]:
players_in_range[enemy].append(player)
func remove_player_in_range(enemy: Node, player: Node) -> void:
if enemy in players_in_range:
players_in_range[enemy].erase(player)
func is_player_in_any_range(player: Node) -> bool:
for enemy in players_in_range:
if is_instance_valid(enemy) and player in players_in_range[enemy]:
return true
return false
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 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 get_enemies_in_radius(source: Node, radius: float) -> Array:
var result: Array = []
for enemy in get_tree().get_nodes_in_group("enemies"):
if enemy != source and is_instance_valid(enemy):
var dist: float = source.global_position.distance_to(enemy.global_position)
if dist <= radius:
result.append(enemy)
return result
func get_alert_radius(entity: Node) -> float:
var base: BaseStats = Stats.get_base(entity)
return base.alert_radius if base is EnemyStats else 10.0
func erase_entity(entity: Node) -> void:
aggro_tables.erase(entity)
players_in_range.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
for enemy in players_in_range:
if is_instance_valid(enemy):
players_in_range[enemy].erase(entity)

View File

@@ -0,0 +1 @@
uid://c7gsu2qddsor6

View File

@@ -1,130 +0,0 @@
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

View File

@@ -1,7 +1,10 @@
[gd_scene format=3 uid="uid://dy1icabu2ssbw"]
[ext_resource type="Script" uid="uid://h0hts425epc6" path="res://systems/ability_system.gd" id="ability_system"]
[ext_resource type="Script" uid="uid://cm7ehl2pexcst" path="res://systems/aggro_system.gd" id="aggro_system"]
[ext_resource type="Script" uid="uid://cysg30lud2ta2" path="res://systems/aggro/aggro_decay.gd" id="aggro_decay"]
[ext_resource type="Script" uid="uid://cyffo1g4uhmwh" path="res://systems/aggro/aggro_events.gd" id="aggro_events"]
[ext_resource type="Script" uid="uid://cm7ehl2pexcst" path="res://systems/aggro/aggro_system.gd" id="aggro_system"]
[ext_resource type="Script" uid="uid://c7gsu2qddsor6" path="res://systems/aggro/aggro_tracker.gd" id="aggro_tracker"]
[ext_resource type="Script" uid="uid://da2jm0awq2lnh" path="res://systems/buff_system.gd" id="buff_system"]
[ext_resource type="Script" uid="uid://ddos7mo8rahou" path="res://systems/cooldown_system.gd" id="cooldown_system"]
[ext_resource type="Script" uid="uid://cbd1bryh0e2dw" path="res://systems/damage_system.gd" id="damage_system"]
@@ -71,6 +74,15 @@ script = ExtResource("cooldown_system")
[node name="AggroSystem" type="Node" parent="Systems" unique_id=1539448343]
script = ExtResource("aggro_system")
[node name="AggroTracker" type="Node" parent="Systems/AggroSystem" unique_id=1597893665]
script = ExtResource("aggro_tracker")
[node name="AggroDecay" type="Node" parent="Systems/AggroSystem" unique_id=1571705506]
script = ExtResource("aggro_decay")
[node name="AggroEvents" type="Node" parent="Systems/AggroSystem" unique_id=1936723580]
script = ExtResource("aggro_events")
[node name="EnemyAISystem" type="Node" parent="Systems" unique_id=2089718042]
script = ExtResource("enemy_ai_system")