i’m working on a game where you run away from hostile npcs that explode when they get close enough to a player. it was all going well until i tried to implement npc respawns.
when i connect the .Died event to the respawn function, the npc never respawns. i’ve even added print statements, but the one that is supposed to print “npc is no longer alive” before promptly respawning the npc just never prints for some reason.
local PathfindingService = game:GetService("PathfindingService")
local Players = game:GetService("Players")
local hostileNpc = script.Parent
local personoid = hostileNpc:FindFirstChildWhichIsA("Humanoid")
local hitbox = hostileNpc.Head -- yeah
local npcRoot = hostileNpc.HumanoidRootPart
local noisemaker = hostileNpc.noisemaker -- invisible part welded to the HumanoidRootPart
local npcClone = hostileNpc:Clone()
local npcFolder = workspace:WaitForChild("current_npcs")
local waypoints
local nextWaypointIndex
local reachedConnection
local blockedConnection
local lastJumpTime = 0
local isExploding = false -- flag to ensure 'explode' only runs once
npcRoot:SetNetworkOwner(nil)
-- pathfinding constants
local AGENT_CAN_JUMP = true
local AGENT_HEIGHT = hitbox.Size.Y / 2
local AGENT_RADIUS = hitbox.Size.X / 2
local PATH_COSTS = {
roller = 0,
Water = 100,
DangerZone = math.huge
}
-- miscellaneous constants
local DESPAWN_TIME = 5
local EXPLOSION_BLAST_PRESSURE = 1500
local EXPLOSION_BLAST_RADIUS = 25
local EXPLOSION_DISTANCE = 25
local EXPLOSION_TIME = 15
local JUMP_INTERVAL = 2
local JUMP_THRESHOLD = 55 -- distance within which npcs start jumping randomly
local NO_PATH_THRESHOLD = 30 -- stop pathing and use Humanoid:MoveTo()
local RESPAWNS_ENABLED = true
local RESPAWN_TIME = 7
local TARGET_CHECK_INTERVAL = 0.5
local TARGET_SEARCH_RADIUS = 1150
local WAYPOINT_INTERVAL = 5
local path = PathfindingService:CreatePath({
AgentHeight = AGENT_HEIGHT,
AgentRadius = AGENT_RADIUS,
AgentCanJump = AGENT_CAN_JUMP,
Costs = PATH_COSTS
})
-- explosion & target finding
function explode()
if isExploding then return end
isExploding = true
print(`starting {hostileNpc} explosion timer`)
task.delay(EXPLOSION_TIME, function()
print(`{hostileNpc} has exploded!`)
noisemaker.Parent = workspace
noisemaker.Anchored = true
local explosion = Instance.new("Explosion")
explosion.Position = npcRoot.Position
explosion.Parent = workspace.Terrain
explosion.Visible = true
explosion.Name = `{hostileNpc} explosion!`
explosion.BlastPressure = EXPLOSION_BLAST_PRESSURE
explosion.BlastRadius = EXPLOSION_BLAST_RADIUS
hitbox:ApplyImpulse(Vector3.new(0, 88, 0))
personoid:ChangeState(Enum.HumanoidStateType.Dead)
task.wait(DESPAWN_TIME)
hostileNpc:Destroy()
end)
end
function findTarget()
local nearestPlayer = nil
local nearestDistance = TARGET_SEARCH_RADIUS
for _, player in pairs(Players:GetPlayers()) do
if player.Character and player.Character:FindFirstChild("HumanoidRootPart") then
local distance = (npcRoot.Position - player.Character.HumanoidRootPart.Position).Magnitude
if distance < nearestDistance then
nearestDistance = distance
nearestPlayer = player
end
end
end
return nearestPlayer
end
-- pathfinding stuff
function handleBlockage(blockedWayPointIndex)
if blockedWayPointIndex >= nextWaypointIndex then
if blockedConnection then
blockedConnection:Disconnect()
blockedConnection = nil
end
local target = findTarget()
if target and target.Character then
local targetRoot = target.Character:FindFirstChild("HumanoidRootPart")
if targetRoot then
moveTo(targetRoot.Position)
end
end
end
end
function handleJump()
if tick() - lastJumpTime > JUMP_INTERVAL then
if math.random() < 0.5 then -- 50% chance to jump
personoid:ChangeState(Enum.HumanoidStateType.Jumping)
end
lastJumpTime = tick()
end
end
function handleWaypoint(reached)
if reached and nextWaypointIndex < #waypoints then
nextWaypointIndex = math.min(nextWaypointIndex + WAYPOINT_INTERVAL, #waypoints)
personoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
if blockedConnection then
blockedConnection:Disconnect()
blockedConnection = nil
end
end
end
function moveTo(destination) -- main pathing function
local distance = (npcRoot.Position - destination).Magnitude
if distance <= NO_PATH_THRESHOLD then
personoid:MoveTo(destination)
return
end
local success, failure = pcall(function()
path:ComputeAsync(npcRoot.Position, destination)
end)
if success and path.Status == Enum.PathStatus.Success then
waypoints = path:GetWaypoints()
if blockedConnection then
blockedConnection:Disconnect()
end
blockedConnection = path.Blocked:Connect(handleBlockage)
if not reachedConnection then
reachedConnection = personoid.MoveToFinished:Connect(handleWaypoint)
end
nextWaypointIndex = 2 -- start at the second waypoint if it exists
personoid:MoveTo(waypoints[nextWaypointIndex].Position)
else
warn(`{hostileNpc} failed to create a path: {failure}`)
end
end
-- handle npc death
function respawnNPC()
if not RESPAWNS_ENABLED then return end
print(`respawn function called`) -- this prints
task.delay(RESPAWN_TIME, function()
print(`{hostileNpc} is no longer alive`) -- this never prints
npcClone.Parent = npcFolder
npcClone:MakeJoints()
end)
end
-- tie everything together
function mainLoop()
local target = findTarget()
if target and target.Character then
local targetRoot = target.Character:FindFirstChild("HumanoidRootPart")
if targetRoot then
local distance = (npcRoot.Position - targetRoot.Position).Magnitude
if distance <= JUMP_THRESHOLD then
handleJump()
end
if distance <= EXPLOSION_DISTANCE then
explode()
end
moveTo(targetRoot.Position)
end
end
end
personoid.Died:Connect(respawnNPC) -- never respawns the npc
while task.wait(TARGET_CHECK_INTERVAL) do
mainLoop()
end