How to use IDs for data storage

So I just came to the painful realization that you can’t store physical instances (Makes sense because of the data load) so now I have to find a work-around. I was gonna set IDs to each item but I’m not sure the best way to do this as I have a few questions.

First, I am using modular items so each version of the basic “Sword” will be different. For example, it will have a random rarity that gives it randomized stats so sword 1 will be Rare with +12 dmg and sword 2 will be Common with +2 dmg and +2 HP. Not exactly sure how I would ID those.

Second, what would be the best way to create and access theses IDs? Would it be best to store my methods and actual IDs within a module script?

Thank you in advance!

make a table for each item the player has and nest more tables into those for modular stats, then write another function and use a pairs loop to see all the weapons with their stats in them

local tools = {
["Sword"] = {
["Damage"] = 13,
["HP"] = 1,
};

["Sword"] = {
["Damage"] = 10,
["HP"] = 3,
};
}
2 Likes

But how should I assign each unique weapon an ID and be able to recall that same ID upon joining the game back up again?

1 Like

Are you looking to have each individual object have its own ID?

Ex. Player 1 has a sword and its ID is 1, and Player two also has a sword but its ID is 2.

Or are you looking for more of a each tool has its own ID

Ex. Common Sword is ID 1 and Rare Sword is 3 for example

2 Likes

thats the thing, its not Just 6 rarities im accounting for. It would be 1 weapon → 6 rarities → 3 different stats → 300 different arrangements.

This is why I’m trying to get a second opinion before diving in because I wouldn’t be able to keep the uniqueness of each weapon by just defining “sword” or “rare sword”

1 Like

The context is saving in a datastore?

2 Likes

They’re all just descendants of the same thing. Generate a GUID for each item in your game. DO NOT do this during runtime; these IDs are set once in Roblox Studio and are final. You can use a script to auto-set the IDs for you. You can then save the items by mapping an array to the item ID, then storing dictionaries within that array which represent each descendant and its unique stats:

{
    [GUID] = {
        {
            X = ...,
            Y = ...,
        },
        {
            X = ...,
            Y = ...,
        },
    },

    [GUID] = {
        {
            X = ...,
            Y = ...,
        },
        {
            X = ...,
            Y = ...,
        },
    },
}

A GUID is used instead of a name so that anything about the item can change without inconvenience

1 Like

If I can’t generate a UUID during run time how can I define each item for each player if they are all different?

This is actually a very good question because games like the Diablo series from Blizzard-Activision do this. The only way this would work is if you make it so players cannot drop items because if they do, it would destroy the item. So in your basic weapon definition you would define a range of random values for a stat. You would ID this item only. Then when the player acquires the weapon, soul bind it so it cannot be traded. It can only be sold to a vendor or destroyed. Then within the player’s inventory, you would set a table which contains the actual values. Something like what @ew_iscool said. So something like this:


local function immolatePlayer(player: Player, target: Player, itemId: number, params: {})
	-- Code to light the target player on fire.
end

-- Enumerations
local enums = {
	-- Enumeration: Item Quality
	ItemQuality = {
		Poor = 0;			-- Grey
		Common = 1;		-- White
		Uncommon = 2;	-- Green
		Rare = 3;			-- Blue
		Epic = 4;			-- Purple
		Legendary = 5;	-- Orange
		Artifact = 6;		-- Yellow
		Moderator = 7	-- Red
		Developer = 8;	-- Cyan
		Variable = 9;		-- Based on item qualities above.
	};

	-- Enumeration: Item Type
	ItemType = {
		Melee =		100;
		Range =		102;
		Thrown =	103;
		Shield =		104;
		Head =		200;
		Neck =		201;
		Shoulder =	202;
		Chest =		203;
		Arm =		204;
		Wrist =		205;
		Finger =		206;
		Leg =		207;
		Feet =		208;
		Back =		209;
		Material =	300;
		Vanity =		400;
		Offhand =	500;
		Other =		600;
	};
}

