# Player.gd
# ─────────────────────────────────────────────────────────────────────────────
# Hauptskript des Spielers – RPG/Souls-Hybrid
#
# Bewegungssystem:
#   • Kein Ziel + kein Walk    → Souls-Modus: Charakter dreht sich zur Laufrichtung
#                                 (relativ zu camera_pivot.world_yaw), Animation = "run"
#                                 RMB gehalten: A/D = Strafe statt Drehung
#   • Kein Ziel + Walk (NumLock) → RPG-Modus: Strafe-Animationen, langsame Geschwindigkeit
#   • Ziel markiert            → Lock-On: Kamera dreht Spieler zum Ziel,
#                                 WASD = Strafe/Rückwärts relativ zur Spielerausrichtung
#
# Kamera-Referenz:
#   dir3 (Bewegungsvektor) nutzt camera_pivot.world_yaw im Souls-Modus, damit sich
#   der Vektor NICHT mit der Spielerrotation dreht (kein Feedback-Loop).
#
# Kampf:
#   Autoattack, Heavy Strike (Krieger), Quick Strike (Schurke), Frostbolt (Magier)
#   Global Cooldown, Skill Cooldowns, Cast-System
#
# Animationen:
#   Werden zur Laufzeit aus FBX-Dateien geladen (_load_anim_from_fbx).
#   Root Motion wird automatisch entfernt (_strip_root_motion).
# ─────────────────────────────────────────────────────────────────────────────
extends CharacterBody3D

# ═══════════════════════════════════════════════════════════════
# KONSTANTEN
# ═══════════════════════════════════════════════════════════════

const SPEED = 5.0
const SPRINT_SPEED = 9.0
const JUMP_VELOCITY = 4.5
const GRAVITY = 9.8

# Scenes für UI-Panels (werden zur Laufzeit erstellt)
const LOOT_WINDOW_SCENE   = preload("res://loot_window.tscn")
const INVENTORY_PANEL_SCENE = preload("res://inventory_panel.tscn")
const SKILL_PANEL_SCENE   = preload("res://skill_panel.tscn")
const CHARACTER_PANEL_SCENE = preload("res://character_panel.tscn")

# Animations-FBX Pfade
const ANIM_IDLE         = "res://assets/Warrior+Animation/idle.fbx"
const ANIM_WALK         = "res://assets/Warrior+Animation/walking.fbx"
const ANIM_RUN          = "res://assets/Warrior+Animation/running.fbx"
const ANIM_JUMP         = "res://assets/Warrior+Animation/jump.fbx"
const ANIM_AUTOATTACK   = "res://assets/Warrior+Animation/Autoattack.fbx"
const ANIM_HEAVY_STRIKE = "res://assets/Warrior+Animation/Heavy Strike.fbx"
const ANIM_DEATH        = "res://assets/Warrior+Animation/Dying Backwards.fbx"
const ANIM_WALK_BACK    = "res://assets/Warrior+Animation/Walking Backwards.fbx"
const ANIM_STRAFE_LEFT       = "res://assets/Warrior+Animation/left strafe walking.fbx"
const ANIM_STRAFE_RIGHT      = "res://assets/Warrior+Animation/right strafe walking.fbx"
const ANIM_RUN_STRAFE_LEFT   = "res://assets/Warrior+Animation/Running Left Strafe.fbx"
const ANIM_RUN_STRAFE_RIGHT  = "res://assets/Warrior+Animation/Running Right Strafe run.fbx"
const ANIM_WALK_JUMP         = "res://assets/Warrior+Animation/Walking Jump.fbx"
const ANIM_RUN_JUMP          = "res://assets/Warrior+Animation/Running Jump.fbx"
const ANIM_ROLL              = "res://assets/Warrior+Animation/Quick Roll To Run.fbx"
const ANIM_TURN_180          = "res://assets/Warrior+Animation/Running Turn 180.fbx"
const ANIM_DRINKING          = "res://assets/Animations/Drinking.fbx"

# ═══════════════════════════════════════════════════════════════
# CHARAKTER-STATS
# ═══════════════════════════════════════════════════════════════

var character_class: CharacterClass = null

var level: int = 1
var current_xp: int = 0
var xp_to_next_level: int = 100

# Basis-Stats (aus Klasse + Items berechnet)
var strength: int = 10
var agility: int = 10
var intelligence: int = 10
var stamina: int = 10
var armor: int = 0

# HP und Ressource
var max_hp: int = 100
var current_hp: int = 100
var max_resource: int = 0
var current_resource: int = 0

# Mana-Aliases (für Consumable-Kompatibilität)
var max_mana: int:
	get: return max_resource
var current_mana: int:
	get: return current_resource

# Wut-Verfall
const RAGE_DECAY_DELAY:  float = 5.0   # Sekunden nach letztem Kampfkontakt
const RAGE_DECAY_RATE:   float = 10.0  # Wut pro Sekunde Verfall
var _rage_decay_timer:   float = 0.0

# Durchbeißen / Schildwall / Trotz
var is_defending:        bool  = false
var _defend_timer:       float = 0.0
var _defend_mode:        String = ""   # "schildwall" oder "trotz"

# Zornfesseln — verzögerter Schlag nach Pull
var _zornfesseln_target        = null
var _zornfesseln_strike_timer: float = 0.0

# Berserker
var is_berserker:        bool  = false
var _berserker_timer:    float = 0.0
var is_berserker_crash:  bool  = false
var _crash_timer:        float = 0.0

# Blutrausch — Blutungs-DOT
var _bleed_target              = null
var _bleed_timer:       float  = 0.0   # verbleibende Dauer
var _bleed_tick_timer:  float  = 0.0   # bis zum nächsten Tick

# ═══════════════════════════════════════════════════════════════
# EQUIPMENT
# ═══════════════════════════════════════════════════════════════

var equipment: Dictionary = {
	Equipment.Slot.HEAD:    null,
	Equipment.Slot.CHEST:   null,
	Equipment.Slot.HANDS:   null,
	Equipment.Slot.LEGS:    null,
	Equipment.Slot.FEET:    null,
	Equipment.Slot.WEAPON:  null,
	Equipment.Slot.OFFHAND: null,
}

# ═══════════════════════════════════════════════════════════════
# INVENTAR
# ═══════════════════════════════════════════════════════════════

var inventory: Inventory = Inventory.new()

# ═══════════════════════════════════════════════════════════════
# SKILL & AKTIONSLEISTE
# ═══════════════════════════════════════════════════════════════

var available_skills: Array = []   # Alle verfügbaren Skills der Klasse
var action_bar: Array = []         # 9 Slots: skill_id (String) oder Consumable oder null
var skill_cooldowns: Dictionary = {}  # skill_id -> verbleibende Zeit
var consumable_cooldowns: Dictionary = {}  # item_name -> verbleibende Zeit

# Global Cooldown
var global_cooldown: float = 0.0
const GLOBAL_COOLDOWN_TIME: float = 1.5

