What is wrong with my luck system?

I am trying to make a luck system where if the player rolls a certain chance, they get a stat in return

The issue is that the chances are not adding up to what they should be, for example, the 1/10 chance looks like a 1/100 chance, and I cant even obtain a 1/50 chance. I did a trial rolling the rune 1000 times, and these are my results

Ive tried looking for solutions on devforum, but couldn’t find any that really suit my needs.

This is my script:

local x = require(game.ReplicatedStorage.EternityNum) -- To use the bignum.
local config = script.Parent.config

script.Parent.part.Touched:Connect(function(hit)
	if hit.Parent:FindFirstChildOfClass("Humanoid") then
		if game.Players:GetPlayerFromCharacter(hit.Parent) then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local s = plr:FindFirstChild("Stats")
			local money = plr.Stats:FindFirstChild("Money")
			local multi = plr.Stats:FindFirstChild("Multi")
			local comm = plr.Stats:FindFirstChild("Common")
			local uncomm = plr.Stats:FindFirstChild("Uncommon")
			local rare = plr.Stats:FindFirstChild("Rare")
			local leg = plr.Stats:FindFirstChild("Epic")
			local cele = plr.Stats:FindFirstChild("Legendary")
			local mvp = plr.Stats:FindFirstChild("MVP")
			local rg = plr.Stats:FindFirstChild("RuneMaster")
			
			local ro = plr.Stats:FindFirstChild("RunesOpened")




			local random1 = math.random(1,2) --50%
			local random2 = math.random(1,5) --20%
			local random3 = math.random(1,10) --10%
			local random4 = math.random(1,50) --2%
			local random5 = math.random(1,1000) --0.1%

			if multi == nil then return false end
			if x.meeq(x.convert(multi.Value),(x.convert(config.cost.Value))) then
				local s = x.convert(multi.Value) 
				local v = x.convert(config.cost.Value) 
				s = x.sub(s,v) 
				multi.Value = x.bnumtostr(s)


				if random1 == 2  then
					local svb = x.convert(comm.Value)
					local val =x.convert(config.amount.Value)
					
					local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
					local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))
					
					svb = x.add(svb, val)
					comm.Value = x.bnumtostr(svb)
					ro.Value = ro.Value + 1


					if random2 == 5 then
						local svb = x.convert(uncomm.Value)
						local val = x.convert(config.amount.Value)
						
						local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
						local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))						
						
						svb = x.add(svb, val)
						uncomm.Value = x.bnumtostr(svb)
						ro.Value = ro.Value + 1


						if random3 == 10 then
							local svb = x.convert(rare.Value)
							local val = x.convert(config.amount.Value)
							
									
							local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
							local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))

							svb = x.add(svb, val)
							rare.Value = x.bnumtostr(svb)
							ro.Value = ro.Value + 1


							if random4 == 50 then
								local svb = x.convert(leg.Value)
								local val = x.convert(config.amount.Value)
								
								local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
								local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))
								

								svb = x.add(svb, val)
								leg.Value = x.bnumtostr(svb)
								ro.Value = ro.Value + 1


								if random5 == 1000 then
									local svb = x.convert(cele.Value)
									local val = x.convert(config.amount.Value)
									
									
									local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
									local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))

									svb = x.add(svb, val)
									cele.Value = x.bnumtostr(svb)
									ro.Value = ro.Value + 1

								end
							end
						end
					end
				end
			end
		end
	end
end)

local svb = x.convert(config.cost.Value)
local sv = x.convert(config.amount.Value)
script.Parent.part.BillboardGui.Cost.Text = "Req: "..x.short(svb).." Rebirths"-- just change the stuff between the ""
script.Parent.part.BillboardGui.Ammt.Text = x.short(sv).." Basic Rune "

3 Likes

Your way of getting a random result is will not work as you intend it to. Because you first check if random1 is 2, which is a 50% chance, and then only if random1 is 2 do you check if random2 is 5 (20%), now essentially random2 will only work if both random1 and random2 are true. This means that the chance of getting random2 is essentially 50% * 20% which is 10%. This will also scale as you go further down. (I didn’t read too much into your code so there’s a chance this explanation isn’t completely true)

Looking at what you want to achieve I would recommend a weighted chance system, which could look like this:

local weigthts = {
	weight1 = 50,
	weight2 = 20,
	weight3 = 10,
	weight4 = 2,
	weight5 = 0.1,
}

local function getRandom()
	local totalWeight = 0
	for _, weight in pairs(weigthts) do
		totalWeight += weight
	end
	
	local currentWeight = 0
	
	local random = math.random() * totalWeight
	
	for prize, chance in pairs(weigthts) do
		currentWeight += chance
		
		if currentWeight >= random then
			return prize
		end
	end