-- Abilities Database
local abilityDatabase = {
	[#4B03003A] = {
		text = "Chance on hit: Sets the player on fire for %duration seconds dealing %damage fire damage."
		handler = immolatePlayer;
	};
}

-- Global Item Database
local itemDatabase = {
	[#0278F223] = {
		name = "Sword of Immolation";
		image = "rbxassetid://";
		mesh = "rbxassetid://";
		itemPath = "game.ServerStorage.Items.Weapons.Melee.SwordOfImmolation-0278F223"
		itemQuality = enums.itemQuality.Variable;
		variableQualityStats = {
			-- Common Quality
			[enums.ItemQuality.Common] = {
				damage = Vector2.new(9, 15);	-- Base weapon damage range (before modifiers)
				durability = Vector2.new(30, 30);	-- Current/Max weapon durability (0 = broken)
				vendorValue = 25;
				stats = {
					stamina = Vector2.new(3, 7);	-- Random stamina value.
					strength = Vector2.new(3, 7);	-- Random strength value.
					agility = 5;
					intelligence = 0;
					spirit = 3;
				};
				specialAbilities = {
					[#4B03003A] = {
						parameters = {
							chance = 5;
							duration = 10;
							damage = 10;
							tick = 2;
							cooldown = 15;
							timestamp = 0;	-- Unix timestamp (tick) of last activation time.
						};
					};
				};
			};

			-- Rare Quality
			[enums.ItemQuality.Common] = {
				damage = Vector2.new(25, 37);	-- Base weapon damage range (before modifiers)
				durability = Vector2.new(90, 90);	-- Current/Max weapon durability (0 = broken)
				vendorValue = 175;
				stats = {
					stamina = 15
					strength = 15;
					agility = 15;
					intelligence = 3;
					spirit = 8;
				};
				specialAbilities = {
					[#4B03003A] = {
						parameters = {
							chance = 25;
							duration = 15;
							damage = 25;
							tick = 2;
							cooldown = 25;
						};
					}
				};
			};
		};
	};
}

-- On a per player/instance basis
local inventory = {
	[#00278F223] = {
		quality = enums.ItemQuality.Rare;
		durability = 90
		stats = {};	-- Overrides default stats
		ability = {
			[#4B03003A] = {
				timestamp = 0;	-- Unix timestamp (tick) of last activation time.
			};
		};
	];
}

This is how I would do it. Stamina relates to how much HP the player has. So the player would have something like 5HP for each point of stamina. Strength is how hard a player hits in melee, how much a player blocks with a shield, and parry chance. Agility is how hard a player hits with ranged weapons (think bows and thrown weapons), and dodge chance. Intelligence dictates how hard a player hits with spells and how much mana a player has (depending on class). Spirit is how fast HP and mana regenerates. Mana is a resource for spell caster classes (mage, wizard, sorcerer, enchanter, warlock, etc…), but other classes can have other resources like rage (warrior), or focus (ranger/hunter). Of course, this comes from an insanely popular MMORPG known as World of Warcraft.

The way that this works is that you create an enumeration for the weapon quality. Then in the main item database, for each quality level of the item, you have various stats. I’ve included the possibility of unique item abilities in an ability database which contains the text of what the ability does and what the handler function for the ability is. This can allow for some pretty unique things. Parameters are unique for each ability.

You can make this as simple or as complicated as you want. But to answer your question, the basic idea is presented. If you plan to have a lot of different items, I would place them in the data store so you can access them by Id globally in your game. The unique part of the item would be stored on a per-player basis. It would still reference the item database to get other things, but your code would modify the stats based on weapon quality and any unique randomized stats. So in the example above, strength and stamina are random. So in the per-player data under stats, you would specify the strength and stamina values. Those would be used for the calculations and would also be displayed to the player.

Hope this helps.

1 Like