# Autoattack
var autoattack_active: bool = false
const HEAVY_STRIKE_COOLDOWN:        float = 3.0
const HEAVY_STRIKE_RANGE:           float = 2.0
const TEKTONISCHER_SCHLAG_COOLDOWN: float = 8.0
const TEKTONISCHER_SCHLAG_RADIUS:   float = 4.0
const TEKTONISCHER_SCHLAG_RAGE:     int   = 30
const DURCHBEISSEN_COOLDOWN:        float = 12.0
const DURCHBEISSEN_DURATION:        float = 4.0
const SCHILDWALL_BLOCK:             float = 0.85   # 85% Schadensreduktion
const TROTZ_MAX_REDUCTION:          float = 0.60   # max 60% Reduktion bei 0 HP
const BLUTRAUSCH_COOLDOWN:          float = 10.0
const BLUTRAUSCH_RANGE:             float = 2.5
const BLUTRAUSCH_RAGE:              int   = 25
const BLUTRAUSCH_DOT_DURATION:      float = 8.0    # Blutungsdauer in Sekunden
const BLUTRAUSCH_DOT_INTERVAL:      float = 1.0    # Schaden alle 1s
const BLUTRAUSCH_DOT_DAMAGE:        int   = 4      # Schaden pro Tick
const WIRBELWIND_COOLDOWN:          float = 6.0
const WIRBELWIND_RADIUS:            float = 3.5
const WIRBELWIND_RAGE:              int   = 40
const ZORNFESSELN_COOLDOWN:         float = 14.0
const ZORNFESSELN_RANGE:            float = 12.0   # Max Zielreichweite
const ZORNFESSELN_RAGE:             int   = 35
const ZORNFESSELN_PULL_DURATION:    float = 0.6    # Sekunden bis Gegner da ist
const BERSERKER_COOLDOWN:           float = 90.0
const BERSERKER_MIN_RAGE:           int   = 50
const BERSERKER_DURATION:           float = 10.0
const BERSERKER_CRASH_DURATION:     float = 2.5
const BERSERKER_GCD_MULT:           float = 0.5    # Autoattack 2x schneller
const BERSERKER_DMG_REDUCTION:      float = 0.25   # 25% weniger Schaden nehmen
const BERSERKER_CRASH_DMG_MULT:     float = 1.5    # 50% mehr Schaden im Crash
const BERSERKER_CRASH_SPEED_MULT:   float = 0.5    # 50% langsamer im Crash

# Cast System
var is_casting: bool = false
var cast_time_remaining: float = 0.0
var cast_total: float = 0.0
var pending_skill_id: String = ""

# ═══════════════════════════════════════════════════════════════
# KAMPF
# ═══════════════════════════════════════════════════════════════

var target = null  # Aktuell markierter Gegner

# ═══════════════════════════════════════════════════════════════
# ANIMATION
# ═══════════════════════════════════════════════════════════════

var anim_player: AnimationPlayer = null
var skeleton: Skeleton3D = null
var weapon_attachment: BoneAttachment3D = null
var current_weapon_model: Node3D = null
var is_attacking: bool = false
var is_dead: bool = false
var is_walking: bool = false
var is_rolling: bool = false
var is_drinking: bool = false
var roll_direction: Vector3 = Vector3.ZERO
var roll_cooldown: float = 0.0
const ROLL_COOLDOWN: float = 1.5
# Eingefrорener Kamerawinkel für Bewegungsberechnung:
# wird nur aktualisiert wenn LMB NICHT gedrückt ist, damit
# LMB-Kamerarotation die Laufrichtung nicht verändert
var _movement_yaw: float = 0.0
var _lmb_press_pos: Vector2 = Vector2.ZERO

# ═══════════════════════════════════════════════════════════════
# NODE-REFERENZEN
# ═══════════════════════════════════════════════════════════════

@onready var camera_pivot = $CameraPivot
@onready var camera = $CameraPivot/Camera3D
@onready var hud = $HUD
@onready var model = $Model

# UI-Panels (zur Laufzeit erstellt)
var loot_window = null
var inventory_panel = null
var skill_panel = null
var character_panel = null

# ═══════════════════════════════════════════════════════════════
# READY
# ═══════════════════════════════════════════════════════════════

func _ready():
	add_to_group("player")

	# Jolt Physics: Boden sicher erkennen
	floor_snap_length = 0.3
	floor_max_angle = deg_to_rad(50.0)

	# Aktionsleiste mit 9 leeren Slots initialisieren
	action_bar.resize(9)
	for i in range(9):
		action_bar[i] = null

	# Animationen einrichten
	_setup_animations()

	# UI-Panels erstellen
	_create_ui_panels()

	# HUD Signale verbinden
	hud.slot_clicked.connect(_on_slot_clicked)
	hud.slot_drag_removed.connect(_on_slot_drag_removed)
	hud.slot_drag_swapped.connect(_on_slot_drag_swapped)

	hud.update_health(current_hp, max_hp)

# ═══════════════════════════════════════════════════════════════
# UI-PANELS
# ═══════════════════════════════════════════════════════════════

func _create_ui_panels():
	loot_window = LOOT_WINDOW_SCENE.instantiate()
	add_child(loot_window)
	loot_window.setup(self)

	inventory_panel = INVENTORY_PANEL_SCENE.instantiate()
	add_child(inventory_panel)
	inventory_panel.setup(self)

	skill_panel = SKILL_PANEL_SCENE.instantiate()
	add_child(skill_panel)
	skill_panel.setup(self)

	character_panel = CHARACTER_PANEL_SCENE.instantiate()
	add_child(character_panel)

	# Gold-Änderungen an HUD weiterleiten
	inventory.gold_changed.connect(func(amount): hud.update_gold(amount))

# ═══════════════════════════════════════════════════════════════
# KLASSEN-INITIALISIERUNG
# ═══════════════════════════════════════════════════════════════

