Struggling with SimplePath

Hi, I have recently came across SimplePath, although its nice and works great, I am stepping into some problems.
Here I have my main ai module:

local Pathfinding = require(script.Pathfinding)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Ai = {}
Ai.__index = Ai

local ACTIVATION_RANGE = 150
local PATH_RECALC_INTERVAL = 0.5
local TARGET_MOVE_THRESHOLD = 5

function Ai.new(Rig)
	local self = setmetatable({}, Ai)
	self.Rig = Rig
	self.Active = false
	self.Target = nil
	self.LastPathTime = 0
	self.LastTargetPos = nil
	self.PathFinding = Pathfinding.new(self.Rig, {AgentCanClimb = true})
	return self
end

function Ai:Function()
	self.Active = true

	RunService.Heartbeat:Connect(function(dt)
		if not self.Active then return end
		self:Tick()
	end)

	self.Rig:FindFirstChildOfClass("Humanoid").Died:Once(function()
		self:Stop()
	end)
end

function Ai:Stop()
	self.Active = false
	self.PathFinding:Stop()
	self.Target = nil
end

function Ai:Tick()
	if self.Active == false then return end

	-- Find nearest player in range
	self.Target = self:SearchForTargets()

	if self.Target then
		self:Chase()
	else
		if self.PathFinding.Status == "Idle" then
			self:Patrol()
		end
	end
end

function Ai:Chase()
	if not self.Target or not self.Target:FindFirstChild("HumanoidRootPart") then return end

	local now = os.clock()
	local targetPos = self.Target.HumanoidRootPart.Position
	local moved = (self.LastTargetPos and (targetPos - self.LastTargetPos).Magnitude > TARGET_MOVE_THRESHOLD)

	if (now - self.LastPathTime) >= PATH_RECALC_INTERVAL or moved then
		self.PathFinding:Run(self.Target.HumanoidRootPart)

		if self.PathFinding._path.Status == Enum.PathStatus.NoPath then
			self.PathFinding:Stop()
		end

		self.LastPathTime = now
		self.LastTargetPos = targetPos
	end
end

function Ai:Patrol()
	if self.Active == false then return end

	local RandomPos = self.Rig.HumanoidRootPart.Position + Vector3.new(
		math.random(-50, 50),
		0,
		math.random(-50, 50)
	)

	self.PathFinding:Run(RandomPos)

	self.PathFinding.WaypointReached:Once(function()
		local newTarget = self:SearchForTargets()
		if newTarget then
			self.Target = newTarget
			self.PathFinding:Stop()
			self:Chase()
		end
	end)
end

function Ai:SearchForTargets()
	if self.Active == false then return end
	if not self.Rig.PrimaryPart then return nil end

	local nearest, dist = nil, ACTIVATION_RANGE

	for _, player in ipairs(Players:GetPlayers()) do
		if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
			local root = player.Character.HumanoidRootPart
			local mag = (root.Position - self.Rig.PrimaryPart.Position).Magnitude

			if mag < dist then
				nearest, dist = player.Character, mag
			end
		end
	end

	return nearest
end

return Ai

When a player is at an unreachable spot, for example on a building with no way for ai to get to it, the ai freezes in place, and when the player gets down, sometimes the ai does not unfreeze and stays stuck, no errors whatsoever, and sometimes it does unfreeze but it just moves randomly, not chasing any players. I have tried alot of solutions, it just became unbearable and I need any help possible!

The issue is that pathfinding failures aren’t being handled. When SimplePath can’t find a route, the AI should cancel the chase and let the target go.

It was freezing because it got stuck in a loop where it kept finding the target and failing really fast. To break that loop and give the AI some time to do the patrol action, we can make it ignore the target for a while after it fails to reach it. It’s not the most elegant solution, but maybe it gives you an idea of how to do it better.


local Pathfinding = require(script.Pathfinding)
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")

local Ai = {}
Ai.__index = Ai