end

Note that since your chances don’t add up to 100%, it will not truly be 50% or 20% but most likely a bit more. When I tested what the actual chances are I got these results:
50% is actually around 60%
20% is around 24%
10% is around 12%
2% is 2.4%
0.1% is mostly 0.1%

1 Like

I saw this before, but I simply have no clue how to make it give the stat when it rolls the right number

1 Like

You could create a dictionary that holds all of the different rarities and do something like this

local results {}

function results.random1()
   — do something here
end

—do this for all potential results

—then you can simply do this
results[getRandom()]()

This calls the function of whatever rarity you get and you can give the player an item of that rarity or whatever you want to do when the player gets something

so for random1, I would make it give the stat that corresponds to weight1? and just make new functions for each stat?

1 Like

Yes, do it for every possible rarity you can get

and I do this as a module script? where would I put this?

I would guess you can put this instead of your previous chance script, but it’s hard for me to say since I don’t know how you structure your code. You should also make sure that the names of the weights are the same as the function you’re making since it indexes the result table with the weight key

Going off what you did with your script … is this any better?

local x = require(game.ReplicatedStorage.EternityNum)
local config = script.Parent.config
local part = script.Parent.part

local function getRandomValue(chance)
    return math.random(1, chance) == chance
end

part.Touched:Connect(function(hit)
    local humanoid = hit.Parent:FindFirstChildOfClass("Humanoid")
    local player = game.Players:GetPlayerFromCharacter(hit.Parent)

    if player and humanoid then
        local stats = player:FindFirstChild("Stats")
        local multi = stats:FindFirstChild("Multi")

        if multi and x.meeq(x.convert(multi.Value), x.convert(config.cost.Value)) then
            local svb = x.convert(stats.Common.Value)
            local val = x.convert(config.amount.Value)
            local mvpMultiplier = x.add(x.mul(x.convert(stats.MVP.Value), {1, 0}), {1, 0})
            local rgMultiplier = x.add(x.mul(x.convert(stats.RuneMaster.Value), {2, 0}), {1, 0})
            val = x.mul(val, mvpMultiplier)
            val = x.mul(val, rgMultiplier)
            svb = x.add(svb, val)

            stats.Common.Value = x.bnumtostr(svb)
            stats.RunedOpened.Value = stats.RunedOpened.Value + 1

            if getRandomValue(2) then
                svb = x.convert(stats.Uncommon.Value)
                val = x.convert(config.amount.Value)
                val = x.mul(val, mvpMultiplier)
                val = x.mul(val, rgMultiplier)
                svb = x.add(svb, val)
                stats.Uncommon.Value = x.bnumtostr(svb)
                stats.RunedOpened.Value = stats.RunedOpened.Value + 1

                if getRandomValue(5) then
                    svb = x.convert(stats.Rare.Value)
                    val = x.convert(config.amount.Value)
                    val = x.mul(val, mvpMultiplier)
                    val = x.mul(val, rgMultiplier)
                    svb = x.add(svb, val)
                    stats.Rare.Value = x.bnumtostr(svb)
                    stats.RunedOpened.Value = stats.RunedOpened.Value + 1

                    if getRandomValue(10) then
                        svb = x.convert(stats.Epic.Value)
                        val = x.convert(config.amount.Value)
                        val = x.mul(val, mvpMultiplier)
                        val = x.mul(val, rgMultiplier)
                        svb = x.add(svb, val)
                        stats.Epic.Value = x.bnumtostr(svb)
                        stats.RunedOpened.Value = stats.RunedOpened.Value + 1

                        if getRandomValue(50) then
                            svb = x.convert(stats.Legendary.Value)
                            val = x.convert(config.amount.Value)
                            val = x.mul(val, mvpMultiplier)
                            val = x.mul(val, rgMultiplier)
                            svb = x.add(svb, val)
                            stats.Legendary.Value = x.bnumtostr(svb)
                            stats.RunedOpened.Value = stats.RunedOpened.Value + 1

                            if getRandomValue(1000) then
                                svb = x.convert(stats.Mythical.Value)
                                val = x.convert(config.amount.Value)
                                val = x.mul(val, mvpMultiplier)
                                val = x.mul(val, rgMultiplier)
                                svb = x.add(svb, val)
                                stats.Mythical.Value = x.bnumtostr(svb)
                                stats.RunedOpened.Value = stats.RunedOpened.Value + 1
                            end
                        end
                    end
                end
            end
        end
    end
end)

