Well, i did something, relatively bennigers friendly code:
(No OOP or module script, just collection service and custom events)
local ServerScript = game:GetService("ServerScriptService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local upadateTarget = Instance.new("BindableEvent")
upadateTarget.Name = "updateTarget"
upadateTarget.Parent = script
local upadateMove = Instance.new("BindableEvent")
upadateMove.Name = "upadateMove"
upadateMove.Parent = script
local upadateJump = Instance.new("BindableEvent")
upadateJump.Name = "upadateJump"
upadateJump.Parent = script
local connections = {} --every npc store his connections here
local function findNearstPlayer(pos) --pos = ai position
local currentDistance = 2000 -- very big number
local target = nil -- no target
for _, plr in pairs(Players:GetPlayers()) do
local char = plr.Character
if not char then continue end --if player dont have char then continue
if char.Humanoid.Health <= 0 then continue end --same for dead players
local hrp = char.HumanoidRootPart
local newDistance = (hrp.Position - pos).Magnitude
if newDistance < currentDistance then --if this player is close than the last, then set the variables
currentDistance = newDistance
target = hrp
end
end
return target, currentDistance
end
local function setupHumanoid(humanoid,config)
if not humanoid then return end
local model = humanoid.Parent --the npc model
local networkOwner = nil --networkOwnership nil is to give server the handling of the npc
for _, descendant in pairs(model:GetDescendants()) do --set every base part of npc ownership to nil
-- Go through each part of the model
if descendant:IsA("BasePart") then
-- Try to set the network owner
local success, errorReason = descendant:CanSetNetworkOwnership()
if success then
descendant:SetNetworkOwner(networkOwner)
else
-- Sometimes this can fail, so throw an error to prevent
-- ... mixed networkownership in the 'model'
error(errorReason)
end
end
end
for propertyName, value in pairs(config) do --use the config dictionary to set humanoid properties
humanoid[propertyName] = value
end
end
local function moveToTarget(humanoid,target)
if not target then return end
humanoid:MoveTo(target.Position)
end
local function startAI(humanoid)
local target
connections[humanoid] = {}
connections[humanoid][1] = upadateTarget.Event:Connect(function()
target = findNearstPlayer(humanoid.Parent.HumanoidRootPart.Position)
end)
connections[humanoid][2] = upadateMove.Event:Connect(function()
moveToTarget(humanoid,target)
end)
connections[humanoid][3] = upadateJump.Event:Connect(function()
humanoid.Jump = true
end)
humanoid.Died:Connect(function()
CollectionService:RemoveTag(humanoid,"ZOMBIE")
warn("TAG REMOVED")
wait(4)
humanoid.Parent:Destroy()
end)
-- for more optimization, our npc will no be able of climbing, swimming, ect
humanoid:SetStateEnabled(Enum.HumanoidStateType.Flying, false)
humanoid:SetStateEnabled(Enum.HumanoidStateType.Seated, false)
humanoid:SetStateEnabled(Enum.HumanoidStateType.Climbing, false)
humanoid:SetStateEnabled(Enum.HumanoidStateType.Swimming, false)
end
CollectionService:GetInstanceAddedSignal("ZOMBIE"):Connect(function(instance) --instance shuould be a humanoid
setupHumanoid(instance,{["WalkSpeed"] = 10;})
startAI(instance)
end)
CollectionService:GetInstanceRemovedSignal("ZOMBIE"):Connect(function(instance) --npc died :(
if not connections[instance] then return end
for _, connection in pairs(connections[instance]) do
connection:Disconnect()
warn("CONNECTION DISCONNECTED")
end
end)
while true do
upadateTarget:Fire()
wait()
for i = 0, 30 do
upadateMove:Fire()
wait()
end
upadateJump:Fire()
end
What the code do?
-An humanoid is tagged
-SetupHumanoid is called for changing his prioperties (like walkspeed), and making his ownership nil (this is server handled)
-StartAI is called for, well, start the AI, connecting the functions of findNearstPlayer(), moveToTarget() and a jump function (just for fun) to the script global events, and storing them in the table of connections, with the humanoid as a key, becouse when humanoid died, it will remove his tag, and the :GetInstanceRemovedSignal(“ZOMBIE”) event will fire, to clean the mess (destroy parts, disconnect the connections)
Thats for every NPC, after, we start a while loop, firing the event in order to tell all the zombies what to do (that maybe a problem, because we cant tell a single AI to do something, without all making the same at same time) (will be better to split the ai in parts, and dont make them work all in the same time dodging the lag spikes)
And for adding a zombie, just add a script inside the model
wait(2)
local CollectionService = game:GetService("CollectionService")
CollectionService:AddTag(script.Parent.Humanoid,"ZOMBIE")
I will wating for more optimizations, i was thinking in firing the events with an argument, like a number, and is the npc number is not the same, it will ignore it, letting us run the AIs in sections
Will not help making the zombis invisible on the server too? and the client just making them visible?