Weight Rarity increasing chances more than it should

So this is the code I have for choosing gamemodes. Also, some unimportant parts of the code have been removed, but it shouldn’t affect how the script performs, unless I forgot to remove an “end” or something:

local GamemodeData = require(script.Parent.GamemodeData)
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- State tables
local currentRarities = {}
local cooldowns = {} -- [gamemodeName] = roundNumber when last picked
local roundsSincePicked = {} -- [gamemodeName] = rounds since last picked

local COOLDOWN_ROUNDS = 5
local RARITY_INCREMENT = 0.0001 -- percent per round (0.01%)

-- Initialize rarity and cooldown tables
local function initializeState()
	for gamemodeName, data in GamemodeData.gamemodes do
		currentRarities[gamemodeName] = data.rarity
		cooldowns[gamemodeName] = -COOLDOWN_ROUNDS
		roundsSincePicked[gamemodeName] = 0
	end
end

initializeState()

local function getEligibleGamemodes(roundNumber)
	local eligible = {}
	for gamemodeName, data in GamemodeData.gamemodes do
		local lastPickedRound = cooldowns[gamemodeName]
		if roundNumber - lastPickedRound >= COOLDOWN_ROUNDS then
			table.insert(eligible, gamemodeName)
		end
	end
	return eligible
end

local function updateRarities(selectedGamemode)
	for gamemodeName, data in GamemodeData.gamemodes do
		if gamemodeName ~= selectedGamemode then
			currentRarities[gamemodeName] = currentRarities[gamemodeName] + RARITY_INCREMENT
			roundsSincePicked[gamemodeName] = roundsSincePicked[gamemodeName] + 1
		else
			currentRarities[gamemodeName] = data.rarity
			roundsSincePicked[gamemodeName] = 0
		end
	end
	end
end

local function weightedRandomChoice(gamemodeList)
	local totalWeight = 0
	for i, gamemodeName in gamemodeList do
		totalWeight = totalWeight + currentRarities[gamemodeName]
	end
	local rand = math.random() * totalWeight
	local cumulative = 0
	for i, gamemodeName in gamemodeList do
		cumulative = cumulative + currentRarities[gamemodeName]
		if rand <= cumulative then
			return gamemodeName
		end
	end
	return gamemodeList[1]
end

local function selectGamemode(queuedGamemode, roundNumber)
	local eligibleGamemodes = getEligibleGamemodes(roundNumber)
	local chosenGamemode = nil

	-- Calculate totalAdjustedRarity for eligible gamemodes
	local totalAdjustedRarity = 0
	for i, gamemodeName in eligibleGamemodes do
		totalAdjustedRarity = totalAdjustedRarity + currentRarities[gamemodeName]
	end

	if queuedGamemode then
		local lastPickedRound = cooldowns[queuedGamemode]
		if roundNumber - lastPickedRound >= COOLDOWN_ROUNDS then
			chosenGamemode = queuedGamemode
		else
			chosenGamemode = weightedRandomChoice(eligibleGamemodes)
		end
	else
		if #eligibleGamemodes == 0 then
			-- If all are on cooldown, ignore cooldowns for this round
			local allGamemodes = {}
			for gamemodeName, data in GamemodeData.gamemodes do
				table.insert(allGamemodes, gamemodeName)
			end
			-- Calculate totalAdjustedRarity for all gamemodes
			totalAdjustedRarity = 0
			for i, gamemodeName in allGamemodes do
				totalAdjustedRarity = totalAdjustedRarity + currentRarities[gamemodeName]
			end
			chosenGamemode = weightedRandomChoice(allGamemodes)
			eligibleGamemodes = allGamemodes
		else
			chosenGamemode = weightedRandomChoice(eligibleGamemodes)
		end
	end

	cooldowns[chosenGamemode] = roundNumber
	updateRarities(chosenGamemode)
	return chosenGamemode, currentRarities, totalAdjustedRarity, eligibleGamemodes
end

return {
	selectGamemode = selectGamemode,
	GetCurrentRarities = function() return currentRarities end,
	GetCooldowns = function() return cooldowns end,
}

And this is the code I have that stores the rarities and other important values for each gamemode.