func _init_class_skills():
	available_skills.clear()

	# Autoattack ist für alle Klassen verfügbar
	available_skills.append({
		"id": "autoattack",
		"name": "Autoattack",
		"description": "Grundangriff mit ausgerüsteter Waffe.",
		"icon": "res://assets/Icons/Autoattack.png",
		"cooldown": 0.0,
		"cast_time": 0.0,
	})

	if character_class == null:
		return

	match character_class.main_stat:
		CharacterClass.MainStat.STRENGTH:  # Krieger
			available_skills.append({
				"id": "heavy_strike",
				"name": "Mächtiger Schlag",
				"description": "Schlägt den Gegner mit voller Kraft. Generiert Wut.",
				"icon": "res://assets/Icons/Heavy_Strike.png",
				"cooldown": HEAVY_STRIKE_COOLDOWN,
				"cast_time": 0.0,
				"locked": false,
				"level_required": 1,
			})
			var has_offhand = _has_shield()
			available_skills.append({
				"id": "durchbeissen",
				"name": "Schildwall" if has_offhand else "Trotz",
				"description": "Schildwall: Blockiert großen Teil des eingehenden Schadens." if has_offhand else "Trotz: Schadensreduktion skaliert mit fehlendem Leben.",
				"icon": "res://assets/Icons/Durchbeißen.png",
				"cooldown": DURCHBEISSEN_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 10,
				"level_required": 10,
			})
			available_skills.append({
				"id": "tektonischer_schlag",
				"name": "Tektonischer Schlag",
				"description": "Erschüttert den Boden um dich herum. Schlägt alle Gegner in der Nähe und verlangsamt sie.",
				"icon": "res://assets/Icons/Tektonischer_Schlag.png",
				"cooldown": TEKTONISCHER_SCHLAG_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 5,
				"level_required": 5,
			})
			available_skills.append({
				"id": "blutrausch",
				"name": "Blutrausch",
				"description": "Reißt eine tiefe Wunde. Sofortschaden + Blutung über 8 Sekunden.",
				"icon": "res://assets/Icons/Blutrausch.png",
				"cooldown": BLUTRAUSCH_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 15,
				"level_required": 15,
			})
			available_skills.append({
				"id": "wirbelwind",
				"name": "Wirbelwind",
				"description": "Drehangriff mit voller Wucht. Trifft alle Gegner in der Nähe für hohen Schaden.",
				"icon": "res://assets/Icons/Wirbelwind.png",
				"cooldown": WIRBELWIND_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 20,
				"level_required": 20,
			})
			available_skills.append({
				"id": "zornfesseln",
				"name": "Zornfesseln",
				"description": "Reißt einen Gegner heran und schlägt ihn mit voller Wucht.",
				"icon": "res://assets/Icons/Zornfesseln.png",
				"cooldown": ZORNFESSELN_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 28,
				"level_required": 28,
			})
			available_skills.append({
				"id": "berserker",
				"name": "Berserker",
				"description": "Entfesselt die gesamte Wut. 10s: schneller, stärker, unaufhaltsamer. Danach 2.5s Erschöpfung.",
				"icon": "res://assets/Icons/Berserker.png",
				"cooldown": BERSERKER_COOLDOWN,
				"cast_time": 0.0,
				"locked": level < 35,
				"level_required": 35,
			})

		CharacterClass.MainStat.INTELLIGENCE:  # Magier
			available_skills.append({
				"id": "frostbolt",
				"name": "Frostblitz",
				"description": "Schießt einen Frostblitz auf den Feind (2s Channeling).",
				"icon": "res://icons/heavy_strike_icon.svg",  # Temp-Icon
				"cooldown": 0.0,
				"cast_time": 2.0,
			})

		CharacterClass.MainStat.AGILITY:  # Schurke
			available_skills.append({
				"id": "quick_strike",
				"name": "Schnellschlag",
				"description": "Blitzschneller Angriff (1s CD).",
				"icon": "res://icons/autoattack_icon.svg",  # Temp-Icon
				"cooldown": 1.0,
				"cast_time": 0.0,
			})

	# Standard-Belegung: Slot 0 = Autoattack, Slot 1 = erster Skill
	action_bar[0] = "autoattack"
	if available_skills.size() > 1:
		action_bar[1] = available_skills[1]["id"]

# ═══════════════════════════════════════════════════════════════
# STAT-BERECHNUNG
# ═══════════════════════════════════════════════════════════════

func _calculate_stats():
	if character_class == null:
		return

	# Basis-Stats aus Klasse + Level
	strength     = character_class.base_strength     + int(character_class.strength_per_level     * (level - 1))
	agility      = character_class.base_agility      + int(character_class.agility_per_level      * (level - 1))
	intelligence = character_class.base_intelligence + int(character_class.intelligence_per_level * (level - 1))
	stamina      = character_class.base_stamina      + int(character_class.stamina_per_level      * (level - 1))

	# Stats aus Equipment addieren
	armor = 0
	for slot in equipment:
		var item = equipment[slot]
		if item == null:
			continue
		strength     += item.strength
		agility      += item.agility
		intelligence += item.intelligence
		stamina      += item.stamina
		armor        += item.armor

	# HP aus Stamina berechnen
	var old_max = max_hp
	max_hp = stamina * CharacterClass.HP_PER_STAMINA
	# HP proportional anpassen
	if old_max > 0:
		current_hp = int(current_hp * float(max_hp) / float(old_max))
	current_hp = clamp(current_hp, 0, max_hp)

	# Ressource berechnen
	match character_class.resource_type:
		CharacterClass.ResourceType.MANA:
			max_resource = character_class.base_resource + intelligence * CharacterClass.MANA_PER_INT
		CharacterClass.ResourceType.RAGE, CharacterClass.ResourceType.ENERGY:
			max_resource = character_class.base_resource
		_:
			max_resource = 0

	current_resource = clamp(current_resource, 0, max_resource)

func get_resource_name() -> String:
	if character_class == null:
		return ""
	match character_class.resource_type:
		CharacterClass.ResourceType.MANA:   return "Mana"
		CharacterClass.ResourceType.RAGE:   return "Wut"
		CharacterClass.ResourceType.ENERGY: return "Energie"
	return ""

# ═══════════════════════════════════════════════════════════════
# EQUIPMENT
# ═══════════════════════════════════════════════════════════════

# Item anlegen — gibt das alte Item zurück (oder null)
func equip_item(item: Equipment) -> Equipment:
	var slot = item.slot
	var old_item = equipment[slot]
	equipment[slot] = item
	_calculate_stats()
	hud.update_health(current_hp, max_hp)
	if max_resource > 0:
		hud.update_resource(current_resource, max_resource, get_resource_name())
	if character_panel and character_panel.panel_visible:
		character_panel.update_stats(self)
	# Waffen-Modell aktualisieren
	if slot == Equipment.Slot.WEAPON and weapon_attachment != null:
		_attach_weapon_model(item)
	return old_item

func get_equipped_weapon() -> Equipment:
	return equipment[Equipment.Slot.WEAPON]

func get_attack_damage() -> int:
	var weapon = get_equipped_weapon()
	if weapon == null:
		if character_class:
			return randi_range(character_class.unarmed_min_damage, character_class.unarmed_max_damage)
		return 1
	# Main-Stat-Bonus
	var bonus = 0
	if character_class:
		match character_class.main_stat:
			CharacterClass.MainStat.STRENGTH:     bonus = int(strength     * CharacterClass.DAMAGE_PER_MAIN_STAT)
			CharacterClass.MainStat.AGILITY:      bonus = int(agility      * CharacterClass.DAMAGE_PER_MAIN_STAT)
			CharacterClass.MainStat.INTELLIGENCE: bonus = int(intelligence * CharacterClass.DAMAGE_PER_MAIN_STAT)
	return randi_range(weapon.min_damage, weapon.max_damage) + bonus

func get_attack_range() -> float:
	var weapon = get_equipped_weapon()
	if weapon == null:
		return 1.5
	return weapon.weapon_range

func get_attack_cooldown() -> float:
	var weapon = get_equipped_weapon()
	if weapon == null:
		if character_class:
			return character_class.unarmed_attack_speed
		return 1.5
	var speed = weapon.attack_speed
	if character_class:
		speed *= max(0.1, 1.0 - (agility * 0.002))  # Agilität reduziert Cooldown leicht
	return speed

func get_dps() -> float:
	var weapon = get_equipped_weapon()
	var avg_dmg: float
	if weapon:
		avg_dmg = float(weapon.min_damage + weapon.max_damage) / 2.0
	elif character_class:
		avg_dmg = float(character_class.unarmed_min_damage + character_class.unarmed_max_damage) / 2.0
	else:
		avg_dmg = 1.5
	var cooldown = get_attack_cooldown()
	if cooldown <= 0:
		return 0.0
	return avg_dmg / cooldown

