my code adds items to a player’s inventory, and I find the way I’m currently doing it to feel really messy
(fetches inventory > updates cache (datastore2 saving))
OLD CODE
the way im currently doing it
local Players = game:GetService("Players")
local Quire = _G.Quire
local DataStore2 = Quire("DataStore2")
local Enums = Quire("Enums")
local NetworkUtil = Quire("NetworkUtil")
local ItemInfos = Quire("ItemInfos")
local SessionData = Quire("SessionData")
local ItemFunctions = Quire("ItemFunctions")
local repr = require(3148021300)
local INVENTORY_KEY = Enums.DataKeys.InventoryKey
local DEFAULT_INVENTORY = {
Slots = {
[1] = {
IsHotSlot = true,
Item = nil
},
[2] = {
IsHotSlot = true,
Item = nil
},
[3] = {
Item = nil
},
[4] = {
Item = nil
},
[5] = {
Item = nil
},
[6] = {
Item = nil
},
[7] = {
Item = nil
},
[8] = {
Item = nil
},
}
}
local InventoryService = {}
local function loadPlayerInventory(player)
local inventory = DataStore2(INVENTORY_KEY, player):GetTable(DEFAULT_INVENTORY)
for _, slotData in ipairs(inventory.Slots) do
local item = slotData.Item
if item then
ItemFunctions.SetItemInfo(item)
end
end
InventoryService:AddItem(player, 1, 15)
end
function InventoryService:AddItem(player, ID, amount)
DataStore2(INVENTORY_KEY, player):Update(function(inventory)
amount = amount or 1
local itemInfo = ItemInfos:GetInfoFromID(ID)
assert(itemInfo, "Invalid Item")
local inventorySlots = inventory.Slots
local function transferAmountToItem(item, amountToTransfer)
ItemFunctions.IncreaseItemStackSize(item, amountToTransfer)
amount = amount - amountToTransfer
end
(function()
-- adds amountToAdd to open stacks of same id
if ItemFunctions.IsItemStackable(itemInfo) then
for slotIndex, slotData in ipairs(inventorySlots) do
local item = slotData.Item
local itemIsID = item and ItemFunctions.IsItemID(item, ID)
local amountToTransfer = itemIsID and math.clamp(amount, 0, ItemFunctions.GetOpenStackSize(item))
if amountToTransfer then
transferAmountToItem(item, amountToTransfer)
if amount == 0 then return end
end
end
end
-- adds items to empty slots
for slotIndex, slotData in ipairs(inventorySlots) do
if not slotData.Item then
local item = {ItemInfo = itemInfo, ID = ID, StackSize = 0}
transferAmountToItem(item, math.clamp(amount, 0, ItemFunctions.GetOpenStackSize(item)))
slotData.Item = item
if amount == 0 then return end
end
end
end)()
print(repr(inventory, {pretty = true}))
return inventory
end)
return amount
end
Players.PlayerAdded:Connect(loadPlayerInventory)
return InventoryService
the main part I’m finding messy is the getting/saving data with datastore2 for every function that is directly called and manipulates player data
Newer Code
Each player has a inventoryCache, the inventory has slots which always exist, and each slot can have an item, each item has an ID, StackSize, and a metatable linking to a iteminfo table (MaxStackSize, ItemClasses, Damage, etc)
in my previous code I was passing the player as a parameter and using Datastore:Update() to get the cache for every method and save it
now what I am doing is calling methods with a inventoryCache parameter instead of player, and it has a metatable which allows me to get the owner of a inventoryCache by doing inventoryCache.Owner which allows me to not have to pass the player as another parameter
I was hesitating a bit to do a cache system on top of the cache system datastore2 already has, but doing so has allowed me to make more use of metatables, and looks cleaner
e.g
InventoryService:AddItem(inventoryCache, ID, amount)
Inventory Service
local Players = game:GetService("Players")
local Quire = _G.Quire
local DataStore2 = Quire("DataStore2")
local Enums = Quire("Enums")
local NetworkUtil = Quire("NetworkUtil")
local ItemInfos = Quire("ItemInfos")
local SessionData = Quire("SessionData")
local ItemFunctions = Quire("ItemFunctions")
local repr = require(3148021300)
local INVENTORY_KEY = Enums.DataKeys.InventoryKey
local DEFAULT_INVENTORY = {
Slots = {
[1] = {
IsHotSlot = true,
Item = nil
},
[2] = {
IsHotSlot = true,
Item = nil
},
[3] = {
Item = nil
},
[4] = {
Item = nil
},
[5] = {
Item = nil
},
[6] = {
Item = nil
},
[7] = {
Item = nil
},
[8] = {
Item = {ID = 1, StackSize = 1}
},
}
}
local InventoryService = {InventoryCaches = {}}
function InventoryService:GetInventoryCache(player)
return self.Inventories[player]
end
function InventoryService:SaveCache(inventoryCache)
DataStore2(INVENTORY_KEY, inventoryCache.Owner):Set(inventoryCache)
end
function InventoryService:AddItem(inventoryCache, ID, amount)
amount = amount or 1
local itemInfo = ItemInfos:GetInfoFromID(ID)
assert(itemInfo, "Invalid Item")
local inventorySlots = inventoryCache.Slots
local function transferAmountToItem(item, amountToTransfer)
ItemFunctions.IncreaseStackSize(item, amountToTransfer)
amount = amount - amountToTransfer
end
-- function is here so I can return to stop both loops when amount == 0, without returning out of this method
(function()
-- adds amountToAdd to open stacks of same id
if ItemFunctions.IsStackable(itemInfo) then
for slotIndex, slotData in ipairs(inventorySlots) do
local item = slotData.Item
local itemIsID = item and ItemFunctions.IsID(item, ID)
local openStackSize = itemIsID and ItemFunctions.GetOpenStackSize(item)
local amountToTransfer = openStackSize and math.clamp(amount, 0, openStackSize)
if amountToTransfer then
transferAmountToItem(item, amountToTransfer)
-- TODO: Replicate item change
if amount == 0 then return end
end
end
end
-- adds items to empty slots
for slotIndex, slotData in ipairs(inventorySlots) do
if not slotData.Item then
local item = ItemFunctions.SetInfoMetatable({ID = ID, StackSize = 0})
transferAmountToItem(item, math.clamp(amount, 0, item.MaxStackSize))
-- TODO: Replicate item creation
slotData.Item = item
if amount == 0 then return end
end
end
end)()
self:SaveCache(inventoryCache)
print(repr(inventoryCache, {pretty = true}))
return amount
end
-- initial loading of inventory
Players.PlayerAdded:Connect(function(player)
local inventoryMetatable = {Owner = player}
inventoryMetatable.__index = inventoryMetatable
local inventoryCache = DataStore2(INVENTORY_KEY, player):GetTable(DEFAULT_INVENTORY)
setmetatable(inventoryCache, inventoryMetatable)
ItemFunctions.SetAllInfoMetatable(inventoryCache.Slots)
InventoryService.InventoryCaches[player] = inventoryCache
InventoryService:AddItem(inventoryCache, 1, 3)
end)
Players.PlayerRemoving:Connect(function(player)
InventoryService.InventoryCaches[player] = nil
end)
return InventoryService
ItemFunctions Module
local Quire = _G.Quire
local ItemInfos = Quire("ItemInfos")
local SharedItemFunctions = Quire("SharedItemFunctions")
local ItemFunctions = setmetatable({}, SharedItemFunctions)
function ItemFunctions.IncreaseStackSize(item, amount)
item.StackSize = item.StackSize + amount
end
function ItemFunctions.GetOpenStackSize(item)
local openStackSize = item.MaxStackSize - item.StackSize
return openStackSize > 0 and openStackSize
end
return ItemFunctions
SharedItemFunctions Module
local Quire = _G.Quire
local ItemInfos = Quire("ItemInfos")
local SharedItemFunctions = {}
SharedItemFunctions.__index = SharedItemFunctions
function SharedItemFunctions.SetInfoMetatable(item)
return setmetatable(item, ItemInfos:GetInfoFromID(item.ID))
end
function SharedItemFunctions.SetAllInfoMetatable(inventorySlots)
for _, slotData in ipairs(inventorySlots) do
local item = slotData.Item
if item then
setmetatable(item, ItemInfos:GetInfoFromID(item.ID))
end
end
end
function SharedItemFunctions.IsStackable(item)
return item.MaxStackSize > 1
end
function SharedItemFunctions.IsID(item, ID)
return item.ID == ID
end
return SharedItemFunctions