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
task.wait()
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.