# ═══════════════════════════════════════════════════════════════
# HP / HEILUNG / TOD
# ═══════════════════════════════════════════════════════════════

func take_damage(amount: int):
	var effective = max(1, amount - int(armor * 0.1))
	if is_defending:
		if _defend_mode == "schildwall":
			effective = int(effective * (1.0 - SCHILDWALL_BLOCK))
		elif _defend_mode == "trotz":
			var missing_hp = 1.0 - (float(current_hp) / float(max_hp))
			var reduction = missing_hp * TROTZ_MAX_REDUCTION
			effective = int(effective * (1.0 - reduction))
	if is_berserker:
		effective = int(effective * (1.0 - BERSERKER_DMG_REDUCTION))
	if is_berserker_crash:
		effective = int(effective * BERSERKER_CRASH_DMG_MULT)
	effective = max(1, effective)
	current_hp = clamp(current_hp - effective, 0, max_hp)
	hud.update_health(current_hp, max_hp)
	_gain_rage(15)
	if current_hp <= 0:
		die()

func heal(amount: int):
	current_hp = clamp(current_hp + amount, 0, max_hp)
	hud.update_health(current_hp, max_hp)

func restore_mana(amount: int):
	current_resource = clamp(current_resource + amount, 0, max_resource)
	hud.update_resource(current_resource, max_resource, get_resource_name())

func _gain_rage(amount: int):
	if character_class == null or character_class.resource_type != CharacterClass.ResourceType.RAGE:
		return
	current_resource = clamp(current_resource + amount, 0, max_resource)
	_rage_decay_timer = RAGE_DECAY_DELAY
	hud.update_resource(current_resource, max_resource, get_resource_name())

func _spend_rage(amount: int) -> bool:
	if is_berserker:
		return true  # Während Berserker kostenlos
	if current_resource < amount:
		return false
	current_resource -= amount
	hud.update_resource(current_resource, max_resource, get_resource_name())
	return true

func _update_rage_decay(delta: float):
	if character_class == null or character_class.resource_type != CharacterClass.ResourceType.RAGE:
		return
	if current_resource <= 0:
		return
	if is_berserker:
		return  # Wut verfällt nicht während Berserker
	if _rage_decay_timer > 0.0:
		_rage_decay_timer -= delta
		return
	current_resource = max(0, current_resource - int(RAGE_DECAY_RATE * delta))
	hud.update_resource(current_resource, max_resource, get_resource_name())

func die():
	if is_dead:
		return
	is_dead = true
	print("Spieler gestorben!")
	_play_anim("death")

# ═══════════════════════════════════════════════════════════════
# XP / LEVELING
# ═══════════════════════════════════════════════════════════════

func gain_xp(amount: int):
	current_xp += amount
	print("+" + str(amount) + " XP")
	while current_xp >= xp_to_next_level:
		current_xp -= xp_to_next_level
		_level_up()
	hud.update_level(level, current_xp, xp_to_next_level)

func _level_up():
	level += 1
	xp_to_next_level = int(xp_to_next_level * 1.5)
	_calculate_stats()
	current_hp = max_hp
	current_resource = max_resource
	hud.update_health(current_hp, max_hp)
	hud.update_resource(current_resource, max_resource, get_resource_name())
	print("LEVEL UP! Jetzt Level " + str(level))

# ═══════════════════════════════════════════════════════════════
# LOOT
# ═══════════════════════════════════════════════════════════════

func receive_loot(loot: Dictionary, world_pos: Vector3):
	if loot_window:
		loot_window.show_loot(loot, world_pos)

# ═══════════════════════════════════════════════════════════════
# AKTIONSLEISTE
# ═══════════════════════════════════════════════════════════════

# Skill auf Slot legen
func assign_skill_to_action_bar(slot_index: int, skill_id: String):
	if slot_index < 0 or slot_index >= 9:
		return
	action_bar[slot_index] = skill_id
	_refresh_action_slot(slot_index)

# Consumable auf Slot legen
func assign_to_action_bar(slot_index: int, item: Consumable):
	if slot_index < 0 or slot_index >= 9:
		return
	action_bar[slot_index] = item
	_refresh_action_slot(slot_index)

# Slot-Icon und Cooldown im HUD aktualisieren
func _refresh_action_slot(slot_index: int):
	if slot_index < 0 or slot_index >= 9:
		return
	var slot_content = action_bar[slot_index]

	if slot_content == null:
		hud.clear_slot_icon(slot_index)
		hud.set_slot_stack_count(slot_index, 0)
		return

	if slot_content is String:
		# Skill
		var skill = _find_skill(slot_content)
		if skill:
			if skill["icon"] != "":
				hud.set_slot_icon(slot_index, skill["icon"])
			hud.set_slot_stack_count(slot_index, 0)
	elif slot_content is Consumable:
		# Consumable
		if slot_content.icon:
			hud.set_slot_icon_texture(slot_index, slot_content.icon)
		hud.set_slot_stack_count(slot_index, slot_content.stack_size)

# Stack-Counts aller Consumable-Slots aktualisieren
func _update_action_bar_stacks():
	for i in range(9):
		if action_bar[i] is Consumable:
			hud.set_slot_stack_count(i, action_bar[i].stack_size)

# Slot geleert per Drag
func _on_slot_drag_removed(slot_index: int):
	action_bar[slot_index] = null
	hud.clear_slot_icon(slot_index)
	hud.set_slot_stack_count(slot_index, 0)

# Zwei Slots tauschen per Drag
func _on_slot_drag_swapped(from_slot: int, to_slot: int):
	var temp = action_bar[from_slot]
	action_bar[from_slot] = action_bar[to_slot]
	action_bar[to_slot] = temp
	_refresh_action_slot(from_slot)
	_refresh_action_slot(to_slot)

func _find_skill(skill_id: String) -> Dictionary:
	for s in available_skills:
		if s["id"] == skill_id:
			return s
	return {}

# ═══════════════════════════════════════════════════════════════
# SKILL-AUSFÜHRUNG
# ═══════════════════════════════════════════════════════════════

func _on_slot_clicked(slot_index: int):
	var slot_content = action_bar[slot_index]
	if slot_content == null:
		return
	if slot_content is String:
		execute_skill(slot_content)
	elif slot_content is Consumable:
		_use_consumable_slot(slot_index, slot_content)

func execute_skill(skill_id: String):
	if is_dead or is_casting or is_drinking or is_berserker_crash:
		return
	var skill = _find_skill(skill_id)
	if skill.is_empty() or skill.get("locked", false):
		return

	# GCD Check
	if global_cooldown > 0:
		print("GCD aktiv: %.1fs" % global_cooldown)
		return

	# Skill-CD Check
	if skill_cooldowns.get(skill_id, 0.0) > 0:
		print(skill["name"] + " im Cooldown: %.1fs" % skill_cooldowns[skill_id])
		return

	# Ziel-Check für Angriffe
	var needs_target = (skill_id != "")
	if skill_id in ["autoattack", "heavy_strike", "quick_strike", "frostbolt"]:
		if target == null or not is_instance_valid(target):
			print("Kein Ziel!")
			return
	# Wut-Check für Krieger-Skills (während Berserker gratis)
	if not is_berserker:
		if skill_id == "tektonischer_schlag" and current_resource < TEKTONISCHER_SCHLAG_RAGE:
			print("Zu wenig Wut!")
			return
		if skill_id == "blutrausch" and current_resource < BLUTRAUSCH_RAGE:
			print("Zu wenig Wut!")
			return
		if skill_id == "wirbelwind" and current_resource < WIRBELWIND_RAGE:
			print("Zu wenig Wut!")
			return
		if skill_id == "zornfesseln" and current_resource < ZORNFESSELN_RAGE:
			print("Zu wenig Wut!")
			return

	# Cast-Zeit?
	if skill["cast_time"] > 0:
		_start_cast(skill_id, skill["cast_time"])
		return

	_apply_skill(skill_id)

