8a8bed8e5d
- Run all settings through a common API. - Use modname prefix consistently for setting keys instead of "nodecore". - Add automatic dumping of settingtypes.txt metadata for maintenance. - Include initial settingtypes.txt for game for main menu use. - Remove per-recipe tuning for pummel recipes, as there were way too many of those to be possibly useful and it was clogging up the settings menu. Use tool rate adjustments to control it instead. - Remove vestigial enable_damage setting.
542 lines
14 KiB
Lua
542 lines
14 KiB
Lua
-- LUALOCALS < ---------------------------------------------------------
|
|
local ItemStack, PcgRandom, ipairs, math, minetest, nodecore, pairs,
|
|
string, tostring, type, unpack, vector
|
|
= ItemStack, PcgRandom, ipairs, math, minetest, nodecore, pairs,
|
|
string, 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 > ---------------------------------------------------------
|
|
|
|
local modname = minetest.get_current_modname()
|
|
|
|
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, rng)
|
|
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 = (rng or 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, rng)
|
|
local r = 0
|
|
while r == 0 do r = (rng or math_random)() end
|
|
return math_floor(-math_log(r) * (mean + 0.5))
|
|
end
|
|
|
|
function nodecore.seeded_rng(seed)
|
|
if PcgRandom then
|
|
seed = math_floor((seed - math_floor(seed)) * 2 ^ 32 - 2 ^ 31)
|
|
local pcg = PcgRandom(seed)
|
|
return function(a, b)
|
|
if b then
|
|
return pcg:next(a, b)
|
|
elseif a then
|
|
return pcg:next(1, a)
|
|
end
|
|
return (pcg:next() + 2 ^ 31) / 2 ^ 32
|
|
end
|
|
end
|
|
return math_random
|
|
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 props = player:get_properties()
|
|
local vs = props and props.visual_size
|
|
return vs and 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.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
|
|
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
|
|
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
|
|
|
|
local function scrubkey(s)
|
|
return string_lower(string_gsub(tostring(s), "%W+", "_"))
|
|
end
|
|
|
|
function nodecore.rate_adjustment(...)
|
|
local rate = 1
|
|
local key = modname .. "_rate"
|
|
local name = ""
|
|
for _, k in ipairs({...}) do
|
|
if not k then break end
|
|
key = key .. "_" .. scrubkey(k)
|
|
name = name .. " > " .. k
|
|
local adj = nodecore.setting_float(key, 1, "Speed adjust" .. name,
|
|
[[Speed adjustment ratio, multiplied by all parent ratios.
|
|
Intended for custom servers and special sub-game types only.]])
|
|
if adj then rate = rate * adj end
|
|
end
|
|
return rate
|
|
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 props = obj:get_properties()
|
|
local cb = props.collisionbox
|
|
if props.static_save
|
|
and 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]
|
|
and obj.get_luaentity and obj:get_luaentity() then
|
|
return obj
|
|
end
|
|
end
|
|
end
|
|
|
|
local gravity = nodecore.setting_float("movement_gravity", 9.81)
|
|
local friction = nodecore.setting_float(modname .. "_air_friction", 0.0004,
|
|
"Air friction", [[Air friction coefficient for velocity-squared
|
|
term. Used to adjust air resistance for player and moving entities,
|
|
especially tuning terminal velocity. Lower terminal velocity may
|
|
reduce players stopping on onloaded chunks while falling on a
|
|
busy/slow server.]])
|
|
|
|
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
|
|
|
|
function nodecore.meta_serializable(meta)
|
|
local mt = type(meta)
|
|
if mt == "table" or mt == "userdata" then
|
|
if type(meta.to_table) == "function" then
|
|
meta = meta:to_table()
|
|
end
|
|
for _, list in pairs(meta.inventory or {}) do
|
|
for i, stack in pairs(list) do
|
|
if type(stack) == "userdata" then
|
|
list[i] = stack:to_string()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return meta
|
|
end
|