Wait not working in a loop

Hey everyone, recently, I’ve been trying to make a shooting AI, however, I ran into this issue where the AI just shoots like 500 bullets a second and have been unable to fix it, I am unsure on how to approach fixing the issue. Thank you in advance!
This is the script:

There’s a coroutine because this is a serverscript that controls all the NPCs.

coroutine.wrap(function()
												local Firerate = Body.Configuration.CanShoot.WeaponProperties.Firerate.Value
												while task.wait(math.random(Firerate,Firerate+.5)) do
													CanShoot = true
													if CanShoot == true then
														print("hurt")
														CanShoot = false
														local rayLength = 15 --//How long the ray should be
														local raycastParams = RaycastParams.new()
														raycastParams.FilterDescendantsInstances = {Body}
														raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
														local rayOrigin = Body.Weapon.FirePos.WorldPosition
														local thing = Vector3.new(math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value))
														local rayDirection = (player.Head.Position+thing - Body.Weapon.FirePos.WorldPosition).Unit * rayLength
														local rayResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
														if rayResult then
															if rayResult.Instance:FindFirstAncestorOfClass("Model") and rayResult.Instance:FindFirstAncestorOfClass("Model"):FindFirstChild("Humanoid") and rayResult.Instance:FindFirstAncestorOfClass("Model") ~=Body then
																local Damage = math.random(Body.Configuration.CanShoot.WeaponProperties.DamageMin.Value, Body.Configuration.CanShoot.WeaponProperties.DamageMax.Value)
																rayResult.Instance:FindFirstAncestorOfClass("Model").Humanoid:TakeDamage(Damage)
																print(Damage)
															end
														end
													end
												end
											end)()
local tempLight = script.Parent.ExtremeTempLight
local pressureLight = script.Parent.ExtremePressureLight
local overheated = workspace.Overheated
local overpressure = workspace.Overpressure
local alarm = workspace.SoundPart.Alarm1

local cycle
while task.wait(.25) do
  cycle = not cycle
  local oh = overheated.Value
  local op = overpressure.Value
  local cycleMat = cycle and 'Neon' or 'Plastic'
  tempLight.Material = oh and cycleMat or 'Plastic'
  pressureLight.Material = op and cycleMat or 'Plastic'
  alarm.Playing = (oh or op) and cycle or false
end

Hey! I am not exactly sure what were you trying to say by this, the code wouldn’t work for my case, nor do I find something that I would find helpful in it, apologies if I missed something.

To my recollection, math.random only works with integers and it might just be returning zero constantly. You might want to check that and see.

I did check on that, and it was returning number 2 always, which is the value of Firerate

So I’ve tried removing math.random, and it still doesn’t work

Can I see the full script please?

Yes, here,

--[[IF YOU WANT TO CONFIGURE SOMETHING, GO TO THE CONFIGURATION IN NPC AND EDIT IT





]]


-------------------------------------------------------------
local Players = game:GetService("Players")
local Pathfinding = require(script.SimplePath)

workspace.DescendantAdded:Connect(function(instance)
	if instance:IsA("Model") and Players:GetPlayerFromCharacter(instance) then
		local String = Instance.new("StringValue")
		String.Parent = instance
		String.Name = "State"
	end
end)

for i, NPC in pairs(workspace.NPCs:GetChildren()) do



	local LookAtPlayerRange = NPC.Configuration.DistanceOfDetection.Value -- Studs between player and dummy that dummy can look.

	-- [Head, Torso, HumanoidRootPart], "Torso" and "UpperTorso" works with both R6 and R15.
	-- Also make sure to not misspell it.
	local PartToLookAt = "Head" -- Where should the npc look at.


	local LookBackOnNil = true -- Should the npc look at where they should when player is out of range.
	local CanShoot = true

