NPC sometimes wont jump onto land

In my script, I am trying to make the NPC always detect if its in the water so the Y position is -2 and just move onto the land. The model is inside ReplicatedStorage and will be placed into workspace and sometimes some of them just dont move at all. When the NPC touches the part, it should jump onto the platform and move forward then stop. Here is a clip of it:

Here is my script:

local humanoid = script.Parent:WaitForChild("Humanoid")
local RunService = game:GetService("RunService")
local pathfindingService = game:GetService("PathfindingService")

local lowerPositionThresholdOffset = -2
local groundMaterial = Enum.Material.Grass
local moveSpeed = 5
local searchRadius = 20
local jumpVelocity = 10
local jumpMoveSpeed = 7
local jumpForwardOffset = 3

local rootPart
local initialY

local function findNearestGround()
	local nearestGroundPart = nil
	local nearestDistanceSqr = math.huge
	local searchOrigin = rootPart.Position
	local partsToCheck = workspace:FindPartsInRegion3WithWhiteList(
		Region3.new(searchOrigin - Vector3.one * searchRadius, searchOrigin + Vector3.one * searchRadius),
		workspace:GetChildren()
	)
	for _, part in ipairs(partsToCheck) do
		if part.Material == groundMaterial then
			local distanceSqr = (part.Position - searchOrigin).Magnitude ^ 2
			if distanceSqr < nearestDistanceSqr then
				nearestDistanceSqr = distanceSqr
				nearestGroundPart = part
			end
		end
	end
	return nearestGroundPart
end

local function moveToGround(groundPart)
	local targetPosition = groundPart.Position + Vector3.new(0, humanoid.HipHeight / 2, 0)
	local path = pathfindingService:CreatePath({
		AgentRadius = 2,
		AgentHeight = 3,
		AgentCanClimb = true,
		AgentCanJump = true,
	})

	local success, waypoints = path:ComputeAsync(rootPart.Position, targetPosition)
	if success and #waypoints > 0 then
		for _, waypoint in ipairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	else
		humanoid:MoveTo(targetPosition)
		humanoid.MoveToFinished:Wait()
	end
end

local function jumpAndMove(groundPart)
	humanoid.Jump = true
	humanoid.WalkSpeed = jumpMoveSpeed
	local forwardVector = rootPart.CFrame.LookVector * jumpForwardOffset
	local sideVector = rootPart.CFrame.RightVector * (math.random() - 0.5) * 2
	humanoid:MoveTo(groundPart.Position + Vector3.new(0, humanoid.HipHeight, 0) + forwardVector + sideVector)
end

if humanoid then
	rootPart = script.Parent:FindFirstChild("HumanoidRootPart")
	initialY = script.Parent:GetPivot().Position.Y

	while true do
		local currentY = rootPart.Position.Y
		local nearestGround = findNearestGround()

		if currentY < initialY + lowerPositionThresholdOffset and nearestGround then
			moveToGround(nearestGround)
			humanoid.MoveToFinished:Wait()
			jumpAndMove(nearestGround)
			humanoid.MoveToFinished:Wait()
		end
		wait(0.1)
	end
