I am making a building game but I have been stuck on trying to get the placement inventory calculations correct.
The main problem is the scaled blocks to add up with the placed blocks.
Placing and deleting the blocks normally works well.
I tried to add it by the total inventory amount minus the total scaled amount but that isnt working right.
local placement = {}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local CollectionService = game:GetService("CollectionService")
local Debris = game:GetService("Debris")
local DataStore2 = require(game.ServerScriptService.ServerModules.DataStore2)
DataStore2.Combine("DATA", "inventory")
local InventoryModule = require(game.ServerScriptService.ServerModules.InventoryServer)
local pooling = require(ReplicatedStorage.Utils.Pooling)
local getBlockInBounds = require(ReplicatedStorage.Utils.BlockInBounds)
local PoolInstance = pooling.new(ReplicatedStorage.Blocks)
local Weld = require(game.ServerScriptService.ServerModules.Utils.SurfaceWeld)
local placedBlocksFolder
local plotsFolder = workspace.Plots
placement.deleteDebounce = {}
placement.PlacementInventory = {}
placement.CachedBlockProperties = {}
local function getAllScaledAmount(model, userFolder)
print("Past 1")
if not model then return 1 end
print("past 2")
if not userFolder then return 1 end
print("past 3")
local BlockId = model:GetAttribute("BLOCKID")
if not BlockId then return 1 end
print("past 4")
local amount = 0
for _, block in pairs(userFolder:GetChildren()) do
print(block)
if block:GetAttribute("BLOCKID") == BlockId then
local part = block:FindFirstChildWhichIsA("BasePart")
local size = part.Size
local blocksX = math.floor(size.X / 2)
local blocksY = math.floor(size.Y / 2)
local blocksZ = math.floor(size.Z / 2)
amount = amount + (blocksX * blocksY * blocksZ)
end
end
return amount
end
local function validateBounds(partInPaidBounds, partInBounds, gamepassValue)
if partInPaidBounds and not partInBounds then
if gamepassValue.Value then
return true
else
return false
end
elseif partInBounds and partInPaidBounds then
return true
end
end
local function getPlacedBlocks(player, targetBLOCKID)
local matchedBlocks = {}
local placedFolder = workspace:WaitForChild("PlacedBlocks"):FindFirstChild(player.UserId)
if not placedFolder then return matchedBlocks end
for _, blockModel in pairs(placedFolder:GetChildren()) do
if blockModel:IsA("Model") and blockModel:GetAttribute("BLOCKID") == targetBLOCKID and blockModel.Name ~= "GhostModel" then
table.insert(matchedBlocks, blockModel)
end
end
return matchedBlocks
end
local function DestroyWeldsReferencingParts(parts, root)
root = root or workspace
local partsSet = {}
for _, part in pairs(parts) do
partsSet[part] = true
end
for _, weld in ipairs(root:GetDescendants()) do
if weld:IsA("WeldConstraint") or weld:IsA("Weld") or weld:IsA("Motor6D") then
if partsSet[weld.Part0] or partsSet[weld.Part1] then
weld:Destroy()
end
end
end
end
local function findItemIndex(inventoryTable, BLOCKID)
for i, item in ipairs(inventoryTable) do
if item.BLOCKID == BLOCKID then
return i
end
end
return nil
end
function placement:Init()
local folder = Instance.new("Folder")
folder.Name = "PlacedBlocks"
placedBlocksFolder = folder
folder.Parent = workspace
game.Players.PlayerAdded:Connect(function(player)
local userId = player.UserId
local playerFolder = Instance.new("Folder")
playerFolder.Name = userId
playerFolder.Parent = folder
local blockConnections = {}
playerFolder.ChildAdded:Connect(function(block)
if not block or not block:IsA("Instance") then return end
local blockId = block:GetAttribute("BLOCKID")
if not blockId then return end
local inventory = InventoryModule.getPlayerInventory(player)
local index = findItemIndex(inventory, blockId)
if blockConnections[block] then
blockConnections[block]:Disconnect()
blockConnections[block] = nil
end
placement.CachedBlockProperties[block] = placement.CachedBlockProperties[block] or {}
local basePart = block:FindFirstChildWhichIsA("BasePart")
if not basePart then return end
placement.CachedBlockProperties[block].Size = basePart.Size
blockConnections[block] = block.AncestryChanged:Connect(function(_, parent)
if parent ~= nil then return end
if blockConnections[block] then
blockConnections[block]:Disconnect()
blockConnections[block] = nil
end
local blockId = block:GetAttribute("BLOCKID")
if not blockId then return end
local amount = 0
if placement.CachedBlockProperties[block] then
amount = getAllScaledAmount(block, playerFolder)
placement.CachedBlockProperties[block] = nil
end
local currentAmount = 0
if index then
currentAmount = inventory[index].AMOUNT or 0
end
placement.PlacementInventory[player.UserId] = placement.PlacementInventory[player.UserId] or {}
placement.PlacementInventory[player.UserId][blockId] = currentAmount + amount
if ReplicatedStorage.Remotes:FindFirstChild("UpdateInventorySlots") then
ReplicatedStorage.Remotes.UpdateInventorySlots:FireClient(
player,
blockId,
placement.PlacementInventory[player.UserId][blockId]
)
end
end)
end)
end)
game.Players.PlayerRemoving:Connect(function(player)
local userId = player.UserId
placement.PlacementInventory[userId] = nil
local playerFolder = folder:FindFirstChild(userId)
if playerFolder then
playerFolder:Destroy()
end
end)
-- Fires when adding new blocks to inventory
ReplicatedStorage.Remotes.UpdatePlacementInventory.Event:Connect(function(player, amount, blockId)
local placement = require(game.ServerScriptService.ServerModules.PlacementServer)
if not placement.PlacementInventory[player.UserId] then
placement.PlacementInventory[player.UserId] = placement.PlacementInventory[player.UserId] or {}
end
placement.PlacementInventory[player.UserId][blockId] = (placement.PlacementInventory[player.UserId][blockId] or 0) + amount
ReplicatedStorage.Remotes.UpdateInventorySlots:FireClient(player, blockId, placement.PlacementInventory[player.UserId][blockId])
end)
end
local function GetBlockById(targetBlockId)
local block = nil
for _, block in pairs(game.ReplicatedStorage.Blocks:GetChildren()) do
if block:GetAttribute("BLOCKID") == targetBlockId then
block = block
return block
end
end
return block
end
function placement.Place(player, pivot, blockId, anchor)
local inventory = InventoryModule.getPlayerInventory(player)
local index = findItemIndex(inventory, blockId)
local validateAmount
if placement.PlacementInventory[player.UserId] then
validateAmount = InventoryModule.ValidatePlayerBlockAmount(player, blockId, placement.PlacementInventory[player.UserId][blockId])
else
validateAmount = InventoryModule.ValidatePlayerBlockAmount(player, blockId, inventory[index] and inventory[index].AMOUNT or 0)
end
if not validateAmount then
return {Result = "Rejected", placedBlock = nil}
end
local block = GetBlockById(blockId)
if not block then
return {Result = "Rejected", placedBlock = nil}
end
local gamePassFolder = player:FindFirstChild("Gamepasses")
if not gamePassFolder then return {Result = "Rejected"} end
local gamepassValue = gamePassFolder:FindFirstChild("Extended_Plot")
if not gamepassValue then return {Result = "Rejected"} end
local playerTeam = player.Team
if not playerTeam then return {Result = "Rejected"} end
local playerPlot = plotsFolder:FindFirstChild(playerTeam.Name)
if not playerPlot then return {Result = "Rejected"} end
local launched = playerPlot:FindFirstChild("Launched")
if not launched or launched.Value then
return {Result = "Rejected"}
end
local normalBounds = playerPlot:FindFirstChild("FreeBuildBounds")
local paidBounds = playerPlot:FindFirstChild("PaidBuildBounds")
if not normalBounds or not paidBounds then return {Result = "Rejected"} end
local partsInBounds = getBlockInBounds.IsPivotInBounds(pivot, normalBounds)
local partsInPaidBounds = getBlockInBounds.IsPivotInBounds(pivot, paidBounds)
local validatedBounds = validateBounds(partsInPaidBounds, partsInBounds, gamepassValue)
if not validatedBounds then
return {Result = "Rejected"}
end
local PlacedBlock = PoolInstance:Get(block.Name)
local blockChildren = PlacedBlock:GetChildren()
PlacedBlock.PrimaryPart = blockChildren[1]
PlacedBlock:SetAttribute("Owner", player.UserId)
PlacedBlock:SetAttribute("Plot", playerTeam.Name)
PlacedBlock:PivotTo(pivot)
local playerFolder = placedBlocksFolder:FindFirstChild(player.UserId)
if not playerFolder then
return {Result = "Rejected"}
end
PlacedBlock.Parent = playerFolder
placement.PlacementInventory[player.UserId] = placement.PlacementInventory[player.UserId] or {}
local scaledAmount = getAllScaledAmount(PlacedBlock, playerFolder)
local currentAmount = 0
if index then
currentAmount = inventory[index].AMOUNT or 0
end
placement.PlacementInventory[player.UserId][blockId] = currentAmount - scaledAmount
ReplicatedStorage.Remotes.UpdateInventorySlots:FireClient(player, blockId, placement.PlacementInventory[player.UserId][blockId])
Weld.WeldModel(PlacedBlock)
if not anchor then
for _, part in pairs(PlacedBlock:GetDescendants()) do
if part:IsA("BasePart") then
part.Anchored = false
end
end
end
return {Result = "Success", placedBlock = PlacedBlock}
end
function placement.DeleteBlock(player, block)
coroutine.wrap(function()
if not block or not block:IsA("Model") then return end
local PlacedBlocks = workspace:FindFirstChild("PlacedBlocks")
if not PlacedBlocks then return end
local userFolder = PlacedBlocks:FindFirstChild(player.UserId)
if not userFolder then return end
local owner = block:GetAttribute("Owner")
if not block:IsDescendantOf(userFolder) then return end
if (not owner) == player.UserId then return end
local partsToDelete = {}
for _, part in ipairs(block:GetDescendants()) do
if part:IsA("BasePart") then
part.Anchored = true
part.CanCollide = false
table.insert(partsToDelete, part)
end
end
DestroyWeldsReferencingParts(partsToDelete, userFolder)
local playerInventory = InventoryModule.getPlayerInventory(player)
if not playerInventory then return end
local blockId = block:GetAttribute("BLOCKID")
if not blockId then return end
local index = findItemIndex(playerInventory, blockId)
if not index then return end
local inventoryEntry = playerInventory[index]
if not inventoryEntry then return end
RunService.Heartbeat:Wait()
block:Destroy()
Weld.PruneWeldCache()
local currentAmount = 0
if index then
currentAmount = playerInventory[index].AMOUNT or 0
end
end)()
end
function placement.Scale(player, part, cframe, size)
local userFolder = placedBlocksFolder:FindFirstChild(player.UserId)
if not userFolder then
return false
end
if not part then
return false
end
local block = part.Parent
if not block then
return false
end
if not CollectionService:HasTag(block, "Scalable") then
return false
end
local blockId = block:GetAttribute("BLOCKID")
if not blockId then
return false
end
local inventory = InventoryModule.getPlayerInventory(player)
local index = findItemIndex(inventory, blockId)
local validateAmount
if placement.PlacementInventory[player.UserId] then
validateAmount = InventoryModule.ValidatePlayerBlockAmount(player, blockId, placement.PlacementInventory[player.UserId][blockId])
else
validateAmount = InventoryModule.ValidatePlayerBlockAmount(player, blockId, inventory[index] and inventory[index].AMOUNT or 0)
end
if not validateAmount then
return false
end
local prevSize = part.Size
local prevCframe = part.CFrame
part.Size = size
part.CFrame = cframe
local totalAmount = getAllScaledAmount(block, userFolder)
print(totalAmount)
local currentAmount = 0
if index then
currentAmount = inventory[index].AMOUNT or 0
end
if currentAmount - totalAmount <= 0 then
part.Size = prevSize
part.CFrame = prevCframe
return false
end
Weld.ClearWelds(block)
Weld.PruneWeldCache(block)
Weld.WeldModel(block)
placement.PlacementInventory[player.UserId][blockId] = currentAmount - totalAmount
ReplicatedStorage.Remotes.UpdateInventorySlots:FireClient(player, blockId, placement.PlacementInventory[player.UserId][blockId])
placement.CachedBlockProperties[block] = placement.CachedBlockProperties[block] or {}
placement.CachedBlockProperties[block].Size = part.Size
return true
end
function placement.ClearAll(folder, player)
coroutine.wrap(function()
for i, block in ipairs(folder:GetChildren()) do
if not folder then return end
placement.DeleteBlock(player, block)
end
end)()
end
ReplicatedStorage.Remotes.InventoryUpdateFinish.OnServerEvent:Connect(function(player)
if placement.deleteDebounce[player.UserId] then
placement.deleteDebounce[player.UserId] = nil
end
end)
return placement
I’d appreciate any help since this is the biggest pain in this game to work on and I’ve been stuck on this for over 3 days.