NPC taking multiple attempts to jump onto something

He seems to be jumping a lot of times before successfully going down, any fixes I could do?
He sometimes also just walks back and jumps multiple times. I want to make him jump once and successfully do it.

function Update()
    path:ComputeAsync(hrp.Position - Vector3.new(0, hrp.Size.Y/0.75, 0), ohrp.Position)

    local wps = path:GetWaypoints()
    table.remove(wps, 1)
    coroutine.wrap(FollowOpponent)(wps)
end

function FollowOpponent(wps)    
    activecoroutine = coroutine.running()
    for _, wp in ipairs(wps) do
        if canrotate then
            bodygyro.CFrame = CFrame.new(hrp.Position, ohrp.Position)
        end
        
        local check_distance = (hrp.Position - ohrp.Position).Magnitude
        if check_distance > 4 then
            if canmove then        
                if wp.Action == Enum.PathWaypointAction.Jump then
                    hum.Jump = true
                end
                
                hum:MoveTo(wp.Position)
                hum.MoveToFinished:Wait()    
            end
        elseif check_distance < 2 then
            local dist = hrp.Position + (ohrp.Position - hrp.Position).Unit * -1 * 2
            hum:MoveTo(dist)
        end
    end
end

3 Likes

one thing I see could be the issue is from where you wrap the FollowOpponent function in a coroutine
do you call update() while a FollowOpponent function is running?
you may have multiple FollowOpponent()s running at the same time where each one is making you jump

1 Like

I believe the issue is that the jump waypoint is on the ledge, thus the NPC is jumping at the ledge and simply walking back onto it. You can fix this by making it walk to the next waypoint, which is guaranteed to be at the baseplate (unless you have a really low WaypointSpacing value or something), if it needs to jump in the current one:

if wp.Action == Enum.PathWaypointAction.Jump then
    hum.Jump = true
    hum:MoveTo(wps[i + 1].Position)
else
    hum:MoveTo(wp.Position)
end
                
hum.MoveToFinished:Wait()

You will also need to modify the loop to receive the i variable, which is the waypoint’s index in the path (AKA where it is in the glorified list that is the table returned by Path:GetWaypoints). Also, why are you using ipairs? It doesn’t do anything different to pairs (a | b) which is no different to just not having iterator function at all because the waypoint list won’t have any “holes” (nil values) in it, because ipairs stops when it hits one of them, so you can change;

for _, wp in ipairs(wps) do

to:

for i, wp in wps do

One small nitpick I have is that your variable names are very short. You can very easily accidently type wps instead of wp, for example, which doesn’t happen with longer but more descriptive variable names like waypoint and waypointsList. It can also be easier for other people to understand your code when you post it here for help, because they are just more descriptive and “to-the-point” rather than three letters that don’t mean anything without context.

EDIT 25/08/2025: Fixed note about iteration order.

3 Likes

Yes! It’s a loop inside a coroutine.wrap.

1 Like

I tried this. He still does the same but he jumps less now? ;(

3 Likes

How often are you calling the Update function? Also, from the looks of it, there can be multiple instances of FollowOpponent running at once, maybe that could also be causing issues?

It updates every 1/3 of a second!

you need to adjust your code so that any existing FollowOpponent calls need to be cancelled when a new one runs
here’s a basic example of it

local currentId = -1

function update()
   coroutine.wrap(followOpponent)()
end

function followOpponent()
   local thisId = os.clock()
   currentId = thisId
   for i=1, 100, 1 do
      task.wait()
      if thisId ~= currentId then
         print("Ended", thisId, "because a new followOpponent is running")
         break
      end
      print(thisId, "doing step", i)
   end
end

while task.wait(math.random() * 5)
   update()
end
1 Like

Sorry for a late reply, I was kinda busy for the past 2 days. I will try this and give you a response whether it worked or not!

Even with this, he still does the same thing.

how does the code look with that added?

This is what the code looks!

function FollowOpponent(wps)	
	activecoroutine = coroutine.running()
	
	local thisId = os.clock()
	currentId = thisId
	
	for _, wp in ipairs(wps) do
		if activecoroutine ~= coroutine.running() then return end		
		if thisId ~= currentId then break end
		
		if canrotate then
			bodygyro.CFrame = CFrame.new(hrp.Position, ohrp.Position)
		end
		
		local check_distance = (hrp.Position - ohrp.Position).Magnitude
		if check_distance > 4 then
			if canmove then		
				if wp.Action == Enum.PathWaypointAction.Jump then
					hum.Jump = true
				end
				
				hum:MoveTo(wp.Position)
				hum.MoveToFinished:Wait()
			end
		elseif check_distance < 2 then
			local dist = hrp.Position + (ohrp.Position - hrp.Position).Unit * -1 * 2
			hum:MoveTo(dist)
		end
	end
end

oh, the way you’re checking for the activeCoroutine now is doing the same thing as the ids, so you could take one of them out


as for why it’s still jumping, is it currently jumping one extra time after a jump for the pathfind is completed? in that case, it might be because setting .Jump while in midair makes the humanoid jump again after it lands (and then it doesnt stack any more than that because it’s already set to true)

if it’s jumping multiple times when it isnt close to somewhere it should jump, idk

if it’s jumping multiple times where each jump is too far from an edge it’s supposed to jump, maybe your pathfind interrupts are slowing the npc’s walking down too much. in this case, maybe only interrump when it’s strictly walking. this would allow users to cheese the npc by making up waste time going up and down ladders or swimming though, so you’ll have to watch for ways users can exploit it