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")
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.
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.
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”.
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.