func _start_cast(skill_id: String, cast_time: float):
	is_casting = true
	cast_total = cast_time
	cast_time_remaining = cast_time
	pending_skill_id = skill_id
	hud.show_castbar(skill_id, cast_time)

func _cancel_cast():
	is_casting = false
	cast_time_remaining = 0.0
	pending_skill_id = ""
	hud.hide_castbar()

func _apply_skill(skill_id: String):
	match skill_id:
		"autoattack":
			start_autoattack()
			perform_autoattack()
		"heavy_strike":
			_do_heavy_strike()
		"quick_strike":
			_do_quick_strike()
		"frostbolt":
			_do_frostbolt()
		"tektonischer_schlag":
			_do_tektonischer_schlag()
		"durchbeissen":
			_do_durchbeissen()
		"blutrausch":
			_do_blutrausch()
		"wirbelwind":
			_do_wirbelwind()
		"zornfesseln":
			_do_zornfesseln()
		"berserker":
			_do_berserker()

# ─── Autoattack ───────────────────────────────────────────────

func start_autoattack():
	autoattack_active = true

func stop_autoattack():
	autoattack_active = false

func perform_autoattack():
	if target == null or not is_instance_valid(target):
		target = null
		autoattack_active = false
		return
	var distance = global_position.distance_to(target.global_position)
	if distance <= get_attack_range():
		var dmg = get_attack_damage()
		target.take_damage(dmg)
		print("Autoattack: " + str(dmg) + " Schaden")
		trigger_global_cooldown()
		_play_anim_once("autoattack")
	else:
		print("Ziel zu weit entfernt")

# ─── Heavy Strike ─────────────────────────────────────────────

func _do_heavy_strike():
	if target == null or not is_instance_valid(target):
		return
	var distance = global_position.distance_to(target.global_position)
	if distance > HEAVY_STRIKE_RANGE:
		print("Ziel zu weit für Heavy Strike!")
		return
	var damage = randi_range(10, 15) + int(strength * 0.5)
	target.take_damage(damage)
	_gain_rage(10)
	skill_cooldowns["heavy_strike"] = HEAVY_STRIKE_COOLDOWN
	trigger_global_cooldown()
	start_autoattack()
	print("Heavy Strike! " + str(damage) + " Schaden")
	_play_anim_once("heavy_strike")

# ─── Tektonischer Schlag ──────────────────────────────────────

func _do_tektonischer_schlag():
	if not _spend_rage(TEKTONISCHER_SCHLAG_RAGE):
		return
	var space_state = get_world_3d().direct_space_state
	var shape = SphereShape3D.new()
	shape.radius = TEKTONISCHER_SCHLAG_RADIUS
	var query = PhysicsShapeQueryParameters3D.new()
	query.shape = shape
	query.transform = global_transform
	query.exclude = [self]
	var hits = space_state.intersect_shape(query)
	var hit_count = 0
	for hit in hits:
		var body = hit.collider
		if body.has_method("take_damage"):
			var damage = randi_range(5, 10) + int(strength * 0.3)
			body.take_damage(damage)
			if body.has_method("apply_slow"):
				body.apply_slow(0.4, 3.0)  # 60% langsamer für 3s
			hit_count += 1
	skill_cooldowns["tektonischer_schlag"] = TEKTONISCHER_SCHLAG_COOLDOWN
	trigger_global_cooldown()
	print("Tektonischer Schlag! %d Gegner getroffen" % hit_count)

# ─── Wirbelwind ───────────────────────────────────────────────

func _do_wirbelwind():
	if not _spend_rage(WIRBELWIND_RAGE):
		return
	var space_state = get_world_3d().direct_space_state
	var shape = SphereShape3D.new()
	shape.radius = WIRBELWIND_RADIUS
	var query = PhysicsShapeQueryParameters3D.new()
	query.shape = shape
	query.transform = global_transform
	query.exclude = [self]
	var hits = space_state.intersect_shape(query)
	var hit_count = 0
	for hit in hits:
		var body = hit.collider
		if body.has_method("take_damage"):
			var damage = randi_range(15, 25) + int(strength * 0.7)
			body.take_damage(damage)
			hit_count += 1
	skill_cooldowns["wirbelwind"] = WIRBELWIND_COOLDOWN
	trigger_global_cooldown()
	print("Wirbelwind! %d Gegner getroffen" % hit_count)

# ─── Zornfesseln ──────────────────────────────────────────────

func _do_zornfesseln():
	if target == null or not is_instance_valid(target):
		return
	var dist = global_position.distance_to(target.global_position)
	if dist > ZORNFESSELN_RANGE:
		print("Ziel zu weit für Zornfesseln!")
		return
	if not _spend_rage(ZORNFESSELN_RAGE):
		return
	target.apply_pull(self, ZORNFESSELN_PULL_DURATION)
	_zornfesseln_target       = target
	_zornfesseln_strike_timer = ZORNFESSELN_PULL_DURATION
	skill_cooldowns["zornfesseln"] = ZORNFESSELN_COOLDOWN
	trigger_global_cooldown()
	print("Zornfesseln! Gegner wird herangezogen...")

# ─── Berserker (Ultimate) ─────────────────────────────────────

func _do_berserker():
	if current_resource < BERSERKER_MIN_RAGE:
		print("Zu wenig Wut für Berserker! (Minimum %d)" % BERSERKER_MIN_RAGE)
		return
	current_resource = 0
	hud.update_resource(current_resource, max_resource, get_resource_name())
	is_berserker       = true
	_berserker_timer   = BERSERKER_DURATION
	is_berserker_crash = false
	skill_cooldowns["berserker"] = BERSERKER_COOLDOWN
	print("BERSERKER! 10 Sekunden Wahnsinn...")

# ─── Durchbeißen / Schildwall / Trotz ────────────────────────

# ─── Blutrausch ───────────────────────────────────────────────

func _do_blutrausch():
	if target == null or not is_instance_valid(target):
		return
	if global_position.distance_to(target.global_position) > BLUTRAUSCH_RANGE:
		print("Ziel zu weit für Blutrausch!")
		return
	if not _spend_rage(BLUTRAUSCH_RAGE):
		return
	var damage = randi_range(8, 14) + int(strength * 0.5)
	target.take_damage(damage)
	_bleed_target     = target
	_bleed_timer      = BLUTRAUSCH_DOT_DURATION
	_bleed_tick_timer = BLUTRAUSCH_DOT_INTERVAL
	skill_cooldowns["blutrausch"] = BLUTRAUSCH_COOLDOWN
	trigger_global_cooldown()
	print("Blutrausch! %d Sofortschaden + Blutung" % damage)

