PathFinderService issue


local PFS = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")

local PathHandler = {}


PathHandler.OriginalPositions = {}
-- Set player WalkSpeed on join
Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:WaitForChild("Humanoid")
		humanoid.WalkSpeed = 50
	end)
end)

function PathHandler.IsInVision(monster, targetCharacter)
	-- Set field of view in degrees; adjust as needed.
	local fovAngle = 45 
	local threshold = math.cos(math.rad(fovAngle))
	local direction = (targetCharacter.PrimaryPart.Position - monster.PrimaryPart.Position).Unit
	local lookVector = monster.PrimaryPart.CFrame.LookVector
	local dot = lookVector:Dot(direction)
	return dot >= threshold
end


-- Internal function to visualize waypoints
local function VisualizeWaypoints(waypoints, duration)
	duration = duration or 5  -- default duration is 5 seconds
	for _, waypoint in ipairs(waypoints) do
		local marker = Instance.new("Part")
		marker.Name = "WaypointMarker"
		marker.Shape = Enum.PartType.Ball
		marker.Material = Enum.Material.Neon
		marker.Color = Color3.fromRGB(255, 0, 0)  -- bright red
		marker.Size = Vector3.new(1, 1, 1)
		marker.Anchored = true
		marker.CanCollide = false
		marker.Position = waypoint.Position
		marker.Parent = workspace
		Debris:AddItem(marker, duration)
	end
end

-- Attempts to follow the target character by computing a path and moving the NPC along it
function PathHandler.FollowPlayer(monster, targetCharacter)
	-- Get the monster's dimensions for agent parameters
	local extents = monster:GetExtentsSize()
	local x = extents.Y                           -- Using Y as height (adjust if needed)
	local y = math.max(extents.X, extents.Z) / 2    -- Approximate horizontal radius

	local agency = {
		AgentRadius = x,
		AgentHeight = y
	}

	-- Create and compute the path using the monster's PrimaryPart
	local path = PFS:CreatePath(agency)
	path:ComputeAsync(monster.PrimaryPart.Position, targetCharacter.PrimaryPart.Position)

	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()

		VisualizeWaypoints(waypoints)

		-- Connect the Blocked event once
		local blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			print("Path is blocked at waypoint:", blockedWaypointIndex)
			-- Optionally: Recalculate the path here if needed.
		end)

		-- Start from waypoint 2 (assuming waypoint 1 is the starting position)
		local currentWaypointIndex = 2

		if currentWaypointIndex <= #waypoints then

			monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
		end

		-- Wait for the NPC to reach each waypoint before moving on
		local moveConnection
		moveConnection = monster.Humanoid.MoveToFinished:Connect(function(reached)
			if reached  then
	
				
				currentWaypointIndex = currentWaypointIndex + 1

				if currentWaypointIndex <= #waypoints then
					monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
				else
					-- Completed the path; disconnect events.
					moveConnection:Disconnect()
					blockedConnection:Disconnect()
				end
			else
				moveConnection:Disconnect()
				blockedConnection:Disconnect()
			end
		end)
	else
		warn("Pathfinding failed with status:", path.Status)
	end
end

function PathHandler.ReturnHome(monster,originalCFrame)
	if originalCFrame then
		local homeTarget = {PrimaryPart = {Position = originalCFrame.Position}}
		
		PathHandler.FollowPlayer(monster, homeTarget)
	end
end

