Inventory custom dragging problem

  1. What do you want to achieve?
    i tried to make cell based inventory system

  2. What is the issue?

when i try to move inventory item it dissappears if there’s no other item like AK beside it from right or left

InventoryLocal script

-- PlayerGui -> InventoryGui -> InventoryLocalScript
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local UserInputService = game:GetService("UserInputService")
local UpdateInventoryEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("UpdateInventory")
local MoveItemEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("MoveItem")
local ItemDatabase = require(ReplicatedStorage:WaitForChild("ItemDatabase"))

local ScrollingFrame = script.Parent:WaitForChild("InventoryScrollingFrame")
local Template = ScrollingFrame:WaitForChild("Template")
Template.Visible = false

local CELL_SIZE = 50 
local INVENTORY_ROWS = 12
local INVENTORY_COLS = 7

local draggingNode = nil
local dragItemKey = nil
local startRow, startCol = nil, nil
local dragOffset = Vector2.new(0, 0)

local function renderInventory(grid)
	for _, child in ipairs(ScrollingFrame:GetChildren()) do
		if child:IsA("ImageButton") and child ~= Template then
			child:Destroy()
		end
	end

	for r = 1, INVENTORY_ROWS do
		if grid[r] then
			for c = 1, INVENTORY_COLS do
				local cellData = grid[r][c]

				if cellData and cellData.isHead then
					local itemKey = cellData.itemKey
					local itemData = ItemDatabase[itemKey]

					if itemData then
						local itemClone = Template:Clone()
						itemClone.Name = itemKey
						itemClone.Image = itemData.Image
						itemClone.Visible = true

						itemClone.Position = UDim2.new(0, (c - 1) * CELL_SIZE, 0, (r - 1) * CELL_SIZE)
						itemClone.Size = UDim2.new(0, itemData.Width * CELL_SIZE, 0, itemData.Height * CELL_SIZE)
						itemClone.Parent = ScrollingFrame

						itemClone.MouseButton1Down:Connect(function()
							if draggingNode then return end

							draggingNode = itemClone
							dragItemKey = itemKey
							startRow = r
							startCol = c

							local mousePos = UserInputService:GetMouseLocation()
							dragOffset = Vector2.new(
								mousePos.X - itemClone.AbsolutePosition.X,
								mousePos.Y - itemClone.AbsolutePosition.Y
							)

							itemClone.ImageTransparency = 0.5
							itemClone.ZIndex = 10
						end)
					end
				end
			end
		end
	end
end

UserInputService.InputChanged:Connect(function(input)
	if draggingNode and input.UserInputType == Enum.UserInputType.MouseMovement then
		local framePos = ScrollingFrame.AbsolutePosition
		local mousePos = UserInputService:GetMouseLocation()

		local relativeX = mousePos.X - framePos.X - dragOffset.X
		local relativeY = mousePos.Y - framePos.Y - dragOffset.Y + ScrollingFrame.CanvasPosition.Y

		draggingNode.Position = UDim2.new(0, relativeX, 0, relativeY)
	end
end)

UserInputService.InputEnded:Connect(function(input)
	if draggingNode and input.UserInputType == Enum.UserInputType.MouseButton1 then
		local framePos = ScrollingFrame.AbsolutePosition
		local mousePos = UserInputService:GetMouseLocation()

		local relativeX = mousePos.X - framePos.X - dragOffset.X
		local relativeY = mousePos.Y - framePos.Y - dragOffset.Y + ScrollingFrame.CanvasPosition.Y

		local calculatedCol = math.round(relativeX / CELL_SIZE) + 1
		local calculatedRow = math.round(relativeY / CELL_SIZE) + 1

		local targetCol = math.clamp(calculatedCol, 1, INVENTORY_COLS)
		local targetRow = math.clamp(calculatedRow, 1, INVENTORY_ROWS)

		local nodeRef = draggingNode
		local savedStartRow = startRow
		local savedStartCol = startCol

		draggingNode = nil

		-- Instant visual return (in case of a ping)
		nodeRef.Position = UDim2.new(0, (savedStartCol - 1) * CELL_SIZE, 0, (savedStartRow - 1) * CELL_SIZE)
		nodeRef.ImageTransparency = 0
		nodeRef.ZIndex = 1

		MoveItemEvent:FireServer(savedStartRow, savedStartCol, targetRow, targetCol)
	end
end)

UpdateInventoryEvent.OnClientEvent:Connect(renderInventory)

InventoryServer script

-- ServerScriptService -> InventoryServer
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ItemDatabase = require(ReplicatedStorage:WaitForChild("ItemDatabase"))
local MoveItemEvent = ReplicatedStorage:WaitForChild("Remotes"):WaitForChild("MoveItem")

local INVENTORY_ROWS = 12
local INVENTORY_COLS = 7  

local PlayerInventories = {}

local function createEmptyGrid()
	local grid = {}
	for r = 1, INVENTORY_ROWS do
		grid[r] = {}
		for c = 1, INVENTORY_COLS do
			grid[r][c] = nil
		end
	end
	return grid
end

