diff --git a/fishing/fishing_presentation.gd b/fishing/fishing_presentation.gd index 8a5ecb0..c6d5099 100644 --- a/fishing/fishing_presentation.gd +++ b/fishing/fishing_presentation.gd @@ -56,6 +56,7 @@ var _rod: Node3D var _rod_tip: Marker3D var _rod_neutral_rotation: Vector3 var _cast_arrival_position: Vector3 +var _active_cast_arc_height: float = 0.0 var _cast_tracks_water_surface: bool = false var _bobber_surface_position: Vector3 var _bobber_idle_elapsed: float = 0.0 @@ -153,6 +154,7 @@ func begin_cast( target: Vector3, blocked_landing_position: Vector3 = Vector3.ZERO, cast_is_blocked: bool = false, + resolved_arc_height: float = -1.0, ) -> void: if _mode != VisualMode.AIMING or _rod_tip == null: return @@ -165,6 +167,11 @@ func begin_cast( _cast_arrival_position = ( blocked_landing_position if cast_is_blocked else target ) + _active_cast_arc_height = ( + maxf(resolved_arc_height, 0.0) + if resolved_arc_height >= 0.0 + else cast_arc_height + ) _cast_tracks_water_surface = not cast_is_blocked var cast_start: Vector3 = _rod_tip.global_position @@ -302,6 +309,7 @@ func cleanup() -> void: _bobber_base_scale = Vector3.ONE _bobber.rotation = Vector3.ZERO _cast_arrival_position = Vector3.ZERO + _active_cast_arc_height = 0.0 _cast_tracks_water_surface = false _bobber_surface_position = Vector3.ZERO _bobber_idle_elapsed = 0.0 @@ -319,7 +327,7 @@ func _set_cast_sample( target: Vector3, ) -> void: var sample: Vector3 = start.lerp(target, progress) - sample.y += sin(progress * PI) * cast_arc_height + sample.y += sin(progress * PI) * _active_cast_arc_height if _cast_tracks_water_surface: sample.y += ( WaterSurfaceMotionType.get_default_height_offset() diff --git a/fishing/fishing_spot.gd b/fishing/fishing_spot.gd index 988c88c..f437c26 100644 --- a/fishing/fishing_spot.gd +++ b/fishing/fishing_spot.gd @@ -783,19 +783,31 @@ func _confirm_cast() -> void: return var cast_launch_position := _get_cast_launch_position() - _cast_path_is_clear = is_cast_path_clear( + var resolved_cast_arc: Dictionary = _surface_resolver.resolve_cast_arc( + get_world_3d().direct_space_state, cast_launch_position, _cast_target, + _presentation.cast_arc_height, ) + var resolved_arc_height: float = float( + resolved_cast_arc.get("arc_height", _presentation.cast_arc_height) + ) + var resolved_cast_collision: Dictionary = resolved_cast_arc.get( + "collision", + {}, + ) + _cast_path_is_clear = resolved_cast_collision.is_empty() var cast_is_invalid: bool = ( not _aim_surface_sample.is_fishable() or not _cast_path_is_clear ) var arrival_position: Vector3 = _cast_target if not _cast_path_is_clear: - arrival_position = _resolve_cast_impact_position( - cast_launch_position, + var collision_position: Variant = resolved_cast_collision.get( + "position", _cast_target, ) + if typeof(collision_position) == TYPE_VECTOR3: + arrival_position = collision_position if _active_player != null: # Movement is available while aiming, then locks once the cast is # released so the active bobber/fishing event remains anchored. @@ -806,6 +818,7 @@ func _confirm_cast() -> void: _cast_target, arrival_position, cast_is_invalid, + resolved_arc_height, ) _presentation.set_line_mode(FishingPresentationType.LineMode.TAUT) @@ -1410,6 +1423,7 @@ func resolve_fishing_surface( get_world_3d().direct_space_state, target, resolved_reference_y, + water_occlusion_tolerance, ) diff --git a/fishing/fishing_surface_resolver.gd b/fishing/fishing_surface_resolver.gd index a764c58..821d8cc 100644 --- a/fishing/fishing_surface_resolver.gd +++ b/fishing/fishing_surface_resolver.gd @@ -23,6 +23,7 @@ func resolve_surface( space_state: PhysicsDirectSpaceState3D, query_position: Vector3, reference_y: float, + maximum_solid_rise: float = INF, ) -> FishingSurfaceSampleType: var sample := FishingSurfaceSampleType.new() var ray_top: float = maxf(query_position.y, reference_y) + ray_start_height @@ -33,12 +34,18 @@ func resolve_surface( ray_top, ray_bottom, ) - var solid_hit: Dictionary = _find_top_solid_surface( - space_state, - query_position, + var solid_ray_top: float = minf( ray_top, - ray_bottom, + reference_y + maximum_solid_rise, ) + var solid_hit: Dictionary = {} + if solid_ray_top > ray_bottom: + solid_hit = _find_top_solid_surface( + space_state, + query_position, + solid_ray_top, + ray_bottom, + ) if water_region != null: var water_height: float = water_region.get_surface_height() diff --git a/tests/fishing_surface_validation.gd b/tests/fishing_surface_validation.gd index 2f4a4b7..15cc428 100644 --- a/tests/fishing_surface_validation.gd +++ b/tests/fishing_surface_validation.gd @@ -50,6 +50,7 @@ func _validate_resolver_layers() -> void: _add_solid_box(world, Vector3(40.0, 2.0, 12.0), Vector3(10.0, -1.0, 0.0)) _add_water_region(world, Vector3(0.0, 2.0, 0.0), Vector2(8.0, 8.0), PondPool) _add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(2.0, 2.25, 0.0)) + _add_solid_box(world, Vector3(1.0, 1.0, 1.0), Vector3(-2.0, 6.0, 0.0)) _add_water_region(world, Vector3(10.0, 5.0, 0.0), Vector2(6.0, 6.0), PondPool) _add_water_region(world, Vector3(20.0, 3.0, 0.0), Vector2(6.0, 6.0), null) await physics_frame @@ -64,6 +65,14 @@ func _validate_resolver_layers() -> void: ) assert(water.is_fishable()) assert(is_equal_approx(water.position.y, 2.0)) + var water_under_overhang: FishingSurfaceSampleType = resolver.resolve_surface( + space_state, + Vector3(-2.0, 4.0, 0.0), + 4.0, + 0.08, + ) + assert(water_under_overhang.is_fishable()) + assert(is_equal_approx(water_under_overhang.position.y, 2.0)) var covered_water: FishingSurfaceSampleType = resolver.resolve_surface( space_state,