-- Checks for the nearest player and, if within range, calls FollowPlayer on that character
function PathHandler.CheckNearestPlayer(monster:Model)
	-- First, check if the monster is too far from its original home position.
	local IsReturning = monster:GetAttribute("IsReturning")
	local originalCFrame = PathHandler.OriginalPositions[monster]
	
	if originalCFrame then
		local distFromHome = (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude
		local MaxAway = monster:GetAttribute("MaxAway")
		if distFromHome > MaxAway then
			monster:SetAttribute("IsReturning", true)
		end
		if monster:GetAttribute("IsReturning") then
			PathHandler.ReturnHome(monster,originalCFrame)
			if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
				monster:SetAttribute("IsReturning", false)
			end
			return
		end
	end

	local players = Players:GetPlayers()
	if #players > 0  or IsReturning == false then
		local MaxDist = monster:GetAttribute("MaxDist")
		for _, player in ipairs(players) do
			local character = player.Character
			-- Ensure the character exists, has a Humanoid, and a PrimaryPart
			if character and character:FindFirstChildWhichIsA("Humanoid") and character.PrimaryPart then
				local distance = (monster.PrimaryPart.Position - character.PrimaryPart.Position).Magnitude
				if distance < 8 or (distance < MaxDist and PathHandler.IsInVision(monster, character))   then
					PathHandler.FollowPlayer(monster, character)

				end
			end
		end	
	end
end

function PathHandler.Start(monster,monsterCframe)
	task.spawn(function()
		PathHandler.OriginalPositions[monster] = monsterCframe
		while true do
			if monster:GetAttribute("IsReturning") == true then
				task.wait()
				continue
				
			end
			PathHandler.CheckNearestPlayer(monster)
			task.wait()
		end
	end)
end

return PathHandler
  1. well the issue on that pathfinder is when the npc returns to its original pos he starts to move randomly and the isreturning variable gets between true/false changed when he does that

I think you need to seperate your functions in the PathHandler.CheckNearestPlayer. Your While true do, which I wish people would stop using lol, is calling it over and over and over again this is causing the NPC to recalculate the path, a lot. Look at noobpath, they use an idle system and do a great job of showing how to do this properly. When the NPC finishes the path they idle, then this makes them stop moving and stops them from recalculating the path 100 times a second pretty much.

if u are still here
i ve changed the whilte loop to while monster:GetAttribute("IsRunning) == false do end
and still the problem occurs
when the npcs returns back he starts to go on and back and that attribute starts to changed from true to false for like 3 secs

That makes sense, looking at your code that is intended logic, seperate your functions, and call them in order. Make sure to never touch the attribute again only 2 times. You should only CHECK for the attribute once during path calculation in the check nearest player. But you are not only checking it, you are setting it and modifying it every tenth of a second. I think if you get that code out and only check it once during that function, and also DO NOT modify it in that same thread you will be fine. I will say side note is you can look at other path modules on here to see how others accomplished what you are doing.

local PFS = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local Debris = game:GetService("Debris")

local PathHandler = {}


PathHandler.OriginalPositions = {}
-- Set player WalkSpeed on join
Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:WaitForChild("Humanoid")
		humanoid.WalkSpeed = 50
	end)
end)

function PathHandler.IsInVision(monster, targetCharacter)
	-- Set field of view in degrees; adjust as needed.
	local fovAngle = 45 
	local threshold = math.cos(math.rad(fovAngle))
	local direction = (targetCharacter.PrimaryPart.Position - monster.PrimaryPart.Position).Unit
	local lookVector = monster.PrimaryPart.CFrame.LookVector
	local dot = lookVector:Dot(direction)
	return dot >= threshold
end


-- Internal function to visualize waypoints
local function VisualizeWaypoints(waypoints, duration)
	duration = duration or 5  -- default duration is 5 seconds
	for _, waypoint in ipairs(waypoints) do
		local marker = Instance.new("Part")
		marker.Name = "WaypointMarker"
		marker.Shape = Enum.PartType.Ball
		marker.Material = Enum.Material.Neon
		marker.Color = Color3.fromRGB(255, 0, 0)  -- bright red
		marker.Size = Vector3.new(1, 1, 1)
		marker.Anchored = true
		marker.CanCollide = false
		marker.Position = waypoint.Position
		marker.Parent = workspace
		Debris:AddItem(marker, duration)
	end
end

-- Attempts to follow the target character by computing a path and moving the NPC along it
function PathHandler.FollowPlayer(monster, targetCharacter)
	-- Get the monster's dimensions for agent parameters
	local extents = monster:GetExtentsSize()
	local x = extents.Y                           -- Using Y as height (adjust if needed)
	local y = math.max(extents.X, extents.Z) / 2    -- Approximate horizontal radius

	local agency = {
		AgentRadius = x,
		AgentHeight = y
	}

	-- Create and compute the path using the monster's PrimaryPart
	local path = PFS:CreatePath(agency)
	path:ComputeAsync(monster.PrimaryPart.Position, targetCharacter.PrimaryPart.Position)

	if path.Status == Enum.PathStatus.Success then
		local waypoints = path:GetWaypoints()

		VisualizeWaypoints(waypoints)

		-- Connect the Blocked event once
		local blockedConnection = path.Blocked:Connect(function(blockedWaypointIndex)
			print("Path is blocked at waypoint:", blockedWaypointIndex)
			-- Optionally: Recalculate the path here if needed.
		end)

		-- Start from waypoint 2 (assuming waypoint 1 is the starting position)
		local currentWaypointIndex = 2

		if currentWaypointIndex <= #waypoints then

			monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
		end

		-- Wait for the NPC to reach each waypoint before moving on
		local moveConnection
		moveConnection = monster.Humanoid.MoveToFinished:Connect(function(reached)
			if reached  then
	
				
				currentWaypointIndex = currentWaypointIndex + 1

				if currentWaypointIndex <= #waypoints then
					monster.Humanoid:MoveTo(waypoints[currentWaypointIndex].Position)
				else
					-- Completed the path; disconnect events.
					moveConnection:Disconnect()
					blockedConnection:Disconnect()
				end
			else
				moveConnection:Disconnect()
				blockedConnection:Disconnect()
			end
		end)
	else
		warn("Pathfinding failed with status:", path.Status)
	end
