Digging for RNG Solutions

This is a remake of a post cause I;

  1. Still Dont Understand :sob:
  2. Think it was worked weirdly.

Basically I’m trying to make a game where the player drills into the ground and randomly gets stuff, getting enough will allow the player to craft better gear, process goes on and on. blah blah :speaking_head:

I’m just kind of confused how a rarity system would work with a game with make items with different rarities, they wont be categorized either so its not as simple as I originally thought. I was originally thinking of cycling through 1/2, if it rolled 1 go onto the next rarity, say 3 if it rolls another 1 go to the next and so on. :recycle:

I tried implementing this already but had no luck getting it to work. If anybody knows if I’m right I’m dumb and dont understand basic common sense, input would be appreciated. :sunglasses:

2 Likes

This may help you. If it doesn’t, tell me.

These post should help.

1 Like

These aren’t exactly what I’m looking for, I’m more looking for a system that cycles through each rarity by 1/2 to say all the way to 1/10000. So basically cycles through chances until your luck runs out, then uses that one as the final result :money_mouth_face::grimacing:

On second thought I’m just kinda looking for a system that accurately uses those fractions to tell you how lucky you were, so like you got a 1/64 chance glass shard or smth :rose::astonished:

Like this?

local rarities = {
    {name = "Mythical", chance = 1/10000},
    {name = "Legendary", chance = 1/1000},
    {name = "Epic", chance = 1/100},
    {name = "Rare", chance = 1/10},
    {name = "Uncommon", chance = 1/4},
    {name = "Common", chance = 1/2}
}

function rollRarity()
    for i = #rarities, 1, -1 do
        if math.random() < rarities[i].chance then
            return rarities[i].name
        end
    end
    return "Common"
end

print("You obtained: " .. rollRarity())

I believe so but Im gonna recheck this in the morning as it’s late and I don’t trust myself lol, if it’s the solution I’ll mark it in the morning :sunrise_over_mountains:

Watching the dark knight rn :bat:
Idk why I feel like sharing just do

1 Like

I was just thinking about how I would implement modules into this, but i believe it would be the same as anything else. Just making sure. This has been driving me nuts :peanuts:

1 Like

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

This will actually give you inaccurate results because math.random() will happen for each rarity in the for loop. You need to do it once instead at the beginning and save it as a variable.

1 Like

While I do love this, this isn’t exactly what im going for. Im sure making this a community resource post would do you alot more justice, as I dont see any errors/over-coding/anything wrong with the script! :grin:

While looking at this I was kind of realizing if two things had the same rarity they would both go through the loop making unfair odds. Anyway of avoiding this?

What do you exactly mean? Isnt it what you want?

I want it to go through each rarity, and if I it’s successful go to the next rarity until you do my successfully roll :croissant:
Nott go through every item individually l, but instead every rarity individually

The script already does that. If math.random is smaller than the current rarity, it returns the rarity.name

Would this make is so if two things have the same rarity only one would ever be picked? :thinking:

1 Like

Yes, only one would be picked, not both.

I’m looking for like something where it would roll on the rarity then use like raritychosetable[math.random(1,#raritychosetable)]

What do you mean? Can you be more specific?

Idk maybe like,

The player rolled a 1/40 chance but two values have a rarity of 1/40, it would random pick either one

1 Like