While you are correct that they are all descendants of the same thing, that thing is unique among different players. In other words, if you have 6,000 varieties of the same thing (different stats, quality levels, etc…), then each variety must be stored with the player who own that item. You can have a base template and store the modifications with the player. Furthermore, I would not use GUIDs for this because you have to compare them, and that takes significant processing time and power, not to mention memory and storage space. I would use a 32-bit integer as the item ID which is a much faster lookup and you can store the item definition in the data store with a minimal data footprint.

I would store them with a item id, sort of like minecraft; a readable yet code safe name. Then store necessary things along with it, such as the rarity. If the buffs are fixed you don’t need to store those and instead you figure them out by the rarity of the item, otherwise store buffs as well. Mainly you’re going to want to avoid storing unnecessary things such as the cost (assuming you can sell thigfs for money) of something and instead have a fixed price and calculate a new price based on the items level and rarity.

By that exact method. The item is not what you need to differentiate. It’s its stats that matter. All players can own the exact same sword, but have different stats. The method I proposed to you makes the idea of “sword” a globally identical thing, while the variations of that sword is what’s distinguished

The processing time is nowhere near the term “significant”. I ran benchmarks for both indexing into dictionaries and comparing values. The results leaned towards smaller 32-bit unsigned integers having a better average time, while an HttpService-generated GUID consistently lead in best total time. I admit, the latter statement does sound strange, but both benchmarking processes were built atop the same code, and --!optimize 0 was used. Altogether, the total difference between the two lied around 0.01 to 0.03 microseconds, which is in the realm of 1/100,000,000th of a second.

Regardless, both solutions are GUIDs, which is what I am ultimately getting at. I would advocate for 32-bit unsigned integers as yes, it has a smaller memory footprint, but in today’s available memory, it’s insignificant. Keeping track of what the last GUID was is what I’m worried about for OP

1 Like

If you want each copy of the item to have a unique identifier, you can use HttpService:GenerateGUID(false) that creates randomized GUID strings. If you want copies that are identical to have the same identifier, then figure out a way to combine their attributes. For example combining them in a string, hashing them or treating them like an N dimensional polynomial, where N is the number of attributes.

Interesting. Perhaps I’m old school in this regard, but I try to keep the memory footprint of programs to a minimum. Perhaps CPU instruction caching played a role in benchmarking. The instruction path to compare GUID’s vs uint32_t’s is much longer for a GUID. But, with the RISC core of many of today’s modern CPUs, the challenge is keeping the caches full.

With that in mind, with gigahertz clock speeds, a microsecond or two doesn’t seem like much, but if you repeat the operation millions of times over the lifetime of the program, it adds up to real savings in CPU processing bandwidth which translates into faster response times and the ability to be able to handle more players.

1 Like

Thanks for the replies! I’ll take a further look into your code when I am home from work :pray:

I agree, but once again, the benchmarking results were sourced from 1e6 (1 million) sequential cycles. This is an extremely unrealistic real-world load from how either type will be utilized. This may be more relevant in extreme performance-dependant environments, but we haven’t seen those since Apollo 11’s specs. The savings over time compared to how often data is loaded is now in the nanosecond realm, still making no difference. With this type of micro-optimization, it’s better to consider what’s more convenient to implement. I call back to OP potentially struggling with keeping track of the latest GUID

1 Like

Can you explain the enumeration piece a little more? Also how did you get the codes for the item/ability? Did you manually set it to a random string? [00278FF23] for example. Thank you for the help!

Those codes are GUIDs. The idea behind enumeration is to pre-create static variants of the item, which in @Maelstorm_1973’s example was quality. There are poor-quality variants, which all have the same base stats, common-quality variants, which all have the same base stats, and then some. The overarching item is stored via its GUID (aka “Sword”), and its quality is associated via enumeration. This allows you to reduce the amount of data stored by only making variations on existing, tiered variants of your item. The overridden stats of the tiered variant is what’s stored, and the rest are inherited from the item database

1 Like

Thanks for clearing that up. So the item GUID is generated once and then the children stats are altered later on to save data usage while achieving the modular stats. Thanks for this.