How to use CollectionService to find out who damaged NPC

first of all, english is not my primary language

Hi, i want to detect all players involved in the death of an NPC within the last 10 seconds (I think the quickest way is through the “CollectionService”(The intention is to be a big game, so the faster the detection happens, the better it will be))

I watched the “kill brick” and “Collection Service Door” tutorial a few times,and I read the CollectionService topic, but I can’t understand how it applies to finding a player who damaged the NPC.

I tried to use an ObjectValue as a “creator” tag, but when I do that I can’t find the player:
npc error

local Debris = game:GetService("Debris")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local Players = game:GetService("Players")

Humanoid.ChildAdded:Connect(function (Child)
	Humanoid.Died:Connect(function ()
		if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
			local player = Child.Value
			print(Child.Value)
			print(Players:FindFirstChild(player, true).Status.XP.Value)
		end
	end)
    if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
        Debris:AddItem(Child, 10)
    end
end)

It detects my name, but I can’t find the “Status” setting in: game.Players.hungria.Status (this is the correct directory)

this is where the tag comes from:

function attacking(player)
	local Character = player.Character
	local Play1 = Character.Humanoid:LoadAnimation(animation1)
	local Play2 = Character.Humanoid:LoadAnimation(animation2)
	--attack
	local Sword = Character:FindFirstChild(Mode.RightHandItem(player))
	local Blade = Sword.Sword	
		--Hitbox
	local NewHitbox = RaycastHitboxModule:Initialize(Blade, {Character} )
	RaycastHitboxModule:DebugMode(true)
	NewHitbox.OnHit:Connect(function(hit, humanoid)
			local creator = Instance.new("ObjectValue", humanoid)
			creator.Name = "creator"
			creator.Value = player
		local Damage = Mode.Strength(player) * Blade.Damage.Value
	  --  print(hit.Parent.Name)
	    humanoid:TakeDamage(Damage)
	end)
 	Character.Humanoid.WalkSpeed = 0

If someone can help me, I’m about 8 hours into it and I’ve tried more things than I can remember
Sorry if the English is bad, done mostly by the translator

4 Likes

You could create a table and update that table with new entries each time the NPC is damaged, and include the time at which the damage happened using tick(). When the NPC dies, iterate over that table and compare each timestamp to the current time to determine if it happened within the last 10 seconds.

It doesn’t have to be a table, you could create a new NumberValue named to the UserId of the player who attacked with its value set to the time of attack each time the NPC is damaged, but creating a table is a far simpler solution.

1 Like

Yes, it could, but my idea is to make the game processing be used as little as possible, since there will be many variables being triggered at certain times. asking the server to store users, the time it happened and then see which is less than 10 seconds since the last entry, does not seem very light, especially if there are 20 or more players killing enemies at the same time, and each player can kill more than one enemy at a time … so I want to understand how to use CollectionService for this, I think it will be much lighter

It is extremely light. There will be almost no performance overhead from storing a few bytes of data in a table and iterating over it; iterating over a table with only a few entries will literally take less than milliseconds. I just tested it on a table of over 40 entries. Even if we’re talking thousands of entries it has no performance overhead.

Even using CollectionService, you’d still have to iterate over a table of items with the tag you’re looking for.

2 Likes

The reason why you don’t get a Player Object in your first script you posted is that you are trying to do FindFirstChild with an Instance and not a string.

local Debris = game:GetService("Debris")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local Players = game:GetService("Players")

Humanoid.ChildAdded:Connect(function (Child)
	Humanoid.Died:Connect(function ()
		if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
			local player = Child.Value
			print(Child.Value) -- also player
			print(player.Status.XP.Value)
		end
	end)
    if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
        Debris:AddItem(Child, 10)
    end
end)

What I changed is that instead of using FindFirstChild I get the player directly with the ObjectValue. Theoretically this should fix your problem.

2 Likes

oh, now it’s explained why it wasn’t working, Ty. what @vastqud suggested using the UserId also worked, but like I said, this path I used must be heavy

1 Like

I do recommend checking in your script that creates the tags to destroy the previous tag from the player if there is already one to make sure there is always one, which should be the most recent one.

1 Like

something like:

if humanoid.creator == nil then
		local creator = Instance.new("ObjectValue", humanoid)
		creator.Name = "creator"
		creator.Value = player
	end

Edit: It doesn’t work.

