NPC "Squad" Pathfinding not working and can't find out why?

The primary goal of this script is to control a “squad” of NPCs using pathfinding to move towards a target (player) and attack them if within range.

(This script is in its early stages so I’m really just trying to get the NPC’s to FOLLOW the nearest player at the moment)
(Many functions that I plan to implement later)

The issue is that the squad of NPC’s are supposed to follow their “Commander”
Then seek and follow (attack) the nearest player however they are just idling where they spawn.
And the worst thing is that the output screen says nothing and I have checked if it run’s until the end using “print” and it does.

I have looked online, watched tutorials and even asked ChatGPT to have a look at it.

I don’t really post on the Dev Forums too often so I apologise if something is unclear.
And thanks for any help provided

local PathfindingService = game:GetService("PathfindingService")
local SEARCH_RADIUS = 100 -- Adjust the search radius as needed
local ATTACK_RADIUS = 20 -- Adjust the attack radius as needed
local squad = {} -- Holds the members of the special response team

-- Helper Functions
local function calculateDangerLevel(player)
	-- Implement code to calculate the player's danger level based on their equipped weapons and other factors
	-- Assign a numerical value to each weapon and calculate the total danger level accordingly
	return 0
end

local function findNearestPlayer(position)
	-- Implement code to find the nearest player to a given position  within a certain radius
	local players = game:GetService("Players"):GetPlayers()
	local nearestPlayer, minDistance = nil, math.huge
	for _, player in ipairs(players) do
		if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
			local distance = (player.Character.HumanoidRootPart.Position - position).Magnitude
			if distance < SEARCH_RADIUS and distance < minDistance then
				minDistance = distance
				nearestPlayer = player.Character.HumanoidRootPart.Position
			end
		end
	end
	return nil
end

-- Function to add a member to the squad
local function addSquadMember(member, role)
	-- Implement code to add the member to the squad and set their initial behavior
	table.insert(squad, {member = member, role = role})

	-- Set the member's role-specific behavior
	if role == "Commander" then
		-- Set the commander's behavior here (e.g., guide other squad members, attack players based on threat level)
		-- You can assign the shotgun and light combat armor to the model instance of the commander
	elseif role == "Spotter" then
		-- Set the spotter's behavior here (e.g., spot players from long distances, alert others, target through walls)
		-- You can assign the Battle Rifle (AR15) and special targeting goggles to the model instance of the spotter
	elseif role == "Sniper" then
		-- Set the sniper's behavior here (e.g., shoot players from long distances, fall back if targeted player gets too close)
		-- You can assign the sniper rifle to the model instance of the sniper
	elseif role == "Medic" then
		-- Set the medic's behavior here (e.g., passively heal wounded squad members)
		-- You can assign the pistol and medkit to the model instance of the medic
	elseif role == "Gunner" then
		-- Set the gunner's behavior here (e.g., rely on suppressing fire, higher armor rating)
		-- You can assign the Light Machine Gun and Heavy combat armor to the model instance of the gunner
	end
end

local Commander = game.Workspace.Commander 
addSquadMember(Commander, "Commander")
local Spotter = game.Workspace.Spotter
addSquadMember(Spotter, "Spotter")
local Sniper = game.Workspace.Sniper
addSquadMember(Sniper, "Sniper")
local Medic = game.Workspace.Medic
addSquadMember(Medic, "Medic")
local Gunner = game.Workspace.Gunner
addSquadMember(Gunner, "Gunner")

-- Function to set the squad's target
local function setSquadTarget(targetPlayer)
	-- Implement code to set the target for the squad
	squad.target = targetPlayer
end

