PetInventory Stack System

Hello, I have an animal inventory here and would like the animals not all to be displayed individually but that the same animals are stacked and equipped are displayed as a single template. For example, I have 5 dogs and instead of displaying 5 dogs individually as a template, they are displayed as ONE template with the amount as a label (is part of the template and is called “AmountLabel”)
Also, equipped animals should be displayed as a single template AS LONG AS they are equipped, otherwise they go back to the stack.

Can someone help me please?

local replicatedStorage = game:GetService("ReplicatedStorage")
local WorkSpace = game:GetService("Workspace")
local runService = game:GetService("RunService") 
local tweenService = game:GetService("TweenService")
local uis = game:GetService("UserInputService")

local player = game:GetService("Players").LocalPlayer

local Module3D = require(replicatedStorage:WaitForChild("Module3D"))

local frame = script.Parent
local container = frame:WaitForChild("Container")
local template = script:WaitForChild("Template")
local unequipAll = frame:WaitForChild("Unequip All")
local MultiDeleteButton = frame:WaitForChild("Options"):WaitForChild("MultiDelete")

local PetInfosFrame = script.Parent:FindFirstChild("PetInformations")
local PetNameText = PetInfosFrame:FindFirstChild("PetName")
local RarityText = PetInfosFrame:FindFirstChild("Rarity")
local multiplierText = PetInfosFrame:FindFirstChild("Multiplier")
local TimeReduce = PetInfosFrame:FindFirstChild("TimeReduce")

local selectedTemplate = nil

local rarityfolder = replicatedStorage:WaitForChild("Raritys")
local raritysInside = rarityfolder:GetChildren()

local mouse = player:GetMouse()
local isHovering = false

-- Create a table to store rarity orders
local rarityOrder = {}

-- Populate rarityOrder with the orders from each rarity folder
for _, rarity in ipairs(raritysInside) do
	local orderValue = rarity:FindFirstChild("Order")
	if orderValue and orderValue:IsA("NumberValue") then
		rarityOrder[rarity.Name] = orderValue.Value
	end
end

function sort(PetObject)
	local Children = replicatedStorage:WaitForChild("Pets"):GetChildren()

	local Pets = {}
	for _, child in ipairs(Children) do
		table.insert(Pets, child)		
	end

	-- Sort Pets first based on their rarity order, then by their Multiplier value
	table.sort(Pets, function(a, b)
		local rarityA = a.Settings.Rarity.Value
		local rarityB = b.Settings.Rarity.Value
		local multiplierA = a.Settings.Multiplier.Value
		local multiplierB = b.Settings.Multiplier.Value

		-- First, compare by rarity order
		if rarityOrder[rarityA] ~= rarityOrder[rarityB] then
			return rarityOrder[rarityA] > rarityOrder[rarityB]  -- Higher rarity comes first
		end

		-- If rarity is the same, compare by multiplier
		return multiplierA > multiplierB  -- Higher multiplier comes first
	end)

	-- Now update LayoutOrder based on sorted pets
	local Counter = 1
	for _, pet in ipairs(Pets) do
		if PetObject:GetAttribute("Equipped") then
			if PetObject.Name == pet.Name then
				PetObject.LayoutOrder = Counter - 10
			end
		else
			if PetObject.Name == pet.Name then
				PetObject.LayoutOrder = Counter + #Pets
			end
		end
		Counter += 1
	end
end

local guiService = game:GetService("GuiService")
local camera = workspace.CurrentCamera

-- Set AnchorPoint once outside the loop (for example, near initialization)
PetInfosFrame.AnchorPoint = Vector2.new(0, 0)  -- Top-left corner positioning

runService.RenderStepped:Connect(function()
	if isHovering and PetInfosFrame then
		local mousePos = uis:GetMouseLocation()
		mousePos = Vector2.new(mousePos.X, mousePos.Y - 36) -- Adjust for top bar

		local localX = mousePos.X - frame.AbsolutePosition.X
		local localY = mousePos.Y - frame.AbsolutePosition.Y

		local infoWidth = PetInfosFrame.AbsoluteSize.X
		local infoHeight = PetInfosFrame.AbsoluteSize.Y
		local frameWidth = frame.AbsoluteSize.X
		local frameHeight = frame.AbsoluteSize.Y

		local offsetX = 20
		local offsetY = 20

		-- Flip horizontally if overflowing to the right
		if localX + offsetX + infoWidth > frameWidth then
			offsetX = -infoWidth - 20
		end

		-- Flip vertically if overflowing to the bottom
		if localY + offsetY + infoHeight > frameHeight then
			offsetY = -infoHeight - 20
		end

		-- Clamp vertically (just in case it still overflows after flipping)
		local finalY = localY + offsetY
		if finalY < 0 then
			finalY = 0
		elseif finalY + infoHeight > frameHeight then
			finalY = frameHeight - infoHeight
		end

		-- Clamp horizontally as well (optional, but safe)
		local finalX = localX + offsetX
		if finalX < 0 then
			finalX = 0
		elseif finalX + infoWidth > frameWidth then
			finalX = frameWidth - infoWidth
		end

		PetInfosFrame.Position = UDim2.new(0, finalX, 0, finalY)
	end
end)


