§ How to · with Picoo

How to make a rarity and luck system in Roblox

By Sametcan Tasgiran, Founder & Developer·Published ·Updated

Rarity is the one system where a subtle bug is invisible — the wrong roll still returns an item, it just returns the wrong one slightly too often.

Weighted selection, luck applied to weights rather than rerolls, and odds you can actually show the player, because for paid rolls Roblox requires it.

Cumulative weight rolling

Weights of any scale, no requirement that they sum to 100, no percentage rounding drift.

Luck multipliers

Luck scales rare weights rather than granting rerolls, so the odds stay computable.

Odds you can display

Real percentages derived from the same table that rolls, so shown odds cannot drift from actual odds.

Server-side rolls

The result is decided on the server; the client only animates it.

Pity counters

Optional guaranteed-rare after N failures, tracked persistently.

Per-server randomness

A Random instance per server rather than shared global state.

Files Picoo ships for this prompt

2 files · 145 lines · ~25s · 1 credit

RarityService

Weight tables, rolling, luck, pity, odds computation.

100 lines

RarityDisplay

Odds panel and rarity colouring derived from the same table.

45 lines

Sample output: ServerScriptService.RarityService

-- Cumulative weights, not percentages. Percentages force you to keep a table
-- summing to exactly 100 by hand — add one item and every other number needs
-- editing, and the rounding you do to fix it quietly changes the real odds.
local TABLE = {
	{ id = "common",    weight = 1000 },
	{ id = "uncommon",  weight = 250 },
	{ id = "rare",      weight = 60 },
	{ id = "epic",      weight = 12 },
	{ id = "legendary", weight = 1 },
}

-- One Random per server. math.random shares global state with every other
-- script in the place, so anything that reseeds it changes your drops too.
local rng = Random.new()

local function roll(luck: number): string
	local total = 0
	for _, e in TABLE do
		-- Luck scales the RARE side. Rerolling on a failure is the other common
		-- approach and it makes the true odds impossible to state honestly.
		total += e.weight * (e.weight < 100 and luck or 1)
	end

	local pick = rng:NextNumber() * total
	for _, e in TABLE do
		pick -= e.weight * (e.weight < 100 and luck or 1)
		if pick <= 0 then return e.id end
	end
	return TABLE[1].id                        -- float drift guard, never reached
end

-- The odds panel reads the SAME table, so what you show can never drift from
-- what you roll. Roblox requires disclosing odds for paid random items.
local function oddsPercent(luck: number): { [string]: number }
	local weights, total = {}, 0
	for _, e in TABLE do
		weights[e.id] = e.weight * (e.weight < 100 and luck or 1)
		total += weights[e.id]
	end
	for id, w in weights do weights[id] = w / total * 100 end
	return weights
end

Building a rarity and luck system in Roblox

Rarity is unusual among game systems in that a bug does not crash anything. A broken weighted roll still hands the player an item; it just hands out the wrong one a little too often, and you find out weeks later when the economy is already distorted.

The first decision is weights over percentages. Percentages have to add to a hundred, so adding a single new drop means editing every other entry, and the rounding people apply to make the total work out shifts the real rates by small amounts nobody tracks. Cumulative weights on any scale avoid the whole problem: add an entry and every other item keeps its relative chance automatically. The roll is a running subtraction against a random point in the total, which is a handful of lines and has no drift.

Luck is the second decision, and the usual implementation causes the trouble. Granting a reroll when the player misses feels natural and makes the true odds genuinely awkward to compute — which matters, because for paid rolls Roblox requires you to disclose them. Scaling the weights of the rare entries instead keeps the system honest: the odds after luck are the same one-line calculation as before.

That leads to the practice worth adopting: generate the odds panel from the same table you roll from. Hardcoded percentages in the UI start correct and go stale the first time someone retunes a weight, and nobody notices because the panel still looks plausible.

Two smaller things. Use Random.new rather than math.random — the global generator's state is shared with every other script in the place, so an unrelated reseed shifts your drop rates. And roll on the server. In a game where the result has value, a client-side roll is not randomness, it is a request.

See more on the Luau generator, the game builder, or browse the full blog.

Frequently asked

Do I have to show drop rates?+

For anything a player pays Robux for, yes — Roblox requires the odds of paid random-item generators to be disclosed to the player before they buy. Beyond compliance, generating the odds panel from the same weight table you roll from means the displayed numbers cannot drift out of sync when you retune, which is how most published odds end up wrong.

Weights or percentages?+

Weights. Percentages must sum to exactly 100, so adding one item means editing every other number, and the rounding people apply to make it add up quietly shifts the real rates. Weights on any scale need no maintenance — add an entry and the others keep their relative odds.

How should a luck multiplier work?+

Scale the weights of rare entries, not the number of rolls. A luck stat that grants a second roll on a failure changes the effective odds in a way that is genuinely hard to state, which matters if you have to disclose them. Multiplying rare weights keeps the odds one line of arithmetic away.

math.random or Random.new?+

Random.new. math.random draws from generator state shared by every script in the place, so anything calling randomseed changes your drop rates as a side effect. A dedicated Random instance is isolated and can be seeded deliberately when you want reproducible tests.

Where should the roll happen?+

On the server, always. A client-side roll is a client-chosen result — and in a game where results have value, that is the whole economy. The client's job is to animate the outcome the server already decided.

Related Picoo prompts