local svb = x.convert(config.cost.Value)
local sv = x.convert(config.amount.Value)
part.BillboardGui.Cost.Text = "Req: " .. x.short(svb) .. " Rebirths"
part.BillboardGui.Ammt.Text = x.short(sv) .. " Basic Rune "

This only gives the common stat, nothing else

Figures … my head hurts now so I’m out … :frowning:

local results {
	local x = require(game.ReplicatedStorage.EternityNum)
	function results.random1()
		game.Players.PlayerAdded:Connect(function(plr)
			local stat = plr:WaitForChild("Stats")
			local prize = plr.Stats:FindFirstChild("Common")
			local mult1 = plr.Stats:FindFirstChild("MVP")
			local mult2 = plr.Stats:FindFirstChild("RuneMaster")
			local ro = plr.Stats:FindFirstChild("RunesOpened")

			local svb = x.convert(prize.Value)
			local val =x.convert(1)

			local val = x.mul(val, (x.add(x.mul(x.convert(mvp.Value),{1,0}),{1,0})))
			local val = x.mul(val, (x.add(x.mul(x.convert(rg.Value),{2,0}),{1,0})))

			svb = x.add(svb, val)
			prize.Value = x.bnumtostr(svb)
			ro.Value = ro.Value + 1


		end)
	end

	function results.random2()
		game.Players.PlayerAdded:Connect(function(plr)
			local stat = plr:WaitForChild("Stats")
			local prize = plr.Stats:FindFirstChild("Uncommon")
			
			local mult1 = plr.Stats:FindFirstChild("MVP")
			local mult2 = plr.Stats:FindFirstChild("RuneMaster")
			local ro = plr.Stats:FindFirstChild("RunesOpened")

			local svb = x.convert(prize.Value)
			local val =x.convert(1)

			local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
			local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))

			svb = x.add(svb, val)
			prize.Value = x.bnumtostr(svb)
			ro.Value = ro.Value + 1
		end)
	end

	function results.random3()
		game.Players.PlayerAdded:Connect(function(plr)
			local stat = plr:WaitForChild("Stats")
			local prize = plr.Stats:FindFirstChild("Rare")
			
			local mult1 = plr.Stats:FindFirstChild("MVP")
			local mult2 = plr.Stats:FindFirstChild("RuneMaster")
			local ro = plr.Stats:FindFirstChild("RunesOpened")

			local svb = x.convert(prize.Value)
			local val =x.convert(1)

			local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
			local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


			svb = x.add(svb, val)
			prize.Value = x.bnumtostr(svb)
			ro.Value = ro.Value + 1
		end)
	end

	function results.random4()
		game.Players.PlayerAdded:Connect(function(plr)
			local stat = plr:WaitForChild("Stats")
			local prize = plr.Stats:FindFirstChild("Epic")
			
			local mult1 = plr.Stats:FindFirstChild("MVP")
			local mult2 = plr.Stats:FindFirstChild("RuneMaster")
			local ro = plr.Stats:FindFirstChild("RunesOpened")

			local svb = x.convert(prize.Value)
			local val =x.convert(1)

			local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
			local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


			svb = x.add(svb, val)
			prize.Value = x.bnumtostr(svb)
			ro.Value = ro.Value + 1
		end)
	end

	function results.random5()
		game.Players.PlayerAdded:Connect(function(plr)
			local stat = plr:WaitForChild("Stats")
			local prize = plr.Stats:FindFirstChild("Legendary")
			
			local mult1 = plr.Stats:FindFirstChild("MVP")
			local mult2 = plr.Stats:FindFirstChild("RuneMaster")
			local ro = plr.Stats:FindFirstChild("RunesOpened")

			local svb = x.convert(prize.Value)
			local val =x.convert(1)

			local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
			local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


			svb = x.add(svb, val)
			prize.Value = x.bnumtostr(svb)
			ro.Value = ro.Value + 1
		end)
	end
}
results[getRandom()]()

