# -A Guide on Rarities-

Let’s talk about rarities. The rarity system that I’ll go over in this post is a loot table based rarity system meaning

``````Legendary = 0.1%, Rare = 20%, Common = 75%, etc.
``````

This isn’t going to be a rarity system like the ones used by those rolling “games” in which the rarities are based off of 1/(input any number here).

Loot tables are very easy, accurate, and manageable. So they’re good for things like daily spin rewards, chest drops, enemy drops, etc.

What are weights?
Weights are basically the rarity you give an item in your loot table, the higher the weight the more common it is.

A good example would be, having a bag(this being our loot table) and putting 50 (Our weight) blue marbles into it, 20 yellow marbles, and 5 green marbles. When we reach into the bag there’s a greater chance to grab the blue marble than anything else.

In code it’d look like this

``````local LootTable = {
{name = "Blue Marble", weight = 50}, -- Name of item, Amount of weight
{name = "Yellow Marble",  weight = 20},
{name = "Green Marble", weight = 5},
}
``````

How to turn this into percentage?
To get the percentages for each item in our loot table we’ll have to add up all the weights to get a total weight, the reason? Adding up all the marbles then dividing the marble were trying to find we’ll give us a percentage on finding that marble out of all the other marbles.

``````50 + 20 + 5 = 75
``````

Say we want to find out the percentage of the blue marble

``````(50/75)*100 = 66.6 -- 50 is the weight of the item, 75 is the total weight of everything in the table, and we times it by 100 to give us a whole number.
66.6% chance to get the blue marble
``````
``````function RaritySystem.TotalWeight(LootTable) -- Our loottable
local TotalWeight = 0 -- Base 0
for i, item in (LootTable) do -- Loops through our table to get the items
TotalWeight = TotalWeight + item.weight -- Adds item weight to the total weight
end
return TotalWeight -- Returns the total weight when we call this function
end
``````

Our total weight is one of the most important processes to a rarity system. It’s what makes a weighted loot table work, adding all the weights gives us a number to compare the items with.

``````function RaritySystem.CalculateAndDisplayChances(LootTable)
local TotalWeight = RaritySystem.TotalWeight(LootTable)
for _, Item in ipairs(LootTable) do
local chance = (Item.weight / TotalWeight) * 100
warn(Item.name .. ": " .. tostring(string.format("%.4f", chance)) .. "% chance")
end
end
``````

How to actually roll the loot table
Okay, now we’re getting to the most complicated part, which isn’t that complicated.

``````function RaritySystem.SelectRarity()
local RandomValue
local SelectedRarity
local TotalWeight = RaritySystem.TotalWeight(LootTable) -- Send the loottable
``````

You can ignore randomvalue and selectedrarity for now, the first thing we do is get the total weight of the loot table.

``````function RaritySystem.SelectRarity(Rarities)
local RandomValue
local SelectedRarity
local TotalWeight = RaritySystem.TotalWeight(LootTable)
repeat
RandomValue = RandomSeed:NextNumber(0, 1)
``````

Here’s our RandomValue which is a decimal number between 0 and 1, meaning we could get an number like 0.986534, the reason we need this will come up in the next section also pay attention that were putting this in a repeated loop

``````function RaritySystem.SelectRarity(LootTable)
local RandomValue
local SelectedRarity
local TotalWeight = RaritySystem.TotalWeight(LootTable)
repeat
RandomValue = RandomSeed:NextNumber(0, 1)

for _, Item in (LootTable) do
if RandomValue <= Item.weight / TotalWeight then
SelectedRarity = Item
break
else
RandomValue = RandomValue - Item.weight / TotalWeight
end
end
``````

You’ll notice after getting a random number we then loop through the loot table, and the if statement here is one of the most important parts

``````if RandomValue <= Item.weight / TotalWeight then
``````

So lets put this in perspective lets say the RandomValue = 0.672, we then loop through the table which checks each item to see if its percentage is greater than the items percentage. Aka. Blue marble is 0.66, so it looks like this

``````if 0.672 <= 0.66 then
``````

Which isn’t true so we go the else part of the if statement

``````RandomValue = RandomValue - Item.weight / TotalWeight
``````

What this does now is makes

``````RandomValue = 0.672 - 0.66 = 0.012
``````

Which now we compare our next item which is the yellow marble

``````if 0.012 <= 20/75 (0.26) then
``````

This statement is true, so the selected item will be the yellow marble and then we break the loop.

Let’s see the whole function

``````function RaritySystem.SelectRarity(LootTable) -- Our loot table
local RandomValue -- A randomvalue
local SelectedRarity -- The item thats selected
local TotalWeight = RaritySystem.TotalWeight(LootTable) -- Total weight of all items
repeat
RandomValue = RandomSeed:NextNumber(0, 1) -- Gets a random number between 0 and 1

for _, Item in (LootTable) do -- Loops through our loot table
if RandomValue <= Item.weight / TotalWeight then -- Compares looped item with the random value
SelectedRarity = Item
break
else
RandomValue = RandomValue - Item.weight / TotalWeight
end
end

until SelectedRarity ~= nil -- if no selection then repeat

return SelectedRarity
end
``````

The reason this is in a repeated loop is because there can be rare cases in which no item is selected in that case we’ll just loop until it does.

The whole minusing the random value by the item’s percentage is a little weird to explain but basically think of it like giving the other items in the loot table a better chance to get picked.

There’s a few things not mention in detail but nothing too important also if you’re curious on what’d you need for the green marble to be picked is

``````if RandomValue(0.94) <= 0.66(Blue marble)
RandomValue = 0.94 - 0.66 = 0.28
...
if RandomValue(0.28) <= 0.26(yellow marble)
RandomValue = 0.28 - 0.26 = 0.02
...
if RandomValue(0.02) <= 0.06(green marble) then
SelectedItem = Green Marble
``````

So you’d need to roll a 0.93 or higher and the green marbles rarity is
6.6% chance.

10 Likes

Now implement luck with this

1 Like

Can’t you just say:

``````local number = math.random(1,100)
if number == 1 then
print("1%")
elseif number >= 2 and number <= 16 then
print("15%")
else
print("84%")
end
``````
1 Like

This will get pretty hard to manage when scaling, I do not recommend you do this.

3 Likes

That’s a very good question with a simple fix, if you want to add luck into a system like this it’s as easy as adding a luck multiplier to the RandomValue since the higher the decimal is for the RandomValue the rarer the stuff you can get

``````local luckMultiplier = 1 + (math.clamp(math.ceil(usersLuck/100), 0, math.huge)) --(1 + 5/100 = 1.05)
RandomValue = RandomValue * luckMultiplier
``````
1 Like

As a previous comment stated this can be a pain to do when you have multiple loot tables. Let’s say you have 20 different mobs in your game and you want to give them all a loot table you wouldn’t want to create a ton of if statements for each mob. Also a rarity system like this is a lot more customizable and modular.

You’d also have to do the calculations for each item you want and math.random(1,100) isn’t as precise as using a random seed which can give you decimal values. Allowing for you to get stuff with a 0.01% chance of dropping, etc.

1 Like

Would such a system work when you are attempting to use rarities up to the 64-bit integer limit? I fear floating-point imprecision would throw off said rarities.

Yes, I also believe that if the rarity chance is insanely low there will be floating point imprecisions, although the size of the impercision I believe wouldn’t be too vast. Hard to tell unless I did some testing with it.

2 Likes