# Enemy.gd # Steuert den Gegner: KI-Bewegung zum Spieler, Angriff, HP, Zielanzeige extends CharacterBody3D signal enemy_died(spawn_position: Vector3, xp_reward: int) signal enemy_dropped_loot(loot: Dictionary, world_position: Vector3) const SPEED = 3.0 const PATROL_SPEED = 1.5 const GRAVITY = 9.8 const ATTACK_RANGE = 1.5 const ATTACK_COOLDOWN = 2.0 const AGGRO_RANGE = 8.0 # Entfernung ab der der Gegner angreift const PATROL_RADIUS = 5.0 # Radius um Spawn-Position für Patrol const PATROL_WAIT_TIME = 2.0 # Wartezeit am Patrol-Punkt # Level-Differenz Konstanten const LEVEL_DIFF_DAMAGE_MOD = 0.1 # 10% mehr/weniger Schaden pro Level-Differenz const MAX_LEVEL_DIFF_MOD = 0.5 # Maximal 50% Modifikation enum State { PATROL, CHASE, ATTACK } # Stats-System @export var level: int = 1 @export var base_strength: int = 8 @export var base_stamina: int = 10 @export var base_armor: int = 5 # Rüstung reduziert Nahkampfschaden # Berechnete Stats var strength: int = 8 var stamina: int = 10 var armor: int = 5 var max_hp: int = 100 var current_hp: int = 100 var attack_damage: int = 5 # XP-Belohnung (skaliert mit Level) var xp_reward: int = 25 # Loot-System @export var loot_table: LootTable var target = null # Spieler-Referenz (wird von World gesetzt) var can_attack = true var spawn_position: Vector3 # Ursprüngliche Spawn-Position var current_state = State.PATROL var patrol_target: Vector3 # Aktuelles Patrol-Ziel var is_waiting = false # Ob Gegner am Patrol-Punkt wartet @onready var health_label = $HealthLabel func _ready(): _calculate_stats() current_hp = max_hp health_label.visible = false _update_label() spawn_position = global_position _pick_new_patrol_target() # Stats basierend auf Level berechnen func _calculate_stats(): var levels_gained = level - 1 strength = base_strength + levels_gained * 2 stamina = base_stamina + levels_gained * 3 armor = base_armor + levels_gained * 2 # HP = Stamina * 10 max_hp = stamina * 10 # Schaden = Stärke / 2 attack_damage = int(strength * 0.5) + 2 # XP = 25 * Level xp_reward = 25 * level print("Enemy Stats (Lv", level, ") - STR:", strength, " STA:", stamina, " ARM:", armor, " HP:", max_hp, " DMG:", attack_damage) # Schaden mit Rüstung und Level-Differenz berechnen func calculate_incoming_damage(raw_damage: int, attacker_level: int, is_melee: bool) -> int: var damage = float(raw_damage) # Rüstung reduziert nur Nahkampfschaden if is_melee: # Rüstungsreduktion: armor / (armor + 50) = Prozent Reduktion # Bei 5 Rüstung: 5/55 = ~9% Reduktion # Bei 20 Rüstung: 20/70 = ~29% Reduktion var armor_reduction = float(armor) / (float(armor) + 50.0) damage = damage * (1.0 - armor_reduction) # Level-Differenz Modifikator var level_diff = attacker_level - level var level_mod = clamp(level_diff * LEVEL_DIFF_DAMAGE_MOD, -MAX_LEVEL_DIFF_MOD, MAX_LEVEL_DIFF_MOD) damage = damage * (1.0 + level_mod) return maxi(1, int(damage)) # Mindestens 1 Schaden # HP-Label Text aktualisieren func _update_label(): health_label.text = "Lv" + str(level) + " " + str(current_hp) + "/" + str(max_hp) # HP-Label anzeigen (wenn Gegner markiert wird) func show_health(): health_label.visible = true # HP-Label verstecken (wenn Markierung aufgehoben wird) func hide_health(): health_label.visible = false # Schaden nehmen und Label aktualisieren (alte Methode für Kompatibilität) func take_damage(amount): current_hp -= amount _update_label() if current_hp <= 0: die() # Schaden mit vollem Schadenssystem (Rüstung, Level-Differenz) func take_damage_from(raw_damage: int, attacker_level: int, is_melee: bool = true): var final_damage = calculate_incoming_damage(raw_damage, attacker_level, is_melee) print("Eingehender Schaden: ", raw_damage, " -> ", final_damage, " (nach Rüstung/Level)") take_damage(final_damage) # Gegner aus der Szene entfernen func die(): print("Gegner besiegt! +", xp_reward, " XP") # XP an Spieler geben if target and target.has_method("gain_xp"): target.gain_xp(xp_reward) # Loot generieren und droppen _drop_loot() enemy_died.emit(spawn_position, xp_reward) queue_free() # Loot generieren basierend auf LootTable func _drop_loot(): if loot_table == null: # Standard-Gold-Drop wenn keine LootTable zugewiesen var gold = randi_range(1, 3) * level var loot = {"gold": gold, "items": []} enemy_dropped_loot.emit(loot, global_position) return var loot = loot_table.generate_loot() # Gold mit Level skalieren loot["gold"] = loot["gold"] * level enemy_dropped_loot.emit(loot, global_position) func _physics_process(delta): if not is_on_floor(): velocity.y -= GRAVITY * delta if target == null: move_and_slide() return # Prüfe Distanz zum Spieler für Aggro var distance_to_player = global_position.distance_to(target.global_position) # State-Wechsel basierend auf Distanz match current_state: State.PATROL: if distance_to_player <= AGGRO_RANGE: current_state = State.CHASE print("Gegner hat Spieler entdeckt!") else: _do_patrol() State.CHASE: if distance_to_player <= ATTACK_RANGE: current_state = State.ATTACK else: _chase_player() State.ATTACK: if distance_to_player > ATTACK_RANGE: current_state = State.CHASE else: velocity.x = 0 velocity.z = 0 if can_attack: _attack() move_and_slide() # Neues Patrol-Ziel in der Nähe der Spawn-Position wählen func _pick_new_patrol_target(): var angle = randf() * TAU # Zufälliger Winkel var distance = randf_range(2.0, PATROL_RADIUS) patrol_target = spawn_position + Vector3(cos(angle) * distance, 0, sin(angle) * distance) # Patrol-Verhalten: Zufällig herumlaufen func _do_patrol(): if is_waiting: return var distance_to_patrol = global_position.distance_to(patrol_target) if distance_to_patrol <= 0.5: # Am Ziel angekommen, warten und neues Ziel wählen velocity.x = 0 velocity.z = 0 _wait_at_patrol_point() else: # Zum Patrol-Ziel laufen var direction = (patrol_target - global_position) direction.y = 0 direction = direction.normalized() velocity.x = direction.x * PATROL_SPEED velocity.z = direction.z * PATROL_SPEED look_at(Vector3(patrol_target.x, global_position.y, patrol_target.z)) # Am Patrol-Punkt warten func _wait_at_patrol_point(): is_waiting = true await get_tree().create_timer(PATROL_WAIT_TIME).timeout is_waiting = false _pick_new_patrol_target() # Spieler verfolgen func _chase_player(): var direction = (target.global_position - global_position) direction.y = 0 direction = direction.normalized() velocity.x = direction.x * SPEED velocity.z = direction.z * SPEED look_at(Vector3(target.global_position.x, global_position.y, target.global_position.z)) # Angriff mit Cooldown func _attack(): can_attack = false # Gegner verwendet auch das Schadenssystem mit Level-Differenz if target.has_method("take_damage_from"): target.take_damage_from(attack_damage, level, true) else: target.take_damage(attack_damage) print("Gegner (Lv", level, ") greift an: ", attack_damage, " Schaden") await get_tree().create_timer(ATTACK_COOLDOWN).timeout can_attack = true