I did this, but its turning up a few errors, like where the table starts with the { its says expected identifier when parsing expression, when I made the EternityNum variable, I get an error on the local part, getRandom says unknown global. Im very new to module scripts so bear with me here

For starters, you don’t properly assign the results variable
Youre doing

local results {

When it should be

local results = {}

The reason you’re getting the unknown global is because you probably dont actually use the function i gave you in my weighted chance example.
The other error I don’t know since I don’t have access to your game.

Is there a reason you have to make this a module script if you’re not yet familiar with them? Can’t you just change the chance script you have already written?

Ok, I have this so far, now how exactly do I make it work? I want it to give the rolled stat but I cant figure out how to tie two and two together

local results ={}
local luck = require(script.LuckHandler)
local x = require(game.ReplicatedStorage.EternityNum)	
function results.weight1()
	game.Players.PlayerAdded:Connect(function(plr)
		local stat = plr:WaitForChild("Stats")
		local prize = plr.Stats:FindFirstChild("Common")
		local mult1 = plr.Stats:FindFirstChild("MVP")
		local mult2 = plr.Stats:FindFirstChild("RuneMaster")
		local ro = plr.Stats:FindFirstChild("RunesOpened")

		local svb = x.convert(prize.Value)
		local val =x.convert(1)

		local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
		local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))

		svb = x.add(svb, val)
		prize.Value = x.bnumtostr(svb)
		ro.Value = ro.Value + 1


	end)
end

function results.weight2()
	game.Players.PlayerAdded:Connect(function(plr)
		local stat = plr:WaitForChild("Stats")
		local prize = plr.Stats:FindFirstChild("Uncommon")

		local mult1 = plr.Stats:FindFirstChild("MVP")
		local mult2 = plr.Stats:FindFirstChild("RuneMaster")
		local ro = plr.Stats:FindFirstChild("RunesOpened")

		local svb = x.convert(prize.Value)
		local val =x.convert(1)

		local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
		local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))

		svb = x.add(svb, val)
		prize.Value = x.bnumtostr(svb)
		ro.Value = ro.Value + 1
	end)	end

function results.weight3()
	game.Players.PlayerAdded:Connect(function(plr)
		local stat = plr:WaitForChild("Stats")
		local prize = plr.Stats:FindFirstChild("Rare")

		local mult1 = plr.Stats:FindFirstChild("MVP")
		local mult2 = plr.Stats:FindFirstChild("RuneMaster")
		local ro = plr.Stats:FindFirstChild("RunesOpened")

		local svb = x.convert(prize.Value)
		local val =x.convert(1)

		local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
		local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


		svb = x.add(svb, val)
		prize.Value = x.bnumtostr(svb)
		ro.Value = ro.Value + 1
	end)
end

function results.weight4()
	game.Players.PlayerAdded:Connect(function(plr)
		local stat = plr:WaitForChild("Stats")
		local prize = plr.Stats:FindFirstChild("Epic")

		local mult1 = plr.Stats:FindFirstChild("MVP")
		local mult2 = plr.Stats:FindFirstChild("RuneMaster")
		local ro = plr.Stats:FindFirstChild("RunesOpened")

		local svb = x.convert(prize.Value)
		local val =x.convert(1)

		local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
		local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


		svb = x.add(svb, val)
		prize.Value = x.bnumtostr(svb)
		ro.Value = ro.Value + 1
	end)
end

function results.weight5()
	game.Players.PlayerAdded:Connect(function(plr)
		local stat = plr:WaitForChild("Stats")
		local prize = plr.Stats:FindFirstChild("Legendary")

		local mult1 = plr.Stats:FindFirstChild("MVP")
		local mult2 = plr.Stats:FindFirstChild("RuneMaster")
		local ro = plr.Stats:FindFirstChild("RunesOpened")

		local svb = x.convert(prize.Value)
		local val =x.convert(1)

		local val = x.mul(val, (x.add(x.mul(x.convert(mult1.Value),{1,0}),{1,0})))
		local val = x.mul(val, (x.add(x.mul(x.convert(mult2.Value),{2,0}),{1,0})))


		svb = x.add(svb, val)
		prize.Value = x.bnumtostr(svb)
		ro.Value = ro.Value + 1
	end)
end

results[luck.getRandom()]()

local config = script.Parent.config

script.Parent.part.Touched:Connect(function(hit)
	if hit.Parent:FindFirstChildOfClass("Humanoid") then
		if game.Players:GetPlayerFromCharacter(hit.Parent) then
			local plr = game.Players:GetPlayerFromCharacter(hit.Parent)
			local s = plr:FindFirstChild("Stats")
			local money = plr.Stats:FindFirstChild("Money")
			local multi = plr.Stats:FindFirstChild("Multi")
			if multi == nil then return false end
			if x.meeq(x.convert(multi.Value),(x.convert(config.cost.Value))) then
				
			end
		end
	end
end)

At the end, I want it to give whatever stat is rolled that’s what that blank line is for

i don’t know what the variable x is, but i recommend using a weighted chance system

chance of one object

if you want to test the chance of one object, you can make a dictionary that consists of the object that you’re testing the chance of, and the other the remaining percent (1-chanceOfObject)