local ACTIVATION_RANGE = 150
local PATH_RECALC_INTERVAL = 0.5
local TARGET_MOVE_THRESHOLD = 5

local UNREACHABLE_COOLDOWN = 3

function Ai.new(Rig)
	local self = setmetatable({}, Ai)
	self.Rig = Rig
	self.Active = false
	self.Target = nil
	self.LastPathTime = 0
	self.LastTargetPos = nil
	self.UnreachableTargets = {}
	self.PathFinding = Pathfinding.new(self.Rig, {AgentCanClimb = true})

	self.PathFinding.Error:Connect(function(errorType)
		-- error due to a failed calculation and we have a target
		if (errorType == Pathfinding.ErrorType.ComputationError or errorType == Pathfinding.ErrorType.TargetUnreachable) and self.Target then

			self.UnreachableTargets[self.Target] = os.clock()

			-- force the patrol
			if self.PathFinding.Status ~= Pathfinding.StatusType.Idle then
				self.PathFinding:Stop()
			end
			self.Target = nil
		end
	end)

	return self
end

function Ai:Function()
	self.Active = true

	RunService.Heartbeat:Connect(function(dt)
		if not self.Active then return end
		self:Tick()
	end)

	self.Rig:FindFirstChildOfClass("Humanoid").Died:Once(function()
		self:Stop()
	end)
end

function Ai:Stop()
	self.Active = false
	if self.PathFinding and self.PathFinding.Status ~= Pathfinding.StatusType.Idle then
		self.PathFinding:Stop()
	end
	self.Target = nil
end

function Ai:Tick()
	if not self.Active then return end

	-- we only look for a new goal if we don't have one already
	if not self.Target then
		self.Target = self:SearchForTargets()
	end

	if self.Target then
		self:Chase()
	else
		if self.PathFinding.Status == "Idle" then
			self:Patrol()
		end
	end
end

function Ai:Chase()
	if not self.Target or not self.Target:FindFirstChild("HumanoidRootPart") then
		self.Target = nil
		return
	end

	local now = os.clock()
	local targetPos = self.Target.HumanoidRootPart.Position
	local moved = (self.LastTargetPos and (targetPos - self.LastTargetPos).Magnitude > TARGET_MOVE_THRESHOLD)

	if (now - self.LastPathTime) >= PATH_RECALC_INTERVAL or moved then
		self.PathFinding:Run(self.Target.HumanoidRootPart)
		self.LastPathTime = now
		self.LastTargetPos = targetPos
	end
end

function Ai:Patrol()
	if not self.Active then return end

	local rootPart = self.Rig:FindFirstChild("HumanoidRootPart")
	if not rootPart then return end

	local randomPos = rootPart.Position + Vector3.new(
		math.random(-50, 50),
		0,
		math.random(-50, 50)
	)

	self.PathFinding:Run(randomPos)
end

function Ai:SearchForTargets()
	if not self.Active then return nil end
	if not self.Rig.PrimaryPart then return nil end

	local nearest, dist = nil, ACTIVATION_RANGE

	--- clearing the blacklist of expired entries
	for target, markedTime in pairs(self.UnreachableTargets) do
		if os.clock() - markedTime > UNREACHABLE_COOLDOWN then
			self.UnreachableTargets[target] = nil
		end
	end

	for _, player in ipairs(Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChild("HumanoidRootPart") then
			if not self.UnreachableTargets[character] then
				local root = character.HumanoidRootPart
				local mag = (root.Position - self.Rig.PrimaryPart.Position).Magnitude

				if mag < dist then
					nearest, dist = character, mag
				end
			end
		end
	end

	return nearest
end

return Ai

Thank you so much for your response, I had a similar idea but the fact that I would have to exclude the player for a while which made me question it, not really sure why, anyways., I will test it out and let you know!

Hmm, it does no longer freeze but it no longer targets me after I get down from an unreachable
place, it just patrols.