_G.newTemplate = function(petName)
	local pet = replicatedStorage.Pets:FindFirstChild(petName)
	if not pet then
		warn("Pet not found: " .. petName)
		return
	end

	local newTemplate = template:Clone()
	newTemplate.Name = petName
	newTemplate.Parent = container

	local petSetting = pet:FindFirstChild("Settings")
	if not petSetting then
		warn("Pet settings not found for: " .. petName)
		return
	end

	-- Attach the 3D model to the ViewportFrame
	local viewPortFrame = Module3D:Attach3D(newTemplate.Display, pet:Clone(), 4)
	local rarityName = pet:GetAttribute("Rarity_Name")

	-- Adjust depth based on rarity
	if rarityName == "Legend" or rarityName == "Mythic" then
		viewPortFrame:SetDepthMultiplier(0.95)
	else
		local sizeHatch = pet:GetAttribute("SizeHatch") or 1.25  -- Default to 1.25 if not set
		viewPortFrame:SetDepthMultiplier(1.2 * (sizeHatch - 1.25))
	end

	-- Camera and lighting settings
	viewPortFrame.Camera.FieldOfView = 7
	viewPortFrame.Visible = true
	viewPortFrame.Ambient = Color3.fromRGB(255, 255, 255)
	viewPortFrame.LightColor = Color3.fromRGB(255, 255, 255)

	local textLabel = newTemplate:WaitForChild("TextLabel")
	local ExcMark = newTemplate:WaitForChild("ExclusiveMark")
	local FivStar = newTemplate:WaitForChild("5_Star_Mark")
	local fourStar = newTemplate:WaitForChild("4_Star_Mark")

	if rarityName == "Legend" then
		textLabel.Text = "???"
		ExcMark.Visible = false
		FivStar.Visible = false
		fourStar.Visible = true
		textLabel.SecretColors.Enabled = true
		textLabel.ExclusiveColors.Enabled = false
	elseif rarityName == "Mythic" then
		textLabel.Text = "????"
		ExcMark.Visible = false
		FivStar.Visible = true
		fourStar.Visible = false
		textLabel.SecretColors.Enabled = true
		textLabel.ExclusiveColors.Enabled = false
	elseif rarityName == "Exclusive" then
		textLabel.Text = "x" .. tostring(petSetting:WaitForChild("Multiplier").Value or 1)
		ExcMark.Visible = true
		FivStar.Visible = false
		fourStar.Visible = false
		textLabel.ExclusiveColors.Enabled = true
	else
		textLabel.Text = "x" .. tostring(petSetting:WaitForChild("Multiplier").Value or 1) -- default to 1 if no attribute
		ExcMark.Visible = false
		FivStar.Visible = false
		fourStar.Visible = false
		textLabel.SecretColors.Enabled = false
		textLabel.ExclusiveColors.Enabled = false
	end

	-- Checking rarity and setting background color
	for _, rarity in ipairs(raritysInside) do
		if petSetting.Rarity.Value == rarity.Name then
			if petSetting.Rarity.Value == "Legendary" then
				newTemplate.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
				newTemplate.LegendaryEffectGrand.Enabled = true
				newTemplate.UIGradient.Enabled = false
				
			elseif petSetting.Rarity.Value == "Mythical" then
				newTemplate.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
				newTemplate.MythicalEffectGrand.Enabled = true
				newTemplate.UIGradient.Enabled = false
			
	
			elseif petSetting.Rarity.Value == "Exclusive" then
					newTemplate.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
					newTemplate.ExclusiveEffectGrand.Enabled = true
					newTemplate.UIGradient.Enabled = false
			else
				newTemplate.BackgroundColor3 = rarity:FindFirstChild("Rarity_Color").Value or Color3.fromRGB(255, 255, 255)
				newTemplate.UIGradient.Enabled = true
				newTemplate.LegendaryEffectGrand.Enabled = false
			end
			break
		end
	end

	if petSetting.Rarity.Value == "Legendary" or petSetting.Rarity.Value == "Mythical" or petSetting.Rarity.Value == "Exclusive" then
		viewPortFrame:SetCFrame(CFrame.Angles(math.rad(10), math.rad(110), 0))
	else
		viewPortFrame:SetCFrame(CFrame.Angles(math.rad(10), math.rad(110), 0))
	end
	
	local hoveredTemplate = nil

	local function isMouseOverGuiObject(guiObject)
		local mousePos = uis:GetMouseLocation() - Vector2.new(0, 36)
		local absPos = guiObject.AbsolutePosition
		local absSize = guiObject.AbsoluteSize
		return mousePos.X >= absPos.X and mousePos.X <= absPos.X + absSize.X
			and mousePos.Y >= absPos.Y and mousePos.Y <= absPos.Y + absSize.Y
	end

	runService.RenderStepped:Connect(function()
		local InforFourMark = PetInfosFrame:FindFirstChild("4_Star_Mark")
		local InforFivMark = PetInfosFrame:FindFirstChild("5_Star_Mark")
		local found = false
		for _, template in pairs(container:GetChildren()) do
			if template:IsA("TextButton") and isMouseOverGuiObject(template) then
				if hoveredTemplate ~= template then
					hoveredTemplate = template
					isHovering = true
					PetInfosFrame.Visible = true

					local pet = replicatedStorage.Pets:FindFirstChild(template.Name)
					if pet then
						local petSetting = pet:FindFirstChild("Settings")
						local hoveredRarity = pet:GetAttribute("Rarity_Name")
						local secretColor = multiplierText:FindFirstChild("SecretColors")
						
						for _, rarity in ipairs(raritysInside) do
							if rarity.Name == petSetting.Rarity.Value then
								local colorValue = rarity:FindFirstChild("Rarity_Color")
								if colorValue and colorValue:IsA("Color3Value") then
									RarityText.TextColor3 = colorValue.Value
								else
									RarityText.TextColor3 = Color3.fromRGB(255, 255, 255)
								end
								break
							end
						end

						if hoveredRarity == "Legend" then
							secretColor.Enabled = true
							InforFourMark.Visible = true
							InforFivMark.Visible = false
							multiplierText.Text = "???"
						elseif hoveredRarity == "Mythic" then
							secretColor.Enabled = true
							InforFourMark.Visible = false
							InforFivMark.Visible = true
							multiplierText.Text = "????"
						else
							secretColor.Enabled = false
							InforFourMark.Visible = false
							InforFivMark.Visible = false
							multiplierText.Text = "x" .. tostring(petSetting.Multiplier.Value or 1)
						end

						if petSetting then
							PetNameText.Text = pet.Name
							RarityText.Text = petSetting.Rarity.Value

							local timeReduce = petSetting:FindFirstChild("TimeReduce")
							if timeReduce and timeReduce.Value > 1 then
								TimeReduce.Text = "x" .. tostring(timeReduce.Value) .. "⌛"
							else
								TimeReduce.Text = "None"
							end
						end
					end
				end
				found = true
				break
			end
		end

		if not found and hoveredTemplate then
			hoveredTemplate = nil
			isHovering = false
			PetInfosFrame.Visible = false
		end
	end)


	
	newTemplate.MouseButton1Click:Connect(function()

		if MultiDeleteButton:GetAttribute("active") == true then
			if newTemplate:GetAttribute("Delete") == false then
				if newTemplate:GetAttribute("Equipped") == true then
				else
					newTemplate:SetAttribute("Delete", true)
				end
			else
				newTemplate:SetAttribute("Delete", false)
			end
		else
			if newTemplate:GetAttribute("Equipped") == true then
				replicatedStorage:WaitForChild("EggRemoteEvents"):WaitForChild("UnEquipPet"):InvokeServer(pet.Name)
				newTemplate:SetAttribute("Equipped", false)
				sort(newTemplate)
			else
				local numberOfPetsEquipped = #WorkSpace:WaitForChild("Player_Pets"):WaitForChild(player.Name):GetChildren()

				if (numberOfPetsEquipped + 1) <= player:WaitForChild("Values"):WaitForChild("MaxPetsEquipped").Value then
					local clonedPet = pet:Clone()

					replicatedStorage:WaitForChild("EggRemoteEvents"):WaitForChild("EquipPet"):InvokeServer(pet)
					newTemplate:SetAttribute("Equipped", true)
					sort(newTemplate)

					return "Equip"
				elseif (numberOfPetsEquipped + 1) > player:WaitForChild("Values"):WaitForChild("MaxPetsEquipped").Value then
					return "Cannot Equip"
				end
			end
		end

	end)
	sort(newTemplate)
end

local unEquipAll = script.Parent["Unequip All"]

wait(0.5)
for _, v in pairs(player.Pets:GetChildren()) do 
	_G.newTemplate(v.Name)
end

replicatedStorage:WaitForChild("EggRemoteEvents"):WaitForChild("addPetList").OnClientEvent:Connect(function(petName)
	_G.newTemplate(petName)
end)

replicatedStorage:WaitForChild("removeCraftPet").OnClientEvent:Connect(function()
	local craftFrame = script.Parent.Parent:WaitForChild("Craft Pets")
	for _, template in pairs(craftFrame:WaitForChild("Container"):GetChildren()) do
		if template:IsA("TextButton") then
			if template:GetAttribute("Selected") == true then
				replicatedStorage:WaitForChild("removeCraftPet"):FireServer(template.Name)
			end
		end
	end
end)

replicatedStorage:WaitForChild("updatePetList").OnClientEvent:Connect(function(petName)
	for _, template in pairs(script.Parent:WaitForChild("Container"):GetChildren()) do
		if template:IsA("TextButton") then
			if template.Name == petName then
				if template:GetAttribute("Equipped") == false then
					template:Destroy()
					return
				else
				end
			end
		end
	end
end)