Here is a system I’ve been working on (this is its first public appearance, so keep in mind it could be improved, and I’ve simplified it to ease the process of adapting it to your needs):
Source Code
local function ChanceTable(chances)
local total = 0
for _, v in chances do
total += v
end
assert(math.abs(total - 1) < .001, `chances do not add up to 1: {table.concat(chances, ', ')}`)
return chances
end
local CHANCE_TABLES = {
["FoodItemsNormalChances"] = ChanceTable({
-- !!WARNING!! These chances must add up to 1
-- !!WARNING!! These chances must add up to 1
-- !!WARNING!! These chances must add up to 1
.5, -- 50% chance for "Common"
.3, -- 30% chance for "Uncommon"
.15, -- 15% chance for "Rare"
.04, -- 4% chance for "Epic"
.01 -- 1% chance for "Legendary"
})
}
local RARITY_FAMILIES = {
["FoodItems"] = {
{
Name = "Common",
Items = {"Apple (common)", "Banana (common)"}
},
{
Name = "Uncommon",
Items = {"Orange (uncommon)", "Strawberry (uncommon)"}
},
{
Name = "Rare",
Items = {"Raspberry (rare)", "Blackberry (rare)"}
},
{
Name = "Epic",
Items = {"Mango (epic)", "Kiwi (epic)"}
},
{
Name = "Legendary",
Items = {"Dragonfruit (legendary)", "Tomato (legendary)"}
}
}
}
local ItemRollUtil = {}
function ItemRollUtil.getRandomRarityTable(
rarityFamilyName: string, -- The name of the rarity family found in `RarityFamilies`
chanceTableName: string, -- The name of the chance table found in `ChanceTables`
chanceBoost: number?, -- A number from 0 to 1 (or higher than 1) determining how much to average out the chances
seed: number? -- Any number for repeated results
)
local chanceTable = CHANCE_TABLES[chanceTableName]
assert(chanceTable, "invalid chance table name")
local rarityFamily = RARITY_FAMILIES[rarityFamilyName]
assert(chanceTable, "invalid rarity family name")
assert(#chanceTable == #rarityFamily, `chance table {chanceTable} and rarity family {rarityFamilyName} do not have the same size`)
local rand
if seed then
rand = Random.new(seed):NextNumber()
else
rand = math.random()
end
local weightedChance = 1
if chanceBoost and chanceBoost ~= 0 then
local averageRarityPercent = 1/(#chanceTable-1)
for i, chance in ipairs(chanceTable) do
weightedChance -= chance + chanceBoost*(averageRarityPercent - chance)
if rand > weightedChance then
return rarityFamily[i]
end
end
else
for i, chance in ipairs(chanceTable) do
weightedChance -= chance
if rand > weightedChance then
return rarityFamily[i]
end
end
end
end
function ItemRollUtil.getRandomItem(
rarityFamilyName: string, -- The name of the rarity family found in `RarityFamilies`
chanceTableName: string, -- The name of the chance table found in `ChanceTables`
chanceBoost: number?, -- A number from 0 to 1 (or higher than 1) determining how much to average out the chances
seed: number? -- Any number for repeated results
)
local randomRarityTable = ItemRollUtil.getRandomRarityTable(rarityFamilyName, chanceTableName, chanceBoost, seed)
local rand
if seed then
rand = Random.new(seed):NextInteger(1, #randomRarityTable.Items)
else
rand = math.random(1, #randomRarityTable.Items)
end
return randomRarityTable.Items[rand]
end
function ItemRollUtil.simulate(
rarityFamilyName: string, -- The name of the rarity family found in `RarityFamilies`
chanceTableName: string, -- The name of the chance table found in `ChanceTables`
numberOfRolls: number, -- The number of item rolls to simulate
chanceBoost: number? -- A number from 0 to 1 (or higher than 1) determining how much to average out the chances
)
local timesPicked = {}
for _ = 1, numberOfRolls do
local randomRarityTable = ItemRollUtil.getRandomRarityTable(rarityFamilyName, chanceTableName, chanceBoost)
timesPicked[randomRarityTable.Name] = (timesPicked[randomRarityTable.Name] or 0) + 1
end
-- Results
print("Simulation of", numberOfRolls, "Item Rolls:")
for k, v in timesPicked do
print(math.round(((v or 0)/numberOfRolls)*10000)/100 .. '%', k)
end
end
return ItemRollUtil
How to use
First setup your items and their chances (this example is already in the above source code):
local CHANCE_TABLES = {
["FoodItemsNormalChances"] = ChanceTable({
-- !!WARNING!! These chances must add up to 1
-- !!WARNING!! These chances must add up to 1
-- !!WARNING!! These chances must add up to 1
.5, -- 50% chance for "Common"
.3, -- 30% chance for "Uncommon"
.15, -- 15% chance for "Rare"
.04, -- 4% chance for "Epic"
.01 -- 1% chance for "Legendary"
})
}
local RARITY_FAMILIES = {
["FoodItems"] = {
{
Name = "Common",
Items = {"Apple (common)", "Banana (common)"}
},
{
Name = "Uncommon",
Items = {"Orange (uncommon)", "Strawberry (uncommon)"}
},
{
Name = "Rare",
Items = {"Raspberry (rare)", "Blackberry (rare)"}
},
{
Name = "Epic",
Items = {"Mango (epic)", "Kiwi (epic)"}
},
{
Name = "Legendary",
Items = {"Dragonfruit (legendary)", "Tomato (legendary)"}
}
}
}
Then use the module from a script:
local ItemRollUtil = require(script.ItemRollUtil)
-- Will print results of picking random rarity 100,000 times to see if our chances are working properly
ItemRollUtil.simulate("FoodItems", "FoodItemsNormalChances", 100_000)
-- Will pick a random item
print(ItemRollUtil.getRandomItem("FoodItems", "FoodItemsNormalChances"))