How to make a max backpack size?

So I’ve been working on a custom inventory and I’m getting pretty closed to finishing it but I can’t seem to figure out how to make it so when the backpack reaches a certain amount of children, it can’t collect anymore. I have attempted to add something where, when a child is added to the backpack, it fires a remote event and then puts the tool back into the workspace but it causes the tool to get launched and even sometimes ends up going into the backpack anyways. I was thinking of when you touched the tool nothing happens, like the tool doesn’t move (similar to what happens in the game Booga Booga)

Here is the local script being used in the backpack gui:

game:GetService('StarterGui'):SetCoreGuiEnabled(Enum.CoreGuiType.Backpack, false)

local uis = game:GetService("UserInputService")
local player = game.Players.LocalPlayer
local char = workspace:WaitForChild(player.Name) -- added WaitForChild
local bp = player.Backpack
local hum = char:WaitForChild("Humanoid")
local mouse = player:GetMouse()
local frame = script.Parent.Hotbar
local template = frame.Template
local equipped = 0 -- icon transparencies
local unequipped = 0.7
local Inventory = script.Parent.Inventory
local Template = Inventory.Items.Template
local InventoryButton = script.Parent.InventoryButton

local HotbarItems = {}
local previousInput 

local inventoryOpen = false



-- Hotbar stuff

local iconSize = template.Size
local iconBorder = {x = 15, y = 5} -- pixel space between icons

local inputKeys = { -- dictionary for effective referencing
	["One"] = {txt = "1"},
	["Two"] = {txt = "2"},
	["Three"] = {txt = "3"},
	["Four"] = {txt = "4"},
	["Five"] = {txt = "5"}
}

local inputOrder = { -- array for storing the order of the keys
	inputKeys["One"],inputKeys["Two"],inputKeys["Three"],inputKeys["Four"],inputKeys["Five"]
}

-- So tool doesn't instasntly equip on pickup

local Tools = {}

for I, v in pairs(player.Backpack:GetChildren()) do
	Tools[v] = true
end

char.ChildAdded:connect(function(Obj)
	if Obj:IsA("Tool") then
		if not Tools[Obj.Name] then
			wait()
			Obj.Parent = player.Backpack
			if previousInput and previousInput["tool"] then
				handleEquip(previousInput["tool"])
			end
		end
	end
end)

--< functions >--

function getToolAmount(tool) -- Gets the total amount of a certain tool
	local toolAmount = 0
	for i,v in pairs(bp:GetChildren()) do
		if v.Name == tool.Name then
			toolAmount += 1
		end
	end
	if char:FindFirstChild(tool.Name) then
		toolAmount += 1
	end
	return toolAmount
end

function adjust() -- adjusts all icons in the hotbar
	for key, value in pairs(inputKeys) do
		local tool = value["tool"]
		local icon = frame:FindFirstChild(value["txt"])
		if icon.ViewportFrame:FindFirstChild("Object") then
			icon.ViewportFrame.Object:Destroy()
		end
		if tool then
			local object = tool:Clone() -- creates an object for the viewport camera to look at
			object.Name = "Object"
			
			object.Handle.Orientation = Vector3.new(0,0,0)
			object.Handle.Position = Vector3.new(0,0,0)
			
			icon.ViewportFrame.Camera.CFrame = CFrame.new(
				object.Handle.Position + (object.Handle.CFrame.LookVector * 5) + Vector3.new(4, 2, 0),
				object.Handle.Position
			)
			
			object.Parent = icon.ViewportFrame
			
			wait() 
			-- There was a problem with the timing of code below and this wait seems to have fixed the problem
			
			local toolAmount = getToolAmount(tool)
			if toolAmount > 1 then
				icon.Label.Visible = true
			else
				icon.Label.Visible = false
			end
			icon.Label.Text = toolAmount
			
			if char:FindFirstChild(tool.Name) then -- if the tool is equipped...
				icon.BackgroundTransparency = equipped
			else
				icon.BackgroundTransparency = unequipped
			end
		else
			icon.Label.Text = 0
			icon.Label.Visible = false
			
			icon.BackgroundTransparency = unequipped
		end
	end
end

function handleEquip(tool) -- Equips and unequips the tools
	if tool then
		if tool.Parent ~= bp then
			Tools[tool.Name] = false
			hum:UnequipTools()
		else
			Tools[tool.Name] = true
			hum:EquipTool(tool)
		end
		adjust()
	end
end

function onKeyPress(inputObject) -- press keys to equip/unequip
	local key = inputObject.KeyCode.Name
	local value = inputKeys[key]
	if value and uis:GetFocusedTextBox() == nil then -- don't equip/unequip while typing in text box
		previousInput = value
		handleEquip(value["tool"])
		if value["tool"].Parent == char then
			previousInput = value
		else
			previousInput = nil
		end
	end 
end

