Humanoid.Died firing multiple times

i’ve got a function that is supposed to handle pathfinding and make tagged npcs spawn an item after they die. this seems fairly simple. however, the implementation presents an unexpected challenge. when an NPC dies, the event will inexplicably trigger up to 1,000 times (per NPC) and i have no idea why.

local function setup(obj)
		local entity_root = obj.PrimaryPart
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)
			
			personoid.Died:Connect(function()
				print(`{obj} is DEAD`)
				path:Destroy() -- destroy the path, as we no longer need it
			end)
		end
	end
1 Like

You might wanna show how you call the function, like if you are doing it in a for loop. But there’s an easy fix aswell, you can just do :Once() which will only fire once and never again if that’s what you want. (I assume you don’t want it to fire only one time though). So could you show how the function (setup) is being called?

1 Like

here’s the entirety of the script

-- handle pathfinding for every tagged NPC
local services = {
	CollectionService = game:GetService("CollectionService");
	ServerStorage = game:GetService("ServerStorage");
	Players = game:GetService("Players");
	RunService = game:GetService("RunService");
}

local path_module = require(services.ServerStorage.SimplePath)
local enemy_tag = "PATHING_ENTITY"
local target, plr_root, died_connection

local function find_nearest(position)
	local nearest_plr
	local shortest_dist = math.huge

	for _, player in ipairs(services.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChildWhichIsA("Humanoid").Health > 0 then
			local dist = (character.PrimaryPart.Position - position).Magnitude

			if dist < shortest_dist then
				shortest_dist = dist
				nearest_plr = player
			end
		end
	end

	return nearest_plr
end

function run_path()
	local function setup(obj)
		local entity_root = obj.PrimaryPart
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)
			
			died_connection = personoid.Died:Connect(function()
				print(`{obj} is DEAD`)
				path:Destroy()
				died_connection:Disconnect() -- added this but that didn't help
			end)
		end
	end
	
	local enemies = services.CollectionService:GetTagged(enemy_tag)
	
	for _, hostile_entity in ipairs(enemies) do
		if not hostile_entity:IsA("Model") then continue end
		setup(hostile_entity)
	end
	
	services.CollectionService:GetInstanceAddedSignal(enemy_tag):Connect(function(obj)
		setup(obj)
	end)
end

local timer = 0
local update_interval = 0.25 -- update every 0.25 seconds

local function update_path(dt)
	timer = timer + dt
	if timer >= update_interval then
		timer = timer - update_interval
		run_path()
	end
end

task.delay(4, function()
	services.RunService.Heartbeat:Connect(update_path)
end)
1 Like

You’re creating a new .Died function every time setup() is run. Disconnect it

1 Like
local services = {
	CollectionService = game:GetService("CollectionService");
	ServerStorage = game:GetService("ServerStorage");
	Players = game:GetService("Players");
	RunService = game:GetService("RunService");
}

local path_module = require(services.ServerStorage.SimplePath)
local enemy_tag = "PATHING_ENTITY"
local target, plr_root, died_connection

local function find_nearest(position)
	local nearest_plr
	local shortest_dist = math.huge

	for _, player in ipairs(services.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChildWhichIsA("Humanoid").Health > 0 then
			local dist = (character.PrimaryPart.Position - position).Magnitude

			if dist < shortest_dist then
				shortest_dist = dist
				nearest_plr = player
			end
		end
	end

	return nearest_plr
end

function run_path()
	local function setup(obj)
		if obj:FindFirstChild("prepped") then return end
		local prepped = Instance.new("BoolValue")
		prepped.Name = "prepped"
		prepped.Value = true
		prepped.Parent = obj
		local entity_root = obj.PrimaryPart
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)

			died_connection = personoid.Died:Connect(function()
				print(`{obj} is DEAD`)
				path:Destroy()
				died_connection:Disconnect() -- added this but that didn't help
			end)
		end
	end

	local enemies = services.CollectionService:GetTagged(enemy_tag)

	for _, hostile_entity in ipairs(enemies) do
		if not hostile_entity:IsA("Model") then continue end
		setup(hostile_entity)
	end

	services.CollectionService:GetInstanceAddedSignal(enemy_tag):Connect(function(obj)
		setup(obj)
	end)
end

local timer = 0
local update_interval = 0.25 -- update every 0.25 seconds

local function update_path(dt)
	timer = timer + dt
	if timer >= update_interval then
		timer = timer - update_interval
		run_path()
	end
end

task.delay(4, function()
	services.RunService.Heartbeat:Connect(update_path)
end)
died_connection = personoid.Died:Connect(function()
	print(`{obj} is DEAD`)
	path:Destroy()
	died_connection:Disconnect() -- added this but that didn't help
end)

It’s not gonna disconnect unless it’s run in this case
You gotta disconnect it each time you run setup()

doing that still does not help

function run_path()
	local function setup(obj)
		local entity_root = obj.PrimaryPart
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)

			
		end
	end

	local enemies = services.CollectionService:GetTagged(enemy_tag)

	for _, hostile_entity in ipairs(enemies) do
		if not hostile_entity:IsA("Model") then continue end
		setup(hostile_entity)
	end

	services.CollectionService:GetInstanceAddedSignal(enemy_tag):Connect(function(obj)
		setup(obj)
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		
		died_connection = personoid.Died:Connect(function()
			print(`{obj} is DEAD`)
			died_connection:Disconnect()
		end)
	end)
end

Did this proposal not work @Dullcimer?

