diff --git a/plan.md b/plan.md index 1e64f7a..0a6017b 100644 --- a/plan.md +++ b/plan.md @@ -54,6 +54,7 @@ - NavigationAgent3D (Wegfindung) - EnemyMovement (Node, enemy_movement.gd) - EnemyCombat (Node, enemy_combat.gd) + - EnemyAggro (Node, enemy_aggro.gd) - Healthbar (Sprite3D + SubViewport, über dem Gegner, enemy_healthbar.gd) - SubViewport - Border (ColorRect, gelb, sichtbar bei Anvisierung) @@ -72,15 +73,21 @@ - 5 Passive: 50% mehr Schaden (permanent aktiv, kein CD) - targeting.gd — Klick/TAB anvisieren, Kampfmodus bei Gegner-Angriff, Auto-Targeting auf nächsten Gegner - event_bus.gd — Autoload-Singleton, globale Signals -- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), Aggro bei Schaden, alarmiert Gegner in 3m +- enemy.gd — Gegner-Kern, State Machine (Idle, Verfolgen, Angreifen, Zurückkehren), alarmiert Gegner in 3m - enemy_movement.gd — Navigation zum Ziel/Spawnpunkt - enemy_combat.gd — Angriff über Event (damage_requested) +- enemy_aggro.gd — Aggro-Tabelle (Spieler → Wert), wählt Ziel mit höchstem Aggro + - Schaden = Aggro (1:1) + - Heilung = Aggro (0.5x) + - Tank = Aggro-Multiplikator (2x) + - Aggro verfällt -1/s + - Bei Spieler-Tod → Aggro auf 0 - health.gd — Leben, 1/s Regeneration, Tod bei 0 (wiederverwendbar) - shield.gd — Schild, regeneriert nach 3s ohne Schaden, in 5s voll (wiederverwendbar) - player_class.gd — Klassenwechsel ALT+1 bis ALT+3 (Tank/Schaden/Heiler), tauscht AbilitySet - respawn.gd — Bei Tod: Spieler deaktivieren, 3s Timer, Respawn am Startpunkt, Leben/Schild voll - hud.gd — Reagiert auf Events, aktualisiert HealthBar/ShieldBar/AbilityBar/RespawnTimer -- enemy_healthbar.gd — Liest Health/Shield vom Gegner, aktualisiert Balken über dem Gegner, gelber Rand bei Anvisierung +- enemy_healthbar.gd — Liest Health/Shield vom Gegner, aktualisiert Balken über dem Gegner, gelber Rand bei Anvisierung, blauer Lebensbalken wenn Spieler Ziel ist ## Abilities (Resources) - ability.gd (Resource) — name, type, damage, range, cooldown, uses_gcd, execute() diff --git a/scenes/enemy/enemy.tscn b/scenes/enemy/enemy.tscn index b6b88a8..1150365 100644 --- a/scenes/enemy/enemy.tscn +++ b/scenes/enemy/enemy.tscn @@ -6,6 +6,7 @@ [ext_resource type="Script" path="res://scripts/enemy/enemy_healthbar.gd" id="4"] [ext_resource type="Script" path="res://scripts/enemy/enemy_movement.gd" id="5"] [ext_resource type="Script" path="res://scripts/enemy/enemy_combat.gd" id="6"] +[ext_resource type="Script" path="res://scripts/enemy/enemy_aggro.gd" id="7"] [sub_resource type="CapsuleShape3D" id="CapsuleShape3D_1"] radius = 0.4 @@ -21,6 +22,9 @@ bg_color = Color(0.3, 0.1, 0.1, 1) [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill"] bg_color = Color(0.2, 0.8, 0.2, 1) +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_health_fill_aggro"] +bg_color = Color(0.2, 0.4, 0.9, 1) + [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_shield_bg"] bg_color = Color(0.1, 0.1, 0.3, 1) @@ -68,6 +72,9 @@ script = ExtResource("5") [node name="EnemyCombat" type="Node" parent="."] script = ExtResource("6") +[node name="EnemyAggro" type="Node" parent="."] +script = ExtResource("7") + [node name="DetectionArea" type="Area3D" parent="."] collision_layer = 0 collision_mask = 1 diff --git a/scripts/enemy/enemy.gd b/scripts/enemy/enemy.gd index 7bd45c0..2e9e29a 100644 --- a/scripts/enemy/enemy.gd +++ b/scripts/enemy/enemy.gd @@ -13,7 +13,6 @@ func _ready() -> void: spawn_position = global_position add_to_group("enemies") EventBus.entity_died.connect(_on_entity_died) - EventBus.damage_dealt.connect(_on_damage_dealt) func _on_entity_died(entity: Node) -> void: if entity == self: @@ -27,15 +26,14 @@ func _physics_process(delta: float) -> void: velocity.y -= gravity * delta move_and_slide() -func _on_damage_dealt(attacker: Node, damage_target: Node, _amount: float) -> void: - if damage_target == self and attacker.name == "Player": - _engage(attacker) - func _engage(new_target: Node3D) -> void: if state == State.CHASE or state == State.ATTACK: return target = new_target state = State.CHASE + var aggro: Node = get_node_or_null("EnemyAggro") + if aggro: + aggro.add_aggro(new_target, 1.0) _alert_nearby() func _alert_nearby() -> void: diff --git a/scripts/enemy/enemy_aggro.gd b/scripts/enemy/enemy_aggro.gd new file mode 100644 index 0000000..99d5a23 --- /dev/null +++ b/scripts/enemy/enemy_aggro.gd @@ -0,0 +1,51 @@ +extends Node + +const AGGRO_DECAY := 1.0 +var aggro_table: Dictionary = {} + +@onready var enemy: CharacterBody3D = get_parent() + +func _ready() -> void: + EventBus.damage_dealt.connect(_on_damage_dealt) + EventBus.entity_died.connect(_on_entity_died) + +func _process(delta: float) -> void: + for player in aggro_table.keys(): + aggro_table[player] -= AGGRO_DECAY * delta + if aggro_table[player] <= 0: + aggro_table.erase(player) + var top_target: Node = _get_top_target() + if top_target and top_target != enemy.target: + enemy.target = top_target + if enemy.state == enemy.State.IDLE or enemy.state == enemy.State.RETURN: + enemy.state = enemy.State.CHASE + +func _on_damage_dealt(attacker: Node, target: Node, amount: float) -> void: + if target != enemy: + return + var multiplier := 1.0 + var player_class: Node = attacker.get_node_or_null("PlayerClass") + if player_class and player_class.current_class == 0: + multiplier = 2.0 + add_aggro(attacker, amount * multiplier) + +func _on_entity_died(entity: Node) -> void: + aggro_table.erase(entity) + +func add_aggro(player: Node, amount: float) -> void: + if player in aggro_table: + aggro_table[player] += amount + else: + aggro_table[player] = amount + +func _get_top_target() -> Node: + var top: Node = null + var top_val := 0.0 + for player in aggro_table: + if is_instance_valid(player) and aggro_table[player] > top_val: + top_val = aggro_table[player] + top = player + return top + +func has_aggro_on(player: Node) -> bool: + return _get_top_target() == player diff --git a/scripts/enemy/enemy_aggro.gd.uid b/scripts/enemy/enemy_aggro.gd.uid new file mode 100644 index 0000000..7a4013e --- /dev/null +++ b/scripts/enemy/enemy_aggro.gd.uid @@ -0,0 +1 @@ +uid://bojdohjxr6uef diff --git a/scripts/enemy/enemy_healthbar.gd b/scripts/enemy/enemy_healthbar.gd index 13098af..0638e53 100644 --- a/scripts/enemy/enemy_healthbar.gd +++ b/scripts/enemy/enemy_healthbar.gd @@ -6,17 +6,29 @@ extends Sprite3D @onready var border: ColorRect = $SubViewport/Border @onready var health: Node = get_parent().get_node("Health") @onready var shield: Node = get_parent().get_node("Shield") +@onready var enemy: CharacterBody3D = get_parent() + +var style_normal: StyleBoxFlat +var style_aggro: StyleBoxFlat func _ready() -> void: texture = viewport.get_texture() health_bar.max_value = health.max_health shield_bar.max_value = shield.max_shield border.visible = false + style_normal = health_bar.get_theme_stylebox("fill").duplicate() + style_aggro = style_normal.duplicate() + style_aggro.bg_color = Color(0.2, 0.4, 0.9, 1) EventBus.target_changed.connect(_on_target_changed) func _process(_delta: float) -> void: health_bar.value = health.current_health shield_bar.value = shield.current_shield + var player: Node = get_tree().get_first_node_in_group("player") + if player and enemy.target == player: + health_bar.add_theme_stylebox_override("fill", style_aggro) + else: + health_bar.add_theme_stylebox_override("fill", style_normal) func _on_target_changed(_player: Node, target: Node) -> void: border.visible = (target == get_parent()) diff --git a/scripts/player/player.gd b/scripts/player/player.gd index 27b2d44..dfc0d8c 100644 --- a/scripts/player/player.gd +++ b/scripts/player/player.gd @@ -1 +1,4 @@ extends CharacterBody3D + +func _ready() -> void: + add_to_group("player")