extends Node3D @export var camera: Camera3D @export var initial_target: Node3D var target: Targetable = null @export var sensitivity := 0.01 @export var charge_time := 1.7 @export var min_charge := 0.2 var camera_tweening = false var time_targets := Dictionary() func _ready(): set_target(initial_target, false) time_targets[0] = target camera.set_as_top_level(true) ControllerEventBus.billiard_touched_billiard.connect(_on_billiard_touched_billiard) TAS_System.FrameIncremented.connect(frame_inc) TAS_System.FramesAdvanced.connect(frames_change) TAS_System.FramesRegressed.connect(frames_change) TAS_System.FramesReset.connect(frames_reset) func frame_inc(frame: int): time_targets[frame] = target func frames_change(_start: int, end: int): if time_targets[end] != target: set_target(time_targets[end].get_billiard()) func frames_reset(): time_targets = Dictionary() time_targets[0] = target func _on_billiard_touched_billiard(who: Billiard, touched: Billiard): var who_target = Targetable.is_targetable(who) if who_target != target or who.can_hit: return set_target(touched) ControllerEventBus.new_target.emit(touched) func get_charge(): return %radial_ui.charge_amount func _input(event): var billiard := target.get_billiard() if camera_tweening or not MouseControl.mouse_is_locked(): return if event is InputEventMouseMotion and get_charge() == 0.0 : rotate_view(event.relative*sensitivity) if event.is_action_released("charge") and not billiard.can_hit and charge_tween != null: release() if event.is_action_pressed("charge") and charge_tween == null: if billiard.can_hit: charge(event.get_action_strength("charge")) else: $NoChargesSound.play() func _process(_delta): transform.origin = target.global_position %camera_spot.look_at(target.global_position) if not camera_tweening: camera.global_transform = %camera_spot.global_transform else: camera.global_position = lerp(camera.global_position, %camera_spot.global_position, 0.5) camera.global_rotation = lerp(camera.global_rotation, %camera_spot.global_rotation, 0.5) var epsilon: float = 0.005 if (camera.global_position - %camera_spot.global_position).length() <= epsilon and (camera.global_rotation - %camera_spot.global_rotation).length() <= epsilon: camera_tweening = false if get_charge() >= 1.0: cancel_charge() func cancel_charge(): %radial_ui.set_charge(0.0) %ChargeSound.stop() if charge_tween != null: charge_tween.kill() charge_tween = null var billiard := target.get_billiard() billiard.can_hit = true func release(): if get_charge() <= min_charge: cancel_charge() return %ChargeReleaseSound.volume_db = get_charge() * 12 %ChargeReleaseSound.play() var billiard := target.get_billiard() TAS_System.StartIncrementingFrames() billiard.hit((target.global_position - %camera_spot.global_position).normalized(), get_charge()) %radial_ui.set_charge(0.0) %ChargeSound.stop() if charge_tween != null: charge_tween.kill() charge_tween = null var charge_tween: Tween = null func charge(_amount: float): var billiard := target.get_billiard() billiard.can_hit = false if charge_tween != null: charge_tween.kill() charge_tween = create_tween() charge_tween.tween_method(%radial_ui.set_charge, 0.0, 1.0, charge_time) %ChargeSound.play() func rotate_view(amount: Vector2): rotate_y(-amount.x) %rotate_helper.rotate_z(amount.y) %rotate_helper.rotation_degrees.z = clampf(%rotate_helper.rotation_degrees.z, -77, 77) func set_target(node: Node3D, should_create_tween=true): camera_tweening = should_create_tween var ntarget = Targetable.is_targetable(node) var billiard = ntarget.get_billiard() billiard.can_hit = true if ntarget == null: push_error("Node is node targetable", node) return if target != null: target.unmake_target() ntarget.make_target() target = ntarget