{SimplePath} NPC Pathfinding AI Breaks Randomly

Introduction

Hello fellow scripters! I am making a simple customer system for my (bad) game.

How it’s supposed to work

When the npc spawns in the map, it will loop through all the seats and check if they are available, if it does then it proceeds, if it doesn’t then the NPC gets :Destroyed() and the selected seated will be set to nil.

The Issue

Sometimes, the checks do pass, but the NPC just spawns and stays there, no movement at all

Things you should know

I would like to note that this pathfinding system uses the SimplePath module made by Wicked_Wizard.

The Code

local ServerStorage = game:GetService("ServerStorage")
local SimplePath = require(ServerStorage.SimplePath)

local TweenService = game:GetService("TweenService")
local ChatService = game:GetService("Chat")

local MenuItems = {"Burger", "Hot Dog", "Soda", "Water", "Coffee", "Soup", "Cooked Chicken"}
local ChatOptions = {"Hurry up!", "I'm gonna fall asleep waiting for my food!", "Tell the cooks to hurry up before I leave!", "Can you cook any faster!", "My food is still not here!", "I knew I should have ordered food from ChatGPT!", "Who let these guys cook, because they are doing a terrible job right now!"}

local RateMin = script:WaitForChild("Min")
local RateMax = script:WaitForChild("Max")

for i,v in pairs(game:GetService("Workspace"):WaitForChild("NPC Pathfinding"):GetChildren()) do
	if v:IsA("BasePart") then
		local Value = Instance.new("BoolValue")
		Value.Name = "Taken"
		Value.Value = false
		Value.Parent = v
	end
end

