extends Node const TARGET_RANGE := 20.0 const COMBAT_TIMEOUT := 3.0 var current_target: Node3D = null var mouse_press_pos: Vector2 = Vector2.ZERO var in_combat := false var combat_timer := 0.0 @onready var player: CharacterBody3D = get_parent() @onready var camera: Camera3D = get_parent().get_node("CameraPivot/Camera3D") func _ready() -> void: EventBus.damage_dealt.connect(_on_damage_dealt) EventBus.entity_died.connect(_on_entity_died) EventBus.enemy_engaged.connect(_on_enemy_engaged) func _process(delta: float) -> void: if in_combat: combat_timer -= delta if combat_timer <= 0: in_combat = false func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT: if event.pressed: mouse_press_pos = event.position else: var drag: float = event.position.distance_to(mouse_press_pos) if drag < 5.0: _try_target_under_mouse(event.position) if event.is_action_pressed("target_next"): _cycle_target() func _try_target_under_mouse(mouse_pos: Vector2) -> void: var from := camera.project_ray_origin(mouse_pos) var to := from + camera.project_ray_normal(mouse_pos) * TARGET_RANGE var space := player.get_world_3d().direct_space_state var query := PhysicsRayQueryParameters3D.create(from, to) query.collision_mask = 4 query.collide_with_areas = true query.collide_with_bodies = false var result := space.intersect_ray(query) if result: var hit_target := result.collider.get_parent() as Node3D set_target(hit_target) else: set_target(null) func _cycle_target() -> void: var targets := get_tree().get_nodes_in_group("enemies") + get_tree().get_nodes_in_group("portals") if targets.is_empty(): set_target(null) return if current_target == null or current_target not in targets: set_target(targets[0]) return var idx := targets.find(current_target) var next_idx := (idx + 1) % targets.size() set_target(targets[next_idx]) func set_target(target: Node3D) -> void: current_target = target EventBus.target_changed.emit(player, target) func _on_enemy_engaged(_enemy: Node, target: Node) -> void: if target == player: combat_timer = COMBAT_TIMEOUT if not in_combat: in_combat = true if current_target == null: _auto_target() func _on_damage_dealt(attacker: Node, target: Node, _amount: float) -> void: if target == player: combat_timer = COMBAT_TIMEOUT if not in_combat: in_combat = true if current_target == null: _auto_target() elif attacker == player and in_combat: combat_timer = COMBAT_TIMEOUT func _on_entity_died(entity: Node) -> void: if entity == current_target: set_target(null) if in_combat: _auto_target(entity) func _auto_target(exclude: Node = null) -> void: # Priorität 1: Nächster Gegner var enemies := get_tree().get_nodes_in_group("enemies") var nearest: Node3D = null var nearest_dist: float = INF for enemy in enemies: if is_instance_valid(enemy) and enemy != exclude: var dist: float = player.global_position.distance_to(enemy.global_position) if dist < nearest_dist: nearest_dist = dist nearest = enemy if nearest: set_target(nearest) return # Priorität 2: Nächstes Portal var portals := get_tree().get_nodes_in_group("portals") for p in portals: if is_instance_valid(p) and p != exclude: var dist: float = player.global_position.distance_to(p.global_position) if dist < nearest_dist: nearest_dist = dist nearest = p if nearest: set_target(nearest)