hi everybody.
i’ve been working on this darn inventory system for weeks now, and i cant figure it out. this thing has been weighing this entire side project and my sanity down and i cant figure out the issues, how to make it better, and if i should even bother.
as you can see, the rock on the right weighs 13.5 (nonsensical), whereas the other one on the left weighs 16-ish.
I don’t understand what i’m doing wrong, and i’ve gotten so desperate i began to plead for AI to spit out something helpful from its toxic maw.
I have a item defs module.
local ServerStorage = game:GetService("ServerStorage")
local Models = ServerStorage.Items
return {
--[[
["Rock"] = {
Name = "Rock",
Description = "Just a rock.",
Stack = 1,
Weight = 5,
Type = "Items",
Rarity = "Common",
Value = 1,
Interaction = function(item: Instance, player: Player)
return
end,
OnSpawn = function(item: Instance, owner: Player)
return
end,
Hooks = {}
}
]]
["Block"] = {
Model = Models.Block,
Description = "THIS IS A DEV ITEM THAT DOES NOTHING",
Stack = 2,
Weight = 100,
Type = "Items",
Rarity = "Legendary",
Value = 100,
Hooks = {}
},
["Rock"] = {
Model = Models.Rock,
Description = "Just a rock.",
Stack = 1,
Weight = 5,
Type = "Items",
Rarity = "Common",
Value = 1,
Hooks = {}
},
}
I got an ItemManager module.
local Item = require(script.Parent.Item)
local ItemManager = {}
ItemManager.Items = {}
ItemManager.Players = {}
-- // SERVICES
local ReplicatedStorage = game:GetService("ReplicatedStorage")
-- // MODULES
local ItemDefs = require(script.Parent.ItemDefinitions)
-- // REMOTES
local UpdateFunction = ReplicatedStorage.Remotes.Inventory:WaitForChild("InventoryUpdateFunction") :: RemoteFunction
local UpdateEvent = ReplicatedStorage.Remotes.Inventory:WaitForChild("InventoryUpdate") :: RemoteEvent
local ActionRemote = ReplicatedStorage.Remotes.Inventory:WaitForChild("InventoryAction") :: RemoteEvent
-- // CODE
-- [[ HELPER ]] --
local function deepCopy(original)
local copy = {}
for k, v in pairs(original) do
if type(v) == "table" then
copy[k] = deepCopy(v)
else
copy[k] = v
end
end
return copy
end
--[[
Ensures the player has an inventory.
]]
function ItemManager:EnsurePlayerInventory(player: Player)
if not self.Players[player.UserId] then
self.Players[player.UserId] = {
TotalWeight = 0,
MaxWeight = 100,
Items = {}
}
end
return self.Players[player.UserId]
end
--[[
Retrieves defined item data, optionally overriding some data.
]]
function ItemManager:GetDefWithOverrides(original, overrides)
local clone = table.clone(original)
for k, v in pairs(overrides) do
clone[k] = v
end
return clone
end
--[[
Creates a new item class, with optional overrides for the item data.
]]
function ItemManager:CreateItem(name: string, model: Instance, overrides: {[string]: any}?)
local base = ItemDefs[name]
assert(base, `No base item definition found for {name}`)
local finalData = self:GetDefWithOverrides(base, overrides or {})
local newItem = Item.new(name, model, finalData)
table.insert(self.Items, newItem)
return newItem
end
--[[
Creates and spawns a new item class.
]]
function ItemManager:SpawnItem(name: string, model: Instance, data: Item.ItemData, Cframe: CFrame, owner: Player?)
local item = self:CreateItem(name, model, deepCopy(data))
item:Spawn(Cframe, owner)
return item
end
--[[
Uses an existing item class to spawn.
]]
function ItemManager:SpawnExisting(item, Cframe: CFrame, owner: Player)
item:Spawn(Cframe, owner)
return item
end
--[[
If the item has a special interaction, trigger it.
Else, the object is picked up.
]]
function ItemManager:Interact(item, player)
if not item.Interaction then
self:PickUp(item, player)
else
item:Interact(player)
end
end
--[[
Picks up the item, and adds it to the player inventory.
]]
function ItemManager:PickUp(item, player: Player, amount: number)
if not item or not item.Data then
warn("Attempted pickup on nil item/data!")
return
end
if not item.Instance then
warn("Tried to pick up an item with no active instance!")
return
end
amount = amount or 1
local newStack = math.clamp(item:GetStack() - amount, 0, math.huge)
item:TweakData("Stack", newStack)
self:AddToInventory(player, item, amount)
if item:GetStack() <= 0 then
self:DestroyItem(item)
end
end
--[[
Destroys the item class.
]]
function ItemManager:DestroyItem(item)
for i, v in ipairs(self.Items) do
if v == item then
item:Destroy()
table.remove(self.Items, i)
break
end
end
end
--[[
Looks up the item name in the item list, returning the first value.
]]
function ItemManager:LookupByName(name: string)
for _, item in ipairs(self.Items) do
if item.Name == name then
return item
end
end
return nil
end
--[[
Looks up the item UUID in the item list, returning a match.
]]
function ItemManager:LookupByUUID(uuid: string)
for _, item in ipairs(self.Items) do
local itemUUID = item.Instance:GetAttribute("UUID")
if itemUUID == uuid then
return item
end
end
warn("LookupByUUID failed for", uuid)
return nil
end
--[[
Looks up the table for matching instances.
]]
function ItemManager:LookupByInstance(instance: Instance)
for _, item in ipairs(self.Items) do
if item.Instance == instance then
return item
end
end
warn("LookupByInstance failed for", instance:GetFullName())
return nil
end
--[[
Adds the item to the player inventory.
]]
function ItemManager:AddToInventory(player: Player, item, amount: number)
assert(item, "Item not provided!")
assert(player, "Player not provided!")
amount = amount or 1
local playerData = self:EnsurePlayerInventory(player)
local items = playerData.Items
local index = items[item.Name]
if index then
index.Amount += amount
index.Weight = item:GetWeight() * index.Amount
index.Value = item:GetValue() * index.Amount
else
items[item.Name] = {
Name = item.Name,
Description = item:GetDescription(),
Rarity = item:GetRarity(),
Weight = item:GetWeight() * amount,
Value = item:GetValue() * amount,
Type = item:GetType(),
Amount = amount,
}
end
local total = 0
for _, entry in pairs(playerData.Items) do
total += entry.Weight
end
playerData.TotalWeight = total
local updatedInventory = self:GetPlayerInvData(player)
UpdateEvent:FireClient(player, updatedInventory)
end
--[[
Removes the item from the player inventory.
]]
function ItemManager:RemoveFromInventory(player: Player, item, amount: number)
assert(item, "Item not provided!")
assert(player, "Player not provided!")
amount = amount or 1
local playerData = self:EnsurePlayerInventory(player)
local items = playerData.Items
local index = items[item.Name]
if index then
index.Amount -= amount
index.Weight = item:GetWeight() * index.Amount
index.Value = item:GetValue() * index.Amount
else
warn("Tried to remove item that doesn't exist in inventory!")
return
end
if index.Amount <= 0 then
items[item.Name] = nil
end
local total = 0
for _, entry in pairs(playerData.Items) do
total += entry.Weight
end
playerData.TotalWeight = total
local updatedInventory = self:GetPlayerInvData(player)
UpdateEvent:FireClient(player, updatedInventory)
end
--[[
Retrieves player's data.
]]
function ItemManager:GetPlayerInvData(player: Player)
local data = self:EnsurePlayerInventory(player)
return data
end
UpdateFunction.OnServerInvoke = function(player: Player, action, args)
if action == "Get" then
return ItemManager:GetPlayerInvData(player)
elseif action == "Drop" then
local itemName = args.Item.Name
local amount = args.Amount or 1
local playerInv = ItemManager.Players[player.UserId]
if not playerInv then return end
local itemData = playerInv.Items[itemName]
if not itemData then return end
local fakeItem = {
Name = itemName,
GetWeight = function() return itemData.Weight / itemData.Amount end,
GetValue = function() return itemData.Value / itemData.Amount end,
}
ItemManager:RemoveFromInventory(player, fakeItem, amount)
local updatedItemDefData = deepCopy(ItemDefs[itemName])
updatedItemDefData.Stack = amount
updatedItemDefData.Value = fakeItem.GetValue() * amount
updatedItemDefData.Weight = fakeItem.GetWeight() * amount
local spawnCFrame = player.Character and player.Character:GetPivot() + Vector3.new(0, 2, 0) or CFrame.new()
--local result = workspace:GetPartBoundsInRadius(spawnCFrame.Position, 7)
--if result then
-- for _, part in result do
-- local item = ItemManager:LookupByInstance(part:FindFirstAncestor(itemName))
-- if item then
-- item:TweakData("Stack", item:GetStack() + amount)
-- break
-- end
-- end
--end
ItemManager:SpawnItem(itemName, nil, updatedItemDefData, spawnCFrame)
return ItemManager:GetPlayerInvData(player)
end
end
return ItemManager
this is the local script.
-- // SERVICES
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- // MODULE
local Rarities = require(ReplicatedStorage.Modules.Rarities)
-- // REMOTES
local UpdateFunc = ReplicatedStorage.Remotes.Inventory.InventoryUpdateFunction
local UpdateEvent = ReplicatedStorage.Remotes.Inventory.InventoryUpdate
local ActionEvent = ReplicatedStorage.Remotes.Inventory.InventoryAction
-- // VARIABLES
local Player = Players.LocalPlayer
local Template = script.Item_Template
local Inventory = script.Parent
local Menu = Inventory.Parent.Parent
local TopBar = Menu.TopPanel.Tabs
local InvButton = TopBar.Inventory
local InventoryContents = Inventory.Items
-- // CODE
local function UpdateInventory(updatedInv)
for _, frame in InventoryContents:GetChildren() do
if frame:IsA("Frame") then
frame:Destroy()
end
end
for itemName, value in pairs(updatedInv.Items) do
local newFrame = Template:Clone()
print(itemName)
newFrame.Name = "Item_"..itemName
local rarityName = value.Rarity or "Common"
newFrame.Rarity.Text = "[ "..string.upper(rarityName).." ]" or "[ COMMON ]"
newFrame.Rarity.TextColor3 = (Rarities[rarityName] and Rarities[rarityName].Color) or Color3.new(1, 1, 1)
newFrame.ItemName.Text = itemName
newFrame.Value.Text = `Prognosis: {value.Value} Rations`
newFrame.Weight.Text = `{value.Weight} wt.`
newFrame.Buttons.Drop.Text = `Drop [x{value.Amount}]`
newFrame.Parent = InventoryContents
newFrame.Visible = true
newFrame.Buttons.Drop.Activated:Connect(function()
local dropArgs = {
Item = value,
Amount = 1
}
UpdateFunc:InvokeServer("Drop", dropArgs)
end)
if value.Type ~= "Tool" and value.Type ~= "Apparel" then
newFrame.Buttons.Equip.Visible = false
end
end
if Inventory:FindFirstChild("Weight") then
Inventory.Weight.Text = `Weight: {updatedInv.TotalWeight}/{updatedInv.MaxWeight}`
end
end
-- Button behavior
local function ButtonTriggered()
local UpdatedInv = UpdateFunc:InvokeServer("Get")
UpdateInventory(UpdatedInv)
end
-- Event connections
UpdateEvent.OnClientEvent:Connect(UpdateInventory)
I get i’m probably doing horrible practices, and ai too, but I genuinely just want to get this thing over with so i could continue working on other things. Please, i am begging anyone to pitch in and help me out here.
sincerely, thank you.
just ask or say so if you need any additional scripts. all and any help is accepted and awaited.