function handleAddition(adding) -- Fires when a tool is added to the backpack
	if adding:IsA("Tool") then
		local new = true

		for key, value in pairs(inputKeys) do
			local tool = value["tool"]
			if tool then -- if there is a tool...
				if tool.Name == adding.Name then -- and the tool has the same name as the tool being added...
					new = false -- then the tool being added is not new
				end
			end
		end
	
		if new then
			for i = 1, #inputOrder do
				local tool = inputOrder[i]["tool"]
				if not tool and table.find(HotbarItems, adding.Name)  then -- if the tool slot is free...
					inputOrder[i]["tool"] = adding
					break
				end
			end
		end
		adjust()
	end
end

function handleRemoval(removing) -- Fires when a tool is removed from the player's character
	if removing and removing:IsA("Tool") then
		for i = 1, #inputOrder do
			if removing.Parent ~= bp then
				if inputOrder[i]["tool"] and inputOrder[i]["tool"].Name == removing.Name then
					Tools[removing.Name] = false
					if getToolAmount(removing) <= 0 then
						-- If there is no more of the tool in the player backpack or on their character, 
						-- it is safe to assume it has been dropped into the workspace
						inputOrder[i]["tool"] = nil
						previousInput = nil
						table.remove(HotbarItems, table.find(HotbarItems, removing.Name))
					else 
						wait() -- added a wait because, without it, it would cause some timing issues
						local tool = bp:FindFirstChild(removing.Name)
						inputOrder[i]["tool"] = tool
						handleEquip(tool)
					end
					break
				end
			end
		end 
	end
	adjust()
end

function create() -- creates all the icons at once (and will only run once)
	local toShow = #inputOrder -- # operator can only be used with an array, not a dictionary
	local totalX = (toShow*iconSize.X.Offset)+((toShow+1)*iconBorder.x)
	local totalY = iconSize.Y.Offset + (2*iconBorder.y)

	frame.Size = UDim2.new(0, totalX, 0, totalY)
	frame.Position = UDim2.new(0.5, -(totalX/2), 1, -(totalY+(iconBorder.y*2)))
	frame.Visible = true -- just in case!

	for i = 1, #inputOrder do

		local value = inputOrder[i]		
		local clone = template:Clone()
		clone.Parent = frame
		clone.Label.Text = value["txt"]
		clone.Name = value["txt"]
		clone.Visible = true
		clone.Position = UDim2.new(0, (i-1)*(iconSize.X.Offset)+(iconBorder.x*i), 0, iconBorder.y)
		clone.ImageTransparency = unequipped
		
		local camera = Instance.new("Camera")
		
		local camera = Instance.new("Camera")
		camera.Parent = clone.ViewportFrame

		clone.ViewportFrame.CurrentCamera = camera

		local tool = value["tool"]
		if tool then
			clone.Tool.Image = tool.TextureId
		end
		
		-- Creates a display label to show the tool's name
		local label
		clone.MouseEnter:Connect(function()
			if value["tool"] then
				local labelClone = script.Parent.ItemLabel:Clone()
				labelClone.Text = value["tool"].Name
				labelClone.Position = UDim2.new(0, mouse.X, 0, mouse.Y + 10)
				labelClone.Visible = true
				label = labelClone
				labelClone.Parent = script.Parent
			end
		end)
		clone.MouseLeave:Connect(function()
			if label then
				label:Destroy()
			end
		end)

		clone.Tool.Activated:Connect(function() -- click icon to equip/unequip
			for key, value in pairs(inputKeys) do
				if value["txt"] == clone.Name and value["tool"] then
					if inventoryOpen == true then 
						-- If the inventory is open, then the player is taking the tool off of their hotbar
						if label then
							label:Destroy()
						end
						previousInput = nil
						table.remove(HotbarItems, table.find(HotbarItems, value["tool"].Name))
						createInventorySlot(inputOrder[i]["tool"])
						if char:FindFirstChild(value["tool"].Name) then
							handleEquip(char:FindFirstChild(value["tool"].Name))
						end
						inputOrder[i]["tool"] = nil
						handleRemoval(value["tool"])
					else -- If it is closed then they are trying to equip/unequip it
						if char:FindFirstChild(value["tool"].Name) then
							previousInput = nil
							handleEquip(char:FindFirstChild(value["tool"].Name))
						else
							previousInput = value
							handleEquip(bp:FindFirstChild(value["tool"].Name))
						end
					end
				end 
			end
		end)

	end	
	template:Destroy()
end

function setup() -- sets up all the tools already in the backpack (and will only run once)
	local tools = bp:GetChildren()
	for i = 1, #tools do 
		if tools[i]:IsA("Tool") then -- does not assume that all objects in the backpack will be a tool (2.11.18)
			for i = 1, #inputOrder do
				local value = inputOrder[i]
				if not value["tool"] then -- if the tool slot is free...
					value["tool"] = tools[i]	
					break -- stop searching for a free slot
				end
			end
		end
	end
	create()
end

--< events >--
uis.InputBegan:Connect(onKeyPress)

--< start >--
setup()

bp.ChildAdded:Connect(handleAddition)
char.ChildRemoved:Connect(handleRemoval)

-- Hotbar script made by Shiro75 on youtube and modified by GooseStranger

-- Inventory stuff

function hotbarAddition(tool) -- Adds a tool into the hotbar
	table.insert(HotbarItems, tool.Name)
	handleAddition(tool)
end