func _has_shield() -> bool:
	var offhand = equipment[Equipment.Slot.OFFHAND]
	return offhand != null and offhand.item_type == Equipment.ItemType.SHIELD

func _do_durchbeissen():
	var has_offhand = _has_shield()
	_defend_mode  = "schildwall" if has_offhand else "trotz"
	is_defending  = true
	_defend_timer = DURCHBEISSEN_DURATION
	skill_cooldowns["durchbeissen"] = DURCHBEISSEN_COOLDOWN
	trigger_global_cooldown()
	print("Durchbeißen! Modus: " + _defend_mode)

# ─── Quick Strike (Schurke) ───────────────────────────────────

func _do_quick_strike():
	if target == null or not is_instance_valid(target):
		return
	var distance = global_position.distance_to(target.global_position)
	if distance > get_attack_range():
		print("Ziel zu weit für Quick Strike!")
		return
	var damage = get_attack_damage()
	target.take_damage(damage)
	skill_cooldowns["quick_strike"] = 1.0
	trigger_global_cooldown()
	start_autoattack()
	print("Quick Strike! " + str(damage) + " Schaden")
	_play_anim_once("autoattack")

# ─── Frostbolt (Magier) ───────────────────────────────────────

func _do_frostbolt():
	if target == null or not is_instance_valid(target):
		return
	var damage = randi_range(8, 14) + int(intelligence * 0.7)
	target.take_damage(damage)
	trigger_global_cooldown()
	start_autoattack()
	print("Frostblitz! " + str(damage) + " Schaden")
	hud.hide_castbar()

# ─── Consumable ───────────────────────────────────────────────

func use_consumable(item: Consumable) -> bool:
	# Cooldown prüfen
	if consumable_cooldowns.get(item.item_name, 0.0) > 0:
		print(item.item_name + " im Cooldown!")
		return false
	if is_drinking:
		return false
	var used = item.use(self)
	if used:
		consumable_cooldowns[item.item_name] = item.cooldown
		_play_drinking_anim()
	return used

func _use_consumable_slot(slot_index: int, item: Consumable):
	if use_consumable(item):
		if item.stack_size <= 0:
			# Stack leer: Item aus Inventar und Slot entfernen
			inventory.remove_item(item)
			action_bar[slot_index] = null
			hud.clear_slot_icon(slot_index)
		else:
			hud.set_slot_stack_count(slot_index, item.stack_size)

# ─── GCD ──────────────────────────────────────────────────────

func trigger_global_cooldown():
	var gcd = get_attack_cooldown()
	if is_berserker:
		gcd *= BERSERKER_GCD_MULT
	global_cooldown = gcd

# ═══════════════════════════════════════════════════════════════
# ZIELAUSWAHL
# ═══════════════════════════════════════════════════════════════

func set_target(new_target, start_attack: bool = false):
	if target != null and is_instance_valid(target):
		target.hide_health()
	target = new_target
	target.show_health()
	print("Ziel: " + target.name)
	if start_attack:
		start_autoattack()

func clear_target():
	if target != null and is_instance_valid(target):
		target.hide_health()
	target = null
	autoattack_active = false
	print("Ziel aufgehoben")

func _try_select_target(start_attack: bool = false, clear_on_miss: bool = true):
	var space_state = get_world_3d().direct_space_state
	var viewport = get_viewport()
	var mouse_pos = viewport.get_mouse_position()
	var ray_origin = camera.project_ray_origin(mouse_pos)
	var ray_end = ray_origin + camera.project_ray_normal(mouse_pos) * 100.0
	var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
	query.exclude = [self]
	var result = space_state.intersect_ray(query)
	if result and result.collider.has_method("take_damage"):
		set_target(result.collider, start_attack)
	elif clear_on_miss:
		# Klick auf freie Fläche → Target entfernen (nur bei LMB)
		clear_target()

# ═══════════════════════════════════════════════════════════════
# ANIMATION SETUP
# ═══════════════════════════════════════════════════════════════

func _setup_animations():
	# AnimationPlayer aus importiertem Modell suchen
	if model:
		anim_player = model.find_child("AnimationPlayer", true, false) as AnimationPlayer
	if anim_player == null:
		push_warning("Player: Kein AnimationPlayer gefunden! Prüfe die Modell-Struktur.")
		return

	_load_anim_from_fbx(ANIM_IDLE,         "idle",         true)
	_load_anim_from_fbx(ANIM_WALK,         "walk",         true)
	_load_anim_from_fbx(ANIM_RUN,          "run",          true)
	_load_anim_from_fbx(ANIM_JUMP,         "jump",         false)
	_load_anim_from_fbx(ANIM_AUTOATTACK,   "autoattack",   false)
	_load_anim_from_fbx(ANIM_HEAVY_STRIKE, "heavy_strike", false)
	_load_anim_from_fbx(ANIM_DEATH,        "death",        false)
	_load_anim_from_fbx(ANIM_WALK_BACK,    "walk_back",    true)
	_load_anim_from_fbx(ANIM_STRAFE_LEFT,      "strafe_left",      true)
	_load_anim_from_fbx(ANIM_STRAFE_RIGHT,     "strafe_right",     true)
	_load_anim_from_fbx(ANIM_RUN_STRAFE_LEFT,  "run_strafe_left",  true)
	_load_anim_from_fbx(ANIM_RUN_STRAFE_RIGHT, "run_strafe_right", true)
	_load_anim_from_fbx(ANIM_WALK_JUMP,        "walk_jump",        false)
	_load_anim_from_fbx(ANIM_RUN_JUMP,         "run_jump",         false)
	_load_anim_from_fbx(ANIM_ROLL,             "roll",             false)
	_load_anim_from_fbx(ANIM_TURN_180,        "turn_180",         false)
	_load_anim_from_fbx(ANIM_DRINKING,         "drinking",         false)

	_play_anim("idle")

	# Waffen-Attachment einrichten
	_setup_weapon_attachment()

func _setup_weapon_attachment():
	if model == null:
		print("Player Weapon: Kein Model!")
		return
	skeleton = model.find_child("Skeleton3D", true, false) as Skeleton3D
	if skeleton == null:
		push_warning("Player: Kein Skeleton3D gefunden!")
		return
	# Alle Bones auflisten zum Debuggen
	print("Player Bones: ", skeleton.get_bone_count())
	for i in range(skeleton.get_bone_count()):
		if "Hand" in skeleton.get_bone_name(i) or "hand" in skeleton.get_bone_name(i):
			print("  Bone ", i, ": ", skeleton.get_bone_name(i))
	var bone_idx = skeleton.find_bone("mixamorig_RightHand")
	if bone_idx == -1:
		push_warning("Player: Bone 'mixamorig_RightHand' nicht gefunden!")
		return
	print("Player Weapon: RightHand Bone gefunden (idx=", bone_idx, ")")
	weapon_attachment = BoneAttachment3D.new()
	weapon_attachment.bone_name = "mixamorig_RightHand"
	skeleton.add_child(weapon_attachment)

