421 lines
14 KiB
Lua
421 lines
14 KiB
Lua
--- misc. common tools for 4dguns
|
|
-- @script misc_helpers
|
|
|
|
Guns4d.math = {}
|
|
Guns4d.table = {}
|
|
|
|
--store this so there arent duplicates
|
|
Guns4d.unique_id = {
|
|
generated = {},
|
|
}
|
|
|
|
--[[format of field modifiers
|
|
{
|
|
int_field = { --the field is an integer
|
|
add = .1 --add .1 to the field (after multiplying)
|
|
mul = 2 --multipy before adding
|
|
},
|
|
int_field_2 = {
|
|
override = 4 --sets the field to 4
|
|
override_priority = 2 --if others set and have a higher priority, this will be it's priority
|
|
remove = false --true if you want to remove it
|
|
}
|
|
table_field = {
|
|
int_field = {. . .}
|
|
}
|
|
}
|
|
]]
|
|
function Guns4d.apply_field_modifiers(props, mods)
|
|
local out_props = {}
|
|
for i, v in pairs(props) do
|
|
if type(v)=="number" then
|
|
local add = 0
|
|
local mul = 1
|
|
local override
|
|
local remove = false
|
|
local priority = math.huge
|
|
for _, modifier in ipairs(mods) do
|
|
local a = modifier[i]
|
|
if a then
|
|
add = add + (a.add or 0)
|
|
mul = mul * (a.mul or 1)
|
|
if a.override and (priority > (a.priority or 10)) then
|
|
override = a.override
|
|
priority = a.priority or 10
|
|
end
|
|
remove = a.remove
|
|
end
|
|
end
|
|
out_props[i] = (((override or v) or 0)*mul)+add
|
|
if remove then
|
|
out_props[i] = nil
|
|
end
|
|
elseif type(v)=="table" then
|
|
for _, modifier in pairs(mods) do
|
|
local a = modifier[i]
|
|
Guns4d.apply_field_modifiers(v, a)
|
|
end
|
|
else
|
|
local override
|
|
local priority = math.huge
|
|
local remove
|
|
for _, modifier in ipairs(mods) do
|
|
local a = modifier[i]
|
|
if type(v)==type(a.override) then
|
|
if a.override and (priority > (a.priority or 10)) then
|
|
override = a.override
|
|
priority = a.priority or 10
|
|
end
|
|
remove = a.remove
|
|
if a.remove then
|
|
out_props[i]=nil
|
|
end
|
|
elseif a then
|
|
minetest.log("error", "modifier name: "..(modifier._modifier_name or "???").."attempted to override a "..type(v).." with a "..type(v).." value")
|
|
end
|
|
end
|
|
out_props[i] = ((override~=nil) and override) or out_props[i]
|
|
if remove then
|
|
out_props[i] = nil
|
|
end
|
|
end
|
|
end
|
|
return out_props
|
|
end
|
|
print(dump(Guns4d.apply_field_modifiers({
|
|
a=0,
|
|
y=1,
|
|
z=10,
|
|
st="string"
|
|
}, {
|
|
a={
|
|
add=1,
|
|
mul=2
|
|
},
|
|
z={
|
|
mul=2,
|
|
add=1
|
|
},
|
|
st={
|
|
override=10
|
|
}
|
|
}
|
|
)))
|
|
|
|
function Guns4d.unique_id.generate()
|
|
local genned_ids = Guns4d.unique_id.generated
|
|
local id = string.sub(tostring(math.random()), 3)
|
|
while genned_ids[id] do
|
|
id = string.sub(tostring(math.random()), 3)
|
|
end
|
|
genned_ids[id] = true
|
|
return id
|
|
end
|
|
|
|
---math helpers.
|
|
-- in guns4d.math
|
|
--@section math
|
|
|
|
--all of the following is disgusting and violates the namespace because I got used to love2d.
|
|
function Guns4d.math.clamp(val, lower, upper)
|
|
if lower > upper then lower, upper = upper, lower end
|
|
return math.max(lower, math.min(upper, val))
|
|
end
|
|
--- picks a random index, with odds based on it's value. Returns the index of the selected.
|
|
-- @param tbl a table containing weights, example
|
|
-- {
|
|
-- ["sound"] = 999, --999 in 1000 chance this is chosen
|
|
-- ["rare_sound"] = 1 --1 in 1000 chance this is chosen
|
|
-- }
|
|
-- @function weighted_randoms
|
|
function Guns4d.math.weighted_randoms(tbl)
|
|
local total_weight = 0
|
|
local new_tbl = {}
|
|
for i, v in pairs(tbl) do
|
|
total_weight=total_weight+v
|
|
table.insert(new_tbl, {i, v})
|
|
end
|
|
local ran = math.random()*total_weight
|
|
--[[the point of the new table is so we can have them
|
|
sorted in order of weight, so we can check if the random
|
|
fufills the lower values first.]]
|
|
table.sort(new_tbl, function(a, b) return a[2] > b[2] end)
|
|
local scaled_weight = 0 --[[so this is added to the weight so it's chances are proportional
|
|
to it's actual weight as opposed to being wether the lower values are picked- if you have
|
|
weight 19 and 20, 20 would have a 1/20th chance of being picked if we didn't do this]]
|
|
for i, v in pairs(new_tbl) do
|
|
if (v[2]+scaled_weight) > ran then
|
|
return v[1]
|
|
end
|
|
scaled_weight = scaled_weight + v[2]
|
|
end
|
|
end
|
|
function Guns4d.math.smooth_ratio(r)
|
|
return ((math.sin((r-.5)*math.pi))+1)/2
|
|
end
|
|
--[[
|
|
--for table vectors that aren't vector objects
|
|
local function tolerance_check(a,b,tolerance)
|
|
return math.abs(a-b) > tolerance
|
|
end
|
|
function Guns4d.math.vectors_in_tolerance(v, vb, tolerance)
|
|
tolerance = tolerance or 0
|
|
return (
|
|
tolerance_check(v.x, vb.x, tolerance) and
|
|
tolerance_check(v.y, vb.y, tolerance) and
|
|
tolerance_check(v.z, vb.z, tolerance)
|
|
)
|
|
end
|
|
]]
|
|
|
|
---table helpers.
|
|
-- in guns4d.table
|
|
--@section table
|
|
|
|
--copy everything
|
|
function Guns4d.table.deep_copy(tbl, copy_metatable, indexed_tables)
|
|
if not indexed_tables then indexed_tables = {} end
|
|
local new_table = {}
|
|
local metat = getmetatable(tbl)
|
|
if metat then
|
|
if copy_metatable then
|
|
setmetatable(new_table, metat)
|
|
end
|
|
end
|
|
for i, v in pairs(tbl) do
|
|
if type(v) == "table" then
|
|
if not indexed_tables[v] then
|
|
indexed_tables[v] = true
|
|
new_table[i] = Guns4d.table.deep_copy(v, copy_metatable)
|
|
end
|
|
else
|
|
new_table[i] = v
|
|
end
|
|
end
|
|
return new_table
|
|
end
|
|
|
|
|
|
function Guns4d.table.contains(tbl, value)
|
|
for i, v in pairs(tbl) do
|
|
if v == value then
|
|
return i
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
local function parse_index(i)
|
|
if type(i) == "string" then
|
|
return "[\""..i.."\"]"
|
|
else
|
|
return "["..tostring(i).."]"
|
|
end
|
|
end
|
|
--dump() sucks.
|
|
local table_contains = Guns4d.table.contains
|
|
function Guns4d.table.tostring(tbl, shallow, list_length_lim, depth_limit, tables, depth)
|
|
--create a list of tables that have been tostringed in this chain
|
|
if not table then return "nil" end
|
|
if not tables then tables = {this_table = tbl} end
|
|
if not depth then depth = 0 end
|
|
depth = depth + 1
|
|
local str = "{"
|
|
local initial_string = "\n"
|
|
for i = 1, depth do
|
|
initial_string = initial_string .. " "
|
|
end
|
|
if depth > (depth_limit or math.huge) then
|
|
return "(TABLE): depth limited reached"
|
|
end
|
|
local iterations = 0
|
|
for i, v in pairs(tbl) do
|
|
iterations = iterations + 1
|
|
local val_type = type(v)
|
|
if val_type == "string" then
|
|
str = str..initial_string..parse_index(i).." = \""..v.."\","
|
|
elseif val_type == "table" and (not shallow) then
|
|
local contains = table_contains(tables, v)
|
|
--to avoid infinite loops, make sure that the table has not been tostringed yet
|
|
if not contains then
|
|
tables[i] = v
|
|
str = str..initial_string..parse_index(i).." = "..Guns4d.table.tostring(v, shallow, list_length_lim, depth_limit, tables, depth)..","
|
|
else
|
|
str = str..initial_string..parse_index(i).." = "..tostring(v).." (index: '"..tostring(contains).."'),"
|
|
end
|
|
else
|
|
str = str..initial_string..parse_index(i).." = "..tostring(v)..","
|
|
end
|
|
end
|
|
if iterations > (list_length_lim or math.huge) then
|
|
return "(TABLE): too long, 100+ indices"
|
|
end
|
|
return str..string.sub(initial_string, 1, -5).."}"
|
|
end
|
|
--[[function Guns4d.table.tostring_structure_only(tbl, shallow, tables, depth)
|
|
--create a list of tables that have been tostringed in this chain
|
|
if not table then return "nil" end
|
|
if not tables then tables = {this_table = tbl} end
|
|
if not depth then depth = 0 end
|
|
depth = depth + 1
|
|
local str = ""
|
|
local initial_string = "\n"
|
|
for i = 1, depth do
|
|
initial_string = initial_string .. " "
|
|
end
|
|
if depth > 20 then
|
|
return "(TABLE): depth limited reached (20 nested tables)"
|
|
end
|
|
local iterations = 0
|
|
if tbl.name then
|
|
str = str..initial_string.."[\"name\"] = \""..tbl.name.."\","
|
|
end
|
|
if tbl.type then
|
|
str = str..initial_string.."[\"type\"] = \""..tbl.type.."\","
|
|
end
|
|
for i, v in pairs(tbl) do
|
|
iterations = iterations + 1
|
|
local val_type = type(v)
|
|
if val_type == "table" then
|
|
local contains = table_contains(tables, v)
|
|
--to avoid infinite loops, make sure that the table has not been tostringed yet
|
|
if not contains then
|
|
tables[parse_index(i).." ["..tostring(v).."]"] = v
|
|
str = str..initial_string..parse_index(i).."("..tostring(v)..") = "..Guns4d.table.tostring_structure_only(v, shallow, tables, depth)..","
|
|
elseif type(v) == "table" then
|
|
str = str..initial_string..parse_index(i).." = "..tostring(v)
|
|
else
|
|
str = str..initial_string..parse_index(i).." = "..tostring(v).." ("..tostring(v).."),"
|
|
end
|
|
end
|
|
end
|
|
if iterations == 0 then
|
|
return "{}"
|
|
elseif iterations > 100 then
|
|
return "table too long"
|
|
end
|
|
return "{"..str..string.sub(initial_string, 1, -5).."}"
|
|
end]]
|
|
|
|
--replace fields (and fill sub-tables) in `tbl` with elements in `replacement`. Recursively iterates all sub-tables. use property __overfill=true for subtables that don't want to be overfilled.
|
|
function Guns4d.table.fill(tbl, replacement, preserve_reference, indexed_tables)
|
|
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
|
local new_table = tbl
|
|
if not preserve_reference then
|
|
new_table = Guns4d.table.deep_copy(tbl)
|
|
end
|
|
for i, v in pairs(replacement) do
|
|
if new_table[i] then
|
|
local replacement_type = type(v)
|
|
if replacement_type == "table" then
|
|
if type(new_table[i]) == "table" then
|
|
if not indexed_tables[v] then
|
|
if not new_table[i].__overfill then
|
|
indexed_tables[v] = true
|
|
new_table[i] = Guns4d.table.fill(tbl[i], replacement[i], false, indexed_tables)
|
|
else --if overfill is present, we don't want to preserve the old table.
|
|
new_table[i] = Guns4d.table.deep_copy(replacement[i])
|
|
end
|
|
end
|
|
elseif not replacement[i].__no_copy then
|
|
new_table[i] = Guns4d.table.deep_copy(replacement[i])
|
|
else
|
|
new_table[i] = replacement[i]
|
|
end
|
|
new_table[i].__overfill = nil
|
|
else
|
|
new_table[i] = replacement[i]
|
|
end
|
|
else
|
|
new_table[i] = replacement[i]
|
|
end
|
|
end
|
|
return new_table
|
|
end
|
|
--for class based OOP, ensure values containing a table in btbl are tables in a_tbl- instantiate, but do not fill.
|
|
--[[function table.instantiate_struct(tbl, btbl, indexed_tables)
|
|
if not indexed_tables then indexed_tables = {} end --store tables to prevent circular referencing
|
|
for i, v in pairs(btbl) do
|
|
if type(v) == "table" and not indexed_tables[v] then
|
|
indexed_tables[v] = true
|
|
if not tbl[i] then
|
|
tbl[i] = table.instantiate_struct({}, v, indexed_tables)
|
|
elseif type(tbl[i]) == "table" then
|
|
tbl[i] = table.instantiate_struct(tbl[i], v, indexed_tables)
|
|
end
|
|
end
|
|
end
|
|
return tbl
|
|
end]]
|
|
function Guns4d.table.shallow_copy(t)
|
|
local new_table = {}
|
|
for i, v in pairs(t) do
|
|
new_table[i] = v
|
|
end
|
|
return new_table
|
|
end
|
|
|
|
---other helpers
|
|
--@section other
|
|
|
|
--for the following function only:
|
|
--for license see the link on the next line (direct permission was granted).
|
|
--https://github.com/3dreamengine/3DreamEngine
|
|
function Guns4d.math.rltv_point_to_hud(pos, fov, aspect)
|
|
local n = .1 --near
|
|
local f = 1000 --far
|
|
local scale = math.tan(fov * math.pi / 360)
|
|
local r = scale * n * aspect
|
|
local t = scale * n
|
|
--optimized matrix multiplication by removing constants
|
|
--looks like a mess, but its only the opengl projection multiplied by the camera
|
|
local a1 = n / r
|
|
--local a6 = n / t * m
|
|
local a6 = n / t
|
|
local fn1 = 1 / (f - n)
|
|
local a11 = -(f + n) * fn1
|
|
local x = (pos.x/pos.z)*a1
|
|
local y = (pos.y/pos.z)*a6
|
|
local z = (pos.z/pos.z)*a11
|
|
return {x=x / 2,y=-y / 2} --output needs to be offset by +.5 on both for HUD elements, but this cannot be integrated.
|
|
end
|
|
|
|
--Code: Elkien3 (CC BY-SA 3.0)
|
|
--https://github.com/Elkien3/spriteguns/blob/1c632fe12c35c840d6c0b8307c76d4dfa44d1bd7/init.lua#L76
|
|
function Guns4d.math.nearest_point_on_line(lineStart, lineEnd, pnt)
|
|
local line = vector.subtract(lineEnd, lineStart)
|
|
local len = vector.length(line)
|
|
line = vector.normalize(line)
|
|
|
|
local v = vector.subtract(pnt, lineStart)
|
|
local d = vector.dot(v, line)
|
|
d = Guns4d.math.clamp(d, 0, len);
|
|
return vector.add(lineStart, vector.multiply(line, d))
|
|
end
|
|
|
|
--[[function Guns4d.math.rand_box_muller(deviation)
|
|
local tau = math.pi*2
|
|
--our first value cant be 0
|
|
math.randomseed(math.random())
|
|
local r1 = 0
|
|
while r1 == 0 do r1=math.random() end
|
|
local r2=math.random()
|
|
|
|
local a = deviation * math.sqrt(-2.0*math.log(r1))
|
|
return a * math.cos(tau * r1), a * math.sin(tau * r2);
|
|
end]]
|
|
local e = 2.7182818284590452353602874713527 --I don't know how to find it otherwise...
|
|
--deviation just changes the distribution, range is the maximum spread
|
|
function Guns4d.math.angular_normal_distribution(deviation)
|
|
local x=math.random()
|
|
--print(x)
|
|
--positive only normal distribution
|
|
local a = 1/(deviation*math.sqrt(2*math.pi))
|
|
local exp = -.5*(x/deviation)^2
|
|
local exp_x_1 = (-.5*(1/deviation)^2) --exp value where x=1
|
|
local y=( (a*e^exp) - (a*e^exp_x_1) )/( a - (a*e^exp_x_1) ) --subtraction is to bring the value of x=1 to 0 on the curve and the division is to keep it normalized to an output of one
|
|
local theta = math.random()*math.pi*2
|
|
return y*math.cos(theta), y*math.sin(theta)
|
|
end
|
|
function Guns4d.math.round(n)
|
|
return (n-math.floor(n)<.5 and math.floor(n)) or math.ceil(n)
|
|
end |