diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 5184f80..a085809 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -1,7 +1,7 @@ extends CharacterBody3D ## NS-23: Nav waypoints + stored server goal for arrival. -## NS-19: convex frustum bumps; `ns19_bump` group — on flat plateau skip nav waypoints (departure). +## NS-19: `ns19_bump` — skip nav waypoints when leaving (goal lower) or on flatish bump top. const MOVE_SPEED: float = 5.0 const ARRIVE_EPS: float = 0.35 @@ -11,8 +11,11 @@ const VERT_ARRIVE_EPS: float = 0.055 const MAX_CLIMB_SPEED: float = 2.6 const MAX_DESCENT_SPEED: float = 2.2 const DIRECT_APPROACH_RADIUS: float = 0.85 -## Floor normal must be this “flat” to count as bump plateau (slopes use nav waypoints). -const NS19_BUMP_TOP_NORMAL_DOT: float = 0.992 +## Plateau / near-rim: floor normal within ~15° of up still counts (0.992 missed convex edges). +const NS19_BUMP_FLAT_NORMAL_DOT: float = 0.964 +const NS19_RAY_DOWN: float = 3.0 +## Second ray origin offset toward feet so a tall capsule still hits bump underfoot. +const NS19_FEET_RAY_Y_OFFSET: float = -0.45 var _has_walk_goal: bool = false var _auth_walk_goal: Vector3 = Vector3.ZERO @@ -43,18 +46,23 @@ func snap_to_server(world_pos: Vector3) -> void: _nav_agent.set_target_position(world_pos) -func _on_ns19_bump_plateau() -> bool: - if not is_on_floor(): +func _should_skip_nav_for_ns19_bump() -> bool: + var under_bump: bool = _ns19_bump_under_ray(global_position) + if not under_bump: + under_bump = _ns19_bump_under_ray(global_position + Vector3(0.0, NS19_FEET_RAY_Y_OFFSET, 0.0)) + if not under_bump: return false - if get_floor_normal().dot(Vector3.UP) < NS19_BUMP_TOP_NORMAL_DOT: - return false - return _raycast_down_hits_group("ns19_bump", 2.5) + # Waypoints pick “down the mesh” first; horizontal steer to goal fixes departure. + if _auth_walk_goal.y < global_position.y - 0.02: + return true + if is_on_floor() and get_floor_normal().dot(Vector3.UP) >= NS19_BUMP_FLAT_NORMAL_DOT: + return true + return false -func _raycast_down_hits_group(group_name: String, cast_len: float) -> bool: +func _ns19_bump_under_ray(from: Vector3) -> bool: var space: PhysicsDirectSpaceState3D = get_world_3d().direct_space_state - var from: Vector3 = global_position - var to: Vector3 = from + Vector3(0.0, -cast_len, 0.0) + var to: Vector3 = from + Vector3(0.0, -NS19_RAY_DOWN, 0.0) var q: PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(from, to) q.collision_mask = 1 q.exclude = [get_rid()] @@ -64,7 +72,7 @@ func _raycast_down_hits_group(group_name: String, cast_len: float) -> bool: var col: Variant = hit.get("collider") var n: Node = col as Node while n: - if n.is_in_group(group_name): + if n.is_in_group("ns19_bump"): return true n = n.get_parent() return false @@ -123,10 +131,8 @@ func _physics_process(_delta: float) -> void: move_and_slide() return - # Nav waypoints on the plateau often point down the slope first; goal-only + horizontal steer - # matches the NS-19 departure fix. Slopes stay on waypoints so obstacle routing still applies - # when crossing the district. - if _on_ns19_bump_plateau(): + # On bump + (goal lower or flat top): skip waypoints so we do not follow vertical “drop” legs. + if _should_skip_nav_for_ns19_bump(): _steer_toward_world_point(_auth_walk_goal) move_and_slide() return