How can I improve this AI following system?

I’m trying to make an AI that will follow the player. One of the things I wanted to fix instead of just doing a constant humanoid:MoveTo() loop is that the AI will eventually run into walls. To fix this, I’m trying to make it dynamically switch from doing the constant humanoid:MoveTo() to pathfinding. I need help to figure out how to improve and optimize this. I only plan to have only a single one of these NPC’s running at a time.

Video of the AI in action:

RBXL file:
movement.rbxl (71.7 KB)

AI Script:

local character = script.Parent
local humanoid = character.Humanoid
local root = character.HumanoidRootPart

local raycastParameters = RaycastParams.new()
raycastParameters.FilterType = Enum.RaycastFilterType.Blacklist
raycastParameters.IgnoreWater = true

local pathfindingService = game:GetService("PathfindingService")
local agentParameters = {}

agentParameters.AgentRadius = 4
agentParameters.AgentHeight = 5
agentParameters.AgentCanJump = true
agentParameters.WaypointSpacing = 4

local path:Path = pathfindingService:CreatePath(agentParameters)

local function positionChanged(old:Vector3, current:Vector3)
	local distance = (old - current).Magnitude
	if distance > 1 then
		print("Position Changed")
		return true
	end
	return false
end

local function blocked(target:Vector3)
	local origin = root.Position
	local direction = target - origin
	local raycastResult = workspace:Raycast(origin, direction, raycastParameters)
	if raycastResult then
		print("Blocked")
		return true
	end
	return false
end

local function resetNetworkOwner()
	for _, child:Instance in pairs(character:GetChildren()) do
		if child:IsA("BasePart") and not child.Anchored then
			child:SetNetworkOwner(nil)
		end
	end
end

local function getClosestPlayer()
	local closestPlayer:Player, closestPlayerDistance:number
	for _, player:Player in pairs(game.Players:GetPlayers()) do
		local playerCharacter = player.Character
		if playerCharacter then
			local playerRoot:Part? = playerCharacter:FindFirstChild("HumanoidRootPart")
			local playerHumanoid:Humanoid? = playerCharacter:FindFirstChild("Humanoid")
			if playerRoot and playerHumanoid and playerHumanoid.Health > 0 then
				local distance = (root.Position - playerRoot.Position).Magnitude
				if not closestPlayer or closestPlayerDistance > distance then
					closestPlayer = player
					closestPlayerDistance = distance
				end
			end
		end
	end
	return closestPlayer
end

local function stuck()
	print("Checking if stuck")
	raycastParameters.FilterDescendantsInstances = {character}
	local raycastResult = workspace:Raycast(root.Position, root.CFrame.LookVector * 1000, raycastParameters)
	if raycastResult then
		print("Found something")
		if (root.Position - raycastResult.Instance.Position).Magnitude <= 4 then
			print("That something is in range, we're stuck")
			return true
		else
			print("That something is outta range, we ain't stuck")
			return false
		end
	end
	print("Didn't find anything, we ain't stuck")
	return false
end

local function makePart(position:Vector3)
	local part = Instance.new("Part")
	part.Name = "Waypoint"
	part.Parent = workspace.Waypoints
	part.Size = Vector3.new(0.3, 0.3, 0.3)
	part.Position = position
	part.Anchored = true
	part.BrickColor = BrickColor.new("Deep orange")
	part.Material = Enum.Material.Neon
	part.Shape = Enum.PartType.Ball
end

local pathfindingTo = false

local function pathfindTo(goal:Vector3, part:Part)
	pathfindingTo = true
	workspace.Waypoints:ClearAllChildren()
	path:ComputeAsync(root.Position, goal)
	local waypoints = path:GetWaypoints()
	for _, waypoint:PathWaypoint in pairs(waypoints) do
		makePart(waypoint.Position)
	end
	for _, waypoint:PathWaypoint in pairs(waypoints) do
		if (blocked(goal) or stuck()) and not positionChanged(goal, part.Position) then
			resetNetworkOwner()
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid:ChangeState(Enum.HumanoidStateType.Jumping)
			end
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
	end
	workspace.Waypoints:ClearAllChildren()
	pathfindingTo = false
end

local lastTargetPosition

while true do
	local target
	repeat 
		target = getClosestPlayer()
		wait()
	until target
	local targetRoot:Part = target.Character.HumanoidRootPart
	raycastParameters.FilterDescendantsInstances = {character, target.Character}
	if blocked(targetRoot.Position) then
		if not pathfindingTo or (not lastTargetPosition or positionChanged(lastTargetPosition, targetRoot.Position)) then
			pathfindTo(targetRoot.Position, targetRoot)
			lastTargetPosition = targetRoot.Position
		end
	else
		if not stuck() then
			resetNetworkOwner()
			humanoid:MoveTo(targetRoot.Position)
		else
			pathfindTo(targetRoot.Position, targetRoot)
			lastTargetPosition = targetRoot.Position
		end
	end
	wait()
end