I have the following code:
task.wait(5)
local shootModule = require(game.ServerScriptService.NPCShoot)
local rayCastModule = require(game.ReplicatedStorage:WaitForChild("LineOfSight"))
local PathfindingService = game:GetService("PathfindingService")
local AI = script.Parent
local RP = script.Parent.HumanoidRootPart
local Hum = script.Parent.Humanoid
local OverheadUI = AI.Head.HeadUI
local Gun = script.Parent:FindFirstChildOfClass("Tool")
local Waypoints = game.Workspace:WaitForChild("Waypoints"):GetChildren()
RP:SetNetworkOwner(nil)
local attackAnim = Hum.Animator:LoadAnimation(script.Attack)
local walkAnim = Hum.Animator:LoadAnimation(script.Walk)
local runAnim = Hum.Animator:LoadAnimation(script.Run)
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {Hum}
local Damage = 25
local AttackRange = 15
local shootTrack = AI.Humanoid.Animator:LoadAnimation(game.ReplicatedStorage.Animations.NPCPistolShoot)
walkAnim.Looped = true
runAnim.Looped = true
walkAnim:Play()
local LastSeenPos
local patrolPath
local RayParams = RaycastParams.new()
RayParams.FilterType = Enum.RaycastFilterType.Exclude
local alreadyDetected = false
local chaseCooldown = false
local pathParams = {
AgentHeight = 1,
AgentRadius = 1,
AgentCanJump = false,
Costs = {}
}
for i, v in pairs(game.Workspace:WaitForChild("AvoidPathing"):GetChildren()) do
pathParams.Costs[v] = math.huge
end
local function getPath(destination)
if typeof(destination) ~= "Vector3" then
warn("Destination variable was not a vector3")
end
if destination then
local path = PathfindingService:CreatePath(pathParams)
path:ComputeAsync(RP.Position , destination)
return path
else
warn("Pathfinding fail")
return "Fail"
end
end
function lineOfSight(target)
if rayCastModule.lineOfSight(AI,target.Parent) then
return true
else
return false
end
end
function getTarget()
local closestTarget = nil
local distanceFromClosestTarget = 1000000000
for i, player in pairs(game.Players:GetChildren()) do
local distance = (player.Character.HumanoidRootPart.Position - RP.Position).Magnitude
if distance < distanceFromClosestTarget and player.Character.Humanoid.Health > 0 then
if lineOfSight(player.Character.HumanoidRootPart) then
if player.Character.Humanoid.Health > 0 then
distanceFromClosestTarget = distance
closestTarget = player
end
end
end
end
if closestTarget ~= nil then
alreadyDetected = true
OverheadUI.TextLabel.Text = "!"
OverheadUI.TextLabel.TextColor3 = Color3.new(1, 0, 0.0156863)
OverheadUI.TextLabel.Visible = true
return(closestTarget)
else
--task.wait(5)
-- patrol()
end
end
local db = false
local runAnimPlayingStatus = false
local walkAnimPlayingStatus = false
function chaseTarget(target)
local path
path = getPath(target.Character.HumanoidRootPart.Position)
if path ~= "Fail" then
local Waypoints = path:GetWaypoints()
if Waypoints[2] then
Hum:MoveTo(Waypoints[2].Position)
task.spawn(shoot)
if lineOfSight(target.Character.HumanoidRootPart) then
patrol()
else
moveToLastSeen(target.Character.HumanoidRootPart.Position)
end
else
OverheadUI.TextLabel.Text = Color3.new(255,255,255)
OverheadUI.TextLabel.Visible = false
patrol()
return
end
end
end
function moveToLastSeen(location)
local path = getPath(location)
if path ~= "Fail" then
for i, waypoint in pairs(path:GetWaypoints()) do
local humTarget = getTarget()
if humTarget then
patrol()
break
else
if walkAnimPlayingStatus == false then
walkAnim:Play()
walkAnimPlayingStatus = true
end
runAnimPlayingStatus = false
runAnim:Stop()
Hum:MoveTo(waypoint.Position)
Hum.MoveToFinished:Wait()
OverheadUI.TextLabel.Text = "?"
OverheadUI.TextLabel.TextColor3 = Color3.new(1, 0, 0.0156863)
OverheadUI.TextLabel.Visible = true
if i == #path:GetWaypoints() then
patrol()
break
end
end
end
else
patrol()
end
end
function moveTo(target)
local humTarget = getTarget()
if humTarget then
if patrolPath then
patrolPath:Destroy()
end
chaseTarget(humTarget)
return
else
patrolPath = getPath(target)
if patrolPath.Status == Enum.PathStatus.Success then
local Waypoints = patrolPath:GetWaypoints()
for i, waypoint in pairs(Waypoints) do
if i == #patrolPath:GetWaypoints() then
patrol()
break
end
local humTarget = getTarget()
if humTarget then
chaseTarget(humTarget)
break
else
OverheadUI.TextLabel.TextColor3 = Color3.new(255,255,255)
OverheadUI.TextLabel.Visible = false
Hum:MoveTo(waypoint.Position)
Hum.MoveToFinished:Wait()
end
end
end
end
end
local patrolDB = false
function patrol()
local ChosenWapoint = math.random(1, #Waypoints)
moveTo(game.Workspace.Waypoints:FindFirstChild(ChosenWapoint).Position)
end
script.Parent.Humanoid.Died:Connect(function()
script:Destroy()
AI.Head.HeadUI:Destroy()
Hum.HealthDisplayDistance = 0
OverheadUI.TextLabel.Visible = false
end)
local lastPos = RP.Position
function stuckDetect()
while task.wait(1) do
if (RP.Position - lastPos).Magnitude < 1 and Gun:GetAttribute("Reloading") == false then
warn("AI got stuck falling back")
if patrolPath then
patrolPath:Destroy()
patrol()
end
else
lastPos = RP.Position
end
end
end
function shoot()
local target = getTarget()
if target and (target.Character.HumanoidRootPart.Position - RP.Position).Magnitude <= 20 and db == false then
db = true
shootTrack:Play()
shootModule.Shoot(target,Gun,AI)
task.wait(Gun:GetAttribute("FireRate"))
db = false
end
end
task.spawn(stuckDetect)
patrol()
it has an issue in that when you get out of sight of the AI chase humanoid it will print
Script 'Workspace.GunAi.GunAi', Line 239 - function patrol - Studio - GunAi:239
21:34:40.577 Script 'Workspace.GunAi.GunAi', Line 140 - function chaseTarget - Studio - GunAi:140
21:34:40.577 Script 'Workspace.GunAi.GunAi', Line 204 - function moveTo - Studio - GunAi:204
21:34:40.577 Script 'Workspace.GunAi.GunAi', Line 239 - function patrol - Studio - GunAi:239
21:34:40.577 Script 'Workspace.GunAi.GunAi', Line 140 - function chaseTarget - Studio - GunAi:140
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 204 - function moveTo - Studio - GunAi:204
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 239 - function patrol - Studio - GunAi:239
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 140 - function chaseTarget - Studio - GunAi:140
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 204 - function moveTo - Studio - GunAi:204
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 239 - function patrol - Studio - GunAi:239
21:34:40.578 Script 'Workspace.GunAi.GunAi', Line 282 - Studio - GunAi:282
21:34:40.578 Stack End - Studio
it prints the lines above StackEnd many times and then the ai just freezes completely, now I know that the issue is that the function calls are circular so function a calls function b calls function c calls function a again 100 times a second and then the script crashes.
What I don’t know is how to fix it, cooldown that prevent functions being called more then 10 times a second result in the AI never moving, I tried getting AI to fix it and it did a “State Management System” that resulted in the AI never even beggining to move.
I know what causes the issue but all my attempts to fix it made it worse and so I’m making this post. And I know some people are seeing the modules, and while I don’t believe they are at all the issue here they are anyways
NPCShoot:
local module = {}
local BulletsFolder = game.Workspace:WaitForChild("Bullets")
local FastCast = require(game.ReplicatedStorage.FastCastRedux)
local castBehavior = FastCast.newBehavior()
local castParams = RaycastParams.new()
castParams.FilterType = Enum.RaycastFilterType.Exclude
castParams.IgnoreWater = true
local animationsFolder = game.ReplicatedStorage.Animations
local bulletTemplate = Instance.new("Part")
bulletTemplate.Anchored = true
bulletTemplate.CanCollide = false
bulletTemplate.Size = Vector3.new(0.1, 0.1, 4)
bulletTemplate.Material = Enum.Material.Neon
bulletTemplate.BrickColor = BrickColor.new("Yellow flip/flop")
castBehavior.RaycastParams = castParams
castBehavior.CosmeticBulletContainer = BulletsFolder
castBehavior.CosmeticBulletTemplate = bulletTemplate
local function Hit(cast, result, velocity, bullet, Gun)
local hit = result.Instance
local character = hit:FindFirstAncestorWhichIsA("Model")
--local gun = cast.UserData
game.Debris:AddItem(bullet, 0.25)
if character:FindFirstChild("ISAI") then return end
if character and character:FindFirstChild("Humanoid") then
character.Humanoid:TakeDamage(bullet:GetAttribute("Damage") or 5)
end
game.Workspace.Bullets:ClearAllChildren()
end
local function onLengthChanged(cast, lastPoint, direction, length, velocity, bullet)
if bullet then
local bulletLength = bullet.Size.Z / 2
local offset = CFrame.new(0, 0, -(length - bulletLength))
bullet.CFrame = CFrame.lookAt(lastPoint, lastPoint + direction):ToWorldSpace(offset)
end
end
local caster = FastCast.new()
caster.LengthChanged:Connect(onLengthChanged)
caster.RayHit:Connect(Hit)
function module.Shoot(target, Gun, AI, reloadTime, stationary)
castParams.FilterDescendantsInstances = {Gun, BulletsFolder, AI, game.Workspace.AIS}
if not AI then return end
local audioPlayer = AI:FindFirstChild("HumanoidRootPart"):FindFirstChild("AudioPlayer")
if not audioPlayer then return end
local bulletsLeft = Gun:GetAttribute("BulletsLeft")
local inMag = Gun:GetAttribute("InMag")
if Gun:GetAttribute("InMag") > 0 then
Gun:SetAttribute("InMag", Gun:GetAttribute("InMag") - 1)
local firePoint = Gun.Barrel.FirePoint
local origin = firePoint.WorldPosition
local direction = target.Character.HumanoidRootPart.Position - origin
audioPlayer.AssetId = Gun.ShootSound.SoundId
Gun.ShootSound:Play()
bulletTemplate:SetAttribute("Damage", Gun:GetAttribute("Damage"))
caster:Fire(origin, direction.Unit * 100, 400, castBehavior, Gun)
else
local animation = animationsFolder:FindFirstChild(Gun.Name .. "Reload")
-- audioPlayer.AssetId = game.ReplicatedStorage.Sounds.GunEmpty.SoundId
--audioPlayer:Play()
Gun:SetAttribute("Reloading", true)
AI.Head.HeadUI.TextLabel.Visible = true
AI.Head.HeadUI.TextLabel.Text = "Reloading..."
if animation then
local animationTrack = AI.Humanoid.Animator:LoadAnimation(animation)
animationTrack:Play()
end
-- audioPlayer.AssetId = game.ReplicatedStorage.Sounds.AIReloading.Reload.SoundId
-- audioPlayer:Play()
task.wait(reloadTime or 2)
local maxMagSize = Gun:GetAttribute("MagSize")
local ammoToFill = maxMagSize - inMag
if bulletsLeft > 0 then
Gun:SetAttribute("Reloading", false)
if bulletsLeft >= ammoToFill then
Gun:SetAttribute("InMag", maxMagSize)
Gun:SetAttribute("BulletsLeft", bulletsLeft - ammoToFill)
else
Gun:SetAttribute("InMag", bulletsLeft)
Gun:SetAttribute("BulletsLeft", 0)
end
AI.Head.HeadUI.TextLabel.Visible = false
-- AI.Head.HeadUI.TextLabel.Text = "Reloading..."
-- module.Shoot(target, Gun, AI)
end
end
end
return module
Line of sight:
local RaycastModule = {}
function RaycastModule.lineOfSight(ai, targetChar)
local raycastParams = RaycastParams.new()
raycastParams.FilterDescendantsInstances = {ai, game.Workspace.Important, game.Workspace["Outside/Inside"],game.Workspace.MainDoor,game.Workspace.MainDoors}
raycastParams.FilterType = Enum.RaycastFilterType.Exclude
raycastParams.IgnoreWater = true
local targetParts = {
targetChar:WaitForChild("Head"),
targetChar:WaitForChild("HumanoidRootPart"),
targetChar:WaitForChild("LeftHand"),
targetChar:WaitForChild("RightHand"),
targetChar:WaitForChild("LeftFoot"),
targetChar:WaitForChild("RightFoot")
}
for _, part in ipairs(targetParts) do
if part and part:IsDescendantOf(targetChar) then
local direction = (part.Position - ai.Head.Position).unit
local ray = workspace:Raycast(ai.Head.Position, direction * 100, raycastParams)
if ray and ray.Instance then
if ray.Instance:IsDescendantOf(targetChar) then
return true
else
end
end
end
end
return false
end
return RaycastModule
Help is much appreciated