local gamemodes = {
    ["Gamemode1"] = {rarity = 0.14, queueableByOwner = true, queueableByPrivateServer = false, coinsReward = 1500},
    ["Gamemode2"] = {rarity = 2, queueableByOwner = true, queueableByPrivateServer = true, coinsReward = 250},
    ["Gamemode3"] = {rarity = 3, queueableByOwner = true, queueableByPrivateServer = true, coinsReward = 250},

}

-- Store the previous gamemode name
local previousGamemode = nil

local function setPreviousGamemode(name)
    previousGamemode = name
end

local function getPreviousGamemode()
    return previousGamemode
end

local function getTotalRarity()
    local total = 0
    for name, data in gamemodes do
        if not data.limitedTimeOnly and not data.queueOnly then
            total = total + data.rarity
        end
    end
    return total
end

local function getGamemodePercent(name)
    local data = gamemodes[name]
    if not data then return 0 end
    if data.limitedTimeOnly or data.queueOnly then return 0 end
    local total = getTotalRarity()
    if total == 0 then return 0 end
    return math.floor((data.rarity / total) * 100 + 0.5)
end

local function canQueueGamemode(player, gamemodeName)
    local data = gamemodes[gamemodeName]
    if not data then return false end
    if data.limitedTimeOnly or data.queueOnly then
        if game.PrivateServerId ~= "" then
            return data.queueableByPrivateServer
        else
            if player.UserId == game.CreatorId then
                return data.queueableByOwner
            end
        end
        return false
    end
    -- For normal gamemodes, use existing logic
    if game.PrivateServerId ~= "" then
        return data.queueableByPrivateServer
    else
        if player.UserId == game.CreatorId then
            return data.queueableByOwner
        end
    end
    return false
end

local function getGamemodeNames()
    local names = {}
    for name, _ in gamemodes do
        table.insert(names, name)
    end
    return names
end

local function getCoinsReward(name)
    local data = gamemodes[name]
    if not data then return 0 end
    return data.coinsReward or 0
end

local function isLimitedTimeOnly(name)
    local data = gamemodes[name]
    return data and data.limitedTimeOnly or false
end

local function isQueueOnly(name)
    local data = gamemodes[name]
    return data and data.queueOnly or false
end

local function getRandomGamemodeName()
    -- Returns a random gamemode name that is NOT limited time only or queue only
    local available = {}
    for name, data in gamemodes do
        if not data.limitedTimeOnly and not data.queueOnly then
            table.insert(available, name)
        end
    end
    if #available == 0 then return nil end
    return available[math.random(1, #available)]
end

return {
    gamemodes = gamemodes,
    getTotalRarity = getTotalRarity,
    getGamemodePercent = getGamemodePercent,
    canQueueGamemode = canQueueGamemode,
    getGamemodeNames = getGamemodeNames,
    getCoinsReward = getCoinsReward,
    isLimitedTimeOnly = isLimitedTimeOnly,
    isQueueOnly = isQueueOnly,
    getRandomGamemodeName = getRandomGamemodeName,
    setPreviousGamemode = setPreviousGamemode,
    getPreviousGamemode = getPreviousGamemode,
}

The problem is that even though the code is SUPPOSED to increase each gamemode’s chance by 0.01% to make them slowly get more common, what it ACTUALLY increases by is a lot more. For example, Gamemode1 would end up as 0.8% when it should be 0.15%. I’ve been trying to fix this for days but came back empty. It’d be greatly appreciated if somebody here has the answer

Looping through gamemodes makes it increase the same amount of time as the amount of gamemodes you’re looping through

Do you mean that the value stored in currentRarities for Gamemode1 becomes 0.8? When exactly do you see this value show up? Your code seems fine. The only thing I can think may be wrong is that selectGamemode or updateRarities might be accidentally called a lot more times than you expect.

It should be mentioned that RARITY_INCREMENT is 0.001, so adding it to the base rarity of Gamemode1 would yield 0.1401. Is it possible you have other places where you’re doing a similar conflation between percentages and weights?

It was more like 0.83 but yes it’s basically that.

Also that was a mistake on my end. That was me trying to see if reducing it made any difference with increasing the chances, but no. I forgot to remove that. I’ll look further into the issue and get back to you if complications still arrive

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.