126 lines
4.8 KiB
Plaintext
126 lines
4.8 KiB
Plaintext
-- LAgrange inteRPolation library
|
|
-- algorithm by peete-q
|
|
-- Migrated to Anomaly by Asshall
|
|
-- Modified and modularized by arti
|
|
|
|
-- To calculate non linear probability I used a type of interpolation called "Lagrange interpolation".
|
|
-- The basic premise is that you input coordinates for x and y, for the exemple of loot x is the condition of the item to drop and y is the probability to drop such condition
|
|
-- For exemple if I input :
|
|
-- x | y
|
|
-- 2 | 0
|
|
-- 25 | 85
|
|
-- The probability of getting an item between 2 and 25 % condition is distributed between 0% proba and 85%
|
|
-- So in theory there is a 0 percent chance to get a 2% condition drop
|
|
-- and a 85% chance to get a 25% condition item with the probalities smoothly distributed between those two
|
|
-- This is an overly simplified explication because it's the best I can do, but the gist is that by interpolating n point in a curve you can get an approximation of the y value for all x
|
|
-- In this case x represents the condition and y the number to beat to drop.
|
|
|
|
-- Here is a table exemple
|
|
-- [weapon_coords]
|
|
-- novice = 2, 25, 50 : 5, 85, 99
|
|
-- trainee = 2, 25, 50 : 10, 81 ,99
|
|
-- experienced = 2, 25, 50 : 15, 76 ,98
|
|
-- professional = 2, 25, 50, 65 : 20 ,71 ,97, 99
|
|
-- veteran = 2, 25, 50, 65 : 20 ,68 ,96, 99
|
|
-- expert = 2, 25, 50, 65 : 20 ,64 ,95, 99
|
|
-- master = 2, 25, 50, 65 : 20 ,62 ,95, 98
|
|
-- legend = 2, 25, 50, 65 : 20 ,57 ,94, 98
|
|
|
|
|
|
-- I did not implement that, peete-q did : https://github.com/peete-q/assets/blob/master/lua-modules/lib/interpolate.lua All credits goes to them
|
|
function lagrange(data, xi)
|
|
local x = data["x"]
|
|
local y = data["y"]
|
|
xi = tonumber(xi)
|
|
local n = #x
|
|
-- Need the same number of points otherwise exit out
|
|
if n ~= #y then return end
|
|
|
|
-- Brilliant optimisation by peete-q
|
|
local i = 1
|
|
while i <= n and x[i] < xi do
|
|
i = i + 1
|
|
end
|
|
local m = math.min(i + 3, n)
|
|
local k = math.max(i - 4, 1)
|
|
|
|
local result = 0
|
|
for i=k, m do
|
|
local term = y[i]
|
|
for j=k, m do
|
|
if j ~= i then
|
|
term = term*( (xi - x[j]) / (x[i] - x[j]) )
|
|
end
|
|
end
|
|
result = result + term -- term used to be on at the start multiplied by y[i] on this line. Don't know if that makes a difference. It's more lisible for me and I'm a dumb ape
|
|
end
|
|
return clamp(result,math.min(unpack(y)),math.max(unpack(y))) -- Here I clamp the result cause largrange interp can do some weird sutff and go above maxy/bellow miny between two xs
|
|
-- This is suboptimal... Maybe i'm not using the right interp for some data sets or I shouldn't be using interp for those at all. It'll do for now.
|
|
end
|
|
|
|
-- Return min a max of data set of format {x,y} in order minx, maxx, miny, maxy
|
|
function get_min_max(coords)
|
|
return math.min(unpack(coords["x"])), math.max(unpack(coords["x"])), math.min(unpack(coords["y"])), math.max(unpack(coords["y"]))
|
|
end
|
|
|
|
-- str_explode but they get parsed into numbers
|
|
function str_explode_num(str, sep, plain)
|
|
if not (str and sep) then
|
|
printe("!ERROR str_explode | missing parameter str = %s, sep = %s",str,sep)
|
|
callstack()
|
|
end
|
|
if not (sep ~= "" and str:find(sep,1,plain)) then
|
|
return { tonumber(str) }
|
|
end
|
|
local t = {}
|
|
local size = 0
|
|
for s in str:gsplit(sep,plain) do
|
|
size = size + 1
|
|
t[size] = tonumber(trim(s))
|
|
end
|
|
return t
|
|
end
|
|
|
|
-- input:
|
|
-- coords is a table with x and y, each a list of integers
|
|
-- iterations is how many times to run, pass nil to run until we get something
|
|
-- returns a weighted value within the x range based on weights in y
|
|
function pick(coords, max_iterations)
|
|
if is_empty(coords) then return 0 end
|
|
if #coords["x"] ~= #coords["y"] then
|
|
printe("Input for coord string %s produced inequal output, returning", str)
|
|
return 0
|
|
end
|
|
local minx, maxx, miny, maxy = get_min_max(coords)
|
|
local iter = 0
|
|
while true do
|
|
rnd_x = math.random(minx, maxx)
|
|
local y = lagrange(coords, rnd_x)
|
|
if math.random(miny, maxy) + math.random() > y then
|
|
return round(rnd_x)
|
|
end
|
|
iter = iter + 1
|
|
if max_iterations and iter >= max_iterations then
|
|
return rnd_x
|
|
end
|
|
end
|
|
end
|
|
|
|
-- input: str of format:
|
|
-- X1, X2, X3...XN : Y1, Y2, Y3...Yn
|
|
function str_to_coords(str)
|
|
coords = {
|
|
["x"] = {},
|
|
["y"] = {},
|
|
}
|
|
buffer = str_explode(str, ":")
|
|
|
|
coords["x"] = str_explode_num(buffer[1], ",")
|
|
coords["y"] = str_explode_num(buffer[2], ",")
|
|
if #coords["x"] ~= #coords["y"] then
|
|
printe("Input for coord string %s produced inequal output, returning", str)
|
|
return nil
|
|
end
|
|
return coords
|
|
end
|