How to handle all NPC's in one script?

I need help trying to figure out what to do to transform this code so it handles all NPC’s

Full Code:

local npcsFolder = workspace:WaitForChild("Npcs")

local RunService = game:GetService("RunService")
local replicatedStorage = game:GetService("ReplicatedStorage")
local Remotes = replicatedStorage:WaitForChild("Remotes")

local Modules = replicatedStorage:WaitForChild("Modules")

local ToolData = require(script.Parent:WaitForChild("ToolData"))

local character = script.Parent.Parent
local humanoid = character:WaitForChild("Hero")
local animator = humanoid:FindFirstChildOfClass("Animator")

local RemotesFolder = character:WaitForChild("RemotesFolder")

local npcs = workspace:WaitForChild("Npcs")

local animationTable = {
	[0] = script:WaitForChild("Attack1"),
}

local tool = character:WaitForChild("Bow")
local hitbox = tool:WaitForChild("Hitbox")
local firePoint = hitbox:WaitForChild("FirePoint")

local range = ToolData.MaxRange

local animationCombo = 0

local currentTime = tick()
local canShoot = true
local damageCooldown = 0.4

local currentTarget = nil

local canAttack = character:WaitForChild("CanAttack")
local track = nil

local function playAnimation()
	if animationTable[animationCombo] then
		local anim = animationTable[animationCombo]
		track = animator:LoadAnimation(anim)
		track:Play()
		track.Stopped:Wait()
		canShoot = true
	end
	animationCombo += 1

	if animationCombo > #animationTable then
		animationCombo = 0
	end
end

local function castRay(target)
	if target.PrimaryPart ~= nil then
		local raycastParams = RaycastParams.new()
		raycastParams.FilterDescendantsInstances = {npcs}
		raycastParams.FilterType = Enum.RaycastFilterType.Whitelist
		raycastParams.IgnoreWater = true
		
		local rayOrigin = character.PrimaryPart.Position
		local rayDestination = target.PrimaryPart.Position
		local rayDirection = (rayDestination - rayOrigin)
		local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
		
		if raycastResult then
			local distance = math.floor(raycastResult.Distance)
			if distance <= range then
				currentTarget = target
				return true, raycastResult.Position, distance
			end
		end
	end
	currentTarget = nil
	return false, nil, nil
end

local function PredictNextPosition(targetPosition, targetVelocity)
	if targetPosition ~= nil and targetVelocity ~= nil then
		local t = ToolData.ProjectileTime
		local endPosition = targetPosition + targetVelocity * t
		return endPosition
	end
end

local function getPlayer()
	local player = Remotes.GetPlayerBindable:Invoke()
	if player then
		return player
	end
end

local player = getPlayer()

local function reset()
	currentTarget = nil
	canShoot = true
end

local function findTarget()
	local checkRay, position, distance = nil, nil, nil

	for i,v in ipairs(npcs:GetChildren()) do
		checkRay, position, distance = castRay(currentTarget or v)

		if currentTarget ~= nil then
			local savedTarget = currentTarget

			local targetRoot = currentTarget.PrimaryPart
			local human = currentTarget:FindFirstChildWhichIsA("Humanoid")
			
			if human and human.Health > 0 then
				if human and human.Health <= 0 then
					currentTarget = nil
				end

				if distance > range then
					currentTarget = nil
				end

				if player and human and human.Health > 0 and currentTarget ~= nil and targetRoot ~= nil and checkRay == true then
					local success, bool = pcall(function()
						return Remotes.CheckOnScreen:InvokeClient(player, targetRoot, {npcs})
					end)
					
					if success and bool then
						local characterRoot = character.PrimaryPart
						characterRoot.CFrame = CFrame.lookAt(characterRoot.Position, Vector3.new(position.X, characterRoot.Position.Y, position.Z))

						if tick() - currentTime >= damageCooldown then
							currentTime = tick()
							playAnimation()
							checkRay, position, distance = castRay(savedTarget)
							local nextPosition = PredictNextPosition(position, targetRoot.AssemblyLinearVelocity)
							if checkRay == true and nextPosition ~= nil and canShoot == true then
								RemotesFolder.FireProjectile:Fire(firePoint, nextPosition)
								canShoot = false
							else
								reset()
							end
						end
					end
				end
			else
				reset()
			end
		end
	end
end

local lastSendTick = tick()
local updatesPerSecond = 10

RunService.Heartbeat:Connect(function()
	if tick() - lastSendTick > 1 / updatesPerSecond and character.PrimaryPart ~= nil and canAttack.Value == true then
		lastSendTick = tick()
		findTarget()
	end
end)

I heard that using CollectionService is one way of doing it, but I never used it so I don’t know how you would do it, another way (very bad I think) is putting all NPCs in a table and handle all NPCs in that table

1 Like

I also don’t know how to use CollectionService.

I think there’s some tutorials on YouTube or here

local currentTargets = {}

Heroes.ChildAdded:Connect(function(child)
	table.insert(currentTarget, child)
end)

Heroes.ChildRemoved:Connect(function(child)
	table.remove(currentTarget, table.find(currentTarget, child))
end)

What’s that? I don’t understand sorry

Thats what you said to do, right?

For the table thing? Yes, it might work but I’m not sure if it is actually a good way, it might have some performance issues