func _attach_weapon_model(weapon: Equipment):
	_remove_weapon_model()
	if weapon == null or weapon.model_scene == null:
		print("Player Weapon: Kein Model an Waffe! model_scene=", weapon.model_scene if weapon else "null")
		return
	if weapon_attachment == null:
		print("Player Weapon: Kein weapon_attachment!")
		return
	current_weapon_model = weapon.model_scene.instantiate()
	current_weapon_model.rotation_degrees = Vector3(90, 180, 0)
	current_weapon_model.position = Vector3(0.5, 0.2, 0)
	current_weapon_model.scale = Vector3(0.8, 0.8, 0.8)
	weapon_attachment.add_child(current_weapon_model)
	print("Player Weapon: Schwert angehängt! Scale=", current_weapon_model.scale)

func _remove_weapon_model():
	if current_weapon_model != null and is_instance_valid(current_weapon_model):
		current_weapon_model.queue_free()
		current_weapon_model = null

func _load_anim_from_fbx(fbx_path: String, anim_name: String, loop: bool = false):
	var scene = load(fbx_path)
	if scene == null:
		push_warning("Player: FBX nicht geladen: " + fbx_path)
		return
	var instance = scene.instantiate()
	var ext_ap = instance.find_child("AnimationPlayer", true, false) as AnimationPlayer
	if ext_ap == null:
		instance.queue_free()
		push_warning("Player: Kein AnimationPlayer in " + fbx_path)
		return
	var anim_list = ext_ap.get_animation_list()
	if anim_list.is_empty():
		instance.queue_free()
		return
	var anim = ext_ap.get_animation(anim_list[0])
	# Root Motion entfernen: verhindert Snapping beim Loop
	_strip_root_motion(anim)
	# Loop-Modus setzen
	anim.loop_mode = Animation.LOOP_LINEAR if loop else Animation.LOOP_NONE
	if not anim_player.has_animation_library(""):
		anim_player.add_animation_library("", AnimationLibrary.new())
	var lib = anim_player.get_animation_library("")
	if not lib.has_animation(anim_name):
		lib.add_animation(anim_name, anim)
	instance.queue_free()

func _strip_root_motion(anim: Animation):
	# Mixamo speichert Root Motion entweder auf dem Armature-Node (kein Subname)
	# ODER auf dem Hips-Knochen (hat Subname, aber bewegt sich in XZ vorwärts).
	# Beide Fälle werden hier behandelt.
	for i in range(anim.get_track_count() - 1, -1, -1):
		if anim.track_get_type(i) != Animation.TYPE_POSITION_3D:
			continue
		var np: NodePath = anim.track_get_path(i)
		if np.get_subname_count() == 0:
			# Node-Position-Track (Armature-Root) → komplett entfernen
			anim.remove_track(i)
		else:
			# Knochen-Position-Track — prüfen ob er sich in XZ bewegt
			var key_count = anim.track_get_key_count(i)
			if key_count < 2:
				continue
			var first: Vector3 = anim.track_get_key_value(i, 0)
			var last: Vector3  = anim.track_get_key_value(i, key_count - 1)
			var xz_delta = Vector2(last.x - first.x, last.z - first.z).length()
			if xz_delta > 0.01:
				# Hat XZ-Bewegung (z.B. Hips-Knochen bei Mixamo) → XZ nullen, Y behalten
				for k in range(key_count):
					var v: Vector3 = anim.track_get_key_value(i, k)
					anim.track_set_key_value(i, k, Vector3(0.0, v.y, 0.0))

# ═══════════════════════════════════════════════════════════════
# ANIMATION ABSPIELEN
# ═══════════════════════════════════════════════════════════════

func _play_anim(name: String, speed: float = 1.0):
	if anim_player == null or not anim_player.has_animation(name):
		return
	if anim_player.current_animation == name and anim_player.speed_scale == speed:
		return
	anim_player.speed_scale = speed
	anim_player.play(name, -1, 1.0, false)

func _play_anim_once(name: String):
	if anim_player == null or not anim_player.has_animation(name):
		return
	is_attacking = true
	anim_player.speed_scale = 1.0
	anim_player.play(name, -1, 1.0, false)
	await anim_player.animation_finished
	is_attacking = false

func _play_drinking_anim():
	if anim_player == null or not anim_player.has_animation("drinking"):
		return
	is_drinking = true
	anim_player.speed_scale = 1.0
	anim_player.play("drinking", 0.3, 0.7, false)
	await anim_player.animation_finished
	is_drinking = false


func _do_roll():
	# Bewegungsrichtung beim Roll-Start erfassen
	var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
	var has_target = target != null and is_instance_valid(target)
	if input_dir.length() > 0.1:
		var ref_angle = rotation.y if (has_target or is_walking) else _movement_yaw
		roll_direction = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, ref_angle).normalized()
		# Spieler in Roll-Richtung drehen, Kamera dabei kompensieren
		var old_rot = rotation.y
		rotation.y = atan2(-roll_direction.x, -roll_direction.z)
		camera_pivot.rotation.y -= (rotation.y - old_rot)
	else:
		# Kein Input: nach vorne rollen
		roll_direction = -global_transform.basis.z.normalized()

	is_rolling = true
	roll_cooldown = ROLL_COOLDOWN
	anim_player.speed_scale = 1.0
	anim_player.play("roll", -1, 1.0, false)
	await anim_player.animation_finished
	is_rolling = false

func _update_movement_animation(is_moving: bool, input_dir: Vector2):
	if is_attacking or is_dead or is_rolling or is_drinking:
		return
	var has_target = target != null and is_instance_valid(target)
	if not is_on_floor():
		if not is_walking:
			_play_anim("run_jump")
		elif is_moving:
			_play_anim("walk_jump")
		else:
			_play_anim("jump")
	elif is_moving:
		if not has_target and not is_walking:
			var rmb = Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT)
			if input_dir.y > 0.1:
				_play_anim("walk_back")
			elif rmb and input_dir.x < -0.1:
				_play_anim("run_strafe_left")
			elif rmb and input_dir.x > 0.1:
				_play_anim("run_strafe_right")
			else:
				_play_anim("run")
		elif input_dir.y > 0.1:
			_play_anim("walk_back")
		elif input_dir.x < -0.1:
			_play_anim("strafe_left" if is_walking else "run_strafe_left")
		elif input_dir.x > 0.1:
			_play_anim("strafe_right" if is_walking else "run_strafe_right")
		elif is_walking:
			_play_anim("walk")
		else:
			_play_anim("run")
	else:
		_play_anim("idle")

# ═══════════════════════════════════════════════════════════════
# PHYSICS PROCESS (Hauptschleife)
# ═══════════════════════════════════════════════════════════════

func _physics_process(delta):
	if is_dead:
		return

	# ── Wut-Verfall ───────────────────────────────────────────
	_update_rage_decay(delta)

	# ── Berserker / Crash Timer ───────────────────────────────
	if is_berserker:
		_berserker_timer -= delta
		if _berserker_timer <= 0.0:
			is_berserker = false
			is_berserker_crash = true
			_crash_timer = BERSERKER_CRASH_DURATION
	if is_berserker_crash:
		_crash_timer -= delta
		if _crash_timer <= 0.0:
			is_berserker_crash = false

	# ── Zornfesseln Schlag ────────────────────────────────────
	if _zornfesseln_strike_timer > 0.0:
		_zornfesseln_strike_timer -= delta
		if _zornfesseln_strike_timer <= 0.0:
			if _zornfesseln_target != null and is_instance_valid(_zornfesseln_target):
				var damage = randi_range(20, 30) + int(strength * 0.8)
				_zornfesseln_target.take_damage(damage)
				print("Zornfesseln Schlag! %d Schaden" % damage)
			_zornfesseln_target = null

	# ── Blutungs-DOT ──────────────────────────────────────────
	if _bleed_timer > 0.0:
		_bleed_timer -= delta
		_bleed_tick_timer -= delta
		if _bleed_tick_timer <= 0.0:
			_bleed_tick_timer = BLUTRAUSCH_DOT_INTERVAL
			if _bleed_target != null and is_instance_valid(_bleed_target):
				_bleed_target.take_damage(BLUTRAUSCH_DOT_DAMAGE)
			else:
				_bleed_timer = 0.0
		if _bleed_timer <= 0.0:
			_bleed_target = null

	# ── Defend-Timer ──────────────────────────────────────────
	if is_defending:
		_defend_timer -= delta
		if _defend_timer <= 0.0:
			is_defending = false
			_defend_mode = ""

	# ── Cooldowns herunterzählen ──────────────────────────────
	var gcd_was_active = global_cooldown > 0
	if global_cooldown > 0:
		global_cooldown = max(0.0, global_cooldown - delta)

	for key in skill_cooldowns.keys():
		skill_cooldowns[key] = max(0.0, skill_cooldowns[key] - delta)

	for key in consumable_cooldowns.keys():
		consumable_cooldowns[key] = max(0.0, consumable_cooldowns[key] - delta)

	# ── Autoattack nach GCD oder wenn in Range ───────────────
	if autoattack_active and global_cooldown <= 0 and not is_casting:
		perform_autoattack()

	# ── Cast-System ───────────────────────────────────────────
	if is_casting:
		cast_time_remaining -= delta
		hud.update_castbar(cast_total - cast_time_remaining, cast_total)
		if cast_time_remaining <= 0:
			is_casting = false
			_apply_skill(pending_skill_id)
			pending_skill_id = ""

	# ── HUD Cooldowns aktualisieren ───────────────────────────
	for i in range(9):
		var slot_content = action_bar[i]
		if slot_content is String:
			var cd = max(global_cooldown, skill_cooldowns.get(slot_content, 0.0))
			hud.set_slot_cooldown(i, cd)
		elif slot_content is Consumable:
			hud.set_slot_cooldown(i, consumable_cooldowns.get(slot_content.item_name, 0.0))

	# ── Schwerkraft ───────────────────────────────────────────
	if not is_on_floor():
		velocity.y -= GRAVITY * delta

	# ── Springen ─────────────────────────────────────────────
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = JUMP_VELOCITY
		if is_casting:
			_cancel_cast()

	# ── Zielauswahl (nur Klick, nicht Drag) ───────────────────
	if Input.is_action_just_pressed("select_target"):
		_lmb_press_pos = get_viewport().get_mouse_position()
	if Input.is_action_just_released("select_target"):
		var release_pos = get_viewport().get_mouse_position()
		if _lmb_press_pos.distance_to(release_pos) < 5.0:
			_try_select_target(false)
	if Input.is_action_just_pressed("ui_right_mouse"):
		_try_select_target(true, false)

	# ── Aktionsleiste (Tasten 1-9) ────────────────────────────
	for i in range(9):
		if Input.is_action_just_pressed("action_" + str(i + 1)):
			hud.set_active_slot(i)
			_on_slot_clicked(i)

	# ── Panel-Shortcuts ───────────────────────────────────────
	if Input.is_action_just_pressed("toggle_inventory") and inventory_panel:
		inventory_panel.toggle()
	if Input.is_action_just_pressed("toggle_character") and character_panel:
		character_panel.toggle()
		if character_panel.panel_visible:
			character_panel.update_stats(self)
	if Input.is_action_just_pressed("toggle_skills") and skill_panel:
		skill_panel.toggle()

	# ── TEST ──────────────────────────────────────────────────
	if Input.is_action_just_pressed("test_damage"):
		take_damage(10)

	# ── Roll Cooldown ─────────────────────────────────────────
	if roll_cooldown > 0:
		roll_cooldown = max(0.0, roll_cooldown - delta)

	# ── Walk Toggle ───────────────────────────────────────────
	if Input.is_action_just_pressed("walk_toggle"):
		is_walking = !is_walking

	# ── Bewegung ─────────────────────────────────────────────
	# get_vector: x = links(-)/rechts(+), y = vor(-)/zurück(+)
	var input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")

	# Cast/Trinken unterbrechen bei Bewegung
	if is_casting and input_dir.length() > 0.1:
		_cancel_cast()
	if is_drinking and input_dir.length() > 0.1:
		is_drinking = false

	var is_moving = input_dir.length() > 0.1

	var has_target = target != null and is_instance_valid(target)

	# ── Ausweichrolle ─────────────────────────────────────────
	if Input.is_action_just_pressed("roll") and is_on_floor() and roll_cooldown <= 0 and not is_rolling:
		_do_roll()

	# ── Bewegungsrichtung ────────────────────────────────────────────
	# Souls: world_yaw (stabil, dreht sich nicht mit Spieler mit)
	# Lock-On/Walk: rotation.y (Strafe relativ zur Spielerausrichtung)
	var lmb = Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT)
	# Bewegungs-Referenzwinkel einfrieren wenn LMB gedrückt
	if not lmb:
		_movement_yaw = camera_pivot.world_yaw

	var dir3: Vector3
	if has_target or is_walking:
		dir3 = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, rotation.y)
	else:
		dir3 = Vector3(input_dir.x, 0, input_dir.y).rotated(Vector3.UP, _movement_yaw)

	# ── Souls-Rotation: Zielwinkel aus _movement_yaw berechnen ─────
	# Nur vorwärts/diagonal (nicht S), nicht bei RMB, nicht Lock-On/Walk
	if is_moving and not is_walking and not has_target and not is_rolling \
			and input_dir.y < 0.1 and not Input.is_mouse_button_pressed(MOUSE_BUTTON_RIGHT):
		var desired_angle = atan2(-dir3.x, -dir3.z)
		var diff = abs(angle_difference(rotation.y, desired_angle))
		if diff > deg_to_rad(150.0):
			rotation.y = desired_angle
		else:
			rotation.y = lerp_angle(rotation.y, desired_angle, delta * 12.0)

	# ── Velocity ─────────────────────────────────────────────
	var current_speed = SPEED if is_walking else SPRINT_SPEED
	if is_berserker_crash:
		current_speed *= BERSERKER_CRASH_SPEED_MULT
	if is_rolling:
		velocity.x = roll_direction.x * SPRINT_SPEED
		velocity.z = roll_direction.z * SPRINT_SPEED
	else:
		velocity.x = dir3.x * current_speed
		velocity.z = dir3.z * current_speed

	_update_movement_animation(is_moving, input_dir)
	move_and_slide()
