extends CharacterBody3D const LERP_VALUE : float = 0.15 @onready var camera : Node = $SpringArmPivot/SpringArm3D/Camera3D @onready var cameraarm : Node = $SpringArmPivot/SpringArm3D const fov_normal : float = 75.0 const fov_aiming : float = 50.0 @onready var camerapos_normal : Vector3 = cameraarm.position const camerapos_aimoffset : Vector3 = Vector3(0.3, 0.15, 0) @onready var camerapos_aim : Vector3 = camerapos_normal + camerapos_aimoffset @onready var promptbox = $hud/hud_bottom/promptbox var snap_vector : Vector3 = Vector3.DOWN var speed : float var crouched : bool = false var aiming : bool = false var walk_speed : float = 2.0 var creep_speed : float = 3.0 var run_speed : float = 5.0 var gravity : float = 50.0 @onready var player_mesh : Node3D = $CharacterArmature @onready var spring_arm_pivot : Node3D = $SpringArmPivot var interaction_points : Array[Node] var ipoints_initialized : bool = false var current_interactables : Array[Node3D] var selected_interactable : Node3D = null var held_object : Node3D = null var locked : bool = false var lock_rotation : float = 0.0 func _physics_process(delta): var move_direction : Vector3 = Vector3.ZERO move_direction.x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left") move_direction.z = Input.get_action_strength("move_backwards") - Input.get_action_strength("move_forwards") move_direction = move_direction.rotated(Vector3.UP, spring_arm_pivot.rotation.y) velocity.y -= gravity * delta if Input.is_action_just_pressed("ui_cancel"): get_tree().quit() if !locked: if Input.is_action_just_pressed("crouch"): if crouched: crouched = false $CharacterArmature/PlayerVisibilityShape/Crouching.disabled = true $CharacterArmature/PlayerVisibilityShape/Standing.disabled = false else: crouched = true $CharacterArmature/PlayerVisibilityShape/Standing.disabled = true $CharacterArmature/PlayerVisibilityShape/Crouching.disabled = false if Input.is_action_pressed("run") and !crouched: speed = run_speed elif crouched: speed = creep_speed else: speed = walk_speed velocity.x = move_direction.x * speed velocity.z = move_direction.z * speed if move_direction: player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(velocity.x, velocity.z), LERP_VALUE) aiming = false if Input.is_action_pressed("aim"): handle_aim() aiming = true elif Input.is_action_just_pressed("use"): handle_use() if Input.is_action_just_pressed("use_alt"): handle_use_alt() if aiming: camera.fov = lerpf(camera.fov, fov_aiming, LERP_VALUE) cameraarm.position = lerp(cameraarm.position, camerapos_aim, LERP_VALUE) elif !aiming: camera.fov = lerpf(camera.fov, fov_normal, LERP_VALUE) cameraarm.position = lerp(cameraarm.position, camerapos_normal, LERP_VALUE) apply_floor_snap() move_and_slide() else: player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, lock_rotation, LERP_VALUE) animate(delta) check_interactables() handle_ui() func handle_use(): if held_object == null and selected_interactable != null: if !selected_interactable.grabject and selected_interactable.note_style != "none": show_note(selected_interactable.note_style, selected_interactable.note) elif selected_interactable.grabject: lock_rotation = angle_toward(selected_interactable.global_transform.origin) locked = true if selected_interactable.low_height: $AnimationPlayer_new.play("GrabLow") else: $AnimationPlayer_new.play("Grab") $LockTimer.start(0.62) held_object = selected_interactable selected_interactable = null held_object.hold(0.62) elif held_object != null: var location : Vector3 = $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.global_transform.origin var direction : Vector3 = get_viewport().get_camera_3d().global_transform.basis.z location = location + -Vector3(0, 0.1, 0) held_object.unhold(location) held_object = null hide_hand_objects() func handle_use_alt(): if held_object == null and current_interactables.size() > 1: current_interactables.pop_front() selected_interactable = current_interactables[0] func handle_aim(): if held_object != null: var cameraangle = -get_viewport().get_camera_3d().global_transform.basis.z player_mesh.rotation.y = lerp_angle(player_mesh.rotation.y, atan2(cameraangle.x, cameraangle.z), LERP_VALUE) if held_object != null and Input.is_action_just_pressed("use"): var location : Vector3 = $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.global_transform.origin var direction : Vector3 = get_viewport().get_camera_3d().global_transform.basis.z location = location + 0.1*(-Vector3(direction.x, 0, direction.z)) held_object.throw(location, -direction, 20) held_object = null hide_hand_objects() func handle_ui(): $hud/icon_eye.visible = false $hud/icon_hand.visible = false if selected_interactable != null and not get_viewport().get_camera_3d().is_position_behind(selected_interactable.global_transform.origin): var offset : Vector3 = Vector3(0, 0, 0) if selected_interactable.grabject and selected_interactable.icon_offset: offset.y = selected_interactable.icon_offset var object_coords = get_viewport().get_camera_3d().unproject_position(selected_interactable.global_transform.origin + offset) var icon : Control if selected_interactable.icon == "eye": icon = $hud/icon_eye elif selected_interactable.icon == "hand": icon = $hud/icon_hand else: pass var half_size = icon.size.x / 2 icon.set_position(object_coords - Vector2(half_size, half_size)) icon.visible = true func unlock(): locked = false if held_object.model == "mug": $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint/mug.visible = true func hide_hand_objects(): for object in $CharacterArmature/Skeleton3D/ItemHold_BoneAttach/ItemHoldPoint.get_children(): object.visible = false func angle_toward(target : Vector3): return (Vector2(target.z, target.x) - Vector2($CharacterArmature.global_transform.origin.z, $CharacterArmature.global_transform.origin.x)).angle() func animate(delta): if !locked: if velocity.length() > 0: if speed == run_speed: $AnimationPlayer_new.play("Run") elif crouched: $AnimationPlayer_new.play("Walk_Crouch") else: $AnimationPlayer_new.play("Walk") else: if crouched: $AnimationPlayer_new.play("Idle_Crouch") elif aiming and held_object != null: $AnimationPlayer_new.play("Idle_Gun_Pointing") else: $AnimationPlayer_new.play("Idle_Neutral") func check_interactables(): var new_interactables : Array[Node3D] if selected_interactable != null and selected_interactable.global_transform.origin.distance_to(global_transform.origin + Vector3(0, 0.9, 0)) > 1: selected_interactable = null var objects = $CharacterArmature/GrabArea_Upper.get_overlapping_bodies() for object in objects: object.low_height = false new_interactables.append(object) objects = $CharacterArmature/GrabArea_Lower.get_overlapping_bodies() for object in objects: object.low_height = true new_interactables.append(object) if ipoints_initialized: for point in interaction_points: if point.global_transform.origin.distance_to(global_transform.origin + Vector3(0, 0.9, 0)) < 1: new_interactables.append(point) var the_interactables : Array[Node3D] for object in current_interactables: if object in new_interactables: the_interactables.append(object) for object in new_interactables: if not object in current_interactables: the_interactables.append(object) current_interactables.clear() current_interactables = the_interactables.duplicate() if current_interactables.is_empty(): $hud/hud_bottom/promptbox/prompt_primary.visible = false $hud/hud_bottom/promptbox/prompt_secondary.visible = false $hud/hud_bottom/promptbox/prompt_tertiary.visible = false else: populate_prompts() func populate_prompts(): if selected_interactable == null: selected_interactable = current_interactables[0] $hud/hud_bottom/promptbox/prompt_primary.text = selected_interactable.prompt $hud/hud_bottom/promptbox/prompt_primary.visible = true if current_interactables.size() > 1: for index in current_interactables.size(): var object = current_interactables[index] if selected_interactable == object and index != 0: current_interactables.push_front(current_interactables.pop_at(index)) $hud/hud_bottom/promptbox/prompt_secondary.text = current_interactables[1].prompt $hud/hud_bottom/promptbox/prompt_secondary.visible = true if current_interactables.size() > 2: $hud/hud_bottom/promptbox/prompt_tertiary.visible = true $hud/hud_bottom/promptbox/prompt_tertiary.text = current_interactables[2].prompt if current_interactables.size() > 3: $hud/hud_bottom/promptbox/prompt_overflow.visible = true else: $hud/hud_bottom/promptbox/prompt_overflow.visible = false else: $hud/hud_bottom/promptbox/prompt_tertiary.visible = false else: $hud/hud_bottom/promptbox/prompt_secondary.visible = false $hud/hud_bottom/promptbox/prompt_tertiary.visible = false func init_interactionpoints(pointgroup): for point in pointgroup.get_children(): interaction_points.append(point) ipoints_initialized = true func show_note(style, string): $note.setup(get_viewport().size, style, string) $hud.visible = false $note.visible = true get_tree().paused = true