Opening Egg is granting thousands of pets

I’m trying to make a pet-saving simulator-style inventory system. Previously, spawning in a pet from an egg would just make it spawn at world-spawn and fly over, which it still does, in theory. I hooked up a print function to it and it fired hundreds of times for some reason. It crashes my Studio when testing in single-player, and I’m very confused about what’s going wrong. The printing line is line 32, printing “Pet”, to signal when a new pet has been added to a player’s inventory. Here’s the script with detection:

local function getXAndZPositions(angle)
local x = math.cos(angle) * 10
local z = math.sin(angle) * 10
return x, z
end

game:GetService("RunService").Heartbeat:Connect(function()
for i,v in pairs(game.Players:GetPlayers()) do
	local petTable = {}
	for e,d in pairs(workspace:GetChildren()) do
		if d:GetAttribute("Pet") == true then
			if d.Owner.Value == v then
				petTable[#petTable+1] = d
			end
		end
	end
	for e,d in pairs(petTable) do
		    local angle = e * (math.pi * 2 / #petTable)
		    local x, z = getXAndZPositions(angle)

		local position = (v.Character.PrimaryPart.CFrame * CFrame.new(x + v.Character.Humanoid.MoveDirection.X, 0, z + v.Character.Humanoid.MoveDirection.Z)).p
		local lookAt = v.Character.PrimaryPart.Position

		d.BodyGyro.CFrame = CFrame.new(position, lookAt)
		d.BodyPosition.Position = position
		
		local petModule = require(game.ServerStorage.PetModule)
		
		if #v.Inventory:GetChildren() > 0 then
			for q,w in pairs(v.Inventory:GetChildren()) do
				if w.Name ~= d.Name then
					print("Pet")
					local folder = Instance.new("Folder", v.Inventory)
					folder.Name = w.Name
					local clickM = Instance.new("IntValue", folder)
					clickM.Name = "Click"
					for f,g in pairs(petModule) do
						if g[1] == w.Name then
							clickM.Value = g[3][1]
						end
					end
				end
			end
		else
			local folder = Instance.new("Folder", v.Inventory)
			folder.Name = d.Name
			local clickM = Instance.new("IntValue", folder)
			for f,g in pairs(petModule) do
				if g[1] == d.Name then
					    clickM.Value = g[3][1]
				    end
			    end
		    end
	    end
    end
end)

Two comments before I get to what I think is causing the issue:

  1. Please use more descriptive names than “e,d” and “q,w” when looping through tables. I.e. “pet” for d in the second loop.
  2. You are connecting to heartbeat, which is running dozens of times per second. Is this necessary? You could just connect to your opening egg event and add it to the inventory and have it move then. Also, you are also looping through every child of workspace. Not sure how large your game is, but that’s a performance hit if I’ve seen one. Try looping through a “Pets” folder under workspace or something, one that you store all the pet models in.

Main issue:
To my understanding, you loop through all of the pets in workspace several times a second. For each pet that you find with the player as its owner, you print “Pet”. Do you ever remove the pet? Change its owner? Move it to a section where it will not be looped through? I do not believe so, at least from the code that I’ve seen. So basically, this pet that you just printed for will trigger another print line for every time you run this code (dozens of times per second or more).

Basically, you do nothing to prevent the same pet from triggering the function again.

Possible solutions:
Put the pets in a “AddedToInventory” folder when you add them to the inventory. Don’t loop through the added to inventory folder when checking in your update function.

Instead of running many times per second, which is unnecessary, try connecting to an event or firing a bindable event after opening the egg (if you need a link to the reference page just ask).

Not-necessary, but highly recommended extras:
Don’t loop through workspace, loop through a folder instead.
Change index, value variable names to more descriptive ones for legibility.

Hope this helps!

2 Likes

Alright, thanks! I’ll try those.

Correct me if I’m wrong here but this shouldn’t work.

local petTable = {}
	for e,d in pairs(workspace:GetChildren()) do
		if d:GetAttribute("Pet") == true then
			if d.Owner.Value == v then
				petTable[#petTable+1] = d -- #petTable always will return 0
			end
		end
	end

From my experience, # table only returns the actual number when you use table.insert to add the item. This loop just sets petTable[1] to d

I don’t believe this is correct. #table simply returns the length of the table. In fact, table.insert also uses the length operator #. Lua 5.1 Reference Manual

The method of table[#table+1] = Something works perfectly fine, and even faster than table.insert.

(lua - What's the difference between table.insert(t, i) and t[#t+1] = i? - Stack Overflow)

It seems I misjudged. I guess it works if the indexes are in order. But this never panned out for me:

local tabl={} tabl[1]=3 tabl[3]=5 tabl[6]=7 
print("number:",#tabl) -- prints '1' even though there are clearly 3 numbers in the table

local n=0 
for i,v in pairs(tabl) do
   n+=1 
end 
print("number:",n) -- i have to resort to this for certain tables, like tables I have with a character as an index and a value indicating the damage they took

clueless as to why. My suspicion is the # function works like this

local num=0
while true do
	num+=1
	if not tabl[num] then num-=1 break end
end
return num

Correct, they have to be in order:
“The length of a table t is defined to be any integer index n such that t[n] is not nil and t[n+1] is nil ; moreover, if t[1] is nil , n can be zero. For a regular array, with non-nil values from 1 to a given n , its length is exactly that n , the index of its last value. If the array has “holes” (that is, nil values between other non-nil values), then #t can be any of the indices that directly precedes a nil value (that is, it may consider any such nil value as the end of the array).” (Lua 5.1 Reference Manual)

In your example, a value with an index of 1 exists while a value with an index of 2 does not. Not sure why you do it like this nor need the exact length of the table after iterating through it. In instances where you do not have a numerically increasing table with only integer keys, you have to iterate over the table using pairs() or ipairs() anyways. I tend to store important information in cases like this in both a string key (not integer) and the value, which can be helpful.

Basically, as soon as your table is no longer an array with only sequential integer indexes, you have to iterate over it using pairs() and ipairs(). I would suggest switching to a different format, either to string or more useful keys than integers, or finding a way to only use sequential indexes.