local function canPlaceItemIgnore(grid, itemKey, startRow, startCol, ignoreRow, ignoreCol)
	local itemData = ItemDatabase[itemKey]
	local w, h = itemData.Width, itemData.Height

	-- 1. Geometric check of grid boundaries
	if startRow < 1 or startRow + h - 1 > INVENTORY_ROWS then return false end
	if startCol < 1 or startCol + w - 1 > INVENTORY_COLS then return false end

	-- 2. Checking each cell for occupancy
	for r = startRow, startRow + h - 1 do
		for c = startCol, startCol + w - 1 do
			local cell = grid[r][c]

			-- If the cell is not empty, we check if it belongs to the same item
			if cell ~= nil then
				local isMyOwnTail = (cell.rootRow == ignoreRow and cell.rootCol == ignoreCol)
				if not isMyOwnTail then
					return false -- There is a FOREIGN object here that cannot be placed
				end
			end
		end
	end

	return true
end

-- Cleaning an item from the grid
local function removeItemFromGrid(grid, rootRow, rootCol)
	local cellData = grid[rootRow][rootCol]
	if not cellData then return end

	local itemData = ItemDatabase[cellData.itemKey]
	for r = rootRow, rootRow + itemData.Height - 1 do
		for c = rootCol, rootCol + itemData.Width - 1 do
			grid[r][c] = nil
		end
	end
end

-- Writing an object to coordinates
local function forcePlaceItem(grid, itemKey, startRow, startCol)
	local itemData = ItemDatabase[itemKey]
	for r = startRow, startRow + itemData.Height - 1 do
		for c = startCol, startCol + itemData.Width - 1 do
			grid[r][c] = { itemKey = itemKey, isHead = false, rootRow = startRow, rootCol = startCol }
		end
	end
	grid[startRow][startCol] = { itemKey = itemKey, isHead = true, rootRow = startRow, rootCol = startCol }
end

-- Auto-add function to start
local function addItemToInventory(grid, itemKey)
	for r = 1, INVENTORY_ROWS do
		for c = 1, INVENTORY_COLS do
			if canPlaceItemIgnore(grid, itemKey, r, c, 0, 0) then
				forcePlaceItem(grid, itemKey, r, c)
				return true
			end
		end
	end
	return false
end

-- Movement processing
MoveItemEvent.OnServerEvent:Connect(function(player, fromRow, fromCol, toRow, toCol)
	local grid = PlayerInventories[player.UserId]
	if not grid then return end

	-- SERVER PROTECTIVE BARRIER: checking the validity of the sent indexes
	if toRow < 1 or toRow > INVENTORY_ROWS or toCol < 1 or toCol > INVENTORY_COLS then
		warn("Сервер отклонил пакет: Координаты вне инвентаря! [", toRow, ",", toCol, "]")
		ReplicatedStorage.Remotes.UpdateInventory:FireClient(player, grid)
		return
	end

	local cellData = grid[fromRow][fromCol]
	if not cellData or not cellData.isHead then 
		ReplicatedStorage.Remotes.UpdateInventory:FireClient(player, grid)
		return 
	end

	local itemKey = cellData.itemKey

	print("--- TRANSFER REQUEST ---")
	print("Item:", itemKey)
	print("From Cell: [Row:", fromRow, ", Col:", fromCol, "]")
	print("To Cell: [Row:", toRow, ", Col:", toCol, "]")

	if canPlaceItemIgnore(grid, itemKey, toRow, toCol, fromRow, fromCol) then
		print("Result: Moved successfully!")
		removeItemFromGrid(grid, fromRow, fromCol)
		forcePlaceItem(grid, itemKey, toRow, toCol)
	else
		print("Result: Denied, cell is busy!")
	end

	ReplicatedStorage.Remotes.UpdateInventory:FireClient(player, grid)
end)

Players.PlayerAdded:Connect(function(player)
	local grid = createEmptyGrid()
	PlayerInventories[player.UserId] = grid

	addItemToInventory(grid, "AK74")
	addItemToInventory(grid, "Medkit")
	addItemToInventory(grid, "Bottle")
	addItemToInventory(grid, "Ammo_545")
	addItemToInventory(grid, "Medkit")
	addItemToInventory(grid, "AK74")

	ReplicatedStorage.Remotes.UpdateInventory:FireClient(player, grid)
end)

Players.PlayerRemoving:Connect(function(player)
	PlayerInventories[player.UserId] = nil
end)

Module script

-- ReplicatedStorage -> ItemDatabase (ModuleScript)
local ItemDatabase = {
	["Medkit"] = { Name = "Medkit", Width = 1, Height = 1, Image = "rbxassetid://10318739285" },
	["Bottle"] = { Name = "Water", Width = 1, Height = 1, Image = "rbxassetid://136821935409252" },
	["AK74"] = { Name = "AK-74m", Width = 5, Height = 2, Image = "rbxassetid://10456861522" },
	["Ammo_545"] = { Name = "5.45x39 ammo", Width = 1, Height = 1, Image = "rbxassetid://99027744712862" }
}
return ItemDatabase