i want to make a reliable enemy npc script
the issue is that sometimes all npcs are frozen, everything else on server works, only npcs lag
raising their update time, from 0.5 to 1, anything higher than that makes the enemy appear a bit clueless
each enemy contains copy of this script inside them, and theres around 20 enemies at a time
-- [SERVICES] --
local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- [REQUIRING MODULE SCRIPTS]
local Settings = require(script.Parent:WaitForChild("Settings"))
local NPC_GetPathModule = require(script:WaitForChild("GetPath"))
local NPC_GetNearestPlayerModule = require(script:WaitForChild("GetNearestPlayer"))
-- [NPC] --
local NPC = script.Parent
local Humanoid = NPC:WaitForChild("Humanoid")
local HRP = NPC:WaitForChild("HumanoidRootPart")
HRP.Anchored = false
NPC.PrimaryPart:SetNetworkOwner(nil)
-- [CONSTANTS] --
local KEEP_BACK = Settings.KeepBackDistance or 5
local AVOID_RADIUS = 5 -- Minimum distance from other NPCs
local MAX_VERTICAL_JUMP = 6 -- Max jump height to avoid jumping on player’s head
-- [TABLES] --
local Waypoints
local index
-- [EVENT CONNECTIONS] --
local BlockedConnection
local Connection
local ReachConnection
local blade = nil
if NPC:WaitForChild("Right Arm"):FindFirstChildWhichIsA("Model") then
blade = NPC:WaitForChild("Right Arm"):FindFirstChildWhichIsA("Model"):WaitForChild("Blade")
else
blade = NPC:WaitForChild("Right Arm"):FindFirstChildWhichIsA("MeshPart")
end
wait(1)
-- [ANIMATIONS] --
if Settings.idleAnim ~= 0 then
local animTrack = Settings.idleAnim
local g = Instance.new("Animation")
g.AnimationId = "rbxassetid://" .. animTrack
local b = Humanoid:WaitForChild("Animator"):LoadAnimation(g):Play(0.1)
end
local attackCol = false
local combo = 1
local animations = Settings.animations
local stunned = false
local pdown = false
local animTrack2 = 91426890984193
local ks = Instance.new("Animation")
ks.AnimationId = "rbxassetid://" .. animTrack2
local attacking = false
local js = Humanoid.Animator:LoadAnimation(ks)
local function Destroy(item, length)
task.spawn(function()
wait(length)
item:Destroy()
end)
end
local function SlashSound()
local sound = nil
local rand = math.random(1,#ReplicatedStorage.WeaponSounds[Settings.SoundFolder]:GetChildren())
sound = ReplicatedStorage.WeaponSounds[Settings.SoundFolder][string.format("s%d", rand)]:Clone()
sound.Parent = NPC.PrimaryPart
sound:Play()
Destroy(sound, sound.TimeLength)
end
local function HitSound()
local sound = nil
local rand = math.random(1,#ReplicatedStorage[("Hit"..Settings.Type)]:GetChildren())
sound = ReplicatedStorage[("Hit"..Settings.Type)][string.format("s%d", rand)]:Clone()
sound.Parent = NPC.PrimaryPart
sound:Play()
Destroy(sound, sound.TimeLength)
end
local function parryEF(pos)
local at = Instance.new("Attachment")
local ef = ReplicatedStorage:WaitForChild("SwordEffects"):WaitForChild("ParryEF"):GetChildren()
for i,v in ef do
v:Clone().Parent = at
end
at.Parent = workspace:WaitForChild("wow")
at.WorldPosition = pos
game:GetService("Debris"):AddItem(at, 0.05)
end
-- [AVOIDANCE FUNCTION]
local function GetAvoidanceOffset()
local offset = Vector3.zero
local npcs = NPC.Parent:GetChildren()
for _, other in pairs(npcs) do
if other ~= NPC and other:FindFirstChild("HumanoidRootPart") then
local otherHRP = other.HumanoidRootPart
local dist = (HRP.Position - otherHRP.Position).Magnitude
if dist < AVOID_RADIUS then
local pushDir = (HRP.Position - otherHRP.Position).Unit
offset += pushDir * (AVOID_RADIUS - dist)
end
end
end
return offset
end
-- [MOVEMENT FUNCTION]
function WalkTo()
task.wait()
local target = NPC_GetNearestPlayerModule.GetNearestPlayer()
if target == nil and script.Raider.Value == true then
target = workspace:WaitForChild("Target").PrimaryPart
end
if target then
Humanoid.WalkSpeed = stunned and (Settings.RunSpeed - 11) or Settings.RunSpeed
local Distance = (HRP.Position - target.Position).Magnitude
if target.Parent.Name == "Target" then
Distance = math.clamp(Distance, 1, 90)
end
local path = NPC_GetPathModule.GetPath(target)
if Distance <= KEEP_BACK then
Humanoid.WalkSpeed = 0
end
if path.Status == Enum.PathStatus.Success then
Waypoints = path:GetWaypoints()
index = 2
local function MoveToNextWaypoint()
if index <= #Waypoints then
BlockedConnection = path.Blocked:Connect(function(blockedIndex)
if blockedIndex >= index then
BlockedConnection:Disconnect()
BlockedConnection = nil
MoveToNextWaypoint()
end
end)
if Distance <= Settings.MaxDistanceToKill and not attackCol and not stunned then
if target.Parent:FindFirstChild("Attacking").Value == true and not pdown then
js:Play(0.1)
Humanoid.Parent.Parry.Value = true
pdown = true
task.spawn(function()
task.wait(1)
pdown = false
end)
wait(0.5)
Humanoid.Parent.Parry.Value = false
js:Stop()
attacking = false
wait(Settings.HitCooldown)
attackCol = false
BlockedConnection:Disconnect()
BlockedConnection = nil
return
end
attacking = true
local animTrack = animations[combo]
local s = Instance.new("Animation")
s.AnimationId = "rbxassetid://" .. animTrack
local a = Humanoid:WaitForChild("Animator"):LoadAnimation(s)
local hitConnection = nil
attackCol = true
a:Play(0.1)
task.spawn(function()
wait(Settings.BeforeSlashSound)
SlashSound()
wait(Settings.TimeToParry)
local cache = {}
hitConnection = blade.Touched:Connect(function(part)
if not attacking or cache[part] then return end
cache[part] = true
if part.Parent and part.Parent:FindFirstChild("Humanoid") then
local humanoid = part.Parent.Humanoid
local parry = part.Parent:FindFirstChild("Parry")
if parry and parry.Value == true then
stunned = true
HRP.AssemblyLinearVelocity = -HRP.CFrame.LookVector * 70
local marker = part.Parent:FindFirstChild("blahlbvld32")
if marker then
local katana = marker:FindFirstChild("Katana")
if katana then
parryEF(katana.Position)
end
end
attacking = false
wait(0.05)
a:AdjustSpeed(-0.5)
combo = 1
script.SParry:Play()
wait(1)
attackCol = false
stunned = false
else
if not part.Parent:FindFirstChild("Evil") then
humanoid:TakeDamage(Settings.Damage / 7)
HitSound()
end
combo = (combo % #animations) + 1
end
end
end)
end)
a.Stopped:Connect(function()
attacking = false
if hitConnection then hitConnection:Disconnect() end
wait(Settings.HitCooldown)
attackCol = false
end)
BlockedConnection:Disconnect()
BlockedConnection = nil
end
if Waypoints and Waypoints[index+1] and Waypoints[index+1].Action == Enum.PathWaypointAction.Jump then
local verticalDelta = Waypoints[index+1].Position.Y - HRP.Position.Y
if verticalDelta < MAX_VERTICAL_JUMP then
Humanoid.Jump = true
local avoidanceOffset = GetAvoidanceOffset()
Humanoid:MoveTo(Waypoints[index+2].Position + avoidanceOffset)
else
index += 2
return
end
elseif Waypoints then
local avoidanceOffset = GetAvoidanceOffset()
Humanoid:MoveTo(Waypoints[index].Position + avoidanceOffset)
end
index += 1
end
end
MoveToNextWaypoint()
end
end
end
local avr = false
-- [MAIN LOOP] --
Connection = RunService.Heartbeat:Connect(function()
wait(1)
if Humanoid.Health > 0 then
WalkTo()
else
if avr then return end
Connection:Disconnect()
script.Parent.Parent = workspace.Corpses
if script.Parent:WaitForChild("Right Arm"):FindFirstChildWhichIsA("Model") then
for _, v in pairs(script.Parent["Right Arm"]:FindFirstChildWhichIsA("Model"):GetChildren()) do
v.CanCollide = true
v.Massless = false
end
end
script.Parent["Right Arm"]:WaitForChild("WeldConstraint"):Destroy()
game:GetService("Debris"):AddItem(script.Parent, 10)
if math.random(1, 4) == 1 then
local book = ReplicatedStorage.items:FindFirstChild("techbook"):Clone()
local volume
local roll = math.random(1, 100)
if roll <= 30 then
volume = math.random(1, 3)
elseif roll <= 60 then
volume = math.random(4, 6)
elseif roll <= 85 then
volume = math.random(7, 9)
else
volume = math.random(10, 11)
end
book.Name = "Book of Technology Vol " .. volume
book:PivotTo(script.Parent.Head.CFrame)
book.Handle.Name = "pickupPart"
book.Parent = workspace.Loot
end
script:Destroy()
avr = true
end
end)
any help appreciated
(yes, some parts from ChatGPT)