it should look like this

local chance = 1/5 -- example

local dictionary = {
	Yes = chance, -- the object that you're testing the chance of
	No = 1 - chance -- the remaining chances
}

let’s make this a function so you can use it in any chance

function testChance(chance: number): boolean
	chance = math.clamp(chance, 0, 1) -- setting the minimum and maximum number
	
	local chances = {
		Yes = chance,
		No = 1 - chance
	}
end

next, we will generate a random number from 0 to 1

function testChance(chance: number): boolean
	chance = math.clamp(chance, 0, 1) -- setting the minimum and maximum number
	
	local chances = {
		Yes = chance,
		No = 1 - chance
	}
	
	local randomNumber = Random.new(workspace.DistributedGameTime):NextNumber(0, 1)
end

lastly, we will add the chances by iterating the dictionary and the value that made the sum equal or more than the random number is the chosen one

function testChance(chance: number): boolean
	chance = math.clamp(chance, 0, 1) -- setting the minimum and maximum number
	
	local chances = {
		Yes = chance,
		No = 1 - chance
	}
	
	local randomNumber = Random.new(workspace.DistributedGameTime):NextNumber(0, 1)
	
	local chosenYes = false
	local weight = 0
	for key, value in pairs(chances) do
		weight += value
		
		if weight >= randomNumber then
			if key == "Yes" then
				chosenYes = true
			else
				chosenYes = false
			end
			break
		end
	end
	
	return chosenYes
end
2 or more chances

it’s the same as the chance of one object and basically @bloffsing’s post

the difference of this and the first one is that the chances dictionary doesn’t need to sum at 1

local chances = {
	Apple = 50,
	Banana = 1.5,
	Carrot = 100,
	DragonFruit = 27
}

local totalWeight = 0
for _, chance in pairs(chances) do
	totalWeight += chance
end

local randomNumber = Random.new(workspace.DistributedGameTime):NextNumber(0, totalWeight)

local weight = 0
for key, value in pairs(chances) do
	weight += value
	
	if weight >= randomNumber then
		print("You won " .. key)
		break
	end
end

summary: the weighted chance system is basically a spinning wheel

local chance = {}
for _ = 1, 50 do table.insert(chance, 50) end
for _ = 1, 20 do table.insert(chance, 20) end
for _ = 1, 10 do table.insert(chance, 10) end
for _ = 1, 8 do table.insert(chance, 8) end
for _ = 1, 6 do table.insert(chance, 6) end
for _ = 1, 4 do table.insert(chance, 4) end
for _ = 1, 2 do table.insert(chance, 2) end

local function getRandomElement()
	local randomIndex = math.random(1, #chance)
	return chance[randomIndex]
end

local result = getRandomElement()
print("Result:", result)

or

local function findClosestNumber(target, numbers)
	local closestNumber = numbers[1]
	local minDifference = math.abs(target - closestNumber)

	for _, number in ipairs(numbers) do
		local difference = math.abs(target - number)
		if difference < minDifference then
			closestNumber = number
			minDifference = difference
		end
	end

	return closestNumber
end

local randomNum = math.random(1, 100)
print("Random Number:", randomNum)

local comparisonNumbers = {50, 20, 10, 8, 6, 4, 2}
local closestNumber = findClosestNumber(randomNum, comparisonNumbers)
print("Closest Number:", closestNumber)

Try moving

results[luck.getRandom()]()

To the blank line

I get an error at line 2 saying attempt to call require with invalid arguments, but how do I get information from the luck script without using require?
EDIT: fixed that part, but now at line 124 its saying getRandom() isn’t a part of the luck handler so what do I put in place of that

Did you define the getRandom function I showed you?

function getRandom()
	local totalWeight = 0
	for _, weight in pairs(weigthts) do
		totalWeight += weight
	end
	
	local currentWeight = 0
	
	local random = math.random() * totalWeight
	
	for prize, chance in pairs(weigthts) do
		currentWeight += chance
		
		if currentWeight >= random then
			return prize
		end
	end
end

I did

local weigthts = {
	weight1 = 50,
	weight2 = 20,
	weight3 = 10,
	weight4 = 2,
	weight5 = 0.1,
}

local function getRandom()
	local totalWeight = 0
	for _, weight in pairs(weigthts) do
		totalWeight += weight
	end

	local currentWeight = 0

	local random = math.random() * totalWeight

	for prize, chance in pairs(weigthts) do
		currentWeight += chance

		if currentWeight >= random then
			return prize
		end
	end
end

Here is my explorer too
image