nodecore-cd2025/mods/nc_api/util_misc.lua
2020-07-11 20:52:22 -04:00

530 lines
14 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ItemStack, ipairs, math, minetest, nodecore, pairs, string,
tonumber, tostring, type, unpack, vector
= ItemStack, ipairs, math, minetest, nodecore, pairs, string,
tonumber, tostring, type, unpack, vector
local math_abs, math_cos, math_floor, math_log, math_pi, math_pow,
math_random, math_sin, math_sqrt, string_format, string_gsub,
string_lower
= math.abs, math.cos, math.floor, math.log, math.pi, math.pow,
math.random, math.sin, math.sqrt, string.format, string.gsub,
string.lower
-- LUALOCALS > ---------------------------------------------------------
for k, v in pairs(minetest) do
if type(v) == "function" then
-- Late-bind in case minetest methods overridden.
nodecore[k] = function(...) return minetest[k](...) end
else
nodecore[k] = v
end
end
local function underride(t, u, u2, ...)
if u2 then underride(u, u2, ...) end
for k, v in pairs(u) do
if t[k] == nil then
t[k] = v
elseif type(t[k]) == "table" and type(v) == "table" then
underride(t[k], v)
end
end
return t
end
nodecore.underride = underride
function nodecore.mkreg()
local t = {}
local f = function(x) t[#t + 1] = x end
return f, t
end
function nodecore.memoize(func)
local cache
return function()
if cache then return cache[1] end
cache = {func()}
return cache[1]
end
end
function nodecore.dirs()
return {
{n = "e", x = 1, y = 0, z = 0},
{n = "w", x = -1, y = 0, z = 0},
{n = "u", x = 0, y = 1, z = 0},
{n = "d", x = 0, y = -1, z = 0},
{n = "n", x = 0, y = 0, z = 1},
{n = "s", x = 0, y = 0, z = -1}
}
end
function nodecore.pickrand(tbl, weight)
weight = weight or function() end
local t = {}
local max = 0
for k, v in pairs(tbl) do
local w = weight(v) or 1
if w > 0 then
max = max + w
t[#t + 1] = {w = w, k = k, v = v}
end
end
if max <= 0 then return end
max = math_random() * max
for _, v in ipairs(t) do
max = max - v.w
if max <= 0 then return v.v, v.k end
end
end
do
local saved
function nodecore.boxmuller()
local old = saved
if old then
saved = nil
return old
end
local r = math_sqrt(-2 * math_log(math_random()))
local t = 2 * math_pi * math_random()
saved = r * math_sin(t)
return r * math_cos(t)
end
end
function nodecore.exporand(mean)
local r = 0
while r == 0 do r = math_random() end
return math_floor(-math_log(r) * (mean + 0.5))
end
function nodecore.extend_item(name, func)
local orig = minetest.registered_items[name] or {}
local copy = {}
for k, v in pairs(orig) do copy[k] = v end
copy = func(copy, orig) or copy
minetest.register_item(":" .. name, copy)
end
function nodecore.fixedbox(x, ...)
return {type = "fixed", fixed = {
x or {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
...
}}
end
function nodecore.interact(player)
if not player then return end
if type(player) ~= "string" then
if not (player.is_player and player:is_player()) then
return true
end
player = player:get_player_name()
end
return minetest.get_player_privs(player).interact
end
function nodecore.player_visible(player)
if type(player) == "string" then player = minetest.get_player_by_name(player) end
if not player then return end
local vs = player:get_properties().visual_size
return vs.x > 0 and vs.y > 0
end
function nodecore.wieldgroup(who, group)
local wielded = who and who:get_wielded_item()
local nodedef = minetest.registered_nodes[wielded:get_name()]
if nodedef then return nodedef.groups and nodedef.groups[group] end
local caps = wielded and wielded:get_tool_capabilities()
return caps and caps.groupcaps and caps.groupcaps[group]
end
function nodecore.toolspeed(what, groups)
if not what then return end
local dg = what:get_tool_capabilities().groupcaps
local t
for gn, lv in pairs(groups) do
local gt = dg[gn]
gt = gt and gt.times
gt = gt and gt[lv]
if gt and (not t or t > gt) then t = gt end
end
if (not t) and (not what:is_empty()) then
return nodecore.toolspeed(ItemStack(""), groups)
end
return t
end
function nodecore.tool_digs(what, groups)
local s = nodecore.toolspeed(what, groups)
return s and s <= 4
end
function nodecore.interval(after, func)
local go
local setnext = (type(after) == "function")
and function() return minetest.after(after(), go) end
or function() return minetest.after(after, go) end
go = function() setnext() return func() end
minetest.after(0, go)
end
function nodecore.wear_wield(player, groups, qty)
local wielded = player:get_wielded_item()
if wielded then
local wdef = wielded:get_definition()
local tp = wielded:get_tool_capabilities()
local dp = minetest.get_dig_params(groups, tp)
if wdef and wdef.after_use then
wielded = wdef.after_use(wielded, player, nil, dp) or wielded
else
if not minetest.settings:get_bool("creative_mode") then
wielded:add_wear(dp.wear * (qty or 1))
if wielded:get_count() <= 0 and wdef.sound
and wdef.sound.breaks then
nodecore.sound_play(wdef.sound.breaks,
{object = player, gain = 0.5})
end
end
end
return player:set_wielded_item(wielded)
end
end
function nodecore.consume_wield(player, qty)
local wielded = player:get_wielded_item()
if wielded then
local wdef = wielded:get_definition()
if wdef.stack_max > 1 and qty then
local have = wielded:get_count() - qty
if have <= 0 then
wielded = ItemStack("")
else
wielded:set_count(have)
end
end
return player:set_wielded_item(wielded)
end
end
function nodecore.loaded_mods()
local t = {}
for _, v in pairs(minetest.get_modnames()) do
t[v] = true
end
return t
end
function nodecore.node_group(name, pos, node)
node = node or minetest.get_node(pos)
local def = minetest.registered_nodes[node.name] or {}
return def.groups and def.groups[name]
end
function nodecore.find_nodes_around(pos, spec, r, s)
r = r or 1
if type(r) == "number" then
return minetest.find_nodes_in_area(
{x = pos.x - r, y = pos.y - r, z = pos.z - r},
{x = pos.x + r, y = pos.y + r, z = pos.z + r},
spec)
end
s = s or r
return minetest.find_nodes_in_area(
{x = pos.x - (r.x or r[1]), y = pos.y - (r.y or r[2]), z = pos.z - (r.z or r[3])},
{x = pos.x + (s.x or s[1]), y = pos.y + (s.y or s[2]), z = pos.z + (s.z or s[3])},
spec)
end
function nodecore.quenched(pos, r)
local qty = #nodecore.find_nodes_around(pos, "group:coolant", r)
return (qty > 0) and qty or nil
end
function nodecore.node_spin_custom(...)
local arr = {...}
arr[0] = false
local lut = {}
for i = 1, #arr do
lut[arr[i - 1]] = arr[i]
end
lut[arr[#arr]] = arr[1]
local qty = #arr
return function(pos, node, clicker, itemstack)
if nodecore.protection_test(pos, clicker) then return end
node = node or minetest.get_node(pos)
node.param2 = lut[node.param2] or lut[false]
if clicker:is_player() then
nodecore.log("action", clicker:get_player_name() .. " spins "
.. node.name .. " at " .. minetest.pos_to_string(pos)
.. " to param2 " .. node.param2 .. " ("
.. qty .. " total)")
end
minetest.swap_node(pos, node)
nodecore.node_sound(pos, "place")
local def = minetest.registered_items[node.name] or {}
if def.on_spin then def.on_spin(pos, node) end
return itemstack
end
end
function nodecore.node_spin_filtered(func)
local rots = {}
for i = 0, 23 do
local f = nodecore.facedirs[i]
local hit
for j = 1, #rots do
if not hit then
local o = nodecore.facedirs[rots[j]]
hit = hit or func(f, o)
end
end
if not hit then rots[#rots + 1] = f.id end
end
return nodecore.node_spin_custom(unpack(rots))
end
function nodecore.node_change(pos, node, newname)
if node.name == newname then return end
return minetest.set_node(pos, underride({name = newname}, node))
end
local function scrubkey(s)
return string_lower(string_gsub(tostring(s), "%W+", "_"))
end
function nodecore.rate_adjustment(...)
local rate = 1
local key = scrubkey(nodecore.product)
for _, k in ipairs({...}) do
if not k then break end
key = key .. "_" .. scrubkey(k)
local adj = tonumber(minetest.settings:get(key))
if adj then rate = rate * adj end
end
return rate
end
local infodump_key = scrubkey(nodecore.product) .. "_infodump"
function nodecore.infodump()
return minetest.settings:get_bool(infodump_key)
end
function nodecore.obstructed(minpos, maxpos)
if not maxpos then
maxpos = {x = minpos.x + 0.5, y = minpos.y + 0.5, z = minpos.z + 0.5}
minpos = {x = minpos.x - 0.5, y = minpos.y - 0.5, z = minpos.z - 0.5}
end
local avgpos = vector.multiply(vector.add(minpos, maxpos), 0.5)
local radius = 4 + vector.distance(minpos, maxpos) / 2
for _, obj in pairs(minetest.get_objects_inside_radius(avgpos, radius)) do
local op = obj:get_pos()
local cb = obj:get_properties().collisionbox
if maxpos.x > op.x + cb[1] and minpos.x < op.x + cb[4]
and maxpos.y > op.y + cb[2] and minpos.y < op.y + cb[5]
and maxpos.z > op.z + cb[3] and minpos.z < op.z + cb[6]
then
local lua = obj.get_luaentity and obj:get_luaentity()
if lua and not lua.is_stack then
return obj
end
end
end
end
local gravity = tonumber(minetest.settings:get("movement_gravity")) or 9.81
local friction = tonumber(minetest.settings:get("nodecore_air_friction")) or 0.0004
local function air_accel_factor(v)
local q = (friction * v * v) * 2 - 1
return q > 0 and q or 0
end
function nodecore.grav_air_physics_player(v)
if v.y > 0 then return 1 end
return 1 - air_accel_factor(v.y)
end
local function air_accel_net(v)
return v == 0 and 0 or v / -math_abs(v) * gravity * air_accel_factor(v)
end
function nodecore.grav_air_accel(v)
return {
x = air_accel_net(v.x),
y = air_accel_net(v.y) - gravity,
z = air_accel_net(v.z)
}
end
function nodecore.grav_air_accel_ent(obj)
local cur = obj:get_acceleration()
local new = nodecore.grav_air_accel(obj:get_velocity())
if vector.equals(cur, new) then return end
return obj:set_acceleration(new)
end
function nodecore.near_unloaded(pos, radius)
return minetest.find_node_near(pos, radius or 1, {"ignore"}, true)
end
function nodecore.get_objects_at_pos(pos)
pos = vector.round(pos)
local t = {}
-- get_objects_inside_radius just loops over these and does a euclidian
-- distance check anyway, which we can skip
for _, obj in pairs(minetest.object_refs) do
local p = obj:get_pos()
if p and vector.equals(vector.round(p), pos) then
t[#t + 1] = obj
end
end
return t
end
function nodecore.get_depth_light(y, qty)
qty = qty or 4/5
if y < 0 then qty = qty * math_pow(2, y / 64) end
return qty
end
nodecore.light_sun = 15
nodecore.light_sky = math_floor(0.5 + nodecore.light_sun * nodecore.get_depth_light(0))
function nodecore.is_full_sun(pos)
return pos.y >= 0 and minetest.get_node_light(pos, 0.5) == nodecore.light_sun
end
function nodecore.get_node_light(pos)
local artificial = minetest.get_node_light(pos, 0)
if not artificial then return end
local natural = math_floor(0.5 + minetest.get_node_light(pos, 0.5)
* nodecore.get_depth_light(pos.y))
return artificial > natural and artificial or natural
end
local liquids = {}
minetest.after(0, function()
for k, v in pairs(minetest.registered_items) do
if v.liquidtype and v.liquidtype ~= "none" then
liquids[k] = v
end
end
end)
nodecore.registered_liquids = liquids
local player_was_swimming = {}
function nodecore.player_swimming(player)
local pname = player:get_player_name()
local pos = player:get_pos()
local r = 0.6
local swimming = true
for dz = -r, r, r do
for dx = -r, r, r do
local p = {
x = pos.x + dx,
y = pos.y,
z = pos.z + dz
}
local node = minetest.get_node(p)
if (node.name == "air" or liquids[node.name]) then
p.y = p.y - 0.35
node = minetest.get_node(p)
end
if node.name == "air" then swimming = nil
elseif not liquids[node.name] then
player_was_swimming[pname] = nil
return
end
end
end
if swimming then
player_was_swimming[pname] = true
return true
end
return player_was_swimming[pname]
end
local function mismatch(a, b)
if type(a) == "table" then
if type(b) ~= "table" then return true end
for k, v in pairs(a) do
if mismatch(v, b[k]) then return true end
end
return
end
if type(a) == "number" and type(b) == "number" then
local ratio = a / b
-- Floating point rounding...
if ratio > 0.99999 and ratio < 1.00001 then return end
end
return a ~= b
end
nodecore.prop_mismatch = mismatch
function nodecore.item_matching_index(items, getnames, idxname, asarray, keymod)
local index = {}
local function itemadd(key, item)
local t = index[key]
if not t then
t = {}
index[key] = t
end
if asarray then
t[#t + 1] = item
else
t[item] = true
end
end
keymod = keymod or function(x) return x end
local report_pending
local function rebuild()
for k in pairs(index) do index[k] = nil end
for _, item in pairs(items) do
for _, name in pairs(getnames(item)) do
if name == true then
for k in pairs(minetest.registered_items) do
itemadd(keymod(k, item), item)
end
elseif type(name) == "string" and name:sub(1, 6) == "group:" then
for k, v in pairs(minetest.registered_items) do
if v and v.groups and v.groups[name:sub(7)] then
itemadd(keymod(k, item), item)
end
end
else
itemadd(keymod(name, item), item)
end
end
end
if idxname and not report_pending then
report_pending = true
minetest.after(0, function()
report_pending = nil
local keys = 0
local defs = 0
local peak = 0
for _, v in pairs(index) do
keys = keys + 1
local n = 0
for _ in pairs(v) do n = n + 1 end
defs = defs + n
if n > peak then peak = n end
end
nodecore.log("action", string_format(
"%s %s: %d keys, %d defs, %d peak",
"item_matching_index",
idxname, keys, defs, peak))
end)
end
end
minetest.after(0, rebuild)
return index, rebuild
end
function nodecore.protection_test(pos, player)
if not player then return end
if type(player) ~= "string" then
if not player:is_player() then return end
player = player:get_player_name()
end
if minetest.is_protected(pos, player) then
minetest.record_protection_violation(pos, player)
return true
end
end