NewHitbox.OnHit:Connect(function(hit, humanoid)
	for i,v in pairs(humanoid:GetChildren()) do
		if v:IsA("ObjectValue") and v.Name == "creator" and v.Value == player then
			v:Destroy()
		end
	end
	local creator = Instance.new("ObjectValue")
	creator.Parent = humanoid
	creator.Name = "creator"
	creator.Value = player
	local Damage = Mode.Strength(player) * Blade.Damage.Value
	humanoid:TakeDamage(Damage)
end)

Somewhat like this, iterate through all Instances, check if they are a ObjectValue, and if yes check if they match with player. There may be better solutions than this, but this should be sufficient for now.

1 Like

using “For” and “in pairs”, or any other variation of it, is something difficult for me to understand, I don’t know why, I see it quite often, but I always forget how it works

is still printing 10x the commands:
10times

for [...] do is called a loop. It executes the code X times inside it and there are multiple variants of it, it either allows you to count up/down from 0 to 10 (or reversed if counting down) or go through all values in a table.
pairs/ipairs comes into the game when you want to iterate through a table (but there are other methods for it as well).


There is only one Instance in there, but you are not unbinding the previous connection, that’s why it gets called X times.
Try changing the code of the first script you sent to the following:

local Debris = game:GetService("Debris")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local Players = game:GetService("Players")

Humanoid.ChildAdded:Connect(function (Child)
    if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
        Debris:AddItem(Child, 10)
    end
end)

Humanoid.Died:Connect(function()
	for Index, Child in pairs(Humanoid:GetChildren()) do
		if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
			local player = Child.Value
			print(Child.Value)
			print(Players:FindFirstChild(player, true).Status.XP.Value)
		end
	end
end)

What I did is move the Humanoid.Died Event outside of the ChildAdded Event, making it that it gets only called once, despite on how many Childs got added.

2 Likes

i changed, still printing 10x :confused:

Let me just change a bit of the code:

NewHitbox.OnHit:Connect(function(hit, humanoid)
	if not humanoid:FindFirstChild(player.Name) then
		local creator = Instance.new("ObjectValue")
		creator.Parent = humanoid
		creator.Name = "creator"
		creator.Value = player
	end
	local Damage = Mode.Strength(player) * Blade.Damage.Value
	humanoid:TakeDamage(Damage)
end)
local Debris = game:GetService("Debris")
local Humanoid = script.Parent:WaitForChild("Humanoid")
local Players = game:GetService("Players")

Humanoid.ChildAdded:Connect(function (Child)
    if Child:IsA("ObjectValue") and Child.Name:lower() == "creator" then
        Debris:AddItem(Child, 10)
    end
end)

Humanoid.Died:Connect(function()
	for Index, Child in pairs(Humanoid:GetChildren()) do
		if Child:IsA("ObjectValue")  then
			local player = Child.Value
			print(Child.Value)
			print(Players:FindFirstChild(player, true).Status.XP.Value)
		end
	end
end)

I am unsure why the other attempt failed, but this one should work I hope.
Primary things I changed:

  • Instead of naming the ObjectValue to creator, I name it to the Players name and then check if it is already created, and if it isn’t, we create it.
1 Like

10times nope :frowning:

My bad, I just noticed I forgot to change "creator" to player.Name.

NewHitbox.OnHit:Connect(function(hit, humanoid)
	if not humanoid:FindFirstChild(player.Name) then
		local creator = Instance.new("ObjectValue")
		creator.Parent = humanoid
		creator.Name = player.Name
		creator.Value = player
	end
	local Damage = Mode.Strength(player) * Blade.Damage.Value
	humanoid:TakeDamage(Damage)
end)

Sorry for wasting your time like this on that typo.

2 Likes

I am grateful to be helping me :slight_smile:

not10times

but now it didn’t show XP, the error only happens in the third Dummy in the first 2 there is no error, but neither “120” (current XP that I have)

edit: my name didn’t appear in the first 2 Dummy either

My bad, I goofed up once more and used the old code and not the new one in Humanoid.Died.

Humanoid.Died:Connect(function()
	for Index, Child in pairs(Humanoid:GetChildren()) do
		if Child:IsA("ObjectValue")  then
			local player = Child.Value
			print(Child.Value)
			print(player.Status.XP.Value)
		end
	end
end)
2 Likes

the other 2 again, nothing came up, if i kill in decreasing order, or increasing the only one that appears “hungria 120” is when i kill the dummy1

Did you update the scripts in Dummy2 and Dummy3 with the one from Dummy1?

1 Like