class_name RemoteFishingPresentation extends Node3D const WaterSurfaceMotionType = preload( "res://world/water_surface_motion.gd" ) signal return_completed var _owner: Player var _bobber: MeshInstance3D var _line: MeshInstance3D var _line_mesh := ImmediateMesh.new() var _target: Vector3 var _pending_target: Vector3 var _active: bool = false var _cast_tween: Tween var _return_tween: Tween var _showcase_tween: Tween var _return_showcase_catch: FishCatch var _bobber_idle_elapsed: float = 0.0 func setup(owning_player: Player) -> void: _owner = owning_player _bobber = MeshInstance3D.new() var bobber_mesh := SphereMesh.new() bobber_mesh.radius = 0.12 bobber_mesh.height = 0.24 _bobber.mesh = bobber_mesh var bobber_material := StandardMaterial3D.new() bobber_material.albedo_color = Color(0.92, 0.12, 0.08, 1.0) bobber_material.roughness = 0.45 _bobber.material_override = bobber_material add_child(_bobber) _line = MeshInstance3D.new() _line.mesh = _line_mesh var line_material := StandardMaterial3D.new() line_material.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED line_material.albedo_color = Color(0.9, 0.9, 0.82, 1.0) _line.material_override = line_material add_child(_line) cleanup() func show_cast(origin: Vector3, target: Vector3) -> void: if ( _owner == null or not origin.is_finite() or not target.is_finite() ): return _kill_cast_tween() _active = true _target = origin _pending_target = target _bobber_idle_elapsed = 0.0 _bobber.global_position = origin _bobber.scale = Vector3.ONE _bobber.visible = true _line.visible = true _owner.set_active_item_is_rod(true) _redraw_line() _cast_tween = create_tween() _cast_tween.set_trans(Tween.TRANS_SINE) _cast_tween.set_ease(Tween.EASE_IN_OUT) _cast_tween.tween_method( _set_cast_sample.bind(origin, target), 0.0, 1.0, 0.65, ) _cast_tween.finished.connect(_on_cast_finished) func update_bobber(world_position: Vector3) -> void: if not _active or not world_position.is_finite(): return _pending_target = world_position if _cast_tween != null: return _target = world_position _bobber.global_position = world_position _redraw_line() func show_bite() -> void: if not _active: return var tween: Tween = create_tween() tween.tween_property(_bobber, "scale", Vector3.ONE * 0.7, 0.08) tween.tween_property(_bobber, "scale", Vector3.ONE, 0.12) func play_return(showcase_catch: FishCatch = null) -> void: if not _active or _bobber == null: cleanup() return_completed.emit() return _kill_cast_tween() _kill_return_tween() _return_showcase_catch = showcase_catch var start: Vector3 = _bobber.global_position var target: Vector3 = start if _owner != null and is_instance_valid(_owner): var tip: Marker3D = _owner.get_fishing_rod_tip() if tip != null: target = tip.global_position _return_tween = create_tween() _return_tween.set_trans(Tween.TRANS_QUAD) _return_tween.set_ease(Tween.EASE_IN) _return_tween.tween_method( _set_return_sample.bind(start, target), 0.0, 1.0, 0.42, ) _return_tween.tween_property(_bobber, "scale", Vector3.ZERO, 0.1) _return_tween.finished.connect(_on_return_finished) func cleanup() -> void: _kill_cast_tween() _kill_return_tween() _kill_showcase_tween() _active = false _pending_target = Vector3.ZERO _return_showcase_catch = null _bobber_idle_elapsed = 0.0 if _owner != null and is_instance_valid(_owner): _owner.end_catch_showcase(Callable(), true) if _bobber != null: _bobber.visible = false _bobber.scale = Vector3.ONE if _line != null: _line.visible = false _line_mesh.clear_surfaces() func _set_cast_sample( progress: float, start: Vector3, target: Vector3, ) -> void: _target = start.lerp(target, progress) _target.y += sin(progress * PI) * 2.0 _target.y += ( WaterSurfaceMotionType.get_default_height_offset() * smoothstep(0.72, 1.0, progress) ) _bobber.global_position = _target func _on_cast_finished() -> void: _cast_tween = null _bobber_idle_elapsed = 0.0 _apply_bobber_idle_motion() _redraw_line() func _apply_bobber_idle_motion() -> void: var phase: float = _bobber_idle_elapsed * 0.7 * TAU _target = _pending_target + Vector3.UP * ( WaterSurfaceMotionType.get_default_height_offset() + sin(phase) * 0.035 ) _bobber.global_position = _target _bobber.rotation.z = deg_to_rad(4.0) * sin(phase * 0.73) func _set_return_sample( progress: float, start: Vector3, target: Vector3, ) -> void: var eased_progress: float = 1.0 - pow(1.0 - progress, 3.0) _target = start.lerp(target, eased_progress) _target.y += sin(eased_progress * PI) * 1.3 _bobber.global_position = _target _bobber.rotation = Vector3.ZERO func _on_return_finished() -> void: _return_tween = null _active = false _bobber.visible = false _line.visible = false _line_mesh.clear_surfaces() if ( _return_showcase_catch != null and _owner != null and is_instance_valid(_owner) ): _owner.begin_remote_catch_showcase(_return_showcase_catch) _return_showcase_catch = null _showcase_tween = create_tween() _showcase_tween.tween_interval(2.5) _showcase_tween.finished.connect(_on_showcase_finished) return return_completed.emit() func _on_showcase_finished() -> void: _showcase_tween = null if _owner != null and is_instance_valid(_owner): _owner.end_catch_showcase() return_completed.emit() func _kill_cast_tween() -> void: if _cast_tween != null and _cast_tween.is_valid(): _cast_tween.kill() _cast_tween = null func _kill_return_tween() -> void: if _return_tween != null and _return_tween.is_valid(): _return_tween.kill() _return_tween = null func _kill_showcase_tween() -> void: if _showcase_tween != null and _showcase_tween.is_valid(): _showcase_tween.kill() _showcase_tween = null func _process(delta: float) -> void: if _owner != null and not _owner.is_remote_presentation_visible(): _bobber.visible = false _line.visible = false return if _active: if _cast_tween == null and _return_tween == null: _bobber_idle_elapsed += delta _apply_bobber_idle_motion() _redraw_line() func _redraw_line() -> void: if _owner == null or not is_instance_valid(_owner): cleanup() return var tip: Marker3D = _owner.get_fishing_rod_tip() if tip == null: return _line_mesh.clear_surfaces() _line_mesh.surface_begin(Mesh.PRIMITIVE_LINE_STRIP) _line_mesh.surface_add_vertex(to_local(tip.global_position)) var midpoint: Vector3 = tip.global_position.lerp(_target, 0.5) midpoint.y -= 0.12 _line_mesh.surface_add_vertex(to_local(midpoint)) _line_mesh.surface_add_vertex(to_local(_target)) _line_mesh.surface_end()