unfortunately not, the npcs would only start moving half the time they were spawned in.

local services = {
	CollectionService = game:GetService("CollectionService");
	ServerStorage = game:GetService("ServerStorage");
	Players = game:GetService("Players");
	RunService = game:GetService("RunService");
}

local path_module = require(services.ServerStorage.SimplePath)
local enemy_tag = "PATHING_ENTITY"
local target, plr_root, died_connection

local function find_nearest(position)
	local nearest_plr
	local shortest_dist = math.huge

	for _, player in ipairs(services.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChildWhichIsA("Humanoid").Health > 0 then
			local dist = (character.PrimaryPart.Position - position).Magnitude

			if dist < shortest_dist then
				shortest_dist = dist
				nearest_plr = player
			end
		end
	end

	return nearest_plr
end

function run_path()
	local function setup(obj)
		local entity_root = obj.PrimaryPart
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)
			
			if obj:FindFirstChild("prepped") then return end
			local prepped = Instance.new("BoolValue")
			prepped.Name = "prepped"
			prepped.Value = true
			prepped.Parent = obj
			died_connection = personoid.Died:Connect(function()
				print(`{obj} is DEAD`)
				path:Destroy()
				died_connection:Disconnect() -- added this but that didn't help
			end)
		end
	end

	local enemies = services.CollectionService:GetTagged(enemy_tag)

	for _, hostile_entity in ipairs(enemies) do
		if not hostile_entity:IsA("Model") then continue end
		setup(hostile_entity)
	end

	services.CollectionService:GetInstanceAddedSignal(enemy_tag):Connect(function(obj)
		setup(obj)
	end)
end

local timer = 0
local update_interval = 0.25 -- update every 0.25 seconds

local function update_path(dt)
	timer = timer + dt
	if timer >= update_interval then
		timer = timer - update_interval
		run_path()
	end
end

task.delay(4, function()
	services.RunService.Heartbeat:Connect(update_path)
end)

dude literally just do this if disconnecting the signal doesn’t help


local Died = false

died_connection = personoid.Died:Connect(function()
    if Died then 
       return
    end

    Died = true
	print(`{obj} is DEAD`)
	path:Destroy()
	died_connection:Disconnect() -- added this but that didn't help
end)

This wouldn’t work. You’re literally setting the variable to false before the connection—if the function runs again the connection will simply connect again. This is why I used an instance which exists in the DataModel and can be tracked.

Is run_path() being called twice in your script? As others have mentioned the setup function may be running more than once on your NPCs resulting in the .Died signal firing multiple times. I suggest putting a print statement on where you suspect it may be running more than once and try working from there.

run_path() is being called every 0.25 seconds so that it would update when an npc is spawned in.

I’ve looked at your script. The best way to do this is to put your death event handler in the initialization section of your script so it gets ran once. Also, as others have suggested, use :Once() instead of :Connect(). The reason behind this is at instance of the NPC can only die once, so once the Died event fires, it’s not needed anymore. So when the NPC dies, you spawn in the item at the location where the NPC met their end.

As a suggestion, if you’re going for something like an adventure type game or a MMORPG like World of Warcraft, you can do what they do: Start a particle emitter on the corpse. So when a player clicks or taps on that corpse, a window opens showing the item or items on that corpse. The NPC would have a loot table containing all items that are possible, and a few are randomly chosen from that list. The items are represented as ID numbers that are indices into the main loot table (which can be huge) which contains the definitions of all items in the game. When the item is transferred into your inventory, the ID number is added to a list for the player. There’s more so if you’re interested, let me know.

local services = {
	CollectionService = game:GetService("CollectionService");
	ServerStorage = game:GetService("ServerStorage");
	Players = game:GetService("Players");
	RunService = game:GetService("RunService");
}

local path_module = require(services.ServerStorage.SimplePath)
local enemy_tag = "PATHING_ENTITY"
local target, plr_root, died_connection

local function find_nearest(position)
	local nearest_plr
	local shortest_dist = math.huge

	for _, player in ipairs(services.Players:GetPlayers()) do
		local character = player.Character
		if character and character:FindFirstChildWhichIsA("Humanoid").Health > 0 then
			local dist = (character.PrimaryPart.Position - position).Magnitude

			if dist < shortest_dist then
				shortest_dist = dist
				nearest_plr = player
			end
		end
	end

	return nearest_plr
end

function run_path()
	local function setup(obj)
		local entity_root = obj.PrimaryPart
		local personoid = obj:FindFirstChildWhichIsA("Humanoid")
		local path = path_module.new(obj)

		target = find_nearest(entity_root.Position)
		entity_root:SetNetworkOwner(nil)

		if target and target.Character then
			plr_root = target.Character.PrimaryPart
			path:Run(plr_root.Position)
			end)
		 else
			path:Destroy()
		end
	end

	local enemies = services.CollectionService:GetTagged(enemy_tag)

	for _, hostile_entity in ipairs(enemies) do
		if not hostile_entity:IsA("Model") then continue end
		setup(hostile_entity)
	end

	services.CollectionService:GetInstanceAddedSignal(enemy_tag):Connect(function(obj)
		setup(obj)
	end)
end

local timer = 0
local update_interval = 0.25 -- update every 0.25 seconds

local function update_path(dt)
	timer = timer + dt
	if timer >= update_interval then
		timer = timer - update_interval
		run_path()
	end
end

task.delay(4, function()
	services.RunService.Heartbeat:Connect(update_path)
end)