-- Main AI Function
local function SpecialResponseTeamAI()
	while true do
		wait(1) -- Adjust the delay as needed to control the AI behavior frequency

		-- Check if the squad has a target
		if not squad.target then
			-- If there's no target, find the nearest player to attack
			local nearestPlayer = findNearestPlayer(squad[1].member.HumanoidRootPart.Position) -- Assuming the first squad member is the leader (Commander)
			if nearestPlayer then
				local dangerLevel = calculateDangerLevel(nearestPlayer)
				if dangerLevel > 0 then
					setSquadTarget(nearestPlayer)
				end
			end
		else
			-- If there's a target, move towards them and attack if within range
			for _, memberData in ipairs(squad) do
				local member = memberData.member
				local role = memberData.role

				if member:IsA("Model") and member:FindFirstChild("HumanoidRootPart") and member.Humanoid.Health > 0 then
					local pathPoints = {
						squad[1].member.HumanoidRootPart.Position, -- The first squad member is the leader (Commander)
						squad.target.Position
					}
					local pathParams = PathfindingService:CreatePath({
						AgentRadius = 2, -- Adjust the radius as needed
						AgentHeight = 5, -- Adjust the height as needed
						AgentCanJump = true,
						AgentJumpHeight = 10, -- Adjust the jump height as needed
						AgentMaxSlope = 45, -- Adjust the max slope angle as needed
					})
					pathParams:ComputeAsync(unpack(pathPoints))
					local path = pathParams.Status == Enum.PathStatus.Success and pathParams or nil

					if path then
						while true do
							member.Humanoid:MoveTo(path:GetPointAtIndex(2)) -- Start moving towards the next point in the path
							if (member.HumanoidRootPart.Position - squad[1].member.HumanoidRootPart.Position).magnitude <= 3 then
								-- Make other squad members closely follow the Commander (within a distance of 3 studs)
								member.Humanoid:MoveTo(squad[1].member.HumanoidRootPart.Position)
							end
							if (member.HumanoidRootPart.Position - squad.target.Position).magnitude <= ATTACK_RADIUS then
								-- Implement code for attacking the target player based on the member's role
								--
								if role == "Commander" then
									-- Commander's attack logic
								elseif role == "Spotter" then
									-- Spotter's attack logic
								elseif role == "Sniper" then
									-- Sniper's attack logic
								elseif role == "Medic" then
									-- Medic's attack logic
								elseif role == "Gunner" then
									-- Gunner's attack logic
								end
							end
							-- Check if the squad no longer has a target or the squad member is defeated
							if not squad.target or member.Humanoid.Health <= 0 then
								break
							end

							wait(0.1) -- Adjust the delay as needed for smooth movement
						end
					end
				end
			end
		end
	end
end


return SpecialResponseTeamAI

1 Like

I’m just asking some potential answers… just to be clear.

  • Did you actually assign roles for the squad members in the addSquadMember function?

  • The calculateDangerLevel function always returns 0, and later down, your main AI is checking if it’s bigger than 0. That means that there isn’t a situation where the squad.Target isn’t nil, meaning that the pathfinding part will simply never happen.

1 Like
  1. I believe the roles for each squad member are assigned accordingly from lines 68 - 77 but if that is the problem I assume the pathfinding would still work regardless

  2. I cooked up this code below to set the default DangerLevel to 1 (Which it should of been thank you for pointing that out) and also made it so if a player has a tool on them, their threat level should increase though nothing changed?

-- Helper Functions
local function calculateDangerLevel(player)
	-- Implement code to calculate the player's danger level based on their equipped weapons and other factors
	-- Assign a numerical value to each weapon and calculate the total danger level accordingly
	local dangerLevel = 1 -- Default danger level

	local character = player.Character
	if character then
		local humanoid = character:FindFirstChildOfClass("Humanoid")
		if humanoid then
			local equippedWeapon = humanoid:FindFirstChildOfClass("Tool")
			if equippedWeapon then
				dangerLevel = dangerLevel + 1 -- Assign a higher danger level if the player is carrying a weapon
			end
		end
	end

	return dangerLevel
end

1 Like

I must also Clarifiy This specific script is a ModuleScript which is being required by a server-side script.

1 Like

I have to ask, does the Server Script actually call the commands when necessary? The main AI function seems to be a local function, which shouldn’t be accessible when calling functions. Maybe change it to simply “function”, and test it out to see what happens?

1 Like

I made the change though nothing interesting happened, neither did anything appear in the output menu. But I did make a discovery myself, adding print statements throughout different sections of the main AI function the only ones activated is a print statement just before the function and just after.

Do you mean the calculateDangerLevel function?

I’m sorry, but it’s really late at night here so I can barely function

No the SpecialResponseTeamAI function

And its alright take your time if you need to :sweat_smile: we can always continue this another day if needed