--[[
	[Horizontal and Vertical limits for head and body tracking.]
	Setting to 0 negates tracking, setting to 1 is normal tracking, and setting to anything higher than 1 goes past real life head/body rotation capabilities.
--]]
	local HeadHorFactor = 0.8
	local HeadVertFactor = 0.5
	local BodyHorFactor = 0.4
	local BodyVertFactor = 0.4

	-- Don't set this above 1, it will cause glitchy behaviour.
	local UpdateSpeed = 0.3 -- How fast the body will rotates.
	local UpdateDelay = 0.05 -- How fast the heartbeat will update.

	-------------------------------------------------------



	--
	local Ang = CFrame.Angles
	local aTan = math.atan
	--
	--------------------------------------------
	local Body = NPC

	local Head = Body:WaitForChild("Head")
	local Hum = Body:WaitForChild("Humanoid")
	local Core = Body:WaitForChild("HumanoidRootPart")
	local IsR6 = (Hum.RigType.Value==0)
	local Trso = (IsR6 and Body:WaitForChild("Torso")) or Body:WaitForChild("UpperTorso")
	local Neck = (IsR6 and Trso:WaitForChild("Neck")) or Head:WaitForChild("Neck")	
	local Waist = (not IsR6 and Trso:WaitForChild("Waist"))
	local Path = Pathfinding.new(Body)

	local NeckOrgnC0 = Neck.C0
	local WaistOrgnC0 = (not IsR6 and Waist.C0)
	--------------------------------------------


	-- Necessery Functions

	local function getClosestPlayer() -- Get the closest player in the range.
		local closest_player, closest_distance = nil, LookAtPlayerRange
		for i, player in pairs(workspace:GetDescendants()) do
			if player:FindFirstChild("Humanoid") and player ~= Body and Players:GetPlayerFromCharacter(player) or player.Parent == workspace.LookAtAbleObjects  then
				local distance = (Core.Position - player.PrimaryPart.Position).Magnitude
				if distance < closest_distance then
					closest_player = player
					closest_distance = distance
				end
			end
		end
		return closest_player
	end

	local function rWait(n) -- Fix many heartbeat lag issue
		n = n or 0.05
		local startTime = os.clock()

		while os.clock() - startTime < n do
			game:GetService("RunService").Heartbeat:Wait()
		end
	end

	local ErrorPart = nil
	local function GetValidPartToLookAt(Char, bodypart)
		local pHum = Char:FindFirstChild("Humanoid")
		if not Char and pHum then return nil end
		local pIsR6 = (pHum.RigType.Value==0)
		local ValidPart
		if bodypart == "Torso" or bodypart == "UpperTorso" then
			if pIsR6 then ValidPart = Char:FindFirstChild("Torso") else ValidPart = Char:FindFirstChild("UpperTorso") end
			if ValidPart ~= Char:FindFirstChild(bodypart) then
				ValidPart = Char.PrimaryPart
			end
		else ValidPart = Char:FindFirstChild(bodypart) end
		if ValidPart then return ValidPart else
			if ErrorPart ~= bodypart then
				warn(Body.Name.." can't find part to look: "..tostring(bodypart))
				ErrorPart = bodypart
			end
			return nil end
	end

	local function LookAt(NeckC0, WaistC0)
		if not IsR6 then
			Neck.C0 = Neck.C0:lerp(NeckC0, UpdateSpeed/2)
			Waist.C0 = Waist.C0:lerp(WaistC0, UpdateSpeed/2)
		else
			Neck.C0 = Neck.C0:lerp(NeckC0, UpdateSpeed/2)
		end
	end

	--------------------------------------------

	game:GetService("RunService").Heartbeat:Connect(function()
		rWait(UpdateDelay)
		local TrsoLV = Trso.CFrame.lookVector
		local HdPos = Head.CFrame.p
		local player = getClosestPlayer()
		local LookAtPart
		if IsR6 and Neck or Neck and Waist then
			if player and NPC.Humanoid.Health>0 then
				if NPC:FindFirstChild("Humanoid") then
					LookAtPart = GetValidPartToLookAt(player, PartToLookAt)
					if LookAtPart and NPC.Humanoid.Health>0 then
						local Dist = nil;
						local Diff = nil;
						local is_in_front = Core.CFrame:ToObjectSpace(LookAtPart.CFrame).Z < 0
						if is_in_front then
							Dist = (Head.CFrame.p-LookAtPart.CFrame.p).magnitude
							Diff = Head.CFrame.Y-LookAtPart.CFrame.Y

							if not IsR6 then
								if Body.Configuration.CurrentDetection.Value <= 10 then
									if player.State.Value == "" then
										Body.Configuration.CurrentDetection.Value += Body.Configuration.DetectionRate.Value
									elseif Body.Configuration.StateRate:FindFirstChild(player.State.Value) then
										Body.Configuration.CurrentDetection.Value +=  Body.Configuration.StateRate:FindFirstChild(player.State.Value).Value
									end
									if Players:GetPlayerFromCharacter(player) then
										game.ReplicatedStorage.NPCRemotes.Detection:FireClient(game.Players:GetPlayerFromCharacter(player), Body.Configuration.CurrentDetection.Value)
									end
								end
								if Body.Configuration.CurrentDetection.Value >= 10 then
									if Body.Configuration.Surrendered.Value == false and Body.Configuration.CanShoot.Value == false then
										Path.Visualize = true

										if Body.Configuration.Running.Value == false then
											Body.Configuration.Running.Value = true
											--Dummy knows to compute path again if something blocks the path
											Path.Blocked:Connect(function()
												Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
											end)

											--If the position of Goal changes at the next waypoint, compute path again
											Path.WaypointReached:Connect(function()
												Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
											end)

											--Dummmy knows to compute path again if an error occurs
											Path.Error:Connect(function(errorType)
												Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
											end)
											Path.Reached:Connect(function()
												Body:Destroy()
												game.ReplicatedStorage.NPCRemotes.Loud:Fire()
												workspace.Loud:Play()
												game:GetService("TweenService"):Create(workspace.Loud, TweenInfo.new(1), {Volume = .5}):Play()
												game:GetService("TweenService"):Create(workspace.Stealth, TweenInfo.new(1), {Volume = 0}):Play()
												wait(1)
												workspace.Stealth:Stop()
											end)
											Body.Humanoid.WalkSpeed = Body.Configuration.RunSpeed.Value
											Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
											wait(5)
											if Body.Humanoid.Health <1 then
												game.ReplicatedStorage.NPCRemotes.Loud:Fire()
												workspace.Loud:Play()
												game:GetService("TweenService"):Create(workspace.Loud, TweenInfo.new(1), {Volume = .5}):Play()
												game:GetService("TweenService"):Create(workspace.Stealth, TweenInfo.new(1), {Volume = 0}):Play()
												wait(1)
												workspace.Stealth:Stop()
											end
										end
									else
										if Body.Configuration.CanShoot.Value == true then
											for i, x in pairs(Body.Weapon:GetDescendants()) do
												if x:IsA("BasePart") then
													x.Transparency = 0
												end
											end
											coroutine.wrap(function()
												while true do
													task.wait()
													if player.Humanoid.Health>0 and Players:GetPlayerFromCharacter(player) then
														local Main = Body.HumanoidRootPart.Position
														local Target = Vector3.new(player.HumanoidRootPart.Position.X, Body.HumanoidRootPart.Position.Y, player.HumanoidRootPart.Position.Z)
														Body.HumanoidRootPart.CFrame = CFrame.lookAt(Main, Target)
													end
												end
											end)()
											coroutine.wrap(function()
												local Firerate = Body.Configuration.CanShoot.WeaponProperties.Firerate.Value
												while task.wait(Firerate) do
													if player.Humanoid.Health>0 and Players:GetPlayerFromCharacter(player) then
														CanShoot = true
														if CanShoot == true then
															print("hurt")
															CanShoot = false
															local rayLength = 15 --//How long the ray should be
															local raycastParams = RaycastParams.new()
															raycastParams.FilterDescendantsInstances = {Body}
															raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
															local rayOrigin = Body.Weapon.FirePos.WorldPosition
															local thing = Vector3.new(math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value))
															local rayDirection = (player.Head.Position+thing - Body.Weapon.FirePos.WorldPosition).Unit * rayLength
															local rayResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
															if rayResult then
																if rayResult.Instance:FindFirstAncestorOfClass("Model") and rayResult.Instance:FindFirstAncestorOfClass("Model"):FindFirstChild("Humanoid") and rayResult.Instance:FindFirstAncestorOfClass("Model") ~=Body then
																	local Damage = math.random(Body.Configuration.CanShoot.WeaponProperties.DamageMin.Value, Body.Configuration.CanShoot.WeaponProperties.DamageMax.Value)
																	rayResult.Instance:FindFirstAncestorOfClass("Model").Humanoid:TakeDamage(Damage)
																	print(Damage)
																end
															end
														end
													end
												end
											end)()
											wait(5)
											if Body.Humanoid.Health <1 then
												game.ReplicatedStorage.NPCRemotes.Loud:Fire()
												workspace.Loud:Play()
												game:GetService("TweenService"):Create(workspace.Loud, TweenInfo.new(1), {Volume = .5}):Play()
												game:GetService("TweenService"):Create(workspace.Stealth, TweenInfo.new(1), {Volume = 0}):Play()
												wait(1)
												workspace.Stealth:Stop()
											end
										end
									end
								end
								LookAt(NeckOrgnC0*Ang(-(aTan(Diff/Dist)*HeadVertFactor), (((HdPos-LookAtPart.CFrame.p).Unit):Cross(TrsoLV)).Y*HeadHorFactor, 0), 
									WaistOrgnC0*Ang(-(aTan(Diff/Dist)*BodyVertFactor), (((HdPos-LookAtPart.CFrame.p).Unit):Cross(TrsoLV)).Y*BodyHorFactor, 0))
							else	
								LookAt(NeckOrgnC0*Ang((aTan(Diff/Dist)*HeadVertFactor), 0, (((HdPos-LookAtPart.CFrame.p).Unit):Cross(TrsoLV)).Y*HeadHorFactor))
							end
						elseif LookBackOnNil and NPC.Humanoid.Health>0 then
							if Body then
								if Players:GetPlayerFromCharacter(player) then
									game.ReplicatedStorage.NPCRemotes.Detection:FireClient(game.Players:GetPlayerFromCharacter(player), Body.Configuration.CurrentDetection.Value)
								end
								if Body.Configuration.CurrentDetection.Value >= 0 then
									Body.Configuration.CurrentDetection.Value -= Body.Configuration.DetectionRate.Value
								end
								LookAt(NeckOrgnC0, WaistOrgnC0)
							end
						end
					end
				end
			end
		end
	end)
	Body.Humanoid.Died:Connect(function()
		if Body.Configuration.Dead.Value == false then
			Body.HumanoidRootPart.GetDisguise.Enabled = true
			Body.Parent = workspace.LookAtAbleObjects
			local String = Instance.new("StringValue")
			String.Parent = Body
			String.Name = "State"
			String.Value = "Dead"
		end
	end)
	Body.HumanoidRootPart.GetDisguise.Triggered:Connect(function(player)
		if player.Character:FindFirstChildOfClass("Pants") then
			player.Character:FindFirstChildOfClass("Pants"):Destroy()
		end
		if player.Character:FindFirstChildOfClass("Shirt") then
			player.Character:FindFirstChildOfClass("Shirt"):Destroy()
		end
		if Body:FindFirstChildOfClass("Shirt") then
			Body:FindFirstChildOfClass("Shirt"):Clone().Parent = player.Character
		end
		if Body:FindFirstChildOfClass("Pants") then
			Body:FindFirstChildOfClass("Pants"):Clone().Parent = player.Character
		end
		player.Character.State.Value = Body.Configuration.IfDisguise.Value
	end)
	game.ReplicatedStorage.NPCRemotes.Loud.Event:Connect(function()
		if Body.Configuration.Running.Value == false and Body.Configuration.CanShoot.Value == false and Body.Configuration.Surrendered.Value == false then
			Body.Configuration.Running.Value = true
			--Dummy knows to compute path again if something blocks the path
			Path.Blocked:Connect(function()
				Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
			end)

			--If the position of Goal changes at the next waypoint, compute path again
			Path.WaypointReached:Connect(function()
				Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
			end)

			--Dummmy knows to compute path again if an error occurs
			Path.Error:Connect(function(errorType)
				Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
			end)
			Path.Reached:Connect(function()
				Body:Destroy()
			end)
			Body.Humanoid.WalkSpeed = Body.Configuration.RunSpeed.Value
			Path:Run(Body.Configuration.WayPoints.Escape:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Escape:GetChildren())].Value)
		end
	end)
	local PrevStand
	local Stand	
	coroutine.wrap(function()
		while true do
			wait(math.random(1,5))
			Path.Reached:Connect(function()
				local PrevStand = Stand
				wait(math.random(1,5))
			end)
			if PrevStand then
				PrevStand.Occupied.Value = false
			end
			Stand = Body.Configuration.WayPoints.Casual:GetChildren()[math.random(1,#Body.Configuration.WayPoints.Casual:GetChildren())].Value
			if Stand.Occupied.Value == false then
				Stand.Occupied.Value = true
				Path:Run(Stand)
			end
		end
	end)()
end

Ah yeah that’s what I wondered. You’ve got this coroutine inside of an event/loop so it is created multiple times. On top of that, the while loop in question never ends and will continue running forever. You’ll need to restructure all of this so that you aren’t creating multiple coroutines in such a tight loop and (if you’re going to be creating multiple coroutines) end the loop.

1 Like

Oh! Right. Could you perhaps please help me figure out how to restructure it properly, as I honestly have no idea how to really do so, thanks in advance.

I gave it a go, but I wasn’t thorough and it might not work like you want. If it doesn’t work, send me a message in DMs to keep the clutter down and I’ll just edit this post once we get it working to explain how it was resolved for anyone else with this problem.

This code replaces the coroutine.wrap bit and removes the while loop.

-- at the top of the script, outside of Heartbeat
local FireDebounce = true

-- this section replaces the coroutine.wrap section
local Firerate = Body.Coinfiguration.CanShoot.WeaponProperties.Firerate.Value
if FireDebounce then
    FireDebounce = false
    task.spawn(function() wait(math.random()*.5+2) FireDebounce = true end)
    -- origimnal code
    if player.Humanoid.Health>0 and Players:GetPlayerFromCharacter(player) then
        CanShoot = true
        if CanShoot == true then
            print("hurt")
            CanShoot = false
            local rayLength = 15 --//How long the ray should be
            local raycastParams = RaycastParams.new()
            raycastParams.FilterDescendantsInstances = {Body}
            raycastParams.FilterType = Enum.RaycastFilterType.Blacklist
            local rayOrigin = Body.Weapon.FirePos.WorldPosition
            local thing = Vector3.new(math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value),math.random(0, Body.Configuration.CanShoot.WeaponProperties.Inaccuracy.Value))
            local rayDirection = (player.Head.Position+thing - Body.Weapon.FirePos.WorldPosition).Unit * rayLength
            local rayResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams)
            if rayResult then
                if rayResult.Instance:FindFirstAncestorOfClass("Model") and rayResult.Instance:FindFirstAncestorOfClass("Model"):FindFirstChild("Humanoid") and rayResult.Instance:FindFirstAncestorOfClass("Model") ~=Body then
                    local Damage = math.random(Body.Configuration.CanShoot.WeaponProperties.DamageMin.Value, Body.Configuration.CanShoot.WeaponProperties.DamageMax.Value)
                    rayResult.Instance:FindFirstAncestorOfClass("Model").Humanoid:TakeDamage(Damage)
                    print(Damage)
                end
            end
        end
    end
end

If you hit any issues putting it in or if it doesn’t work properly, send me a private message and I’ll take a look at it again when I’m not at school/work.

1 Like