end```

This does not work because setting Humanoid.Jump = true only initiates the jump but calling MoveTo immediately after interrupts it MoveTo makes the humanoid start walking which cancels the jump before it actually happens so the NPC never properly leaves the ground and fails to reach elevated targets

You should wait until the humanoid is truly in the air for example by checking HumanoidStateChanged for Freefall or use BodyVelocity to control the jump manually

edit line:

local function jumpAndMove(groundPart)
    humanoid.JumpPower = jumpVelocity
    humanoid.UseJumpPower = true
    humanoid.Jump = true

    task.wait(0.1)

	local forwardVector = rootPart.CFrame.LookVector * jumpForwardOffset
	local sideVector = rootPart.CFrame.RightVector * (math.random() - 0.5) * 2

    local moveTarget = groundPart.Position + Vector3.new(0, humanoid.HipHeight, 0) + forwardVector + sideVector
    humanoid:MoveTo(moveTarget)
end
local humanoid = script.Parent:WaitForChild("Humanoid")
local RunService = game:GetService("RunService")
local pathfindingService = game:GetService("PathfindingService")

local lowerPositionThresholdOffset = -2
local groundMaterial = Enum.Material.Grass
local moveSpeed = 5
local searchRadius = 20
local jumpVelocity = 10
local jumpMoveSpeed = 7
local jumpForwardOffset = 3

local rootPart
local initialY

local function findNearestGround()
	local nearestGroundPart = nil
	local nearestDistanceSqr = math.huge
	local searchOrigin = rootPart.Position
	local partsToCheck = workspace:FindPartsInRegion3WithWhiteList(
		Region3.new(searchOrigin - Vector3.one * searchRadius, searchOrigin + Vector3.one * searchRadius),
		workspace:GetChildren()
	)
	for _, part in ipairs(partsToCheck) do
		if part.Material == groundMaterial then
			local distanceSqr = (part.Position - searchOrigin).Magnitude ^ 2
			if distanceSqr < nearestDistanceSqr then
				nearestDistanceSqr = distanceSqr
				nearestGroundPart = part
			end
		end
	end
	return nearestGroundPart
end

local function moveToGround(groundPart)
	local targetPosition = groundPart.Position + Vector3.new(0, humanoid.HipHeight / 2, 0)
	local path = pathfindingService:CreatePath({
		AgentRadius = 2,
		AgentHeight = 3,
		AgentCanClimb = true,
		AgentCanJump = true,
	})

	local success, waypoints = path:ComputeAsync(rootPart.Position, targetPosition)
	if success and #waypoints > 0 then
		for _, waypoint in ipairs(waypoints) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	else
		humanoid:MoveTo(targetPosition)
		humanoid.MoveToFinished:Wait()
	end
end

local function jumpAndMove(groundPart)
    humanoid.JumpPower = jumpVelocity
    humanoid.UseJumpPower = true
    humanoid.Jump = true

    task.wait(0.1)

	local forwardVector = rootPart.CFrame.LookVector * jumpForwardOffset
	local sideVector = rootPart.CFrame.RightVector * (math.random() - 0.5) * 2

    local moveTarget = groundPart.Position + Vector3.new(0, humanoid.HipHeight, 0) + forwardVector + sideVector
    humanoid:MoveTo(moveTarget)
end

if humanoid then
	rootPart = script.Parent:FindFirstChild("HumanoidRootPart")
	initialY = script.Parent:GetPivot().Position.Y

	while true do
		local currentY = rootPart.Position.Y
		local nearestGround = findNearestGround()

		if currentY < initialY + lowerPositionThresholdOffset and nearestGround then
			moveToGround(nearestGround)
			humanoid.MoveToFinished:Wait()
			jumpAndMove(nearestGround)
			humanoid.MoveToFinished:Wait()
		end
		wait(0.1)
	end
end

Sorry I am late I had something to do. The NPC is still not jumping when touching the grass part

Calling Humanoid:MoveTo() after forcing a jump (humanoid.Jump = true) is unreliable, because MoveTo internally cancels jump momentum and waits for the character to land before completing.

And you’re adding an offset to groundPart.Position, but not using a path to it. If that new position isn’t walkable or is mid-air, the NPC will stop and do nothing.

MoveToFinished waits until the humanoid finishes walking to the destination. But if the NPC jumps, this can stall indefinitely if they never “finish” walking.

local humanoid = script.Parent:WaitForChild("Humanoid")
local RunService = game:GetService("RunService")
local PathfindingService = game:GetService("PathfindingService")

local groundMaterial = Enum.Material.Grass
local lowerPositionThresholdOffset = -2
local moveSpeed = 5
local searchRadius = 20
local jumpVelocity = 50
local jumpForwardOffset = 3

local rootPart = script.Parent:WaitForChild("HumanoidRootPart")
local initialY = rootPart.Position.Y

local function findNearestGround()
	local nearest = nil
	local nearestDist = math.huge
	local origin = rootPart.Position

	for _, part in ipairs(workspace:GetDescendants()) do
		if part:IsA("BasePart") and part.Material == groundMaterial then
			local dist = (part.Position - origin).Magnitude
			if dist < nearestDist then
				nearest = part
				nearestDist = dist
			end
		end
	end

	return nearest
end

local function moveToGround(groundPart)
	local target = groundPart.Position + Vector3.new(0, humanoid.HipHeight, 0)

	local path = PathfindingService:CreatePath({
		AgentRadius = 2,
		AgentHeight = 5,
		AgentCanClimb = true,
		AgentCanJump = true,
	})

	local success, _ = pcall(function()
		path:ComputeAsync(rootPart.Position, target)
	end)

	if path.Status == Enum.PathStatus.Complete then
		for _, waypoint in ipairs(path:GetWaypoints()) do
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	else
		humanoid:MoveTo(target)
		humanoid.MoveToFinished:Wait()
	end
end

local function jumpToward(groundPart)
	local direction = (groundPart.Position - rootPart.Position).Unit
	rootPart.Velocity = Vector3.new(direction.X * jumpForwardOffset, jumpVelocity, direction.Z * jumpForwardOffset)
end

while true do
	local y = rootPart.Position.Y
	local ground = findNearestGround()

	if y < initialY + lowerPositionThresholdOffset and ground then
		moveToGround(ground)
		task.wait(0.2)
		jumpToward(ground)
	end

	task.wait(0.1)
end

I am getting this error when placing down the NPC

try Enum.PathStatus.Success

I tried it and it still did not work but without any errors so I just didnt say anything about it. When the NPC touches the part, it wont jump and move to the EDGE but a little bit forward afterwards. I want this to happen so not all of the NPCs will try to go to the middle

Why not make another script/section of the script where if the NPC touches land, they jump, allowing them to get onto Land? Just check if the NPC is swimming

if swimming == false then
-- yada yada yada
end

I think you get what I mean?

More organized to just keep it a singular script

Yeah so I went through all the replies and tested some things. The problem is that when you do humanoid.Jump = true and then immediately call MoveTo(), it cancels the jump before the NPC even leaves the ground.

MoveTo basically starts a walk command, which overrides the jump. That’s why sometimes the NPC doesn’t actually jump or move onto the land correctly.

What fixed it for me was waiting until the NPC is actually in the air before calling MoveTo.

local function jumpAndMove(groundPart)
	humanoid.JumpPower = jumpVelocity
	humanoid.UseJumpPower = true
	humanoid.Jump = true

	local jumped = false
	local conn
	conn = humanoid.StateChanged:Connect(function(_, new)
		if new == Enum.HumanoidStateType.Freefall then
			jumped = true
			conn:Disconnect()
		end
	end)

	local timeout = 0
	while not jumped and timeout < 0.5 do
		task.wait(0.05)
		timeout += 0.05
	end

	local forwardVector = rootPart.CFrame.LookVector * jumpForwardOffset
	local sideOffset = rootPart.CFrame.RightVector * math.random(-2, 2)
	local moveTarget = groundPart.Position + Vector3.new(0, humanoid.HipHeight, 0) + forwardVector + sideOffset

	humanoid:MoveTo(moveTarget)
end

This way the NPC actually jumps first, then moves while in the air toward a slightly randomized position so they don’t all land on the same spot.