Advanced Enemy AI with tasks

I want to make an advanced Enemy AI, I instantly want to start by saying that this AI is very similar to that of the ‘Hello Neighbor’ AI, with there being 4 modes: Idle, Hunt, Chase, Offline. Idle is the only relevant one here. Idle mode means the AI is doing tasks within the ‘house’ location. I already have some tasks:

  • Cook Food
  • Watch TV
  • Fix Car
  • Look through boxes

The issue is roblox’s pathfinding. Maybe it’s just me or the physical NPC im using (An r6 roblox character, HumanoidRootPart being the only collidable object). But the AI is often bumping into walls ext.

I’ve tried fixing the pathfinding with agent parameters but not much difference is made.

I do not NEED the AI to pathfind between each task, opting for a method where the AI walks between pathfinding connectors which are close and easy to walk to without pathfinding error, and then when the opportunity presents itself, the AI will move towards a task part. I just want the AI to have tasks and work.

Main Idle mode module:

-- local idle = {}

local waypoint_folder = workspace.WaypointsFolder

local ai_utils = require(script.Parent.other)

local downstairs_activities = {
	"tv",
	"fruit",
	"box",
}

local upstairs_activities = {
	"sink",
	"sleep",
}


function idle.setup(npc_stats)
	
	local humanoid = npc_stats.npc.Humanoid
	
end

function idle.activity(npc_stats)
	
	npc_stats.can_do_activity = false
	
	local chance_of_changing_floor = math.random(1, npc_stats.chance_of_changing_floor)

	if chance_of_changing_floor == 1 then
		ai_utils.change_floor(npc_stats, function(result)
			if result == false then
				idle.activity(npc_stats)
				return
			elseif result == true then
				task.wait(1)
				play_activity(npc_stats)
			end
		end)
	else
		task.wait(1)
		play_activity(npc_stats)
	end
end

function play_activity(npc_stats)
	
	local activities

	if npc_stats.current_floor == 1 then
		activities = downstairs_activities
	else
		activities = upstairs_activities
	end

	local random_activity = math.random(1,#activities)
	local activity_function = activities[random_activity]

	if activity_function == npc_stats.last_activity then
		idle.activity(npc_stats)
		return
	end

	npc_stats.last_activity = activity_function

	idle[activity_function](npc_stats)
end




function idle.tv(npc_stats)
	
	local tv_part = waypoint_folder.tasks.downstairs.watch_tv.chair
	local hum : Humanoid = npc_stats.npc.Humanoid
	
	ai_utils.pathfind(npc_stats, tv_part.Position, npc_stats.agent_paramaters, function(result)
		
		if result == true then
			
			task.wait()
			
			--npc_stats.npc.HumanoidRootPart.Orientation = Vector3.new(0, tv_part.Orientation.Y, 0)
			npc_stats.npc.HumanoidRootPart.CFrame = tv_part.CFrame * CFrame.new(0,2.5,0)
			
			task.wait(10)
			
			npc_stats.can_do_activity = true
			
		elseif result == false then
			
			npc_stats.can_do_activity = true
		end
	end)
end


function idle.fruit(npc_stats)

	local fruit_part = waypoint_folder.tasks.downstairs.take_fruit.fruit
	local hum : Humanoid = npc_stats.npc.Humanoid

	ai_utils.pathfind(npc_stats, fruit_part.Position, npc_stats.agent_paramaters, function(result)

		if result == true then

			task.wait()

			--npc_stats.npc.HumanoidRootPart.Orientation = Vector3.new(0, tv_part.Orientation.Y, 0)
			npc_stats.npc.HumanoidRootPart.CFrame = fruit_part.CFrame * CFrame.new(0,2.5,0)

			task.wait(10)

			npc_stats.can_do_activity = true

		elseif result == false then

			npc_stats.can_do_activity = true
		end
	end)
end


function idle.box(npc_stats)

	local box_part = waypoint_folder.tasks.downstairs.search_box.box
	local hum : Humanoid = npc_stats.npc.Humanoid

	ai_utils.pathfind(npc_stats, box_part.Position, npc_stats.agent_paramaters, function(result)

		if result == true then

			task.wait()

			--npc_stats.npc.HumanoidRootPart.Orientation = Vector3.new(0, tv_part.Orientation.Y, 0)
			npc_stats.npc.HumanoidRootPart.CFrame = box_part.CFrame * CFrame.new(0,2.5,0)

			task.wait(10)

			npc_stats.can_do_activity = true

		elseif result == false then

			npc_stats.can_do_activity = true
		end
	end)
end

-- UPSTAIRS

function idle.sink(npc_stats)

	local sink_part = waypoint_folder.tasks.upstairs.wash_hands.sink
	local hum : Humanoid = npc_stats.npc.Humanoid

	ai_utils.pathfind(npc_stats, sink_part.Position, npc_stats.agent_paramaters, function(result)

		if result == true then

			task.wait()

			--npc_stats.npc.HumanoidRootPart.Orientation = Vector3.new(0, tv_part.Orientation.Y, 0)
			npc_stats.npc.HumanoidRootPart.CFrame = sink_part.CFrame * CFrame.new(0,2.5,0)

			task.wait(10)

			npc_stats.can_do_activity = true

		elseif result == false then

			npc_stats.can_do_activity = true
		end
	end)
end


function idle.sleep(npc_stats)

	local sleep_part = waypoint_folder.tasks.upstairs.sleep.floor
	local sleep_part2 = waypoint_folder.tasks.upstairs.sleep.bed
	local hum : Humanoid = npc_stats.npc.Humanoid

	ai_utils.pathfind(npc_stats, sleep_part.Position, npc_stats.agent_paramaters, function(result)

		if result == true then
			-- Pathfinding successful
			task.wait()
			-- Adjust NPC position
			npc_stats.npc.HumanoidRootPart.CFrame = sleep_part.CFrame * CFrame.new(0,2.5,0)
			-- Slight wait
			task.wait(5)
			
			npc_stats.can_do_activity = true

		elseif result == false then
			-- Pathfinding Unsuccessfull
			npc_stats.can_do_activity = true
		end
	end)
end

return idle

Also, if anyone wants to give some tips or critiques on how this is coded, feel free. (The idle module is within the main module which is ran through a runservice loop.