local function SpawnCar()
	local CarCoroutine = coroutine.create(function()
		wait(math.random(4,8))

		local Cars = ServerStorage:WaitForChild("Cars"):GetChildren()
		local Selected_Car = Cars[math.random(1,#Cars)]:Clone()

		Selected_Car.Parent = game:GetService("Workspace"):WaitForChild("NPCS")

		local Tween = TweenService:Create(Selected_Car.PrimaryPart, TweenInfo.new(10), {CFrame = Selected_Car.PrimaryPart.CFrame - Vector3.new(1000,0,0)})
		Tween:Play()
		Tween.Completed:Wait()
		Selected_Car:Destroy()
	end)
	
	coroutine.resume(CarCoroutine)
end

local function SpawnCustomer()
	local NPCS = ServerStorage:WaitForChild("NPCS"):GetChildren()
	local Selected_NPC = NPCS[math.random(1,#NPCS)]:Clone()
	
	local Selected_Item = MenuItems[math.random(1,#MenuItems)]
	
	local Billboard = ServerStorage:WaitForChild("BillboardGui"):Clone()
	local ProximityPrompt = Instance.new("ProximityPrompt")
	ProximityPrompt.RequiresLineOfSight = false
	ProximityPrompt.Enabled = false
	ProximityPrompt.HoldDuration = 0.5
	ProximityPrompt.ActionText = "Give Item"
	ProximityPrompt.ObjectText = "Customer"
	ProximityPrompt.Parent = Selected_NPC
	
	Billboard.Enabled = false
	Billboard:WaitForChild("Frame"):WaitForChild("TextLabel").Text = Selected_Item
	Billboard.Parent = Selected_NPC:WaitForChild("Head")
	
	local Entrance = game:GetService("Workspace"):WaitForChild("Map"):WaitForChild("NPC Start")
	
	local function GetSeat()
		local NPC_Seats = game:GetService("Workspace"):WaitForChild("NPC Pathfinding"):GetChildren()
		local availableSeats = {}

		for i, seat in ipairs(NPC_Seats) do
			if not seat.Taken.Value then
				table.insert(availableSeats, seat)
			end
		end

		if #availableSeats > 0 then
			local selectedSeat = availableSeats[math.random(1, #availableSeats)]
			selectedSeat.Taken.Value = true
			Current_Seat = selectedSeat
		else
			Current_Seat = nil
			return
		end
	end
	
	GetSeat()
	
	for i,v in pairs(Selected_NPC:GetChildren()) do
		if v:IsA("BasePart") then
			v.CollisionGroup = "Players"
		end
	end
	
	-- || NPC Spawns ||
	
	if Current_Seat == nil then Selected_NPC:Destroy() return end
	
	local Customer_Seat = Current_Seat
	
	Selected_NPC:PivotTo(Entrance.CFrame)
	Selected_NPC.Parent = workspace.NPCS
	local Seat_Path = SimplePath.new(Selected_NPC)
	
	Seat_Path:Run(Customer_Seat)
	
	Seat_Path.Reached:Wait()
	
	task.wait(0.5)
	Selected_NPC:PivotTo(Customer_Seat.Seat.CFrame)
	Selected_NPC:FindFirstChildOfClass("Humanoid").Sit = true
	Selected_NPC.HumanoidRootPart.Anchored = true
	
	local HasWarned = false
	local WarnedTwice = false
	
	local NPC_Patience_Timer = 0
	local NPC_Max_Time = 5
	
	ProximityPrompt.Enabled = true
	Billboard.Enabled = true
	
	local function NPC_Leave()
		if Current_Seat == nil then Selected_NPC:Destroy() return end
		
		ProximityPrompt.Enabled = false
		Billboard.Enabled = false
		wait(0.5)
		
		Selected_NPC:PivotTo(Customer_Seat.Seat.CFrame)
		Selected_NPC.HumanoidRootPart.Anchored = false
		Selected_NPC.Humanoid.Sit = false

		local NPC_Leave_Path = SimplePath.new(Selected_NPC)
		NPC_Leave_Path:Run(Entrance)
		NPC_Leave_Path.Blocked:Connect(function()
			NPC_Leave_Path:Run(Entrance)
		end)
		NPC_Leave_Path.Error:Connect(function()
			NPC_Leave_Path:Run(Entrance)
		end)
		NPC_Leave_Path.Reached:Connect(function()
			Selected_NPC:Destroy()
			Current_Seat.Taken.Value = false
		end)
	end
	
	ProximityPrompt.Triggered:Connect(function(Player)
		local Character = Player.Character or Player.CharacterAdded:Wait()
		local Tool = Character:FindFirstChildOfClass("Tool")
		
		if Tool and Tool.Name == Selected_Item then
			ChatService:Chat(Selected_NPC.Head, "Thank you!", Enum.ChatColor.White)
			Player.leaderstats.Money.Value += 15
			Tool:Destroy()
			NPC_Leave()
		else
			local Failed_Snd = ServerStorage:WaitForChild("Failed"):Clone()
			Failed_Snd.Parent = Selected_NPC:WaitForChild("HumanoidRootPart")
			Failed_Snd:Play()
			ChatService:Chat(Selected_NPC.Head, "That's not what I wanted", Enum.ChatColor.White)
			NPC_Leave()
		end
	end)
	
	local NPC_Timer_Thread = coroutine.create(function()
		while task.wait(1) do
			NPC_Patience_Timer += 1
			if NPC_Patience_Timer == NPC_Max_Time / 2 and not HasWarned then
				HasWarned = true
				local Selected_Message = ChatOptions[math.random(1,#ChatOptions)]
				ChatService:Chat(Selected_NPC:WaitForChild("Head"), Selected_Message, Enum.ChatColor.White)
			elseif NPC_Patience_Timer >= NPC_Max_Time and not WarnedTwice then
				WarnedTwice = true
				ChatService:Chat(Selected_NPC:WaitForChild("Head"), "I'm leaving!", Enum.ChatColor.White)
				NPC_Leave()
				break
			end
		end
	end)
	
	coroutine.resume(NPC_Timer_Thread)
end

while task.wait(math.random(RateMin.Value, RateMax.Value)) do
	RateMin.Value = script.Min.Value
	RateMax.Value = script.Max.Value
	SpawnCustomer()
	SpawnCar()
end
3 Likes

I think I saw that if pathfinding takes over 8 seconds it’ll stop the process.
Try using more nodes so that the paths don’t take that long to complete.

2 Likes

the pathfinding doesnt and shouldnt take over 8 seconds

1 Like

this is the map, it shouldnt take well over 8 seconds to pathfind anywhere here

1 Like

They also provide Pathfinding Visualization which may show you where the path is plotted.

1 Like

I’ve tried doing that as well, however when the bug happens, no visual appears

it prints after running Seat_Path:Run(CurrentSeat)

1 Like

Try this:

		if Seats_Looped > 8 then Selected_Seat = nil return end

        print(Selected_Seat.Taken.Value, "   ", Selected_Seat)

		if Selected_Seat.Taken.Value and Seat ~= Selected_Seat then		

[/quote]
Just to see if Taken.Value is ever false.

1 Like

it returns false everytime it loops, after it loops though, it turns to true, so basically whenever its false it means it works whenever its true then no i guess?

2 Likes

i updated the :GetSeat() function so its better
(i def did not ask chatgpt for help)

2 Likes

There are only two things I can guess that are happening, 1: My coding skills are so bad that I manage to repeat the bug everytime I redo my customer code or 2: Pathfinding at its “peak”

1 Like

I’m just gonna pull a “Modern problems require modern solutions” and just make it so the NPC gets sent to the shadow realm (they have 10 seconds to get to a seat).

Edit: I found out that I just had to add a delay since pathfinding would start on the air

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.