class_name Player extends CharacterBody3D enum State { NORMAL, DASHING, LOCKED, } const MOVE_SPEED := 7.5 const DASH_SPEED := 25. static var instance: Player @export var shake_noise: FastNoiseLite var shake_duration := 0. @onready var camera_transform: Transform3D = %Camera3D.transform var damage_clock := 0. var aim_angle: float var health := 3: set(v): health = v if is_node_ready(): %HealthLabel.text = "Health: %d" % v var state := State.NORMAL var dash_direction: Vector2 var stamina := 1.0 @export var guns: Array[Gun] var gun_index := 0: set(v): gun_index = v % guns.size() %GunLabel.text = gun.name var gun: Gun: get: return guns[gun_index] func _init() -> void: instance = self func _ready() -> void: health = health func _process_stamina(delta: float) -> void: stamina = move_toward(stamina, 1., delta * 0.2) %StaminaBar.value = stamina func exp_lerp(a: Variant, b: Variant, decay: float, dt: float) -> Variant: return lerp(a, b, 1 - exp(-decay * dt)) func _process_movement(delta: float) -> void: var input = Input.get_vector("move_left", "move_right", "move_up", "move_down") input.normalized() var mul := 5. if DebugMenu.high_speed_hack else 1. var desired_velocity = Vector3(input.x, 0., input.y) * MOVE_SPEED * mul velocity = exp_lerp(velocity, desired_velocity, 20, delta) move_and_slide() if Input.is_action_just_pressed("dash") and stamina >= 1. and not input.is_zero_approx(): stamina = 0. dash_direction = input state = State.DASHING await get_tree().create_timer(.25, false).timeout state = State.LOCKED await get_tree().create_timer(.5, false).timeout state = State.NORMAL func _process_dash() -> void: velocity = Vector3(dash_direction.x, 0., dash_direction.y) * DASH_SPEED move_and_slide() func _process_aim() -> void: var viewport_mouse_pos := get_viewport().get_mouse_position() var r_origin: Vector3 = %Camera3D.project_ray_origin(viewport_mouse_pos) var r_dir: Vector3 = %Camera3D.project_ray_normal(viewport_mouse_pos) # y = mx + b # 0 = mx + b # -b / m = x var t := -r_origin.y / r_dir.y var world_mouse_pos = r_origin + r_dir * t var to_mouse_pos = world_mouse_pos - global_position var angle = Vector2(to_mouse_pos.x, -to_mouse_pos.z).angle() %Cannon.rotation.y = angle aim_angle = angle %Reticle.position = to_mouse_pos func _process_shoot(delta: float) -> void: var clock_mul := 1. match state: State.DASHING: clock_mul = 2.5 State.LOCKED: clock_mul = 2.5 gun.fire_clock -= delta * clock_mul if Input.is_action_pressed("fire") and gun.fire_clock <= 0.: gun.fire(aim_angle) func _process_cam_shake(delta: float) -> void: shake_duration -= delta %Camera3D.transform = camera_transform if shake_duration <= 0: return var x := shake_noise.get_noise_1d(shake_duration * 10000) var y := shake_noise.get_noise_1d(-shake_duration * 10000) var v := \ camera_transform.basis.x * x + \ camera_transform.basis.y * y print("v = ", v) %Camera3D.position += v * .5 func _process(delta: float) -> void: match state: State.NORMAL: _process_movement(delta) State.DASHING: _process_dash() State.LOCKED: pass _process_aim() _process_shoot(delta) _process_cam_shake(delta) _process_stamina(delta) damage_clock -= delta if Input.is_action_just_pressed("swap_weapons"): gun_index += 1 func damage(damager: Node3D) -> void: if state == State.DASHING: damager.queue_free() else: if damage_clock <= 0.: health -= 1 damage_clock = 3 shake_duration = .25 if health == 0: get_tree().reload_current_scene()