extends Node3D signal selected(target: Node3D) signal deselected(target: Node3D) signal clicked(target: Node3D) const RUNNER2CAMERA_LERP: float = 0.125 @export var spin_speed: float = 1.0 @export var hover_amplitude: float = 1.0 @export var hover_frequency: float = 0.5 @export var scale_timeout: float = 0.5 @export var click_scale_timeout: float = 0.25 @export var click_spin_boost: float = 4.0 @export var select_sound: AudioStream @export var deselect_sound: AudioStream @export var click_sound: AudioStream @onready var _audio_player := $AudioStreamPlayer as AudioStreamPlayer @onready var _material := ( ($Cube as MeshInstance3D).get_surface_override_material(0) ) as ShaderMaterial @onready var _test_camera := $SceneTestCamera as SceneTestCamera @onready var _lock := NonReentrantCoroutineMutex.new() var _target: Node3D = null var _target_offset := Vector3.ZERO var _hover_phase: float = 0.0 var _camera_distance_on_interact: float = FX.DEFAULT_CAMERA_DISTANCE var _saved_camera_distance: float = FX.DEFAULT_CAMERA_DISTANCE var target: Node3D: get: return _target set(whom): await select(whom) func _ready() -> void: if not _test_camera.test_mode: hide() func _process(delta: float) -> void: if visible: if _target: global_position = _target.global_position + _target_offset global_position += delta*Vector3.UP*hover_amplitude*sin(_hover_phase) _hover_phase = fposmod( _hover_phase + delta*TAU*hover_frequency, TAU ) rotate(Vector3.UP, spin_speed*delta) func select( whom: Node3D, color: Color = Color.WHITE, offset: Vector3 = Vector3.ZERO, camera_distance_on_interact: float = FX.DEFAULT_CAMERA_DISTANCE ) -> void: if whom == _target: pass elif not whom: await deselect() else: await Wait.until(PlayerControl.enabled) if _target: await deselect() await _lock.acquire() await _do_select_anim( whom, color, offset, camera_distance_on_interact ) _target = whom selected.emit(_target) _lock.relinquish() func deselect(whom: Node3D = null) -> void: await _lock.acquire() if _target: if not whom: whom = _target if _target == whom: _target = null await _do_deselect_anim() deselected.emit(whom) _lock.relinquish() func targeting(whom: Node3D = self) -> bool: if not whom: return not _target elif whom == self: return not not _target else: return _target == whom func click() -> void: await _lock.acquire() if _target: PlayerControl.borrow() await _do_click_anim_first_part() PlayerControl.restore() FX.set_camera_target_override(self) _saved_camera_distance = FX.get_camera_distance() FX.set_camera_distance(_camera_distance_on_interact) clicked.emit(_target) await Wait.tick() await Wait.until(PlayerControl.enabled) FX.clear_camera_target_override() FX.set_camera_distance(_saved_camera_distance) await _do_click_anim_second_part() _lock.relinquish() func _do_select_anim( new_target: Node3D, color: Color, offset: Vector3, camera_distance_on_interact: float ) -> void: _target_offset = offset _camera_distance_on_interact = camera_distance_on_interact color = Color(color, 0.75) _material.set_shader_parameter(&'color', color) _material.set_shader_parameter(&'glow', color/2.0) _audio_player.stream = select_sound _audio_player.play() show() scale = Vector3.ZERO var timeout: float = scale_timeout var new_position := new_target.global_position + _target_offset var old_position := new_position var runner := PlayerControl.get_runner() if runner: old_position = runner.global_position.lerp( PlayerControl.get_camera().global_position, RUNNER2CAMERA_LERP ) while timeout > 0.0: var delta: float = await Wait.tick() scale += Vector3.ONE*delta/scale_timeout global_position = old_position.lerp( new_position, 1.0 - timeout/scale_timeout ) timeout -= delta scale = Vector3.ONE func _do_deselect_anim() -> void: _audio_player.stream = deselect_sound _audio_player.play() scale = Vector3.ONE var timeout: float = scale_timeout/2.0 var old_position := global_position var new_position := old_position var runner := PlayerControl.get_runner() while timeout > 0.0: if runner: new_position = runner.global_position.lerp( PlayerControl.get_camera().global_position, RUNNER2CAMERA_LERP ) var delta: float = await Wait.tick() scale -= Vector3.ONE*4.0*delta/scale_timeout global_position = old_position.lerp( new_position, 1.0 - timeout/scale_timeout ) timeout -= delta scale = Vector3.ZERO hide() func _do_click_anim_first_part() -> void: _audio_player.stream = click_sound _audio_player.play() scale = Vector3.ONE var runner := PlayerControl.get_runner() if runner: runner.turn_toward(global_position) var timeout: float = click_scale_timeout while timeout > 0.0: var delta: float = await Wait.tick() scale -= Vector3.ONE*delta/click_scale_timeout rotate(Vector3.UP, click_spin_boost*delta) timeout -= delta scale = Vector3.ZERO hide() func _do_click_anim_second_part() -> void: show() var timeout: float = click_scale_timeout while timeout > 0.0: var delta: float = await Wait.tick() scale += Vector3.ONE*delta/click_scale_timeout rotate(Vector3.UP, click_spin_boost*delta) timeout -= delta scale = Vector3.ONE func _input(event: InputEvent) -> void: if ( (not not _target) and PlayerControl.enabled() and event.is_action_released(&'interact') ): get_viewport().set_input_as_handled() click()