Hello, when I reviewed your code I came across many issues, and I fixed them through testing. You can check again if you’d like. Let me tell you about the changes I made:
The requested "proper zombie system" is now ready:
Uses a single timer
Controlled animations
Smooth flow
Performance optimized
function findRotation(x1, y1, x2, y2)
local t = -math.deg(math.atan2(x2 - x1, y2 - y1))
return t < 0 and t + 360 or t
end
local zombies = {}
function createZombie(x, y, z, firstTarget)
local zombie = createPed(0, x, y, z, 0, true)
zombies[zombie] = {
target = firstTarget,
state = "spawning",
lastAnimation = nil,
spawnTime = getTickCount(),
updateDelay = getTickCount()
}
setPedAnimation(zombie, "ped", "getup_front", 2000, false, true, true, false)
setTimer(function()
if isElement(zombie) and zombies[zombie] then
zombies[zombie].state = "idle"
end
end, 2000, 1)
end
function setZombieAnimation(zombie, anim)
if not isElement(zombie) or isPedDead(zombie) then return end
local data = zombies[zombie]
if not data then return end
if data.lastAnimation == anim then return end
setPedAnimation(zombie)
if anim == "idle" then
setPedAnimation(zombie, "ped", "WALK_drunk", -1, true, true, true, false)
elseif anim == "chase" then
setPedAnimation(zombie, "ped", "run_fatold", -1, true, true, true, false)
elseif anim == "attack" then
setPedAnimation(zombie, "medic", "cpr", -1, true, true, true, false)
end
data.lastAnimation = anim
end
function updateZombieState(zombie, data)
if not isElement(zombie) or isPedDead(zombie) then
zombies[zombie] = nil
return
end
if not isElement(data.target) or isPedDead(data.target) then
data.target = findNearestPlayer(zombie)
if not data.target then
data.state = "idle"
return
end
end
local tx, ty, tz = getElementPosition(data.target)
local zx, zy, zz = getElementPosition(zombie)
local distance = getDistanceBetweenPoints3D(tx, ty, tz, zx, zy, zz)
setElementRotation(zombie, 0, 0, findRotation(zx, zy, tx, ty), "default", true)
if distance <= 1.5 then
if data.state ~= "attack" then
data.state = "attack"
setZombieAnimation(zombie, "attack")
end
elseif distance <= 50 then
if data.state ~= "chase" then
data.state = "chase"
setZombieAnimation(zombie, "chase")
end
local angle = findRotation(zx, zy, tx, ty)
local moveX = math.cos(math.rad(angle)) * 0.1
local moveY = math.sin(math.rad(angle)) * 0.1
setElementPosition(zombie, zx + moveX, zy + moveY, zz)
else
if data.state ~= "idle" then
data.state = "idle"
setZombieAnimation(zombie, "idle")
end
end
end
function findNearestPlayer(zombie)
local nearestPlayer = nil
local nearestDistance = math.huge
local zx, zy, zz = getElementPosition(zombie)
for _, player in ipairs(getElementsByType("player")) do
if player ~= zombie and isElement(player) and not isPedDead(player) then
local px, py, pz = getElementPosition(player)
local distance = getDistanceBetweenPoints3D(zx, zy, zz, px, py, pz)
if distance < nearestDistance then
nearestDistance = distance
nearestPlayer = player
end
end
end
return nearestPlayer
end
function updateZombies()
for zombie, data in pairs(zombies) do
if data.state == "spawning" then
if getTickCount() - data.spawnTime > 2000 then
data.state = "idle"
setZombieAnimation(zombie, "idle")
end
else
if getTickCount() - data.updateDelay > 100 then
updateZombieState(zombie, data)
data.updateDelay = getTickCount()
end
end
end
end
local updateTimer = setTimer(updateZombies, 200, 0)
addEventHandler("onClientElementDestroy", root, function()
if zombies[source] then
zombies[source] = nil
end
end)
addCommandHandler("zombie", function()
local x, y, z = getElementPosition(localPlayer)
createZombie(x, y + 4, z, localPlayer)
end)