function createInventorySlot(tool) -- creates an item slot for a tool
	local clone = Template:Clone() -- creates a clone of the item to display it
	clone.Name = tool.Name

	local toolAmount = getToolAmount(tool)
	if toolAmount > 1 then
		clone.Label.Visible = true
	end
	clone.Label.Text = tostring(toolAmount)
	
	local object = tool:Clone()
	object.Parent = clone.ViewportFrame
	
	object.Handle.Orientation = Vector3.new(0,0,0)
	object.Handle.Position = Vector3.new(0,0,0)

	local camera = Instance.new("Camera")
	camera.CFrame = CFrame.new(object.Handle.Position + (object.Handle.CFrame.LookVector * 5) + Vector3.new(4, 2, 0), object.Handle.Position)
	camera.Parent = clone.ViewportFrame

	clone.ViewportFrame.CurrentCamera = camera
	
	local label -- creates a label to display the name
	clone.MouseEnter:Connect(function()
		local labelClone = script.Parent.ItemLabel:Clone()
		labelClone.Text = object.Name
		labelClone.Position = UDim2.new(0, mouse.X, 0, mouse.Y + 10)
		labelClone.Visible = true
		label = labelClone
		labelClone.Parent = script.Parent
	end)
	clone.MouseLeave:Connect(function()
		if label then
			label:Destroy()
		end
	end)
	
	clone.Activated:Connect(function()
		local FilledInputs = 0
		for i = 1, #inputOrder do -- counts how many spot on the hotbar are full
			if inputOrder[i]["tool"] then
				FilledInputs += 1
			end
		end
		
		if FilledInputs < #inputOrder then -- if the hotbar has an open space
			clone:Destroy()
			if label then
				label:Destroy()
			end
			hotbarAddition(tool)
		end
		
	end)
	
	clone.Visible = true
	clone.Parent = Inventory.Items
end

function adjustInventory(child)
	if Inventory.Items:FindFirstChild(child.Name) then
		local toolAmount = getToolAmount(child)

		if toolAmount <= 0 then
			Inventory.Items:FindFirstChild(child.Name):Destroy()
		else
			if toolAmount == 1 then
				Inventory.Items:FindFirstChild(child.Name).Label.Text = toolAmount
				Inventory.Items:FindFirstChild(child.Name).Label.Visible = false
			else
				Inventory.Items:FindFirstChild(child.Name).Label.Text = toolAmount
				Inventory.Items:FindFirstChild(child.Name).Label.Visible = true
			end

		end
	else if not table.find(HotbarItems, child.Name) then
			createInventorySlot(child)
		end
	end
end

function createInventory() -- setups the entire initial inventory
	for _,tool in pairs(bp:GetChildren()) do
		if not Inventory.Items:FindFirstChild(tool) then
			createInventorySlot(tool)
		end
	end
end

InventoryButton.Activated:Connect(function() -- toggles the inventory
	if Inventory.Visible == true then
		Inventory.Visible = false
		inventoryOpen = false
	else
		Inventory.Visible = true
		inventoryOpen = true
	end
end)

bp.ChildAdded:Connect(adjustInventory)
bp.ChildRemoved:Connect(adjustInventory)

char.ChildAdded:Connect(adjustInventory)
char.ChildRemoved:Connect(adjustInventory)

createInventory()

Any Help would be much appreciated!

Also the inventory system itself is open sourced so you guys can go here and edit it and even use it in your own experiences:
https://www.roblox.com/games/7065167080/Open-Source-Custom-Inventory

You’d need to write custom logic for picking up tools. This is how I sorta had it in one of my games:

local ItemsService = {}

function ItemsService.GiveItem(Player, Item, successFunction)
  local TotalItemsPlayerHas = GetAllTools(Player)
  local PlayerMaxItemAmount = Player:GetAttribute("MaxItemAmount")
  if #TotalItemsPlayerHas < PlayerMaxItemAmount then
    Item:Clone().Parent=Player.Backpack
    successFunction(true)
  else
    successFunction(false)
  end
end

return ItemsService

Then, if you have a merchant NPC or something, you’d have something like this:

local ItemsService = require(game.ServerStorage.Modules.ItemsService)

function PlayerBuysItem(Player,Item)
  if PlayerCanBuy(Player,Item) then
    ItemsService.GiveItem(Player, Item, function(success)
      if success then
        Player:SetAttribute("Money",Player:GetAttribute("Money")-ItemCost)
      else
        ShowDialog(Player,"You don't have enough inventory space!")
      end
    end)
  else
    ShowDialog(Player,"You can't buy this!")
  end
end

The annoying part is that you’d have to have a unique custom fallback function for every case where a player might receive an item (ex: trading with other players, NPCs, picking items up, looting chests, tools that give other tools, etc.) If you don’t do this, players might find sneaky ways to have an infinite backpack size.

It’s not diffucult, just extra stuff you could avoid. Personally I’ve decided to not have a backpack size limit in my own game, even though I wrote most of the code for the system. Instead, I’m opting to have tools decrease walkspeed by varying amounts, depending on the tool. It achieves the same thing, makes more sense, leads to more emergent behaviors, and requires less code in the long run.