diff --git a/client/scripts/player.gd b/client/scripts/player.gd index 295836c..32a507e 100644 --- a/client/scripts/player.gd +++ b/client/scripts/player.gd @@ -1,8 +1,8 @@ extends CharacterBody3D ## NS-23: Nav agent suggests waypoints; arrival uses stored server goal only (not agent “finished”). -## NS-19 bumps: thin box colliders need **auto-step** (`test_move` up+forward); Y steering alone -## is not enough for vertical faces. +## NS-19 bumps: **autostep** via `test_move` — up+forward to mount, down+forward to leave (pure down +## hits the bump volume you’re standing on). const MOVE_SPEED: float = 5.0 const ARRIVE_EPS: float = 0.35 @@ -10,10 +10,11 @@ const VERT_ARRIVE_EPS: float = 0.16 const MAX_CLIMB_SPEED: float = 2.6 const MAX_DESCENT_SPEED: float = 2.2 const DIRECT_APPROACH_RADIUS: float = 0.85 -## NS-19 bumps ≤ ~0.15 m; probe slightly above for capsule + nav tolerance. const STEP_MAX_HEIGHT: float = 0.32 const STEP_PROBE_FWD: float = 0.38 const STUCK_HORIZ_SPEED: float = 0.28 +## Drop tries when leaving a bump (combined with forward over the lip). +const LEDGE_DROP_STEPS: PackedFloat32Array = PackedFloat32Array([0.08, 0.12, 0.18, 0.24, 0.32]) var _has_walk_goal: bool = false var _auth_walk_goal: Vector3 = Vector3.ZERO @@ -55,19 +56,27 @@ func _steer_toward_world_point(point: Vector3) -> void: ## Godot `test_move`: returns **true** if the motion would hit something (blocked). -func _try_autostep(delta: float) -> bool: +func _try_autostep(delta: float, full_to_goal: Vector3) -> bool: var to_goal_h: Vector3 = Vector3( - _auth_walk_goal.x - global_position.x, 0.0, _auth_walk_goal.z - global_position.z + full_to_goal.x, 0.0, full_to_goal.z ) if to_goal_h.length_squared() < 0.0225: return false var dir_h: Vector3 = to_goal_h.normalized() var fwd_len: float = maxf(MOVE_SPEED * delta, 0.1) var motion_fwd: Vector3 = dir_h * minf(fwd_len, STEP_PROBE_FWD) - var motion_up: Vector3 = Vector3(0.0, STEP_MAX_HEIGHT, 0.0) var xf: Transform3D = global_transform if not test_move(xf, motion_fwd): return false + if full_to_goal.y > 0.02: + return _autostep_up(xf, motion_fwd, dir_h) + if full_to_goal.y < -0.02: + return _autostep_down(xf, motion_fwd, dir_h) + return false + + +func _autostep_up(xf: Transform3D, motion_fwd: Vector3, dir_h: Vector3) -> bool: + var motion_up: Vector3 = Vector3(0.0, STEP_MAX_HEIGHT, 0.0) if test_move(xf, motion_up): return false var xf_up := Transform3D(xf.basis, xf.origin + motion_up) @@ -80,6 +89,20 @@ func _try_autostep(delta: float) -> bool: return true +func _autostep_down(xf: Transform3D, motion_fwd: Vector3, dir_h: Vector3) -> bool: + for i: int in range(LEDGE_DROP_STEPS.size()): + var drop: float = LEDGE_DROP_STEPS[i] + var motion_off: Vector3 = motion_fwd + Vector3(0.0, -drop, 0.0) + if test_move(xf, motion_off): + continue + global_position += motion_off + velocity.x = dir_h.x * MOVE_SPEED + velocity.z = dir_h.z * MOVE_SPEED + velocity.y = -minf(MAX_DESCENT_SPEED, 1.2) + return true + return false + + func _physics_process(delta: float) -> void: if not _has_walk_goal: velocity = Vector3.ZERO @@ -129,6 +152,8 @@ func _maybe_autostep_after_slide( var hspd: float = Vector2(velocity.x, velocity.z).length() var want_step: bool = hspd < STUCK_HORIZ_SPEED if not want_step and is_on_wall(): - want_step = full_to_goal.y > 0.03 and horiz_dist > 0.12 - if want_step and _try_autostep(delta): + want_step = horiz_dist > 0.12 and ( + full_to_goal.y > 0.03 or full_to_goal.y < -0.03 + ) + if want_step and _try_autostep(delta, full_to_goal): move_and_slide()