class_name Rogue extends Player # Animation-/Audio-Listen als Konstanten const ATTACK_ANIMS: Array[String] = ["2H_Ranged_Shoot"] const RELOAD_ANIMS: Array[String] = ["2H_Ranged_Reload"] const AIM_ANIMS: Array[String] = ["2H_Ranged_Aiming"] const FOOTSTEP_SOUNDS: Array[String] = [ "res://resources/audio/footstep_grass_000.ogg", "res://resources/audio/footstep_grass_001.ogg", "res://resources/audio/footstep_grass_002.ogg", "res://resources/audio/footstep_grass_003.ogg", "res://resources/audio/footstep_grass_004.ogg" ] # Tuning-Konstanten const FOV_AIM: float = 40.0 const FOV_DEFAULT: float = 70.0 const CROSSHAIR_RAY_DISTANCE: float = 100.0 const SHOOT_RAY_LENGTH: float = 1000.0 const SPRING_ARM_AIM_OFFSET_X: float = 0.5 const SPRING_ARM_AIM_LENGTH: float = 3.0 @export var arrow_scene: PackedScene @onready var shoot_point: Node3D = $"Rig/Skeleton3D/handslot_r/2H_Crossbow/ShootPoint" @onready var crossbow: MeshInstance3D = $"Rig/Skeleton3D/handslot_r/2H_Crossbow" @onready var ik_target: Node3D = $Rig/Skeleton3D/ik_target @onready var hand_ik: SkeletonIK3D = $Rig/Skeleton3D/ik_hand_r @export var is_loaded: bool var current_arrow: Projectile func _ready() -> void: # Falls im Editor als geladen markiert, Pfeil instantiieren if is_loaded: load_arrow() func _process(delta: float) -> void: var camera := get_viewport().get_camera_3d() if camera: camera.fov = FOV_AIM if state == States.aiming else FOV_DEFAULT # IK-Ziel und Waffe Richtung Fadenkreuz ausrichten hand_ik.target = ik_target.global_transform hand_ik.start() var screen_center: Vector2 = get_viewport().get_visible_rect().size * 0.5 var target_pos: Vector3 = camera.project_ray_origin(screen_center) + camera.project_ray_normal(screen_center) * CROSSHAIR_RAY_DISTANCE ik_target.global_position = target_pos crossbow.look_at(target_pos, Vector3.UP) crossbow.rotate_object_local(Vector3.UP, PI) # Falls -Z nach vorne zeigt if state == States.aiming: spring_arm.transform.origin.x = SPRING_ARM_AIM_OFFSET_X spring_arm.spring_length = SPRING_ARM_AIM_LENGTH super._process(delta) func _input(event: InputEvent) -> void: # Schießen if event.is_action_pressed("attack"): handle_attack() # Nachladen if event.is_action_pressed("reload"): handle_reload() # Zielen (gedrückt/losgelassen) if event.is_action_pressed("block"): state = States.aiming if event.is_action_released("block"): state = States.idle anim_tree.set("parameters/conditions/aiming", state == States.aiming) anim_tree.set("parameters/conditions/not_aiming", state != States.aiming) super._input(event) func handle_attack() -> void: if not is_loaded: print("No arrow loaded!") return if not enough_stamina_available(attack_cost): return use_stamina(attack_cost) anim_state.travel(ATTACK_ANIMS.pick_random()) is_loaded = false func handle_reload() -> void: if is_loaded: print("Already loaded!") return anim_state.travel(RELOAD_ANIMS.pick_random()) is_loaded = true func shoot_arrow() -> void: if current_arrow == null: return var camera := get_viewport().get_camera_3d() if camera: var screen_center: Vector2 = get_viewport().get_visible_rect().size * 0.5 var from: Vector3 = camera.project_ray_origin(screen_center) var to: Vector3 = from + camera.project_ray_normal(screen_center) * SHOOT_RAY_LENGTH var direction: Vector3 = (to - shoot_point.global_position).normalized() current_arrow.shoot(direction) else: current_arrow.shoot() # Fallback # Beibehaltung für evtl. Animation-CallMethod; delegiert auf die neue Implementierung func arrow_loaded() -> void: load_arrow() func load_arrow() -> void: print("Arrow loaded...") if arrow_scene == null: push_warning("arrow_scene is null; cannot instantiate arrow.") return var arrow := arrow_scene.instantiate() as Projectile if arrow == null: push_warning("arrow_scene is not a Projectile.") return current_arrow = arrow shoot_point.add_child(current_arrow)