AI Target Problems

Hello. I have been having some problems with my AI for about a year now. I thought it’s finally time to get some DevForum help, but anyways.

I have a game where tons of NPCs spawn that use this build of AI. This AI is very laggy and pretty dumb.
Example:
Let’s say my AI targets an enemy with 10,000 HP. The AI will refuse to look for any other targets until that enemy’s HP is 0. This AI also tends to freeze in place sometimes, but I assume that’s because other NPCs interfere with it’s pathfinding. And sometimes when enemies die, They just stop working.

I have recently tried to change my AI up by making it run on a single module.
This AI build I believe I used Y3ll0wMustangs (or whatever his name was) Zombie AI, but I heavily edited it to be compatible with my game. This code was written a long time ago, So I apologize for the messy spaghetti code.

Anyways, this is the code that focuses on finding targets, and adds them to a table of potential enemies.

Keep in mind that the coding below is all in a single module.

function Activity.FindEnemy(NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
	local dist = math.huge
	local target = nil
	local potentialTargets = {}
	local seeTargets = {}
	for i,v in ipairs(workspace:GetDescendants()) do
		local human = v:FindFirstChild("Humanoid")
		local torso = v:FindFirstChild("Torso") or v:FindFirstChild("HumanoidRootPart")
		local TEAMFIND = v:FindFirstChild("TEAM")
		local CPropsFolder = v:FindFirstChild("CharacterProperties")
		if human and torso and TEAMFIND and TEAMFIND.Value ~= Traits.Team and CPropsFolder then
			local DETType = CPropsFolder:FindFirstChild("Types")
			if DETType and DETType:FindFirstChild("Aerial").Value == false then
					if DETType and DETType:FindFirstChildOfClass("BoolValue") then
					if (Root.Position - torso.Position).magnitude < dist and human.Health > 0 then
							table.insert(potentialTargets,torso)
					end
				else
					--Targets all enemies
					if (Root.Position - torso.Position).magnitude < dist and human.Health > 0 then
						--	print(torso.Parent.Name)
						table.insert(potentialTargets,torso)
					end
				end	
			end	
		end
	end
	if #potentialTargets > 0 then
		for i,v in ipairs(potentialTargets) do
			if Activity.CheckSight(v, NPC, Torso, Root, Humanoid, Head, Traits, Angular) then
				table.insert(seeTargets, v)
			elseif #seeTargets == 0 and (Root.Position - v.Position).magnitude < dist then
				target = v
				dist = (Root.Position - v.Position).magnitude
			end
		end
	end
	if #seeTargets > 0 then
		dist = math.huge
		for i,v in ipairs(seeTargets) do
			if (Root.Position - v.Position).magnitude < dist then
				target = v
				dist = (Root.Position - v.Position).magnitude
			end
		end
	end
	return target
end

This code block checks the NPC’s area / surroundings for enemies.

function Activity.CheckSight(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular)
--	print(Target.Position)
	local ray = Ray.new(Root.Position, (Target.Position - Root.Position).Unit * 40)
	local hit,position = workspace:FindPartOnRayWithIgnoreList(ray, RayPartsToIgnore)
	if hit then
		if hit:IsDescendantOf(Target.Parent) and math.abs(hit.Position.Y - Root.Position.Y) < 3 then
			wait()
			return true
		end
	end
	return false
end

NPC Chases the enemy:

function Activity.ChaseEnemy(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
	local path = game:GetService("PathfindingService"):CreatePath()
	path:ComputeAsync(Root.Position,Target.Position)
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success then
		for _, waypoint in ipairs(waypoints) do
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				Humanoid.Jump = true
			end
			------------------------------------------------------------------
			--Important Functions
			Humanoid:MoveTo(waypoint.Position) --Moving
			Activity.Attack(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events) --Attacking
			------------------------------------------------------------------
			if Traits.LookAtEnemy == true then
				Angular.Attachment1.WorldCFrame = CFrame.new(Root.Position, Target.Position)
			end
			------------------------------------------------------------------
			if Root:FindFirstChild("BodyVelocity") then
				Activity.ChaseEnemy(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
				break 
			end
			local timeOut = Humanoid.MoveToFinished:Wait(1)
			if not timeOut then
				Humanoid.Jump = true
				Activity.ChaseEnemy(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
				break
			end
			--Move Function
			if Activity.CheckSight(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular) then
				repeat
					------------------------
					Humanoid:MoveTo(Target.Position)	
					------------------------
					--Uses transport ability if present
					for _, v in pairs(Events) do
						if v:GetAttribute("Type") == "Transport" then
							v:Fire()
						end
					end
					------------------------
					if Target == nil then
						break
					elseif Target.Parent == nil then
						break
					end
				until Activity.CheckSight(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular) == false or Humanoid.Health < 1 or Target.Parent.Humanoid.Health < 1
				break
			end
			if (Root.Position - waypoints[1].Position).magnitude > 20 then
				Activity.ChaseEnemy(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
				wait()
				break
			end
		end
	end
end

^ I believe it has something to do with this, I remember vividly adding a function where it still checks it’s sight while targetting an enemy, but It still did not work.

Another thing, I have set my NPCs torso network ownership to nil, But that seems to lag the server when there are multiple NPCs inside the workspace.

Any help is appreciated. Thanks for reading if you did :slight_smile:

Another thing, no errors are printed either.

Do you really need to check every descendant of the workspace?
If all you need is the players then game:GetService("Players"):GetPlayers() should be sufficient, this would also cut out the logic checks for humanoids and torsos etc, and it’ll be more straightforward checking for teams as well (depending on your setup).
If you did need it to be able to target other NPCs (and Players), you could use tags to easily find them in workspace.
This should help with the laggy-ness you mentioned.

This line of code is temporary, Since I am making this script in a separate place, i have it check the workspace instead. But in the main game, it checks through a folder. Also, this is not meant to check players. There are other NPCs that can be enemies as well, not only players.

Okay that makes more sense!

How and when is the Activity.FindEnemy function called? I can’t see any logic in the code provided for it to switch to another target?

In activity.ChaseEnemy The repeat loop ends once the enemy health is below 1 or it loses sight of the target, if you set up an event to fire/return value here to start finding another enemy it should help with the freezing, and other NPCs interfering with pathfinding.

FindEnemy is called here.

------------------------
--The Main function that helps maintain all other functions run at a time.
function Activity.Main(NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
--	warn("Called for " .. NPC.Name)
	table.insert(RayPartsToIgnore, Torso.Parent)
	local Target = Activity.FindEnemy(NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
	if Target then
		Activity.ChaseEnemy(Target, NPC, Torso, Root, Humanoid, Head, Traits, Angular, Events)
	else
		Activity.Roam(NPC, Torso, Root, Angular, Humanoid)
	end
end
------------------------

The script inside the NPC, not the module:

while wait(.5) do
	if Humanoid.Health < 1 then
		break
	else
		CoreModule.Main(
			NPC, --The parent of this script
			Torso, -- NPC torso
			Root, -- NPC HRP
			Humanoid, -- NPC humanoid
			Head, -- NPC head
			Traits, -- NPC trait stats n stuff
			Angular, -- The thing for looking around.
			Events
		)
	end
end

if you set up an event to fire/return value here to start finding another enemy it should help with the freezing, and other NPCs interfering with pathfinding.

I don’t think I quite understand, Do you think you could write out an example for me? sorry.

Now I’ve seen the other script it probably won’t help, as it is looking for a target on a loop anyway.

Well the humanoid in the other script is the humanoid help by the NPC the AI is running on, not the enemy humanoid if that’s what you were thinking.

Any other possible solutions or tips? If not, it’s fine; thank you for trying to help
me :grin:

I think I’d need to set it up to try and run it myself, but don’t have time to do so.
Sorry :frowning: