I am trying to make an ore generation system, I tried it with this weight system which works fairly well, but it’s not quite what I’m envisioning. I am wanting to change it to some kind of % system as I think that would work better for me. With the weight system, it is spawning gold and diamond more than i’d like. Anyone have some solutions? I appreciate it!
local items = {
{Name = "Stone"},
{Name = "Copper"},
{Name = "Iron"},
{Name = "Gold"},
{Name = "Diamond"}
}
local lootTable = {
{Item = items[1], Weight = 100}, -- each one of these is an entry
{Item = items[2], Weight = 60},
{Item = items[3], Weight = 15},
{Item = items[4], Weight = 5}, -- each one of these is an entry
{Item = items[5], Weight = 1}
}
-- used in weight random number generation
local function returnSumOfWeight(lootTable)
local sum = 0
for _, entry in ipairs(lootTable) do
sum = sum + entry.Weight
end
return sum
end
-- returns a random item from given lootTable
local function getRandomItem(lootTable)
-- returns a random number based on total weight of given lootTable
local randomNumber = math.random(returnSumOfWeight(lootTable))
for _, entry in ipairs(lootTable) do
if randomNumber <= entry.Weight then
return entry.Item
else
randomNumber = randomNumber - entry.Weight
end
end
end
The most common solution and the worst is by adding each item x the amount (weight) to a table and then getting a random from the table.
The one I would recommend is:
From the walker alias method. The algorithm typically use O( n log n ) or O ( n ) preprocessing time, after which random values can be drawn from the distribution in O (1) time! Credit to Ryan Pattison
local alias_table = {}
function alias_table:new(weights)
local total = 0
for _,v in ipairs(weights) do
assert(v >= 0, "all weights must be non-negative")
total = total + v
end
assert(total > 0, "total weight must be positive")
local normalize = #weights / total
local norm = {}
local small_stack = {}
local big_stack = {}
for i,w in ipairs(weights) do
norm[i] = w * normalize
if norm[i] < 1 then
table.insert(small_stack, i)
else
table.insert(big_stack, i)
end
end
local prob = {}
local alias = {}
while small_stack[1] and big_stack[1] do -- both non-empty
small = table.remove(small_stack)
large = table.remove(big_stack)
prob[small] = norm[small]
alias[small] = large
norm[large] = norm[large] + norm[small] - 1
if norm[large] < 1 then
table.insert(small_stack, large)
else
table.insert(big_stack, large)
end
end
for _, v in ipairs(big_stack) do prob[v] = 1 end
for _, v in ipairs(small_stack) do prob[v] = 1 end
self.__index = self
return setmetatable({alias=alias, prob=prob, n=#weights}, self)
end
function alias_table:__call()
local index = math.random(self.n)
return math.random() < self.prob[index] and index or self.alias[index]
end
return alias_table
--[[ -- usage:
alias_table = require"alias_table"
sample = alias_table:new{10, 20, 15, 2, 2.3, 130} -- assign weights for 1, 2, 3, 4, 5, 6 etc.
math.randomseed(os.time()); math.random(); math.random(); math.random();
print(sample())
print(sample())
print(sample())
print(sample())
print(sample())
--]]
If you are doing percentage then just remove the % sign. 55% becomes 55 on weight
As I understand it you want to make it so that you assign a percentage rather than a weight to the table. In order to do that, all you have to do is make the numbers in the table add up to a power of 10. It won’t directly be a value between 0 and 1, but you can think of it as such since you are really just shifting a decimal
for example
The total here is 1000
Iron = 500 (500/1000 = 0.5)
Gold = 300 (300/1000 = 0.3)
Diamond = 200 (300/1000 = 0.2)
You could also put in decimals directly if you would rather that, but then you need to change how it picks the random number since it’s currently only going to pick an integer.