Finding specific instance of an item within a table without using GUIDs

So I am making an inventory system and have it properly saving each instance with a weapon ID and its own special stats (damage, rarity, quality etc) but when I want to remove an item from a players inventory, I have no way of identifying which one of said item is the exact one they want to remove.

Say a player tries to delete a common iron sword with 2 damage. I want to make sure that one is deleted instead of another iron sword with different stats he might have. I figured I could accomplish this by creating a GUID for each unique instance of a weapon and then store it both in data service and the physical object and just compare the two when deleting but I believe that the data footprint of that might be too large if there are too many weapons. Does anyone have any alternatives?

Here is an example of what an item would be saved as

local ItemDatabase = {
	[1] = {
		["ID"] = 1,
		["ItemName"] = "Leather Gloves",
		["Model"] = Assets.Fist.Models.Fist,
		["Stats"] = {
			["DamageMultiplier"] = 0,
			["HP"] = 10,
			["AttackSpeed"] = 0
		},
	},

Keep in mind the ID only corresponds to the base item I.e Leather gloves.

I suggest saving the same weapons like:
["Weapon1"] = 3 – how many you have
and placing properties for each weapon inside a module.

If you want a UUID for each weapon you can loop through the data table to find it:

for i, weapon in pairs(your_table) do
      if weapon["ID"] == id then
         pathtoweapon:Destroy()
   end
end

The problem is that each instance of the weapon is different, even if it is the same base weapon. Because of this, just deleting items based off the weapon ID won’t work.

You cannot save instances in datastores, only tables, strings, booleans and numbers. I suggest changing the current method you are using.

Before the runtime ends, I convert each stat or unique quality to a string, integer, or table and store it that way. I’ve already achieved that but am now trying to find a way to correctly distinguish between each saved instance (now portrayed as strings, integers, or tables like in my example) to delete or equip a certain one

Assign a runtime UID. It’s as simple as an ascending integer. You might as well use the index of the rendered/selected item

1 Like

You can still use a GUID, but you don’t need to store it. Assuming all your interactions with the object don’t care about maintaining a reference between serialization and deserialization steps, then when you serialize it, just don’t include a GUID. When you deserialize it, generate a new GUID.

Similarly, if you wanted to avoid a GUID, you could use a reference to the model for the same reason. This is possibly even preferable, since it saves a lookup when you need to access the model from the data object in memory.

1 Like

If I didn’t store the ID how would I be able to determine the difference after a player leaves and reloads their inventory?

Saving instances is especially hard because they dont have a specific UniqueId as of right now. You can try setting an attribute to each item but that has caused me problems before. Can I get a little bit more context on what you are going for here? Are thr instances you are attempting to save pre-made or made in-game

1 Like

You defeat an enemy twice. The first time he drops a Iron sword (ID:1, Rarity: Common) and the second time he drops a iron sword again but with (ID:1, Rarity: Rare). I can properly save these values to datastore service and load them back in in bulk but if i need to access one specific version of an item that a player obtained, say the Rare sword, I cant figure out a way to

I’m not sure what your serialization/deserialization steps look like, but I imagine something like this:

local DataStoreService = game:GetService("DataStoreService")
local ServerStorage = game:GetService("ServerStorage")

local dataStore = DataStoreService:GetDataStore("Inventory")

local ITEM_STORAGE_FOLDER = ServerStorage.ItemStorageFolder

local inventoryByPlayer = {
    -- [player1] = { -- Example inventory
    --     [swordModel1] = {
    --         itemType = "Sword",
    --         damage = 10,
    --         instance = swordModel1
    --     },
    --     [swordModel2] = {
    --         itemType = "Sword",
    --         damage = 50,
    --         instance = swordModel2
    --     }
    -- },
}

local function saveInventory(player)
    local serializedItemData = {}
    for _, itemData in inventoryByPlayer[player] do
        table.insert(serializedItemData, itemData)
    end
    dataStore:SetAsync(player.UserId, serializedItemData)
end

local function loadInventory(player)
    local inventory = {}
    local serializedInventory = dataStore:GetAsync(player.UserId)

    for _, itemData in serializedInventory do
        local itemModel = ITEM_STORAGE_FOLDER:FindFirstChild(itemData.itemType):Clone()
        itemData.instance = itemModel
        inventory[itemModel] = itemData
    end

    return inventory
end

… obviously with better data handling practices than this example

1 Like

You can save each item with a UUID. When loading in you can loop through the inventory to equip the items, you just have to save the model name, find the model, clone it and place it in the inventory. The UUID would only be used to distinguish 2 of the same swords obtained

1 Like

Slight issue here, instances cannot be saved in datastores.

1 Like

His code implies that the data is serialized before storage

Yeah that’s fine, the instance is a value which will just be nil when saved to data, effectively making that instance key/value pair disappear from the saved data. But we don’t care, because it gets populated with a new instance when deserialized.

So this might work if the item is loaded in as a physical item, but what about if it is a UI element like it is in my instance?

The data is all serialized and unserialized through int values, string values, and the weapon’s model (which is only used for the viewport frames and is identical to all others as you can see)

Good point, but isnt the instance we are assigning to just the base item name as I have seen from your script?

1 Like

Instance is a clone of some prefab/template item somewhere. The point is it’s a unique instance you could then go on to modify, like set the color of the sword based on damage property or something.

1 Like

Thanks for clarifying, I was a bit confused becsuse you did not state any instance properties in the script.

If your items aren’t customized individually, but rather are classed by a category (like itemType + itemRarity) you can consider each of those categories to be a unique type, and that allows you to store your inventory as a count which is very efficient. You can then have a lookup table of data for each category, like look up damage for a RareSword from some constant centralized table, for example.

When deserializing, you can generate unique guids for each object and use that as the item reference in your code. Here’s a relevant example

local DataStoreService = game:GetService("DataStoreService")
local HttpService = game:GetService("HttpService")
local ServerStorage = game:GetService("ServerStorage")

local dataStore = DataStoreService:GetDataStore("Inventory")

local GUI_OBJECT_TEMPLATE_FOLDER = ServerStorage.GuiObjectTemplateFolder

local STATS_BY_ITEM_TYPE = {
    CommonSword = {
        DamageMultiplier = 0,
        HP = 10,
        AttackSpeed = 0
    },
    RareSword = {
        DamageMultiplier = 2,
        HP = 100,
        AttackSpeed = 20
    },
}

local inventoryByPlayer = {
    -- [player1] = { -- Example inventory
    --     CommonSword = { guid1, guid2 },
    --     RareSword = { guid3 },
    -- },
}

local function saveInventory(player)
    local inventory = inventoryByPlayer[player]
    local serializedItemData = {}

    for itemType, itemGuids in inventory do
        serializedItemData[itemType] = #itemGuids
    end
    dataStore:SetAsync(player.UserId, serializedItemData)
end

local function loadInventory(player)
    local serializedInventory = dataStore:GetAsync(player.UserId)
	local inventory = {}

    for itemType, itemCount in serializedInventory do
        local itemGuids = {}
        
        for _ = 1, itemCount do
            table.insert(itemGuids, HttpService:GenerateGuid(false))
        end

        inventory[itemType] = itemGuids
    end

    return inventory
end


local guiObjectsByGuid = {
    -- [guid1] = guiObject1,
    -- [guid2] = guiObject2,
}
local function drawInventory(inventory)
    local isGuidInInventory = {}
    
    -- Draw new items
    for itemType, itemGuids in inventory do
        for _, guid in itemGuids do
            isGuidInInventory[guid] = true
            local existingGuiObejct = guiObjectsByGuid[guid]
            if not existingGuiObejct then
                local guiObjectTemplate = GUI_OBJECT_TEMPLATE_FOLDER[itemType]
                local guiObject = guiObjectTemplate:Clone()
                guiObject.TextLabel.DamageMultiplier.Text = STATS_BY_ITEM_TYPE[itemType].DamageMultiplier
                guiObjectsByGuid[guid] = guiObject
            end
        end
    end
    
    -- Destroy items no longer in inventory
    for guid, guiObject in guiObjectsByGuid do
        if not isGuidInInventory[guid] then
            guiObject:Destroy()
            guiObjectsByGuid[guid] = nil
        end
    end
end
1 Like