Wandering state is too frequent despite being on a cooldown

So uh, i have some code that makes enemies wander around if there are no players, thing is, they’re meant to wander once every x seconds, but for some reason they just wander right after finishing a previous wander :^(

visual presentation (yes i am playing doom eternal with hdr shut up)


code that’s responsible for wandering;

local sScriptService = game:GetService("ServerScriptService")
local pfService = game:GetService("PathfindingService")
local dService = game:GetService("Debris")

-- Path to modules;
local enemies = sScriptService:WaitForChild("Enemies")

-- Modules;
local enemyStats = require(enemies.EnemyStats)

-- Path parameters!!
local pathParameters = {
	AgentRadius = 2,
	AgentHeight = 2,
	AgentCanJump = true,
	AgentCanClimb = false
}

function VisualizePath(waypoint: PathWaypoint)
	local pathVisualizer = Instance.new("Part")
	pathVisualizer.Shape = Enum.PartType.Ball
	pathVisualizer.Parent = workspace
	pathVisualizer.Position = waypoint.Position

	pathVisualizer.Color = Color3.fromRGB(255,255,255)
	pathVisualizer.Material = Enum.Material.Neon

	pathVisualizer.Size = Vector3.new(2,2,2)

	pathVisualizer.CanCollide = false
	pathVisualizer.Anchored = true

	dService:AddItem(pathVisualizer, 2)
end

function RandomWander(part: BasePart)
	local minBound = part.Position - part.Size / 2
	local maxBound = part.Position + part.Size / 2
	
	-- Calculate bounds;
	local minX = part.Position.X - part.Size.X / 2
	local maxX = part.Position.X + part.Size.X / 2
	local minZ = part.Position.Z - part.Size.Z / 2
	local maxZ = part.Position.Z + part.Size.Z / 2
	
	-- Randomize positions;
	local randomX = math.random() * (maxX - minX) + minX
	local randomZ = math.random() * (maxZ - minZ) + minZ

	local fixedY = part.Position.Y

	return Vector3.new(randomX, fixedY, randomZ)
end


-- Runtime;

local pathfinding = {}

function pathfinding.Wander(humanoid: Humanoid) -- Generates a random position and pathfinds to it (if player is not in LoS)
	local char: Model = humanoid.Parent
	
	local detectionPart: BasePart = char:FindFirstChild("DetectionPart") 
	
	local path: Path = pfService:CreatePath(pathParameters)
	local humanoidRootPart: BasePart = char:FindFirstChild("HumanoidRootPart")
	
	local startPos = humanoidRootPart.Position
	local targetPos = RandomWander(detectionPart)
	
	local success, result = pcall(function()
		path:ComputeAsync(startPos, targetPos)
	end)
	
	if not success then
		warn("Pathfinding failed: " .. result)
		return
	end
	
	local waypoints = path:GetWaypoints()
	
	if path.Status == Enum.PathStatus.Success and #waypoints > 0 then
		humanoid.WalkSpeed = enemyStats[char.Name]["WANDER_SPEED"]
		for i = 2, #waypoints do
			local waypoint: PathWaypoint = waypoints[i]
			
			if waypoint.Action == Enum.PathWaypointAction.Jump then
				humanoid.Jump = true
			end
			
			VisualizePath(waypoint)
			
			humanoid:MoveTo(waypoint.Position)
			humanoid.MoveToFinished:Wait()
		end
		
		task.spawn(function()
			local cooldown = math.random(5,10)
			
			char:SetAttribute("WanderCooldown", true)
			task.wait(cooldown)
			char:SetAttribute("WanderCooldown", false)
		end)
	end
end

code that calls the wandering function

local pathfindingModule = require(game:GetService("ServerScriptService"):WaitForChild("Enemies").PathfindingModule)

local runService = game:GetService("RunService")
local enemiesFolder = workspace:WaitForChild("Enemies")

while task.wait(0.05) do
	if #enemiesFolder:GetChildren() > 0 then
		local enemies = enemiesFolder:GetChildren()

		for _, enemy in ipairs(enemies) do
			if enemy.EnemyType.Value == "MELEE" then
				local detectionBox = enemy:FindFirstChild("DetectionPart")
				local humanoid = enemy:FindFirstChildOfClass("Humanoid")


				local closestPlayer = nil
				local LOS = workspace:GetPartBoundsInBox(detectionBox.CFrame, detectionBox.Size)

				for _, v in pairs(LOS) do
					if v.Parent:FindFirstChild("Humanoid") then
						local targetPlayer = game.Players:GetPlayerFromCharacter(v.Parent)

						if targetPlayer then
							if closestPlayer == nil or (enemy.HumanoidRootPart.Position - targetPlayer.Character.HumanoidRootPart.Position).Magnitude < (enemy.HumanoidRootPart.Position - closestPlayer.Character.HumanoidRootPart.Position).Magnitude then
								closestPlayer = targetPlayer
							end
						end
					end
				end

				task.spawn(function()
					if closestPlayer then
						local targetPosition = closestPlayer.Character.Torso.PathfindPart.Position
						pathfindingModule.Chase(humanoid, targetPosition)
						enemy:SetAttribute("Chasing", true)
					else
						if not enemy:GetAttribute("WanderCooldown") then
							pathfindingModule.Wander(humanoid)
						end
					end
				end)
			end
		end
	end
end
2 Likes

Try running the cooldown variable and the task.wait outside of the task.spawn function

1 Like

nope, same problem

charrrrrrrrrrrrr

My best advice since you have nested everything together, is to seperate them, and use bindable events. Also task.spawn in a while wait do (basically a while true do) is just not good practice. Also don’t put for loops inside each other either unless you are forced too. To implement all this into your code would take quite some time but I use to nest things like you and my code works so much better now.

bindable events?

what arguments would i even send? (or parameters whatever)
the humanoid? the enemy model?

You can make a bindable event for anything, I do not think you are limited to parameters. You would only be sending information to the server since they control the NPC’s.

well yeah, but…

how can i even use them, just handle the wandering cooldown in a seperate script?
i need some elaboration on this because i happen to be extremely clueless at the moment

Step 1. make local function and seperate your 7 nested loop lol
Step 2. Create bindable event in RS, call it “Wandering” then create a local function called “main” just an example, and call your functions then call RS.Wander:Fire(). Server see this and will fire the event to the server, in your main script or whever ever you have in your other script (you can call it anywhere) do OnServerEvent. This will stop the function there and stop causing it to loop. Now you have way more control over how the server sees the bindable event through a cooldown. I mean i’d have to re-write your code to really get the point across and I am at work. My best advice is to learn how to use bindable events (you don’t have too it is just IMO the best thing to use). You can technically still use a while true loop but I can do one better. Go into roblox and add the robots to your game(the ones roblox added as free models they are NPCS), they all have animations, and not only that, they have pathing on a set cooldown. But guess what, they don’t nest for loops.

what the hell is a nested for loop :sob: