How's this Item system so far?

This is based similarly to how minecraft does their items, but i’m adding a few extra features. Almost all of these files are replicated except for the very last one for spawning items. The client will create their own version of the item when prompted, and will also change the GUID of the item to the one on the server.

Item Config File / ItemInit

local Item = require(script.Parent.Parent.Item.Item)
local ItemSword = require(script.Parent.Parent.Item.ItemSword)
local EnumMaterial = require(script.Parent.Parent.Util.EnumMaterial)
local EnumRarity = require(script.Parent.Parent.Util.EnumRarity)

return {
	["leather"] = function() return Item.new("leather", 10, EnumRarity.Common) end,
	["iron_sword"] = function() return ItemSword.new("iron_sword", 100, EnumRarity.Uncommon, EnumMaterial.Iron) end,
}

Base Item Class

-- Authorized Use By Dissonant Realms and Galicate
-- Programmer : Galicate @ 6/20/2024 2:51 PM

local HttpService = game:GetService("HttpService")
local Item = {}

local REGISTRY = {}

function Item.new(itemId: string, value: number, rarity: {})
	local self = setmetatable({}, Item)
	
	self.Id = itemId
	self.GUID = HttpService:GenerateGUID()
	self.BaseValue = value
	self.Rarity = rarity
	
	REGISTRY[self.GUID] = self
	
	return self
end

function Item:GetId()
	return self.Id
end

-- SHOULD ONLY BE USED BY THE CLIENT WHEN RECIEVING A GUID FROM THE SERVER
function Item:_SetGUID(guid: {})
	self.GUID = guid
end

function Item:GetGUID()
	return self.GUID
end

-- Returns the item from the provided GUID
function Item.GetItemFromGUID(guid: string)
	return REGISTRY[guid] or nil
end

-- Returns the Id of the item from the provided GUID
function Item.GetIdFromItemGUID(guid: string)
	return Item.GetItemFromGUID(guid).Id
end

function Item:GetBaseValue()
	return self.BaseValue
end

-- Item Value = Base Value + Rarity Value + Enchantments Value
function Item:GetTotalValue()
	return self:GetBaseValue() + self:GetRarity():GetValue()
end

function Item:GetRarity()
	return self.Rarity
end

Item.__index = Item
return Item

Material Enum Class

local Material = {}
Material.__index = Material

function Material.new(name: string, durability: number)
	local self = setmetatable({}, Material)
	
	self.Name = name
	self.Durability = durability or 0
	
	return self
end

function Material:GetName()
	return self.Name
end

function Material:GetDurability()
	return self.Durability
end

-- MaterialName, Durability
local EnumMaterials = {
	Leather = Material.new("Leather", 100),
	Wood = Material.new("Wood", 250),
	Iron = Material.new("Iron", 500)
}
return EnumMaterials

Spawning Items

function ItemService.SpawnItem(itemId: string)
	local item = ItemsInit[itemId]()
	return item
end

local sword = self.SpawnItem("iron_sword")
local leather = self.SpawnItem("leather")

image
image

1 Like

GUID is not guaranteed to be unique. It’s just a large randomly generated string.

It would be better to count the number of items that have been dropped; and to make that the unique Id.

Otherwise, if you wanted to save this data, and preserve uniqueness across servers, then there’s two common approaches. The first being to count the number of servers created in a datastore; and to track both the server the item was created in, as well as drop id from that server. Whereas the second would be to implement session locking, track the player who originally collected the item, and instead count how many items that player had collected.

GUIDs are so long the risk of it happening is extremely low, but what I can do is when I create an item or load it from someones inventory, check if an item in the server is already using that GUID, then change the GUID of the new item.

I really don’t need the GUIDs for anything but referencing them, so I don’t need to save them, since I can just do table[1] = item for my data, and then just recreate the item class with its data when I load it. No need to count the number of servers when I can just re-write the GUID when necessary.

But thank you for the input it’s really valuable.

You should still totally be able to leverage this. Even if you only need to preserve uniqueness within a session, an incrementing Id would serve the same purpose, and constitute much less data than a GUID string. This implies cheaper remote communications. Which seems important, considering that from what you’ve told me, it seems the sole use-case here, is to ensure that the client and server are referring to the same item.

Looks good, but just remember there is a small chance of the same GUID happening, I know the chances are low but still I’m just reminding you.

I should be able to solve that by just re-writing the GUID if I find an item with that existing GUID.

1 Like

I don’t understand why using GUID would be advantageous here in the first place though? GUIDs are 36 character strings. If you just count, you’ll accomplish the same, and end up with a much smaller unique value.

Replication to the client. If a player joins after an item is made, and the server tells the client that something needs to happen with a certain item, It’s likely that the clients REGISTRY[1] is different from the servers.

I think you misunderstand me. The client would not be keeping an independent count. The server is the only one counting; and so all Ids should always be consistent across the client/server boundary. A client that joins after an item is made, would have to retrieve that item’s Id either way. And an incrementing numerical Id would be incomparably cheaper to send across the network, than a 36 character string, every time that a change needs to be replicated.

The client needs to have its own table to hold the data as well, it would be horrible having to send the entire registry to the client everytime you need to tell the client something, an incremental Id could be accidentally overwritten if I used anything other than REGISTRY[#REGISTRY + 1].

It wouldn’t make sense to be going off of the length of the registry table. You’d have a static count variable on the server, which you’d only ever increment. You wouldn’t be treating the registry as a standard array with either approach. Just as with using a GUID string, the Id is purely for lookup.

I don’t see why you would have to send over the entire registry with every change? Just as you’re doing in your current approach, the unique Id is a property of the item. And this doesn’t change.

Okay so lemme restart. I have a table called REGISTRY, there’s both a client and server version because the item classes are shared. When the server creates an item, it tells all clients to create their own version of the item using a GUID, and any attributes it sends over. The GUID is to make sure that if the server needs to tell a client to change something about their item, there’s no issue with the items being the wrong index in the table.

Items are only saved in player inventories, and since you can’t save metatables, I’ll just save the item ID, NOT the GUID, and any attributes it has. When the player rejoins it’ll tell the server to recreate those items with the itemid and attributes.

The item ID is different from the guid, an example would be “iron_sword”.

1 Like

I think I see some of the confusion. To be clear, when I say Id, I’m referring to ‘unique Id’—the same purpose you were fulfulling using the GUID string. It’s apparent from your code that item classes also have a separate Id, but that’s not really relevant to what I’m trying to explain.

When a player’s inventory is saved, the server is the one that has to retrieve it from datastores, when that player joins in. It could just as well treat these as newly spawned items, and use the count to assign them a unique numerical Id for that server. Then, for any changes you would need to replicate, you’d only need to send that Id; and the client, knowing the Id of every item the server has told it exists (since Id is, in a computational sense, a property of that item), would know exactly what the server is referring to.