end

function PathHandler.ReturnHome(monster,originalCFrame)
	if originalCFrame then
		local homeTarget = {PrimaryPart = {Position = originalCFrame.Position}}
		if monster.Name == "Rig2" then
			print(homeTarget)
			end
		if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
			monster:SetAttribute("IsReturning", false)
			return
		end
		
		PathHandler.FollowPlayer(monster, homeTarget)
		
	end
end

-- Checks for the nearest player and, if within range, calls FollowPlayer on that character
function PathHandler.CheckNearestPlayer(monster:Model)
	-- First, check if the monster is too far from its original home position.
	local IsReturning = monster:GetAttribute("IsReturning")
	local originalCFrame = PathHandler.OriginalPositions[monster]
	if originalCFrame then
		local distFromHome = (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude
		local MaxAway = monster:GetAttribute("MaxAway")
		if distFromHome > MaxAway then
			monster:SetAttribute("IsReturning", true)
			PathHandler.ReturnHome(monster,originalCFrame)
		end
			
	end

	local players = Players:GetPlayers()
	if #players > 0  then
		local MaxDist = monster:GetAttribute("MaxDist")
		for _, player in ipairs(players) do
			local character = player.Character
			-- Ensure the character exists, has a Humanoid, and a PrimaryPart
			if character and character:FindFirstChildWhichIsA("Humanoid") and character.PrimaryPart then
				local distance = (monster.PrimaryPart.Position - character.PrimaryPart.Position).Magnitude
				if distance < 8 or (distance < MaxDist and PathHandler.IsInVision(monster, character))   then
					PathHandler.FollowPlayer(monster, character)
				end
			end
		end	
	end
end

function PathHandler.Start(monster,monsterCframe)
	task.spawn(function()
		PathHandler.OriginalPositions[monster] = monsterCframe
		while monster:GetAttribute("IsReturning") == false do
			PathHandler.CheckNearestPlayer(monster)
			task.wait()
		end
	end)
end

return PathHandler

i ve updated the last three functions and yet the problem stiil occurs im so confused

and about i ve taken a look about the noobpath module but i don’t how to reverse engineer it (some stuff i don’t understand it )

You are still setting the attribute in the SAME thread as you are checking it. Make one function called PathHandler.SetReturnControl, one to handle just that. You moved it out but still have the same problem. Also I guess it is a little hard to understand some modules, but you don’t have to really reverse engineer it. Try looking at SimplePath as well.
Think about this, you are defining local IsReturning to the attribute, and you are ALSO checking that attribute to change it in the very next thread of if originalCFrame. Why not make one global function that just handles only setting that attribute, and removing it, or rather “updating” it.
Here is an example to stop the thread from even modifying the attribute in the returnhome function.

function PathHandler.ReturnHome(monster, originalCFrame)
    if originalCFrame then
        if (monster.PrimaryPart.Position - originalCFrame.Position).Magnitude <= 5 then
            monster:SetAttribute("IsReturning", false)
            monster.Humanoid:MoveTo(originalCFrame.Position) --Move to the exact position to stop random movement.
            return
        end

        local homeTarget = { PrimaryPart = { Position = originalCFrame.Position } }
        PathHandler.FollowPlayer(monster, homeTarget)
    end
end

Note: you still need to fix checknearestplayer function! To STOP changing the attribute.

i have a question when the npcs ( there are more than 1 ) return or genereally just move with the pathfinder that does computing every secondes does the new path overwrite the previous one and does the event movetofinished gets removed im so damned confused

It is suppose to yeild until movetofinished but the problem really comes in how you call it. Even if I try to yield a script if I keep calling the function, I create new threads. If I develop 10 threads, unwanted behavior starts happening. This is why you should always avoid while true do loops. This is why pathfinding modules make waypoints, and disconnect them then make new ones.

can u write a snippet about disconnecting waypoints pls

I will be honest, if you just youtube different pathfinding guides, you will understand a lot more than me trying to type out a full lesson. But I think you are on the right path! Good luck.