From adb4b4fdaf51da4a18c8ce80c22153dd9a6d9f88 Mon Sep 17 00:00:00 2001 From: Lars Mueller Date: Sat, 27 Mar 2021 20:10:49 +0100 Subject: [PATCH] Indentation: Use only tabs --- b3d.lua | 644 +++++++++++++------------- binary.lua | 168 +++---- bluon.lua | 520 ++++++++++----------- conf.lua | 542 +++++++++++----------- data.lua | 12 +- file.lua | 10 +- heap.lua | 74 +-- init.lua | 212 ++++----- kdtree.lua | 60 +-- log.lua | 94 ++-- minetest/collisionboxes.lua | 256 +++++------ minetest/colorspec.lua | 142 +++--- minetest/liquid.lua | 206 ++++----- minetest/misc.lua | 290 ++++++------ minetest/raycast.lua | 258 +++++------ minetest/wielditem_change.lua | 64 +-- mod.lua | 212 ++++----- quaternion.lua | 30 +- ranked_set.lua | 480 ++++++++++---------- schema.lua | 544 +++++++++++----------- table.lua | 822 +++++++++++++++++----------------- test.lua | 422 ++++++++--------- vector.lua | 224 ++++----- 23 files changed, 3143 insertions(+), 3143 deletions(-) diff --git a/b3d.lua b/b3d.lua index efa0b64..fab144e 100644 --- a/b3d.lua +++ b/b3d.lua @@ -6,350 +6,350 @@ local metatable = {__index = getfenv(1)} --+ See `b3d_specification.txt` as well as https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp --> B3D model function read(stream) - local left = 8 + local left = 8 - local function byte() - left = left - 1 - return assert(stream:read(1):byte()) - end + local function byte() + left = left - 1 + return assert(stream:read(1):byte()) + end - local function int() - local value = byte() + byte() * 0x100 + byte() * 0x10000 + byte() * 0x1000000 - if value >= 2^31 then - return value - 2^32 - end - return value - end + local function int() + local value = byte() + byte() * 0x100 + byte() * 0x10000 + byte() * 0x1000000 + if value >= 2^31 then + return value - 2^32 + end + return value + end - local function id() - return int() + 1 - end + local function id() + return int() + 1 + end - local function optional_id() - local id = int() - if id == -1 then - return - end - return id + 1 - end + local function optional_id() + local id = int() + if id == -1 then + return + end + return id + 1 + end - local function string() - local rope = {} - while true do - left = left - 1 - local char = assert(stream:read(1)) - if char == "\0" then - return table.concat(rope) - end - table.insert(rope, char) - end - end + local function string() + local rope = {} + while true do + left = left - 1 + local char = assert(stream:read(1)) + if char == "\0" then + return table.concat(rope) + end + table.insert(rope, char) + end + end - local read_single = modlib.binary.read_single - local function float() - return read_single(byte) - end + local read_single = modlib.binary.read_single + local function float() + return read_single(byte) + end - local function float_array(length) - local list = {} - for index = 1, length do - list[index] = float() - end - return list - end + local function float_array(length) + local list = {} + for index = 1, length do + list[index] = float() + end + return list + end - local function color() - return { - r = float(), - g = float(), - b = float(), - a = float() - } - end + local function color() + return { + r = float(), + g = float(), + b = float(), + a = float() + } + end - local function vector3() - return float_array(3) - end + local function vector3() + return float_array(3) + end - local function quaternion() - return {[4] = float(), [1] = float(), [2] = float(), [3] = float()} - end + local function quaternion() + return {[4] = float(), [1] = float(), [2] = float(), [3] = float()} + end - local function content() - assert(left >= 0, stream:seek()) - return left ~= 0 - end + local function content() + assert(left >= 0, stream:seek()) + return left ~= 0 + end - local chunk - local chunks = { - TEXS = function() - local textures = {} - while content() do - table.insert(textures, { - file = string(), - flags = int(), - blend = int(), - pos = float_array(2), - scale = float_array(2), - rotation = float() - }) - end - return textures - end, - BRUS = function() - local brushes = {} - brushes.n_texs = int() - assert(brushes.n_texs <= 8) - while content() do - local brush = { - name = string(), - color = color(), - shininess = float(), - blend = float(), - fx = float(), - texture_id = {} - } - for index = 1, brushes.n_texs do - brush.texture_id[index] = optional_id() - end - table.insert(brushes, brush) - end - return brushes - end, - VRTS = function() - local vertices = { - flags = int(), - tex_coord_sets = int(), - tex_coord_set_size = int() - } - assert(vertices.tex_coord_sets <= 8 and vertices.tex_coord_set_size <= 4) - local has_normal = (vertices.flags % 2 == 1) or nil - local has_color = (math.floor(vertices.flags / 2) % 2 == 1) or nil - while content() do - local vertex = { - pos = vector3(), - normal = has_normal and vector3(), - color = has_color and color(), - tex_coords = {} - } - for tex_coord_set = 1, vertices.tex_coord_sets do - local tex_coords = {} - for tex_coord = 1, vertices.tex_coord_set_size do - tex_coords[tex_coord] = float() - end - vertex.tex_coords[tex_coord_set] = tex_coords - end - table.insert(vertices, vertex) - end - return vertices - end, - TRIS = function() - local tris = { - brush_id = id(), - vertex_ids = {} - } - while content() do - table.insert(tris.vertex_ids, {id(), id(), id()}) - end - return tris - end, - MESH = function() - local mesh = { - brush_id = optional_id(), - vertices = chunk{VRTS = true} - } - mesh.triangle_sets = {} - repeat - local tris = chunk{TRIS = true} - table.insert(mesh.triangle_sets, tris) - until not content() - return mesh - end, - BONE = function() - local bone = {} - while content() do - local vertex_id = id() - assert(not bone[vertex_id], "duplicate vertex weight") - local weight = float() - if weight > 0 then - -- Many exporters include unneeded zero weights - bone[vertex_id] = weight - end - end - return bone - end, - KEYS = function() - local flags = int() - local _flags = flags % 8 - local rotation, scale, position - if _flags >= 4 then - rotation = true - _flags = _flags - 4 - end - if _flags >= 2 then - scale = true - _flags = _flags - 2 - end - position = _flags >= 1 - local bone = { - flags = flags - } - while content() do - table.insert(bone, { - frame = int(), - position = position and vector3() or nil, - scale = scale and vector3() or nil, - rotation = rotation and quaternion() or nil - }) - end - -- Ensure frames are sorted ascending - table.sort(bone, function(a, b) return a.frame < b.frame end) - return bone - end, - ANIM = function() - return { - -- flags are unused - flags = int(), - frames = int(), - fps = float() - } - end, - NODE = function() - local node = { - name = string(), - position = vector3(), - scale = vector3(), - keys = {}, - rotation = quaternion(), - children = {} - } - local node_type - -- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263 - -- Order is not validated; double occurences of mutually exclusive node def are - while content() do - local elem, type = chunk() - if type == "MESH" then - assert(not node_type) - node_type = "mesh" - node.mesh = elem - elseif type == "BONE" then - assert(not node_type) - node_type = "bone" - node.bone = elem - elseif type == "KEYS" then - assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame") - modlib.table.append(node.keys, elem) - elseif type == "NODE" then - table.insert(node.children, elem) - elseif type == "ANIM" then - node.animation = elem - else - assert(not node_type) - node_type = "pivot" - end - end - -- TODO somehow merge keys - return node - end, - BB3D = function() - local version = int() - local self = { - version = { - major = math.floor(version / 100), - minor = version % 100, - raw = version - }, - textures = {}, - brushes = {} - } - assert(self.version.major <= 2, "unsupported version: " .. self.version.major) - while content() do - local field, type = chunk{TEXS = true, BRUS = true, NODE = true} - if type == "TEXS" then - modlib.table.append(self.textures, field) - elseif type == "BRUS" then - modlib.table.append(self.brushes, field) - else - self.node = field - end - end - return self - end - } + local chunk + local chunks = { + TEXS = function() + local textures = {} + while content() do + table.insert(textures, { + file = string(), + flags = int(), + blend = int(), + pos = float_array(2), + scale = float_array(2), + rotation = float() + }) + end + return textures + end, + BRUS = function() + local brushes = {} + brushes.n_texs = int() + assert(brushes.n_texs <= 8) + while content() do + local brush = { + name = string(), + color = color(), + shininess = float(), + blend = float(), + fx = float(), + texture_id = {} + } + for index = 1, brushes.n_texs do + brush.texture_id[index] = optional_id() + end + table.insert(brushes, brush) + end + return brushes + end, + VRTS = function() + local vertices = { + flags = int(), + tex_coord_sets = int(), + tex_coord_set_size = int() + } + assert(vertices.tex_coord_sets <= 8 and vertices.tex_coord_set_size <= 4) + local has_normal = (vertices.flags % 2 == 1) or nil + local has_color = (math.floor(vertices.flags / 2) % 2 == 1) or nil + while content() do + local vertex = { + pos = vector3(), + normal = has_normal and vector3(), + color = has_color and color(), + tex_coords = {} + } + for tex_coord_set = 1, vertices.tex_coord_sets do + local tex_coords = {} + for tex_coord = 1, vertices.tex_coord_set_size do + tex_coords[tex_coord] = float() + end + vertex.tex_coords[tex_coord_set] = tex_coords + end + table.insert(vertices, vertex) + end + return vertices + end, + TRIS = function() + local tris = { + brush_id = id(), + vertex_ids = {} + } + while content() do + table.insert(tris.vertex_ids, {id(), id(), id()}) + end + return tris + end, + MESH = function() + local mesh = { + brush_id = optional_id(), + vertices = chunk{VRTS = true} + } + mesh.triangle_sets = {} + repeat + local tris = chunk{TRIS = true} + table.insert(mesh.triangle_sets, tris) + until not content() + return mesh + end, + BONE = function() + local bone = {} + while content() do + local vertex_id = id() + assert(not bone[vertex_id], "duplicate vertex weight") + local weight = float() + if weight > 0 then + -- Many exporters include unneeded zero weights + bone[vertex_id] = weight + end + end + return bone + end, + KEYS = function() + local flags = int() + local _flags = flags % 8 + local rotation, scale, position + if _flags >= 4 then + rotation = true + _flags = _flags - 4 + end + if _flags >= 2 then + scale = true + _flags = _flags - 2 + end + position = _flags >= 1 + local bone = { + flags = flags + } + while content() do + table.insert(bone, { + frame = int(), + position = position and vector3() or nil, + scale = scale and vector3() or nil, + rotation = rotation and quaternion() or nil + }) + end + -- Ensure frames are sorted ascending + table.sort(bone, function(a, b) return a.frame < b.frame end) + return bone + end, + ANIM = function() + return { + -- flags are unused + flags = int(), + frames = int(), + fps = float() + } + end, + NODE = function() + local node = { + name = string(), + position = vector3(), + scale = vector3(), + keys = {}, + rotation = quaternion(), + children = {} + } + local node_type + -- See https://github.com/blitz-research/blitz3d/blob/master/blitz3d/loader_b3d.cpp#L263 + -- Order is not validated; double occurences of mutually exclusive node def are + while content() do + local elem, type = chunk() + if type == "MESH" then + assert(not node_type) + node_type = "mesh" + node.mesh = elem + elseif type == "BONE" then + assert(not node_type) + node_type = "bone" + node.bone = elem + elseif type == "KEYS" then + assert((node.keys[#node.keys] or {}).frame ~= (elem[1] or {}).frame, "duplicate frame") + modlib.table.append(node.keys, elem) + elseif type == "NODE" then + table.insert(node.children, elem) + elseif type == "ANIM" then + node.animation = elem + else + assert(not node_type) + node_type = "pivot" + end + end + -- TODO somehow merge keys + return node + end, + BB3D = function() + local version = int() + local self = { + version = { + major = math.floor(version / 100), + minor = version % 100, + raw = version + }, + textures = {}, + brushes = {} + } + assert(self.version.major <= 2, "unsupported version: " .. self.version.major) + while content() do + local field, type = chunk{TEXS = true, BRUS = true, NODE = true} + if type == "TEXS" then + modlib.table.append(self.textures, field) + elseif type == "BRUS" then + modlib.table.append(self.brushes, field) + else + self.node = field + end + end + return self + end + } - local function chunk_header() - left = left - 4 - return stream:read(4), int() - end + local function chunk_header() + left = left - 4 + return stream:read(4), int() + end - function chunk(possible_chunks) - local type, new_left = chunk_header() - local parent_left - left, parent_left = new_left, left - if possible_chunks and not possible_chunks[type] then - error("expected one of " .. table.concat(modlib.table.keys(possible_chunks), ", ") .. ", found " .. type) - end - local res = assert(chunks[type])() - assert(left == 0) - left = parent_left - new_left - return res, type - end + function chunk(possible_chunks) + local type, new_left = chunk_header() + local parent_left + left, parent_left = new_left, left + if possible_chunks and not possible_chunks[type] then + error("expected one of " .. table.concat(modlib.table.keys(possible_chunks), ", ") .. ", found " .. type) + end + local res = assert(chunks[type])() + assert(left == 0) + left = parent_left - new_left + return res, type + end - local self = chunk{BB3D = true} - return setmetatable(self, metatable) + local self = chunk{BB3D = true} + return setmetatable(self, metatable) end -- TODO function write(self, stream) local binary_search_frame = modlib.table.binary_search_comparator(function(a, b) - return modlib.table.default_comparator(a, b.frame) + return modlib.table.default_comparator(a, b.frame) end) --> [bonename] = { position = vector, rotation = quaternion, scale = vector } function get_animated_bone_properties(self, keyframe, interpolate) - local function get_frame_values(keys) - local values = keys[keyframe] - if values and values.frame == keyframe then - return { - position = values.position, - rotation = values.rotation, - scale = values.scale - } - end - local index = binary_search_frame(keys, keyframe) - if index > 0 then - return keys[index] - end - index = -index - assert(index > 1 and index <= #keys) - local a, b = keys[index - 1], keys[index] - if not interpolate then - return a - end - local ratio = (keyframe - a.frame) / (b.frame - a.frame) - return { - position = (a.position and b.position and modlib.vector.interpolate(a.position, b.position, ratio)) or a.position or b.position, - rotation = (a.rotation and b.rotation and modlib.quaternion.interpolate(a.rotation, b.rotation, ratio)) or a.rotation or b.rotation, - scale = (a.scale and b.scale and modlib.vector.interpolate(a.scale, b.scale, ratio)) or a.scale or b.scale, - } - end - local bone_properties = {} - local function get_props(node) - local properties = {} - if node.keys and next(node.keys) ~= nil then - properties = modlib.table.add_all(properties, get_frame_values(node.keys)) - end - for _, property in pairs{"position", "rotation", "scale"} do - properties[property] = properties[property] or modlib.table.copy(node[property]) - end - if node.bone then - assert(not bone_properties[node.name]) - bone_properties[node.name] = properties - end - for _, child in pairs(node.children or {}) do - get_props(child) - end - end - get_props(self.node) - return bone_properties + local function get_frame_values(keys) + local values = keys[keyframe] + if values and values.frame == keyframe then + return { + position = values.position, + rotation = values.rotation, + scale = values.scale + } + end + local index = binary_search_frame(keys, keyframe) + if index > 0 then + return keys[index] + end + index = -index + assert(index > 1 and index <= #keys) + local a, b = keys[index - 1], keys[index] + if not interpolate then + return a + end + local ratio = (keyframe - a.frame) / (b.frame - a.frame) + return { + position = (a.position and b.position and modlib.vector.interpolate(a.position, b.position, ratio)) or a.position or b.position, + rotation = (a.rotation and b.rotation and modlib.quaternion.interpolate(a.rotation, b.rotation, ratio)) or a.rotation or b.rotation, + scale = (a.scale and b.scale and modlib.vector.interpolate(a.scale, b.scale, ratio)) or a.scale or b.scale, + } + end + local bone_properties = {} + local function get_props(node) + local properties = {} + if node.keys and next(node.keys) ~= nil then + properties = modlib.table.add_all(properties, get_frame_values(node.keys)) + end + for _, property in pairs{"position", "rotation", "scale"} do + properties[property] = properties[property] or modlib.table.copy(node[property]) + end + if node.bone then + assert(not bone_properties[node.name]) + bone_properties[node.name] = properties + end + for _, child in pairs(node.children or {}) do + get_props(child) + end + end + get_props(self.node) + return bone_properties end \ No newline at end of file diff --git a/binary.lua b/binary.lua index e1919a6..54d4c40 100644 --- a/binary.lua +++ b/binary.lua @@ -3,113 +3,113 @@ --+ Reads doubles (f64) or floats (f32) --: double reads an f64 if true, f32 otherwise function read_float(read_byte, double) - -- First read the mantissa - local mantissa = 0 - for _ = 1, double and 6 or 2 do - mantissa = (mantissa + read_byte()) / 0x100 - end - -- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent - local byte_2, byte_1 = read_byte(), read_byte() - local sign = 1 - if byte_1 >= 0x80 then - sign = -1 - byte_1 = byte_1 - 0x80 - end - local exponent = byte_1 * 2 - if byte_2 >= 0x80 then - exponent = exponent + 1 - byte_2 = byte_2 - 0x80 - end - mantissa = (mantissa + byte_2) / 0x80 - if exponent == 0xFF then - if mantissa == 0 then - return sign * math.huge - end - -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it - -- HACK ((0/0)^1) yields nan, 0/0 yields -nan - return sign == 1 and ((0/0)^1) or 0/0 - end - assert(mantissa < 1) - if exponent == 0 then - -- subnormal value - return sign * 2^-126 * mantissa - end - return sign * 2 ^ (exponent - 127) * (1 + mantissa) + -- First read the mantissa + local mantissa = 0 + for _ = 1, double and 6 or 2 do + mantissa = (mantissa + read_byte()) / 0x100 + end + -- Second and first byte in big endian: last bit of exponent + 7 bits of mantissa, sign bit + 7 bits of exponent + local byte_2, byte_1 = read_byte(), read_byte() + local sign = 1 + if byte_1 >= 0x80 then + sign = -1 + byte_1 = byte_1 - 0x80 + end + local exponent = byte_1 * 2 + if byte_2 >= 0x80 then + exponent = exponent + 1 + byte_2 = byte_2 - 0x80 + end + mantissa = (mantissa + byte_2) / 0x80 + if exponent == 0xFF then + if mantissa == 0 then + return sign * math.huge + end + -- Differentiating quiet and signalling nan is not possible in Lua, hence we don't have to do it + -- HACK ((0/0)^1) yields nan, 0/0 yields -nan + return sign == 1 and ((0/0)^1) or 0/0 + end + assert(mantissa < 1) + if exponent == 0 then + -- subnormal value + return sign * 2^-126 * mantissa + end + return sign * 2 ^ (exponent - 127) * (1 + mantissa) end --+ Reads a single floating point number (f32) function read_single(read_byte) - return read_float(read_byte) + return read_float(read_byte) end --+ Reads a double (f64) function read_double(read_byte) - return read_float(read_byte, true) + return read_float(read_byte, true) end function read_uint(read_byte, bytes) - local factor = 1 - local uint = 0 - for _ = 1, bytes do - uint = uint + read_byte() * factor - factor = factor * 0x100 - end - return uint + local factor = 1 + local uint = 0 + for _ = 1, bytes do + uint = uint + read_byte() * factor + factor = factor * 0x100 + end + return uint end function write_uint(write_byte, uint, bytes) - for _ = 1, bytes do - write_byte(uint % 0x100) - uint = math.floor(uint / 0x100) - end - assert(uint == 0) + for _ = 1, bytes do + write_byte(uint % 0x100) + uint = math.floor(uint / 0x100) + end + assert(uint == 0) end --: on_write function(double) --: double set to true to force f64, false for f32, nil for auto function write_float(write_byte, number, on_write, double) - local sign = 0 - if number < 0 then - number = -number - sign = 0x80 - end - local mantissa, exponent = math.frexp(number) - exponent = exponent + 127 - if exponent > 1 then - -- TODO ensure this deals properly with subnormal numbers - mantissa = mantissa * 2 - 1 - exponent = exponent - 1 - end - local sign_byte = sign + math.floor(exponent / 2) - mantissa = mantissa * 0x80 - local exponent_byte = (exponent % 2) * 0x80 + math.floor(mantissa) - mantissa = mantissa % 1 - local mantissa_bytes = {} - -- TODO ensure this check is proper - if double == nil then - double = mantissa % 2^-23 > 0 - end - if on_write then - on_write(double) - end - local len = double and 6 or 2 - for index = len, 1, -1 do - mantissa = mantissa * 0x100 - mantissa_bytes[index] = math.floor(mantissa) - mantissa = mantissa % 1 - end - assert(mantissa == 0) - for index = 1, len do - write_byte(mantissa_bytes[index]) - end - write_byte(exponent_byte) - write_byte(sign_byte) + local sign = 0 + if number < 0 then + number = -number + sign = 0x80 + end + local mantissa, exponent = math.frexp(number) + exponent = exponent + 127 + if exponent > 1 then + -- TODO ensure this deals properly with subnormal numbers + mantissa = mantissa * 2 - 1 + exponent = exponent - 1 + end + local sign_byte = sign + math.floor(exponent / 2) + mantissa = mantissa * 0x80 + local exponent_byte = (exponent % 2) * 0x80 + math.floor(mantissa) + mantissa = mantissa % 1 + local mantissa_bytes = {} + -- TODO ensure this check is proper + if double == nil then + double = mantissa % 2^-23 > 0 + end + if on_write then + on_write(double) + end + local len = double and 6 or 2 + for index = len, 1, -1 do + mantissa = mantissa * 0x100 + mantissa_bytes[index] = math.floor(mantissa) + mantissa = mantissa % 1 + end + assert(mantissa == 0) + for index = 1, len do + write_byte(mantissa_bytes[index]) + end + write_byte(exponent_byte) + write_byte(sign_byte) end function write_single(write_byte, number) - return write_float(write_byte, number, nil, false) + return write_float(write_byte, number, nil, false) end function write_double(write_byte, number) - return write_float(write_byte, number, nil, true) + return write_float(write_byte, number, nil, true) end \ No newline at end of file diff --git a/bluon.lua b/bluon.lua index ce828c0..a5eb62a 100644 --- a/bluon.lua +++ b/bluon.lua @@ -3,23 +3,23 @@ local bluon = getfenv(1) local metatable = {__index = bluon} function new(self) - return setmetatable(self or {}, metatable) + return setmetatable(self or {}, metatable) end function aux_is_valid() - return false + return false end function aux_len(object) - error("unsupported type: " .. type(object)) + error("unsupported type: " .. type(object)) end function aux_read(type) - error(("unsupported type: 0x%02X"):format(type)) + error(("unsupported type: 0x%02X"):format(type)) end function aux_write(object) - error("unsupported type: " .. type(object)) + error("unsupported type: " .. type(object)) end local uint_widths = {1, 2, 4, 8} @@ -27,291 +27,291 @@ local uint_types = #uint_widths local type_ranges = {} local current = 0 for _, type in ipairs{ - {"boolean", 2}; - -- 0, -nan, +inf, -inf: sign of nan can be ignored - {"number_constant", 4}; - {"number_negative", uint_types}; - {"number_positive", uint_types}; - {"number_f32", 1}; - {"number", 1}; - {"string_constant", 1}; - {"string", uint_types}; - -- (T0, T8, T16, T32, T64) x (L0, L8, L16, L32, L64) - {"table", (uint_types + 1) ^ 2}; - {"reference", uint_types} + {"boolean", 2}; + -- 0, -nan, +inf, -inf: sign of nan can be ignored + {"number_constant", 4}; + {"number_negative", uint_types}; + {"number_positive", uint_types}; + {"number_f32", 1}; + {"number", 1}; + {"string_constant", 1}; + {"string", uint_types}; + -- (T0, T8, T16, T32, T64) x (L0, L8, L16, L32, L64) + {"table", (uint_types + 1) ^ 2}; + {"reference", uint_types} } do - local typename, length = unpack(type) - current = current + length - type_ranges[typename] = current + local typename, length = unpack(type) + current = current + length + type_ranges[typename] = current end local constants = { - [false] = "\0", - [true] = "\1", - [0] = "\2", - -- not possible as table entry as Lua doesn't allow +/-nan as table key - -- [0/0] = "\3", - [math.huge] = "\4", - [-math.huge] = "\5", - [""] = "\20" + [false] = "\0", + [true] = "\1", + [0] = "\2", + -- not possible as table entry as Lua doesn't allow +/-nan as table key + -- [0/0] = "\3", + [math.huge] = "\4", + [-math.huge] = "\5", + [""] = "\20" } local constant_nan = "\3" local function uint_type(uint) - --U8 - if uint <= 0xFF then return 1 end - --U16 - if uint <= 0xFFFF then return 2 end - --U32 - if uint <= 0xFFFFFFFF then return 3 end - --U64 - return 4 + --U8 + if uint <= 0xFF then return 1 end + --U16 + if uint <= 0xFFFF then return 2 end + --U32 + if uint <= 0xFFFFFFFF then return 3 end + --U64 + return 4 end local valid_types = modlib.table.set{"nil", "boolean", "number", "string"} function is_valid(self, object) - local _type = type(object) - if valid_types[_type] then - return true - end - if _type == "table" then - for key, value in pairs(object) do - if not (is_valid(self, key) and is_valid(self, value)) then - return false - end - end - return true - end - return self.aux_is_valid(object) + local _type = type(object) + if valid_types[_type] then + return true + end + if _type == "table" then + for key, value in pairs(object) do + if not (is_valid(self, key) and is_valid(self, value)) then + return false + end + end + return true + end + return self.aux_is_valid(object) end local function uint_len(uint) - return uint_widths[uint_type(uint)] + return uint_widths[uint_type(uint)] end local function is_map_key(key, list_len) - return type(key) ~= "number" or (key < 1 or key > list_len or key % 1 ~= 0) + return type(key) ~= "number" or (key < 1 or key > list_len or key % 1 ~= 0) end function len(self, object) - if constants[object] then - return 1 - end - local _type = type(object) - if _type == "number" then - if object ~= object then - stream:write(constant_nan) - return - end - if object % 1 == 0 then - return 1 + uint_len(object > 0 and object or -object) - end - -- TODO ensure this check is proper - if mantissa % 2^-23 > 0 then - return 9 - end - return 5 - end - local id = object_ids[object] - if id then - return 1 + uint_len(id) - end - current_id = current_id + 1 - object_ids[object] = current_id - if _type == "string" then - local object_len = object:len() - return 1 + uint_len(object_len) + object_len - end - if _type == "table" then - if next(object) == nil then - -- empty {} table - byte(type_ranges.string + 1) - return 1 - end - local list_len = #object - local kv_len = 0 - for key, _ in pairs(object) do - if is_map_key(key, list_len) then - kv_len = kv_len + 1 - end - end - local table_len = 1 + uint_len(list_len) + uint_len(kv_len) - for index = 1, list_len do - table_len = table_len + len(self, object[index]) - end - for key, value in pairs(object) do - if is_map_key(key, list_len) then - table_len = table_len + len(self, key) + len(self, value) - end - end - return len - end - return self.aux_len(object) + if constants[object] then + return 1 + end + local _type = type(object) + if _type == "number" then + if object ~= object then + stream:write(constant_nan) + return + end + if object % 1 == 0 then + return 1 + uint_len(object > 0 and object or -object) + end + -- TODO ensure this check is proper + if mantissa % 2^-23 > 0 then + return 9 + end + return 5 + end + local id = object_ids[object] + if id then + return 1 + uint_len(id) + end + current_id = current_id + 1 + object_ids[object] = current_id + if _type == "string" then + local object_len = object:len() + return 1 + uint_len(object_len) + object_len + end + if _type == "table" then + if next(object) == nil then + -- empty {} table + byte(type_ranges.string + 1) + return 1 + end + local list_len = #object + local kv_len = 0 + for key, _ in pairs(object) do + if is_map_key(key, list_len) then + kv_len = kv_len + 1 + end + end + local table_len = 1 + uint_len(list_len) + uint_len(kv_len) + for index = 1, list_len do + table_len = table_len + len(self, object[index]) + end + for key, value in pairs(object) do + if is_map_key(key, list_len) then + table_len = table_len + len(self, key) + len(self, value) + end + end + return len + end + return self.aux_len(object) end --: stream any object implementing :write(text) function write(self, object, stream) - if object == nil then - return - end - local object_ids = {} - local current_id = 0 - local function byte(byte) - stream:write(string.char(byte)) - end - local write_uint = modlib.binary.write_uint - local function uint(type, uint) - write_uint(byte, uint, uint_widths[type]) - end - local function uint_with_type(base, _uint) - local type_offset = uint_type(_uint) - byte(base + type_offset) - uint(type_offset, _uint) - end - local write_float = modlib.binary.write_float - local function float_on_write(double) - byte(double and type_ranges.number or type_ranges.number_f32) - end - local function float(number) - write_float(byte, number, float_on_write) - end - local aux_write = self.aux_write - local function _write(object) - local constant = constants[object] - if constant then - stream:write(constant) - return - end - local _type = type(object) - if _type == "number" then - if object ~= object then - stream:write(constant_nan) - return - end - if object % 1 == 0 then - uint_with_type(object > 0 and type_ranges.number_constant or type_ranges.number_negative, object > 0 and object or -object) - return - end - float(object) - return - end - local id = object_ids[object] - if id then - uint_with_type(type_ranges.table, id) - return - end - if _type == "string" then - local len = object:len() - current_id = current_id + 1 - object_ids[object] = current_id - uint_with_type(type_ranges.number, len) - stream:write(object) - return - end - if _type == "table" then - current_id = current_id + 1 - object_ids[object] = current_id - if next(object) == nil then - -- empty {} table - byte(type_ranges.string + 1) - return - end - local list_len = #object - local kv_len = 0 - for key, _ in pairs(object) do - if is_map_key(key, list_len) then - kv_len = kv_len + 1 - end - end - local list_len_sig = uint_type(list_len) - local kv_len_sig = uint_type(kv_len) - byte(type_ranges.string + list_len_sig + kv_len_sig * 5 + 1) - uint(list_len_sig, list_len) - uint(kv_len_sig, kv_len) - for index = 1, list_len do - _write(object[index]) - end - for key, value in pairs(object) do - if is_map_key(key, list_len) then - _write(key) - _write(value) - end - end - return - end - aux_write(object, object_ids) - end - _write(object) + if object == nil then + return + end + local object_ids = {} + local current_id = 0 + local function byte(byte) + stream:write(string.char(byte)) + end + local write_uint = modlib.binary.write_uint + local function uint(type, uint) + write_uint(byte, uint, uint_widths[type]) + end + local function uint_with_type(base, _uint) + local type_offset = uint_type(_uint) + byte(base + type_offset) + uint(type_offset, _uint) + end + local write_float = modlib.binary.write_float + local function float_on_write(double) + byte(double and type_ranges.number or type_ranges.number_f32) + end + local function float(number) + write_float(byte, number, float_on_write) + end + local aux_write = self.aux_write + local function _write(object) + local constant = constants[object] + if constant then + stream:write(constant) + return + end + local _type = type(object) + if _type == "number" then + if object ~= object then + stream:write(constant_nan) + return + end + if object % 1 == 0 then + uint_with_type(object > 0 and type_ranges.number_constant or type_ranges.number_negative, object > 0 and object or -object) + return + end + float(object) + return + end + local id = object_ids[object] + if id then + uint_with_type(type_ranges.table, id) + return + end + if _type == "string" then + local len = object:len() + current_id = current_id + 1 + object_ids[object] = current_id + uint_with_type(type_ranges.number, len) + stream:write(object) + return + end + if _type == "table" then + current_id = current_id + 1 + object_ids[object] = current_id + if next(object) == nil then + -- empty {} table + byte(type_ranges.string + 1) + return + end + local list_len = #object + local kv_len = 0 + for key, _ in pairs(object) do + if is_map_key(key, list_len) then + kv_len = kv_len + 1 + end + end + local list_len_sig = uint_type(list_len) + local kv_len_sig = uint_type(kv_len) + byte(type_ranges.string + list_len_sig + kv_len_sig * 5 + 1) + uint(list_len_sig, list_len) + uint(kv_len_sig, kv_len) + for index = 1, list_len do + _write(object[index]) + end + for key, value in pairs(object) do + if is_map_key(key, list_len) then + _write(key) + _write(value) + end + end + return + end + aux_write(object, object_ids) + end + _write(object) end local constants_flipped = modlib.table.flip(constants) -- See https://www.lua.org/manual/5.1/manual.html#2.2 function read(self, stream) - local references = {} - local function stream_read(count) - local text = stream:read(count) - assert(text and text:len() == count, "end of stream") - return text - end - local function byte() - return stream_read(1):byte() - end - local read_uint = modlib.binary.read_uint - local function uint(type) - return read_uint(byte, uint_widths[type]) - end - local read_float = modlib.binary.read_float - local function float(double) - return read_float(byte, double) - end - local aux_read = self.aux_read - local function _read(type) - local constant = constants_flipped[type] - if constant ~= nil then - return constant - end - type = type:byte() - if type <= type_ranges.number then - if type <= type_ranges.number_negative then - return uint(type - type_ranges.number_constant) - end - if type <= type_ranges.number_positive then - return -uint(type - type_ranges.number_negative) - end - return float(type == type_ranges.number) - end - if type <= type_ranges.string then - local string = stream_read(uint(type - type_ranges.number)) - table.insert(references, string) - return string - end - if type <= type_ranges.table then - type = type - type_ranges.string - 1 - local tab = {} - table.insert(references, tab) - if type == 0 then - return tab - end - local list_len = uint(type % 5) - local kv_len = uint(math.floor(type / 5)) - for index = 1, list_len do - tab[index] = _read(stream_read(1)) - end - for _ = 1, kv_len do - tab[_read(stream_read(1))] = _read(stream_read(1)) - end - return tab - end - if type <= type_ranges.reference then - return references[uint(type - type_ranges.table)] - end - return aux_read(type, stream, references) - end - local type = stream:read(1) - if type == nil then - return - end - return _read(type) + local references = {} + local function stream_read(count) + local text = stream:read(count) + assert(text and text:len() == count, "end of stream") + return text + end + local function byte() + return stream_read(1):byte() + end + local read_uint = modlib.binary.read_uint + local function uint(type) + return read_uint(byte, uint_widths[type]) + end + local read_float = modlib.binary.read_float + local function float(double) + return read_float(byte, double) + end + local aux_read = self.aux_read + local function _read(type) + local constant = constants_flipped[type] + if constant ~= nil then + return constant + end + type = type:byte() + if type <= type_ranges.number then + if type <= type_ranges.number_negative then + return uint(type - type_ranges.number_constant) + end + if type <= type_ranges.number_positive then + return -uint(type - type_ranges.number_negative) + end + return float(type == type_ranges.number) + end + if type <= type_ranges.string then + local string = stream_read(uint(type - type_ranges.number)) + table.insert(references, string) + return string + end + if type <= type_ranges.table then + type = type - type_ranges.string - 1 + local tab = {} + table.insert(references, tab) + if type == 0 then + return tab + end + local list_len = uint(type % 5) + local kv_len = uint(math.floor(type / 5)) + for index = 1, list_len do + tab[index] = _read(stream_read(1)) + end + for _ = 1, kv_len do + tab[_read(stream_read(1))] = _read(stream_read(1)) + end + return tab + end + if type <= type_ranges.reference then + return references[uint(type - type_ranges.table)] + end + return aux_read(type, stream, references) + end + local type = stream:read(1) + if type == nil then + return + end + return _read(type) end \ No newline at end of file diff --git a/conf.lua b/conf.lua index 4a59258..f8552ae 100644 --- a/conf.lua +++ b/conf.lua @@ -1,296 +1,296 @@ -- not deprecated function build_tree(dict) - local tree = {} - for key, value in pairs(dict) do - local path = modlib.text.split_unlimited(key, ".") - local subtree = tree - for i = 1, #path - 1 do - local index = tonumber(path[i]) or path[i] - subtree[index] = subtree[index] or {} - subtree = subtree[index] - end - subtree[path[#path]] = value - end - return tree + local tree = {} + for key, value in pairs(dict) do + local path = modlib.text.split_unlimited(key, ".") + local subtree = tree + for i = 1, #path - 1 do + local index = tonumber(path[i]) or path[i] + subtree[index] = subtree[index] or {} + subtree = subtree[index] + end + subtree[path[#path]] = value + end + return tree end if minetest then - function build_setting_tree() - modlib.conf.settings = build_tree(minetest.settings:to_table()) - end - -- deprecated, use modlib.mod.configuration instead - minetest.mkdir(minetest.get_worldpath().."/config") - function get_path(confname) - return minetest.get_worldpath().."/config/"..confname - end + function build_setting_tree() + modlib.conf.settings = build_tree(minetest.settings:to_table()) + end + -- deprecated, use modlib.mod.configuration instead + minetest.mkdir(minetest.get_worldpath().."/config") + function get_path(confname) + return minetest.get_worldpath().."/config/"..confname + end end function read_conf(text) - local lines = modlib.text.split_lines(text, nil, true) - local dict = {} - for i, line in ipairs(lines) do - local error_base = "Line " .. (i+1) .. ": " - line = modlib.text.trim_left(lines[i]) - if line ~= "" and line:sub(1,1) ~= "#" then - line = modlib.text.split(line, "=", 2) - if #line ~= 2 then - error(error_base .. "No value given") - end - local prop = modlib.text.trim_right(line[1]) - if prop == "" then - error(error_base .. "No key given") - end - local val = modlib.text.trim_left(line[2]) - if val == "" then - error(error_base .. "No value given") - end - if modlib.text.starts_with(val, '"""') then - val = val:sub(3) - local total_val = {} - local function readMultiline() - while i < #lines do - if modlib.text.ends_with(val, '"""') then - val = val:sub(1, val:len() - 3) - return - end - table.insert(total_val, val) - i = i + 1 - val = lines[i] - end - i = i - 1 - error(error_base .. "Unclosed multiline block") - end - readMultiline() - table.insert(total_val, val) - val = table.concat(total_val, "\n") - else - val = modlib.text.trim_right(val) - end - if dict[prop] then - error(error_base .. "Duplicate key") - end - dict[prop] = val - end - end - return dict + local lines = modlib.text.split_lines(text, nil, true) + local dict = {} + for i, line in ipairs(lines) do + local error_base = "Line " .. (i+1) .. ": " + line = modlib.text.trim_left(lines[i]) + if line ~= "" and line:sub(1,1) ~= "#" then + line = modlib.text.split(line, "=", 2) + if #line ~= 2 then + error(error_base .. "No value given") + end + local prop = modlib.text.trim_right(line[1]) + if prop == "" then + error(error_base .. "No key given") + end + local val = modlib.text.trim_left(line[2]) + if val == "" then + error(error_base .. "No value given") + end + if modlib.text.starts_with(val, '"""') then + val = val:sub(3) + local total_val = {} + local function readMultiline() + while i < #lines do + if modlib.text.ends_with(val, '"""') then + val = val:sub(1, val:len() - 3) + return + end + table.insert(total_val, val) + i = i + 1 + val = lines[i] + end + i = i - 1 + error(error_base .. "Unclosed multiline block") + end + readMultiline() + table.insert(total_val, val) + val = table.concat(total_val, "\n") + else + val = modlib.text.trim_right(val) + end + if dict[prop] then + error(error_base .. "Duplicate key") + end + dict[prop] = val + end + end + return dict end function check_config_constraints(config, constraints, handler) - local no_error, error_or_retval = pcall(function() check_constraints(config, constraints) end) - if not no_error then - handler(error_or_retval) - end + local no_error, error_or_retval = pcall(function() check_constraints(config, constraints) end) + if not no_error then + handler(error_or_retval) + end end function load(filename, constraints) - local config = minetest.parse_json(modlib.file.read(filename)) - if constraints then - check_config_constraints(config, constraints, function(message) - error('Configuration of file "'..filename.."\" doesn't satisfy constraints: "..message) - end) - end - return config + local config = minetest.parse_json(modlib.file.read(filename)) + if constraints then + check_config_constraints(config, constraints, function(message) + error('Configuration of file "'..filename.."\" doesn't satisfy constraints: "..message) + end) + end + return config end function load_or_create(filename, replacement_file, constraints) - modlib.file.create_if_not_exists_from_file(filename, replacement_file) - return load(filename, constraints) + modlib.file.create_if_not_exists_from_file(filename, replacement_file) + return load(filename, constraints) end function import(modname, constraints, no_settingtypes) - local default_config = modlib.mod.get_resource(modname, "default_config.json") - local default_conf = minetest.parse_json(modlib.file.read(default_config)) - local config = load_or_create(get_path(modname)..".json", default_config, constraints) - local formats = { - { extension = ".lua", load = minetest.deserialize }, - { extension = ".luon", load = function(text) minetest.deserialize("return "..text) end }, - { extension = ".conf", load = function(text) return fix_types(build_tree(read_conf(text)), constraints) end } - } - for _, format in ipairs(formats) do - local conf = modlib.file.read(get_path(modname)..format.extension) - if conf then - config = merge_config(config, format.load(conf)) - end - end - if not no_settingtypes then - constraints.name = modname - local settingtypes = generate_settingtypes(default_conf, constraints) - modlib.file.write(modlib.mod.get_resource(modname, "settingtypes.txt"), settingtypes) - end - local additional_settings = modlib.conf.settings[modname] or {} - additional_settings = fix_types(additional_settings, constraints) - -- TODO implement merge_config_legal(default_conf, ...) - config = merge_config(config, additional_settings) - if constraints then - check_config_constraints(config, constraints, function(message) - error('Configuration of mod "'..modname.."\" doesn't satisfy constraints: "..message) - end) - end - return config + local default_config = modlib.mod.get_resource(modname, "default_config.json") + local default_conf = minetest.parse_json(modlib.file.read(default_config)) + local config = load_or_create(get_path(modname)..".json", default_config, constraints) + local formats = { + { extension = ".lua", load = minetest.deserialize }, + { extension = ".luon", load = function(text) minetest.deserialize("return "..text) end }, + { extension = ".conf", load = function(text) return fix_types(build_tree(read_conf(text)), constraints) end } + } + for _, format in ipairs(formats) do + local conf = modlib.file.read(get_path(modname)..format.extension) + if conf then + config = merge_config(config, format.load(conf)) + end + end + if not no_settingtypes then + constraints.name = modname + local settingtypes = generate_settingtypes(default_conf, constraints) + modlib.file.write(modlib.mod.get_resource(modname, "settingtypes.txt"), settingtypes) + end + local additional_settings = modlib.conf.settings[modname] or {} + additional_settings = fix_types(additional_settings, constraints) + -- TODO implement merge_config_legal(default_conf, ...) + config = merge_config(config, additional_settings) + if constraints then + check_config_constraints(config, constraints, function(message) + error('Configuration of mod "'..modname.."\" doesn't satisfy constraints: "..message) + end) + end + return config end function merge_config(config, additional_settings) - if not config or type(additional_settings) ~= "table" then - return additional_settings - end - for setting, value in pairs(additional_settings) do - if config[setting] then - config[setting] = merge_config(config[setting], value) - end - end - return config + if not config or type(additional_settings) ~= "table" then + return additional_settings + end + for setting, value in pairs(additional_settings) do + if config[setting] then + config[setting] = merge_config(config[setting], value) + end + end + return config end -- format: # comment -- name (Readable name) type type_args function generate_settingtypes(default_conf, constraints) - local constraint_type = constraints.type - if constraints.children or constraints.possible_children or constraints.required_children or constraints.keys or constraints.values then - constraint_type = "table" - end - local settingtype, type_args - local title = constraints.title - if not title then - title = modlib.text.split(constraints.name, "_") - title[1] = modlib.text.upper_first(title[1]) - title = table.concat(title, " ") - end - if constraint_type == "boolean" then - settingtype = "bool" - default_conf = default_conf and "true" or "false" - elseif constraint_type == "string" then - settingtype = "string" - elseif constraint_type == "number" then - settingtype = constraints.int and "int" or "float" - local range = constraints.range - if range then - -- TODO consider better max - type_args = (constraints.int and "%d %d" or "%f %f"):format(range[1], range[2] or (2 ^ 30)) - end - -- HACK - if not default_conf then default_conf = range[1] end - elseif constraint_type == "table" then - local handled = {} - local settings = {} - local function setting(key, value_constraints) - if handled[key] then - return - end - handled[key] = true - value_constraints.name = constraints.name .. "." .. key - value_constraints.title = title .. " " .. key - table.insert(settings, generate_settingtypes(default_conf and default_conf[key], value_constraints)) - end - for _, table in ipairs{"children", "required_children", "possible_children"} do - for key, constraints in pairs(constraints[table] or {}) do - setting(key, constraints) - end - end - return table.concat(settings, "\n") - end - if not constraint_type then - return "" - end - local comment = constraints.comment - if comment then - comment = "# " .. comment .. "\n" - else - comment = "" - end - assert(type(default_conf) == "string" or type(default_conf) == "number" or type(default_conf) == "nil", dump(default_conf)) - return comment .. constraints.name .. " (" .. title .. ") " .. settingtype .. " " .. (default_conf or "") ..(type_args and (" "..type_args) or "") + local constraint_type = constraints.type + if constraints.children or constraints.possible_children or constraints.required_children or constraints.keys or constraints.values then + constraint_type = "table" + end + local settingtype, type_args + local title = constraints.title + if not title then + title = modlib.text.split(constraints.name, "_") + title[1] = modlib.text.upper_first(title[1]) + title = table.concat(title, " ") + end + if constraint_type == "boolean" then + settingtype = "bool" + default_conf = default_conf and "true" or "false" + elseif constraint_type == "string" then + settingtype = "string" + elseif constraint_type == "number" then + settingtype = constraints.int and "int" or "float" + local range = constraints.range + if range then + -- TODO consider better max + type_args = (constraints.int and "%d %d" or "%f %f"):format(range[1], range[2] or (2 ^ 30)) + end + -- HACK + if not default_conf then default_conf = range[1] end + elseif constraint_type == "table" then + local handled = {} + local settings = {} + local function setting(key, value_constraints) + if handled[key] then + return + end + handled[key] = true + value_constraints.name = constraints.name .. "." .. key + value_constraints.title = title .. " " .. key + table.insert(settings, generate_settingtypes(default_conf and default_conf[key], value_constraints)) + end + for _, table in ipairs{"children", "required_children", "possible_children"} do + for key, constraints in pairs(constraints[table] or {}) do + setting(key, constraints) + end + end + return table.concat(settings, "\n") + end + if not constraint_type then + return "" + end + local comment = constraints.comment + if comment then + comment = "# " .. comment .. "\n" + else + comment = "" + end + assert(type(default_conf) == "string" or type(default_conf) == "number" or type(default_conf) == "nil", dump(default_conf)) + return comment .. constraints.name .. " (" .. title .. ") " .. settingtype .. " " .. (default_conf or "") ..(type_args and (" "..type_args) or "") end function fix_types(value, constraints) - local type = type(value) - local expected_type = constraints.type - if expected_type and expected_type ~= type then - assert(type == "string", "Can't fix non-string value") - if expected_type == "boolean" then - assert(value == "true" or value == "false", "Not a boolean (true or false): " .. value) - value = value == "true" - elseif expected_type == "number" then - assert(tonumber(value), "Not a number: " .. value) - value = tonumber(value) - end - end - if type == "table" then - for key, val in pairs(value) do - for _, child_constraints in ipairs{"required_children", "children", "possible_children"} do - child_constraints = (constraints[child_constraints] or {})[key] - if child_constraints then - val = fix_types(val, child_constraints) - end - end - if constraints.values then - val = fix_types(val, constraints.values) - end - if constraints.keys then - value[key] = nil - value[fix_types(key, constraints.keys)] = val - else - value[key] = val - end - end - end - return value + local type = type(value) + local expected_type = constraints.type + if expected_type and expected_type ~= type then + assert(type == "string", "Can't fix non-string value") + if expected_type == "boolean" then + assert(value == "true" or value == "false", "Not a boolean (true or false): " .. value) + value = value == "true" + elseif expected_type == "number" then + assert(tonumber(value), "Not a number: " .. value) + value = tonumber(value) + end + end + if type == "table" then + for key, val in pairs(value) do + for _, child_constraints in ipairs{"required_children", "children", "possible_children"} do + child_constraints = (constraints[child_constraints] or {})[key] + if child_constraints then + val = fix_types(val, child_constraints) + end + end + if constraints.values then + val = fix_types(val, constraints.values) + end + if constraints.keys then + value[key] = nil + value[fix_types(key, constraints.keys)] = val + else + value[key] = val + end + end + end + return value end function check_constraints(value, constraints) - local t = type(value) - if constraints.type and constraints.type ~= t then - error("Wrong type: Expected "..constraints.type..", found "..t) - end - if (t == "number" or t == "string") and constraints.range then - if value < constraints.range[1] or (constraints.range[2] and value > constraints.range[2]) then - error("Not inside range: Expected value >= "..constraints.range[1].." and <= "..(constraints.range[2] or "inf")..", found "..minetest.write_json(value)) - end - end - if t == "number" and constraints.int and value % 1 ~= 0 then - error("Not an integer number: " .. minetest.write_json(value)) - end - if constraints.possible_values and not constraints.possible_values[value] then - error("None of the possible values: Expected one of "..minetest.write_json(modlib.table.keys(constraints.possible_values))..", found "..minetest.write_json(value)) - end - if t == "table" then - if constraints.children then - for key, val in pairs(value) do - local child_constraints = constraints.children[key] - if not child_constraints then - error("Unexpected table entry: Expected one of "..minetest.write_json(modlib.table.keys(constraints.children))..", found "..minetest.write_json(key)) - else - check_constraints(val, child_constraints) - end - end - for key, _ in pairs(constraints.children) do - if value[key] == nil then - error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value)) - end - end - end - if constraints.required_children then - for key, value_constraints in pairs(constraints.required_children) do - local val = value[key] - if val then - check_constraints(val, value_constraints) - else - error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value)) - end - end - end - if constraints.possible_children then - for key, value_constraints in pairs(constraints.possible_children) do - local val = value[key] - if val then - check_constraints(val, value_constraints) - end - end - end - if constraints.keys then - for key,_ in pairs(value) do - check_constraints(key, constraints.keys) - end - end - if constraints.values then - for _, val in pairs(value) do - check_constraints(val, constraints.values) - end - end - end - if constraints.func then - local possible_errors = constraints.func(value) - if possible_errors then - error(possible_errors) - end - end + local t = type(value) + if constraints.type and constraints.type ~= t then + error("Wrong type: Expected "..constraints.type..", found "..t) + end + if (t == "number" or t == "string") and constraints.range then + if value < constraints.range[1] or (constraints.range[2] and value > constraints.range[2]) then + error("Not inside range: Expected value >= "..constraints.range[1].." and <= "..(constraints.range[2] or "inf")..", found "..minetest.write_json(value)) + end + end + if t == "number" and constraints.int and value % 1 ~= 0 then + error("Not an integer number: " .. minetest.write_json(value)) + end + if constraints.possible_values and not constraints.possible_values[value] then + error("None of the possible values: Expected one of "..minetest.write_json(modlib.table.keys(constraints.possible_values))..", found "..minetest.write_json(value)) + end + if t == "table" then + if constraints.children then + for key, val in pairs(value) do + local child_constraints = constraints.children[key] + if not child_constraints then + error("Unexpected table entry: Expected one of "..minetest.write_json(modlib.table.keys(constraints.children))..", found "..minetest.write_json(key)) + else + check_constraints(val, child_constraints) + end + end + for key, _ in pairs(constraints.children) do + if value[key] == nil then + error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value)) + end + end + end + if constraints.required_children then + for key, value_constraints in pairs(constraints.required_children) do + local val = value[key] + if val then + check_constraints(val, value_constraints) + else + error("Table entry missing: Expected key "..minetest.write_json(key).." to be present in table "..minetest.write_json(value)) + end + end + end + if constraints.possible_children then + for key, value_constraints in pairs(constraints.possible_children) do + local val = value[key] + if val then + check_constraints(val, value_constraints) + end + end + end + if constraints.keys then + for key,_ in pairs(value) do + check_constraints(key, constraints.keys) + end + end + if constraints.values then + for _, val in pairs(value) do + check_constraints(val, constraints.values) + end + end + end + if constraints.func then + local possible_errors = constraints.func(value) + if possible_errors then + error(possible_errors) + end + end end \ No newline at end of file diff --git a/data.lua b/data.lua index 075be8c..2e908dd 100644 --- a/data.lua +++ b/data.lua @@ -1,19 +1,19 @@ minetest.mkdir(minetest.get_worldpath().."/data") function create_mod_storage(modname) minetest.mkdir(minetest.get_worldpath().."/data/"..modname) -end + function get_path(modname, filename) return minetest.get_worldpath().."/data/"..modname.."/"..filename -end + function load(modname, filename) return minetest.deserialize(modlib.file.read(get_path(modname, filename)..".lua")) -end + function save(modname, filename, stuff) return modlib.file.write(get_path(modname, filename)..".lua", minetest.serialize(stuff)) -end + function load_json(modname, filename) return minetest.parse_json(modlib.file.read(get_path(modname, filename)..".json") or "null") -end + function save_json(modname, filename, stuff) return modlib.file.write(get_path(modname, filename)..".json", minetest.write_json(stuff)) -end \ No newline at end of file + \ No newline at end of file diff --git a/file.lua b/file.lua index cd2686e..c80fa64 100644 --- a/file.lua +++ b/file.lua @@ -15,11 +15,11 @@ function write(filename, new_content) end function ensure_content(filename, ensured_content) - local content = read(filename) - if content ~= ensured_content then - return write(filename, ensured_content) - end - return true + local content = read(filename) + if content ~= ensured_content then + return write(filename, ensured_content) + end + return true end function append(filename, new_content) diff --git a/heap.lua b/heap.lua index 81bf383..caac50f 100644 --- a/heap.lua +++ b/heap.lua @@ -4,47 +4,47 @@ function less_than(a, b) return a < b end --> empty min heap function new(less_than) - return setmetatable({less_than = less_than}, metatable) + return setmetatable({less_than = less_than}, metatable) end function push(self, value) - table.insert(self, value) - local function heapify(index) - if index == 1 then - return - end - local parent = math.floor(index / 2) - if self.less_than(self[index], self[parent]) then - self[parent], self[index] = self[index], self[parent] - heapify(parent) - end - end - heapify(#self) + table.insert(self, value) + local function heapify(index) + if index == 1 then + return + end + local parent = math.floor(index / 2) + if self.less_than(self[index], self[parent]) then + self[parent], self[index] = self[index], self[parent] + heapify(parent) + end + end + heapify(#self) end function pop(self) - local value = self[1] - local last = #self - if last == 1 then - self[1] = nil - return value - end - self[1], self[last] = self[last], nil - last = last - 1 - local function heapify(index) - local left_child = index * 2 - if left_child > last then - return - end - local smallest_child = left_child + 1 - if smallest_child > last or self.less_than(self[left_child], self[smallest_child]) then - smallest_child = left_child - end - if self.less_than(self[smallest_child], self[index]) then - self[index], self[smallest_child] = self[smallest_child], self[index] - heapify(smallest_child) - end - end - heapify(1) - return value + local value = self[1] + local last = #self + if last == 1 then + self[1] = nil + return value + end + self[1], self[last] = self[last], nil + last = last - 1 + local function heapify(index) + local left_child = index * 2 + if left_child > last then + return + end + local smallest_child = left_child + 1 + if smallest_child > last or self.less_than(self[left_child], self[smallest_child]) then + smallest_child = left_child + end + if self.less_than(self[smallest_child], self[index]) then + self[index], self[smallest_child] = self[smallest_child], self[index] + heapify(smallest_child) + end + end + heapify(1) + return value end \ No newline at end of file diff --git a/init.lua b/init.lua index 2224352..3bac9ac 100644 --- a/init.lua +++ b/init.lua @@ -1,123 +1,123 @@ -- Lua version check if _VERSION then - if _VERSION < "Lua 5" then - error("Outdated Lua version! modlib requires Lua 5 or greater.") - end - if _VERSION > "Lua 5.1" then - -- not throwing error("Too new Lua version! modlib requires Lua 5.1 or smaller.") anymore - unpack = unpack or table.unpack -- unpack was moved to table.unpack in Lua 5.2 - loadstring = load - function setfenv(fn, env) - local i = 1 - while true do - name = debug.getupvalue(fn, i) - if name == "_ENV" then - debug.setupvalue(fn, i, env) - break - elseif not name then - break - end - end - return fn - end - function getfenv(fn) - local i = 1 - local name, val - repeat - name, val = debug.getupvalue(fn, i) - if name == "_ENV" then - return val - end - i = i + 1 - until not name - end - end + if _VERSION < "Lua 5" then + error("Outdated Lua version! modlib requires Lua 5 or greater.") + end + if _VERSION > "Lua 5.1" then + -- not throwing error("Too new Lua version! modlib requires Lua 5.1 or smaller.") anymore + unpack = unpack or table.unpack -- unpack was moved to table.unpack in Lua 5.2 + loadstring = load + function setfenv(fn, env) + local i = 1 + while true do + name = debug.getupvalue(fn, i) + if name == "_ENV" then + debug.setupvalue(fn, i, env) + break + elseif not name then + break + end + end + return fn + end + function getfenv(fn) + local i = 1 + local name, val + repeat + name, val = debug.getupvalue(fn, i) + if name == "_ENV" then + return val + end + i = i + 1 + until not name + end + end end modlib = { - dir_delim = rawget(_G, "DIR_DELIM") or "/", - _RG = setmetatable({}, { - __index = function(_, index) - return rawget(_G, index) - end, - __newindex = function(_, index, value) - return rawset(_G, index, value) - end - }), - assertdump = function(v, value) - if not v then - error(dump(value), 2) - end - end + dir_delim = rawget(_G, "DIR_DELIM") or "/", + _RG = setmetatable({}, { + __index = function(_, index) + return rawget(_G, index) + end, + __newindex = function(_, index, value) + return rawset(_G, index, value) + end + }), + assertdump = function(v, value) + if not v then + error(dump(value), 2) + end + end } local function get_resource(modname, resource, ...) - if not resource then - resource = modname - modname = minetest.get_current_modname() - end - return table.concat({minetest.get_modpath(modname), resource, ...}, modlib.dir_delim) + if not resource then + resource = modname + modname = minetest.get_current_modname() + end + return table.concat({minetest.get_modpath(modname), resource, ...}, modlib.dir_delim) end local function loadfile_exports(filename) - local env = setmetatable({}, {__index = _G}) - local file = assert(loadfile(filename)) - setfenv(file, env) - file() - return env + local env = setmetatable({}, {__index = _G}) + local file = assert(loadfile(filename)) + setfenv(file, env) + file() + return env end local minetest_only = { - mod = true, - minetest = true, - data = true, - log = true, - player = true, - -- not actually minetest-only, but a deprecated component - conf = true + mod = true, + minetest = true, + data = true, + log = true, + player = true, + -- not actually minetest-only, but a deprecated component + conf = true } for _, component in ipairs{ - "mod", - "conf", - "schema", - "data", - "file", - "func", - "log", - "math", - "player", - "table", - "text", - "vector", - "quaternion", - { - name = "minetest", - "misc", - "collisionboxes", - "liquid", - "raycast", - "wielditem_change", - "colorspec" - }, - "trie", - "kdtree", - "heap", - "ranked_set", - "binary", - "b3d", - "bluon" + "mod", + "conf", + "schema", + "data", + "file", + "func", + "log", + "math", + "player", + "table", + "text", + "vector", + "quaternion", + { + name = "minetest", + "misc", + "collisionboxes", + "liquid", + "raycast", + "wielditem_change", + "colorspec" + }, + "trie", + "kdtree", + "heap", + "ranked_set", + "binary", + "b3d", + "bluon" } do - if component.name then - if minetest then - modlib[component.name] = loadfile_exports(get_resource(minetest.get_current_modname(), component.name, component[1] .. ".lua")) - for index = 2, #component do - modlib.mod.include_env(get_resource(minetest.get_current_modname(), component.name, component[index] .. ".lua"), modlib[component.name]) - end - end - elseif minetest or not minetest_only[component] then - local path = minetest and get_resource(component .. ".lua") or component .. ".lua" - modlib[component] = loadfile_exports(path) - end + if component.name then + if minetest then + modlib[component.name] = loadfile_exports(get_resource(minetest.get_current_modname(), component.name, component[1] .. ".lua")) + for index = 2, #component do + modlib.mod.include_env(get_resource(minetest.get_current_modname(), component.name, component[index] .. ".lua"), modlib[component.name]) + end + end + elseif minetest or not minetest_only[component] then + local path = minetest and get_resource(component .. ".lua") or component .. ".lua" + modlib[component] = loadfile_exports(path) + end end -- Aliases @@ -125,10 +125,10 @@ modlib.string = modlib.text modlib.number = modlib.math if minetest then - modlib.conf.build_setting_tree() + modlib.conf.build_setting_tree() - modlib.mod.get_resource = get_resource - modlib.mod.loadfile_exports = loadfile_exports + modlib.mod.get_resource = get_resource + modlib.mod.loadfile_exports = loadfile_exports end _ml = modlib diff --git a/kdtree.lua b/kdtree.lua index bf9935e..73b3873 100644 --- a/kdtree.lua +++ b/kdtree.lua @@ -5,9 +5,9 @@ distance = modlib.vector.distance --: vectors first vector is used to infer the dimension --: distance (vector, other_vector) -> number, default: modlib.vector.distance function new(vectors, distance) - assert(#vectors > 0, "vector list must not be empty") - local dimension = #vectors[1] - local function builder(vectors, axis) + assert(#vectors > 0, "vector list must not be empty") + local dimension = #vectors[1] + local function builder(vectors, axis) if #vectors == 1 then return { value = vectors[1] } end table.sort(vectors, function(a, b) return a[axis] > b[axis] end) local median = math.floor(#vectors / 2) @@ -19,36 +19,36 @@ function new(vectors, distance) right = builder({ unpack(vectors, median + 1) }, next_axis) }, metatable) end - local self = builder(vectors, 1) - self.distance = distance - return setmetatable(self, metatable) + local self = builder(vectors, 1) + self.distance = distance + return setmetatable(self, metatable) end function get_nearest_neighbor(self, vector) - local min_distance = math.huge - local nearest_neighbor - local distance_func = self.distance - local function visit(tree) - local axis = tree.axis - if tree.value ~= nil then - local distance = distance_func(tree.value, vector) - if distance < min_distance then - min_distance = distance - nearest_neighbor = tree.value - end - return - else - local this_side, other_side = tree.left, tree.right - if vector[axis] < tree.pivot[axis] then this_side, other_side = other_side, this_side end - visit(this_side) - if tree.pivot then - local dist = math.abs(tree.pivot[axis] - vector[axis]) - if dist <= min_distance then visit(other_side) end - end - end - end - visit(self) - return nearest_neighbor, min_distance + local min_distance = math.huge + local nearest_neighbor + local distance_func = self.distance + local function visit(tree) + local axis = tree.axis + if tree.value ~= nil then + local distance = distance_func(tree.value, vector) + if distance < min_distance then + min_distance = distance + nearest_neighbor = tree.value + end + return + else + local this_side, other_side = tree.left, tree.right + if vector[axis] < tree.pivot[axis] then this_side, other_side = other_side, this_side end + visit(this_side) + if tree.pivot then + local dist = math.abs(tree.pivot[axis] - vector[axis]) + if dist <= min_distance then visit(other_side) end + end + end + end + visit(self) + return nearest_neighbor, min_distance end -- TODO insertion & deletion + rebalancing \ No newline at end of file diff --git a/log.lua b/log.lua index 9bd2d3d..37817ac 100644 --- a/log.lua +++ b/log.lua @@ -3,74 +3,74 @@ minetest.mkdir(minetest.get_worldpath() .. "/logs") channels = {} last_day = os.date("%d") function get_path(logname) - return minetest.get_worldpath() .. "/logs/" .. logname + return minetest.get_worldpath() .. "/logs/" .. logname end function create_channel(title) - local dir = get_path(title) - minetest.mkdir(dir) - channels[title] = {dirname = dir, queue = {}} - write(title, "Initialisation") + local dir = get_path(title) + minetest.mkdir(dir) + channels[title] = {dirname = dir, queue = {}} + write(title, "Initialisation") end function write(channelname, msg) - local channel = channels[channelname] - local current_day = os.date("%d") - if current_day ~= last_day then - last_day = current_day - write_to_file(channelname, channel, os.date("%Y-%m-%d")) - end - table.insert(channel.queue, os.date("[%H:%M:%S] ") .. msg) + local channel = channels[channelname] + local current_day = os.date("%d") + if current_day ~= last_day then + last_day = current_day + write_to_file(channelname, channel, os.date("%Y-%m-%d")) + end + table.insert(channel.queue, os.date("[%H:%M:%S] ") .. msg) end function write_to_all(msg) - for channelname, _ in pairs(channels) do - write(channelname, msg) - end + for channelname, _ in pairs(channels) do + write(channelname, msg) + end end function write_to_file(name, channel, current_date) - if not channel then - channel = channels[name] - end - if #(channel.queue) > 0 then - local filename = channel.dirname .. "/" .. (current_date or os.date("%Y-%m-%d")) .. ".txt" - local rope = {} - for _, msg in ipairs(channel.queue) do - table.insert(rope, msg) - end - modlib.file.append(filename, table.concat(rope, "\n") .. "\n") - channels[name].queue = {} - end + if not channel then + channel = channels[name] + end + if #(channel.queue) > 0 then + local filename = channel.dirname .. "/" .. (current_date or os.date("%Y-%m-%d")) .. ".txt" + local rope = {} + for _, msg in ipairs(channel.queue) do + table.insert(rope, msg) + end + modlib.file.append(filename, table.concat(rope, "\n") .. "\n") + channels[name].queue = {} + end end function write_all_to_file() - local current_date = os.date("%Y-%m-%d") - for name, channel in pairs(channels) do - write_to_file(name, channel, current_date) - end + local current_date = os.date("%Y-%m-%d") + for name, channel in pairs(channels) do + write_to_file(name, channel, current_date) + end end function write_safe(channelname, msg) - write(channelname, msg) - write_all_to_file() + write(channelname, msg) + write_all_to_file() end local timer = 0 minetest.register_globalstep( - function(dtime) - timer = timer + dtime - if timer > 5 then - write_all_to_file() - timer = 0 - end - end + function(dtime) + timer = timer + dtime + if timer > 5 then + write_all_to_file() + timer = 0 + end + end ) minetest.register_on_mods_loaded( - function() - write_to_all("Mods loaded") - end + function() + write_to_all("Mods loaded") + end ) minetest.register_on_shutdown( - function() - write_to_all("Shutdown") - write_all_to_file() - end + function() + write_to_all("Shutdown") + write_all_to_file() + end ) diff --git a/minetest/collisionboxes.lua b/minetest/collisionboxes.lua index cef0822..2fa9992 100644 --- a/minetest/collisionboxes.lua +++ b/minetest/collisionboxes.lua @@ -1,135 +1,135 @@ -- Minetest allows shorthand collisionbox = {...} instead of {{...}} local function get_collisionboxes(box_or_boxes) - return type(box_or_boxes[1]) == "number" and {box_or_boxes} or box_or_boxes + return type(box_or_boxes[1]) == "number" and {box_or_boxes} or box_or_boxes end --> list of collisionboxes in Minetest format function get_node_collisionboxes(pos) - local node = minetest.get_node(pos) - local node_def = minetest.registered_nodes[node.name] - if (not node_def) or node_def.walkable == false then - return {} - end - local boxes = {{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}} - local def_collision_box = node_def.collision_box or (node_def.drawtype == "nodebox" and node_def.node_box) - if def_collision_box then - local box_type = def_collision_box.type - if box_type == "regular" then - return boxes - end - local fixed = def_collision_box.fixed - boxes = get_collisionboxes(fixed or {}) - local paramtype2 = node_def.paramtype2 - if box_type == "leveled" then - boxes = table.copy(boxes) - local level = (paramtype2 == "leveled" and node.param2 or node_def.leveled or 0) / 255 - 0.5 - for _, box in pairs(boxes) do - box[5] = level - end - elseif box_type == "wallmounted" then - -- TODO complete if only wall_top is given - local dir = minetest.wallmounted_to_dir((paramtype2 == "colorwallmounted" and node.param2 % 8 or node.param2) or 0) - local box - if dir.y > 0 then - box = def_collision_box.wall_top - elseif dir.y < 0 then - box = def_collision_box.wall_bottom - else - box = def_collision_box.wall_side - if dir.z > 0 then - box = {box[3], box[2], -box[4], box[6], box[5], -box[1]} - elseif dir.z < 0 then - box = {-box[6], box[2], box[1], -box[3], box[5], box[4]} - elseif dir.x > 0 then - box = {-box[4], box[2], box[3], -box[1], box[5], box[6]} - else - box = {box[1], box[2], -box[6], box[4], box[5], -box[3]} - end - end - return {assert(box, "incomplete wallmounted collisionbox definition of " .. node.name)} - end - if box_type == "connected" then - boxes = table.copy(boxes) - local connect_sides = { - top = {x = 0, y = 1, z = 0}, - bottom = {x = 0, y = -1, z = 0}, - front = {x = 0, y = 0, z = -1}, - left = {x = -1, y = 0, z = 0}, - back = {x = 0, y = 0, z = 1}, - right = {x = 1, y = 0, z = 0} - } - if node_def.connect_sides then - for side in pairs(connect_sides) do - if not node_def.connect_sides[side] then - connect_sides[side] = nil - end - end - end - local function add_collisionbox(key) - for _, box in ipairs(get_collisionboxes(def_collision_box[key] or {})) do - table.insert(boxes, box) - end - end - local matchers = {} - for _, nodename_or_group in pairs(node_def.connects_to or {}) do - table.insert(matchers, nodename_matcher(nodename_or_group)) - end - local function connects_to(nodename) - for _, matcher in pairs(matchers) do - if matcher(nodename) then - return true - end - end - end - local connected, connected_sides - for side, direction in pairs(connect_sides) do - local neighbor = minetest.get_node(vector.add(pos, direction)) - local connects = connects_to(neighbor.name) - connected = connected or connects - connected_sides = connected_sides or (side ~= "top" and side ~= "bottom") - add_collisionbox((connects and "connect_" or "disconnected_") .. side) - end - if not connected then - add_collisionbox("disconnected") - end - if not connected_sides then - add_collisionbox("disconnected_sides") - end - return boxes - end - if box_type == "fixed" and paramtype2 == "facedir" or paramtype2 == "colorfacedir" then - local param2 = paramtype2 == "colorfacedir" and node.param2 % 32 or node.param2 or 0 - if param2 ~= 0 then - boxes = table.copy(boxes) - local axis = ({5, 6, 3, 4, 1, 2})[math.floor(param2 / 4) + 1] - local other_axis_1, other_axis_2 = (axis % 3) + 1, ((axis + 1) % 3) + 1 - local rotation = (param2 % 4) / 2 * math.pi - local flip = axis > 3 - if flip then axis = axis - 3; rotation = -rotation end - local sin, cos = math.sin(rotation), math.cos(rotation) - if axis == 2 then - sin = -sin - end - for _, box in pairs(boxes) do - for off = 0, 3, 3 do - local axis_1, axis_2 = other_axis_1 + off, other_axis_2 + off - local value_1, value_2 = box[axis_1], box[axis_2] - box[axis_1] = value_1 * cos - value_2 * sin - box[axis_2] = value_1 * sin + value_2 * cos - end - if not flip then - box[axis], box[axis + 3] = -box[axis + 3], -box[axis] - end - local function fix(coord) - if box[coord] > box[coord + 3] then - box[coord], box[coord + 3] = box[coord + 3], box[coord] - end - end - fix(other_axis_1) - fix(other_axis_2) - end - end - end - end - return boxes + local node = minetest.get_node(pos) + local node_def = minetest.registered_nodes[node.name] + if (not node_def) or node_def.walkable == false then + return {} + end + local boxes = {{-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}} + local def_collision_box = node_def.collision_box or (node_def.drawtype == "nodebox" and node_def.node_box) + if def_collision_box then + local box_type = def_collision_box.type + if box_type == "regular" then + return boxes + end + local fixed = def_collision_box.fixed + boxes = get_collisionboxes(fixed or {}) + local paramtype2 = node_def.paramtype2 + if box_type == "leveled" then + boxes = table.copy(boxes) + local level = (paramtype2 == "leveled" and node.param2 or node_def.leveled or 0) / 255 - 0.5 + for _, box in pairs(boxes) do + box[5] = level + end + elseif box_type == "wallmounted" then + -- TODO complete if only wall_top is given + local dir = minetest.wallmounted_to_dir((paramtype2 == "colorwallmounted" and node.param2 % 8 or node.param2) or 0) + local box + if dir.y > 0 then + box = def_collision_box.wall_top + elseif dir.y < 0 then + box = def_collision_box.wall_bottom + else + box = def_collision_box.wall_side + if dir.z > 0 then + box = {box[3], box[2], -box[4], box[6], box[5], -box[1]} + elseif dir.z < 0 then + box = {-box[6], box[2], box[1], -box[3], box[5], box[4]} + elseif dir.x > 0 then + box = {-box[4], box[2], box[3], -box[1], box[5], box[6]} + else + box = {box[1], box[2], -box[6], box[4], box[5], -box[3]} + end + end + return {assert(box, "incomplete wallmounted collisionbox definition of " .. node.name)} + end + if box_type == "connected" then + boxes = table.copy(boxes) + local connect_sides = { + top = {x = 0, y = 1, z = 0}, + bottom = {x = 0, y = -1, z = 0}, + front = {x = 0, y = 0, z = -1}, + left = {x = -1, y = 0, z = 0}, + back = {x = 0, y = 0, z = 1}, + right = {x = 1, y = 0, z = 0} + } + if node_def.connect_sides then + for side in pairs(connect_sides) do + if not node_def.connect_sides[side] then + connect_sides[side] = nil + end + end + end + local function add_collisionbox(key) + for _, box in ipairs(get_collisionboxes(def_collision_box[key] or {})) do + table.insert(boxes, box) + end + end + local matchers = {} + for _, nodename_or_group in pairs(node_def.connects_to or {}) do + table.insert(matchers, nodename_matcher(nodename_or_group)) + end + local function connects_to(nodename) + for _, matcher in pairs(matchers) do + if matcher(nodename) then + return true + end + end + end + local connected, connected_sides + for side, direction in pairs(connect_sides) do + local neighbor = minetest.get_node(vector.add(pos, direction)) + local connects = connects_to(neighbor.name) + connected = connected or connects + connected_sides = connected_sides or (side ~= "top" and side ~= "bottom") + add_collisionbox((connects and "connect_" or "disconnected_") .. side) + end + if not connected then + add_collisionbox("disconnected") + end + if not connected_sides then + add_collisionbox("disconnected_sides") + end + return boxes + end + if box_type == "fixed" and paramtype2 == "facedir" or paramtype2 == "colorfacedir" then + local param2 = paramtype2 == "colorfacedir" and node.param2 % 32 or node.param2 or 0 + if param2 ~= 0 then + boxes = table.copy(boxes) + local axis = ({5, 6, 3, 4, 1, 2})[math.floor(param2 / 4) + 1] + local other_axis_1, other_axis_2 = (axis % 3) + 1, ((axis + 1) % 3) + 1 + local rotation = (param2 % 4) / 2 * math.pi + local flip = axis > 3 + if flip then axis = axis - 3; rotation = -rotation end + local sin, cos = math.sin(rotation), math.cos(rotation) + if axis == 2 then + sin = -sin + end + for _, box in pairs(boxes) do + for off = 0, 3, 3 do + local axis_1, axis_2 = other_axis_1 + off, other_axis_2 + off + local value_1, value_2 = box[axis_1], box[axis_2] + box[axis_1] = value_1 * cos - value_2 * sin + box[axis_2] = value_1 * sin + value_2 * cos + end + if not flip then + box[axis], box[axis + 3] = -box[axis + 3], -box[axis] + end + local function fix(coord) + if box[coord] > box[coord + 3] then + box[coord], box[coord + 3] = box[coord + 3], box[coord] + end + end + fix(other_axis_1) + fix(other_axis_2) + end + end + end + end + return boxes end diff --git a/minetest/colorspec.lua b/minetest/colorspec.lua index 80638cb..a2cdc0e 100644 --- a/minetest/colorspec.lua +++ b/minetest/colorspec.lua @@ -154,107 +154,107 @@ colorspec = {} local colorspec_metatable = {__index = colorspec} function colorspec.new(table) - return setmetatable({ - r = assert(table.r), - g = assert(table.g), - b = assert(table.b), - a = table.a or 255 - }, colorspec_metatable) + return setmetatable({ + r = assert(table.r), + g = assert(table.g), + b = assert(table.b), + a = table.a or 255 + }, colorspec_metatable) end colorspec.from_table = colorspec.new function colorspec.from_string(string) - local hex = "#([A-Fa-f%d]+)" - local number, alpha = named_colors[string], 0xFF - if not number then - local name, alpha_text = string:match("^([a-z]+)" .. hex .. "$") + local hex = "#([A-Fa-f%d]+)" + local number, alpha = named_colors[string], 0xFF + if not number then + local name, alpha_text = string:match("^([a-z]+)" .. hex .. "$") if name then assert(alpha_text:len() == 2) number = assert(named_colors[name]) alpha = tonumber(alpha_text, 16) end - end - if number then - return colorspec.from_number(number * 0x100 + alpha) - end - local hex_text = string:match(hex) - local len, num = hex_text:len(), tonumber(hex_text, 16) - if len == 8 then - return colorspec.from_number(num) - end - if len == 6 then - return colorspec.from_number(num * 0x100 + 0xFF) - end - local floor = math.floor - if len == 4 then - return colorspec.from_table{ - a = (num % 16) * 17, - b = (floor(num / 16) % 16) * 17, - g = (floor(num / (16 ^ 2)) % 16) * 17, - r = (floor(num / (16 ^ 3)) % 16) * 17 - } - end - if len == 3 then - return colorspec.from_table{ - b = (num % 16) * 17, - g = (floor(num / 16) % 16) * 17, - r = (floor(num / (16 ^ 2)) % 16) * 17 - } - end - error("Invalid colorstring: " .. string) + end + if number then + return colorspec.from_number(number * 0x100 + alpha) + end + local hex_text = string:match(hex) + local len, num = hex_text:len(), tonumber(hex_text, 16) + if len == 8 then + return colorspec.from_number(num) + end + if len == 6 then + return colorspec.from_number(num * 0x100 + 0xFF) + end + local floor = math.floor + if len == 4 then + return colorspec.from_table{ + a = (num % 16) * 17, + b = (floor(num / 16) % 16) * 17, + g = (floor(num / (16 ^ 2)) % 16) * 17, + r = (floor(num / (16 ^ 3)) % 16) * 17 + } + end + if len == 3 then + return colorspec.from_table{ + b = (num % 16) * 17, + g = (floor(num / 16) % 16) * 17, + r = (floor(num / (16 ^ 2)) % 16) * 17 + } + end + error("Invalid colorstring: " .. string) end colorspec.from_text = colorspec.from_string function colorspec.from_number(number) - local floor = math.floor - return colorspec.from_table{ - a = number % 0x100, - b = floor(number / 0x100) % 0x100, - g = floor(number / 0x10000) % 0x100, - r = floor(number / 0x1000000) - } + local floor = math.floor + return colorspec.from_table{ + a = number % 0x100, + b = floor(number / 0x100) % 0x100, + g = floor(number / 0x10000) % 0x100, + r = floor(number / 0x1000000) + } end function colorspec.from_number_rgb(number) - local floor = math.floor - return colorspec.from_table{ - a = 0xFF, - b = number % 0x100, - g = floor(number / 0x100) % 0x100, - r = floor(number / 0x10000) - } + local floor = math.floor + return colorspec.from_table{ + a = 0xFF, + b = number % 0x100, + g = floor(number / 0x100) % 0x100, + r = floor(number / 0x10000) + } end function colorspec.from_any(value) - local type = type(value) - if type == "table" then - return colorspec.from_table(value) - end - if type == "string" then - return colorspec.from_string(value) - end - if type == "number" then - return colorspec.from_number(value) - end - error("Unsupported type " .. type) + local type = type(value) + if type == "table" then + return colorspec.from_table(value) + end + if type == "string" then + return colorspec.from_string(value) + end + if type == "number" then + return colorspec.from_number(value) + end + error("Unsupported type " .. type) end function colorspec:to_table() - return self + return self end --> hex string, omits alpha if possible (if opaque) function colorspec:to_string() - if self.a == 255 then - return ("%02X02X02X"):format(self.r, self.g, self.b) - end - return ("%02X02X02X02X"):format(self.r, self.g, self.b, self.a) + if self.a == 255 then + return ("%02X02X02X"):format(self.r, self.g, self.b) + end + return ("%02X02X02X02X"):format(self.r, self.g, self.b, self.a) end function colorspec:to_number() - return self.r * 0x1000000 + self.g * 0x10000 + self.b * 0x100 + self.a + return self.r * 0x1000000 + self.g * 0x10000 + self.b * 0x100 + self.a end function colorspec:to_number_rgb() @@ -262,5 +262,5 @@ function colorspec:to_number_rgb() end colorspec_to_colorstring = minetest.colorspec_to_colorstring or function(spec) - return colorspec.from_any(spec):to_string() + return colorspec.from_any(spec):to_string() end diff --git a/minetest/liquid.lua b/minetest/liquid.lua index 064dd8c..30c9d49 100644 --- a/minetest/liquid.lua +++ b/minetest/liquid.lua @@ -2,73 +2,73 @@ liquid_level_max = 8 --+ Calculates the corner levels of a flowingliquid node --> 4 corner levels from -0.5 to 0.5 as list of `modlib.vector` function get_liquid_corner_levels(pos) - local node = minetest.get_node(pos) - local def = minetest.registered_nodes[node.name] - local source, flowing = def.liquid_alternative_source, node.name - local range = def.liquid_range or liquid_level_max - local neighbors = {} - for x = -1, 1 do - neighbors[x] = {} - for z = -1, 1 do - local neighbor_pos = {x = pos.x + x, y = pos.y, z = pos.z + z} - local neighbor_node = minetest.get_node(neighbor_pos) - local level - if neighbor_node.name == source then - level = 1 - elseif neighbor_node.name == flowing then - local neighbor_level = neighbor_node.param2 % 8 - level = (math.max(0, neighbor_level - liquid_level_max + range) + 0.5) / range - end - neighbor_pos.y = neighbor_pos.y + 1 - local node_above = minetest.get_node(neighbor_pos) - neighbors[x][z] = { - air = neighbor_node.name == "air", - level = level, - above_is_same_liquid = node_above.name == flowing or node_above.name == source - } - end - end - local function get_corner_level(x, z) - local air_neighbor - local levels = 0 - local neighbor_count = 0 - for nx = x - 1, x do - for nz = z - 1, z do - local neighbor = neighbors[nx][nz] - if neighbor.above_is_same_liquid then - return 1 - end - local level = neighbor.level - if level then - if level == 1 then - return 1 - end - levels = levels + level - neighbor_count = neighbor_count + 1 - elseif neighbor.air then - if air_neighbor then - return 0.02 - end - air_neighbor = true - end - end - end - if neighbor_count == 0 then - return 0 - end - return levels / neighbor_count - end - local corner_levels = { - {0, nil, 0}, - {1, nil, 0}, - {1, nil, 1}, - {0, nil, 1} - } - for index, corner_level in pairs(corner_levels) do - corner_level[2] = get_corner_level(corner_level[1], corner_level[3]) - corner_levels[index] = modlib.vector.subtract_scalar(modlib.vector.new(corner_level), 0.5) - end - return corner_levels + local node = minetest.get_node(pos) + local def = minetest.registered_nodes[node.name] + local source, flowing = def.liquid_alternative_source, node.name + local range = def.liquid_range or liquid_level_max + local neighbors = {} + for x = -1, 1 do + neighbors[x] = {} + for z = -1, 1 do + local neighbor_pos = {x = pos.x + x, y = pos.y, z = pos.z + z} + local neighbor_node = minetest.get_node(neighbor_pos) + local level + if neighbor_node.name == source then + level = 1 + elseif neighbor_node.name == flowing then + local neighbor_level = neighbor_node.param2 % 8 + level = (math.max(0, neighbor_level - liquid_level_max + range) + 0.5) / range + end + neighbor_pos.y = neighbor_pos.y + 1 + local node_above = minetest.get_node(neighbor_pos) + neighbors[x][z] = { + air = neighbor_node.name == "air", + level = level, + above_is_same_liquid = node_above.name == flowing or node_above.name == source + } + end + end + local function get_corner_level(x, z) + local air_neighbor + local levels = 0 + local neighbor_count = 0 + for nx = x - 1, x do + for nz = z - 1, z do + local neighbor = neighbors[nx][nz] + if neighbor.above_is_same_liquid then + return 1 + end + local level = neighbor.level + if level then + if level == 1 then + return 1 + end + levels = levels + level + neighbor_count = neighbor_count + 1 + elseif neighbor.air then + if air_neighbor then + return 0.02 + end + air_neighbor = true + end + end + end + if neighbor_count == 0 then + return 0 + end + return levels / neighbor_count + end + local corner_levels = { + {0, nil, 0}, + {1, nil, 0}, + {1, nil, 1}, + {0, nil, 1} + } + for index, corner_level in pairs(corner_levels) do + corner_level[2] = get_corner_level(corner_level[1], corner_level[3]) + corner_levels[index] = modlib.vector.subtract_scalar(modlib.vector.new(corner_level), 0.5) + end + return corner_levels end flowing_downwards = modlib.vector.new{0, -1, 0} @@ -76,40 +76,40 @@ flowing_downwards = modlib.vector.new{0, -1, 0} --> `modlib.minetest.flowing_downwards = modlib.vector.new{0, -1, 0}` if only flowing downwards --> surface direction as `modlib.vector` else function get_liquid_flow_direction(pos) - local corner_levels = get_liquid_corner_levels(pos) - local max_level = corner_levels[1][2] - for index = 2, 4 do - local level = corner_levels[index][2] - if level > max_level then - max_level = level - end - end - local dir = modlib.vector.new{0, 0, 0} - local count = 0 - for max_level_index, corner_level in pairs(corner_levels) do - if corner_level[2] == max_level then - for offset = 1, 3 do - local index = (max_level_index + offset - 1) % 4 + 1 - local diff = corner_level - corner_levels[index] - if diff[2] ~= 0 then - diff[1] = diff[1] * diff[2] - diff[3] = diff[3] * diff[2] - if offset == 3 then - diff = modlib.vector.divide_scalar(diff, math.sqrt(2)) - end - dir = dir + diff - count = count + 1 - end - end - end - end - if count ~= 0 then - dir = modlib.vector.divide_scalar(dir, count) - end - if dir == modlib.vector.new{0, 0, 0} then - if minetest.get_node(pos).param2 % 32 > 7 then - return flowing_downwards - end - end - return dir + local corner_levels = get_liquid_corner_levels(pos) + local max_level = corner_levels[1][2] + for index = 2, 4 do + local level = corner_levels[index][2] + if level > max_level then + max_level = level + end + end + local dir = modlib.vector.new{0, 0, 0} + local count = 0 + for max_level_index, corner_level in pairs(corner_levels) do + if corner_level[2] == max_level then + for offset = 1, 3 do + local index = (max_level_index + offset - 1) % 4 + 1 + local diff = corner_level - corner_levels[index] + if diff[2] ~= 0 then + diff[1] = diff[1] * diff[2] + diff[3] = diff[3] * diff[2] + if offset == 3 then + diff = modlib.vector.divide_scalar(diff, math.sqrt(2)) + end + dir = dir + diff + count = count + 1 + end + end + end + end + if count ~= 0 then + dir = modlib.vector.divide_scalar(dir, count) + end + if dir == modlib.vector.new{0, 0, 0} then + if minetest.get_node(pos).param2 % 32 > 7 then + return flowing_downwards + end + end + return dir end \ No newline at end of file diff --git a/minetest/misc.lua b/minetest/misc.lua index 50abb9f..8158944 100644 --- a/minetest/misc.lua +++ b/minetest/misc.lua @@ -1,40 +1,40 @@ max_wear = 2 ^ 16 - 1 function override(function_name, function_builder) - local func = minetest[function_name] - minetest["original_" .. function_name] = func - minetest[function_name] = function_builder(func) + local func = minetest[function_name] + minetest["original_" .. function_name] = func + minetest[function_name] = function_builder(func) end -- TODO fix modlib.minetest.get_gametime() messing up responsible "mod" determined by engine on crash get_gametime = minetest.get_gametime local get_gametime_initialized local function get_gametime_init(dtime) - if get_gametime_initialized then - -- if the profiler is being used, the globalstep can't be unregistered - return - end - get_gametime_initialized = true - assert(dtime == 0) - local gametime = minetest.get_gametime() - assert(gametime) - function modlib.minetest.get_gametime() - local imprecise_gametime = minetest.get_gametime() - if imprecise_gametime > gametime then - minetest.log("warning", "modlib.minetest.get_gametime(): Called after increment and before first globalstep") - return imprecise_gametime - end - return gametime - end - for index, globalstep in pairs(minetest.registered_globalsteps) do - if globalstep == get_gametime_init then - table.remove(minetest.registered_globalsteps, index) - break - end - end - -- globalsteps of mods which depend on modlib will execute after this - minetest.register_globalstep(function(dtime) - gametime = gametime + dtime - end) + if get_gametime_initialized then + -- if the profiler is being used, the globalstep can't be unregistered + return + end + get_gametime_initialized = true + assert(dtime == 0) + local gametime = minetest.get_gametime() + assert(gametime) + function modlib.minetest.get_gametime() + local imprecise_gametime = minetest.get_gametime() + if imprecise_gametime > gametime then + minetest.log("warning", "modlib.minetest.get_gametime(): Called after increment and before first globalstep") + return imprecise_gametime + end + return gametime + end + for index, globalstep in pairs(minetest.registered_globalsteps) do + if globalstep == get_gametime_init then + table.remove(minetest.registered_globalsteps, index) + break + end + end + -- globalsteps of mods which depend on modlib will execute after this + minetest.register_globalstep(function(dtime) + gametime = gametime + dtime + end) end minetest.register_globalstep(get_gametime_init) @@ -42,156 +42,156 @@ delta_times={} delays={} callbacks={} function register_globalstep(interval, callback) - if type(callback) ~= "function" then - return - end - table.insert(delta_times, 0) - table.insert(delays, interval) - table.insert(callbacks, callback) + if type(callback) ~= "function" then + return + end + table.insert(delta_times, 0) + table.insert(delays, interval) + table.insert(callbacks, callback) end function texture_modifier_inventorycube(face_1, face_2, face_3) - return "[inventorycube{" .. string.gsub(face_1, "%^", "&") - .. "{" .. string.gsub(face_2, "%^", "&") - .. "{" .. string.gsub(face_3, "%^", "&") + return "[inventorycube{" .. string.gsub(face_1, "%^", "&") + .. "{" .. string.gsub(face_2, "%^", "&") + .. "{" .. string.gsub(face_3, "%^", "&") end function get_node_inventory_image(nodename) - local n = minetest.registered_nodes[nodename] - if not n then - return - end - local tiles = {} - for l, tile in pairs(n.tiles or {}) do - tiles[l] = (type(tile) == "string" and tile) or tile.name - end - local chosen_tiles = { tiles[1], tiles[3], tiles[5] } - if #chosen_tiles == 0 then - return false - end - if not chosen_tiles[2] then - chosen_tiles[2] = chosen_tiles[1] - end - if not chosen_tiles[3] then - chosen_tiles[3] = chosen_tiles[2] - end - local img = minetest.registered_items[nodename].inventory_image - if string.len(img) == 0 then - img = nil - end - return img or texture_modifier_inventorycube(chosen_tiles[1], chosen_tiles[2], chosen_tiles[3]) + local n = minetest.registered_nodes[nodename] + if not n then + return + end + local tiles = {} + for l, tile in pairs(n.tiles or {}) do + tiles[l] = (type(tile) == "string" and tile) or tile.name + end + local chosen_tiles = { tiles[1], tiles[3], tiles[5] } + if #chosen_tiles == 0 then + return false + end + if not chosen_tiles[2] then + chosen_tiles[2] = chosen_tiles[1] + end + if not chosen_tiles[3] then + chosen_tiles[3] = chosen_tiles[2] + end + local img = minetest.registered_items[nodename].inventory_image + if string.len(img) == 0 then + img = nil + end + return img or texture_modifier_inventorycube(chosen_tiles[1], chosen_tiles[2], chosen_tiles[3]) end function get_color_int(color) - return color.b + (color.g*256) + (color.r*256*256) + return color.b + (color.g*256) + (color.r*256*256) end function check_player_privs(playername, privtable) - local privs=minetest.get_player_privs(playername) - local missing_privs={} - local to_lose_privs={} - for priv, expected_value in pairs(privtable) do - local actual_value=privs[priv] - if expected_value then - if not actual_value then - table.insert(missing_privs, priv) - end - else - if actual_value then - table.insert(to_lose_privs, priv) - end - end - end - return missing_privs, to_lose_privs + local privs=minetest.get_player_privs(playername) + local missing_privs={} + local to_lose_privs={} + for priv, expected_value in pairs(privtable) do + local actual_value=privs[priv] + if expected_value then + if not actual_value then + table.insert(missing_privs, priv) + end + else + if actual_value then + table.insert(to_lose_privs, priv) + end + end + end + return missing_privs, to_lose_privs end minetest.register_globalstep(function(dtime) - for k, v in pairs(delta_times) do - local v=dtime+v - if v > delays[k] then - callbacks[k](v) - v=0 - end - delta_times[k]=v - end + for k, v in pairs(delta_times) do + local v=dtime+v + if v > delays[k] then + callbacks[k](v) + v=0 + end + delta_times[k]=v + end end) form_listeners = {} function register_form_listener(formname, func) - local current_listeners = form_listeners[formname] or {} - table.insert(current_listeners, func) - form_listeners[formname] = current_listeners + local current_listeners = form_listeners[formname] or {} + table.insert(current_listeners, func) + form_listeners[formname] = current_listeners end minetest.register_on_player_receive_fields(function(player, formname, fields) - local handlers = form_listeners[formname] - if handlers then - for _, handler in pairs(handlers) do - handler(player, fields) - end - end + local handlers = form_listeners[formname] + if handlers then + for _, handler in pairs(handlers) do + handler(player, fields) + end + end end) --+ Improved base64 decode removing valid padding function decode_base64(base64) - local len = base64:len() - local padding_char = base64:sub(len, len) == "=" - if padding_char then - if len % 4 ~= 0 then - return - end - if base64:sub(len-1, len-1) == "=" then - base64 = base64:sub(1, len-2) - else - base64 = base64:sub(1, len-1) - end - end - return minetest.decode_base64(base64) + local len = base64:len() + local padding_char = base64:sub(len, len) == "=" + if padding_char then + if len % 4 ~= 0 then + return + end + if base64:sub(len-1, len-1) == "=" then + base64 = base64:sub(1, len-2) + else + base64 = base64:sub(1, len-1) + end + end + return minetest.decode_base64(base64) end local object_refs = minetest.object_refs --+ Objects inside radius iterator. Uses a linear search. function objects_inside_radius(pos, radius) - radius = radius^2 - local id, object, object_pos - return function() - repeat - id, object = next(object_refs, id) - object_pos = object:get_pos() - until (not object) or ((pos.x-object_pos.x)^2 + (pos.y-object_pos.y)^2 + (pos.z-object_pos.z)^2) <= radius - return object - end + radius = radius^2 + local id, object, object_pos + return function() + repeat + id, object = next(object_refs, id) + object_pos = object:get_pos() + until (not object) or ((pos.x-object_pos.x)^2 + (pos.y-object_pos.y)^2 + (pos.z-object_pos.z)^2) <= radius + return object + end end --+ Objects inside area iterator. Uses a linear search. function objects_inside_area(min, max) - local id, object, object_pos - return function() - repeat - id, object = next(object_refs, id) - object_pos = object:get_pos() - until (not object) or ( - (min.x <= object_pos.x and min.y <= object_pos.y and min.z <= object_pos.z) - and - (max.y >= object_pos.x and max.y >= object_pos.y and max.z >= object_pos.z) - ) - return object - end + local id, object, object_pos + return function() + repeat + id, object = next(object_refs, id) + object_pos = object:get_pos() + until (not object) or ( + (min.x <= object_pos.x and min.y <= object_pos.y and min.z <= object_pos.z) + and + (max.y >= object_pos.x and max.y >= object_pos.y and max.z >= object_pos.z) + ) + return object + end end --: node_or_groupname "modname:nodename", "group:groupname[,groupname]" --> function(nodename) -> whether node matches function nodename_matcher(node_or_groupname) - if modlib.text.starts_with(node_or_groupname, "group:") then - -- TODO consider using modlib.text.split instead of Minetest's string.split - local groups = node_or_groupname:sub(("group:"):len() + 1):split(",") - return function(nodename) - for _, groupname in pairs(groups) do - if minetest.get_item_group(nodename, groupname) == 0 then - return false - end - end - return true - end - else - return function(nodename) - return nodename == node_or_groupname - end - end + if modlib.text.starts_with(node_or_groupname, "group:") then + -- TODO consider using modlib.text.split instead of Minetest's string.split + local groups = node_or_groupname:sub(("group:"):len() + 1):split(",") + return function(nodename) + for _, groupname in pairs(groups) do + if minetest.get_item_group(nodename, groupname) == 0 then + return false + end + end + return true + end + else + return function(nodename) + return nodename == node_or_groupname + end + end end \ No newline at end of file diff --git a/minetest/raycast.lua b/minetest/raycast.lua index 61aa3ba..f1daabf 100644 --- a/minetest/raycast.lua +++ b/minetest/raycast.lua @@ -1,132 +1,132 @@ --+ Raycast wrapper with proper flowingliquid intersections function raycast(_pos1, _pos2, objects, liquids) - local raycast = minetest.raycast(_pos1, _pos2, objects, liquids) - if not liquids then - return raycast - end - local pos1 = modlib.vector.from_minetest(_pos1) - local _direction = vector.direction(_pos1, _pos2) - local direction = modlib.vector.from_minetest(_direction) - local length = vector.distance(_pos1, _pos2) - local function next() - for pointed_thing in raycast do - if pointed_thing.type ~= "node" then - return pointed_thing - end - local _pos = pointed_thing.under - local pos = modlib.vector.from_minetest(_pos) - local node = minetest.get_node(_pos) - local def = minetest.registered_nodes[node.name] - if not (def and def.drawtype == "flowingliquid") then return pointed_thing end - local corner_levels = get_liquid_corner_levels(_pos) - local full_corner_levels = true - for _, corner_level in pairs(corner_levels) do - if corner_level[2] < 0.5 then - full_corner_levels = false - break - end - end - if full_corner_levels then - return pointed_thing - end - local relative = pos1 - pos - local inside = true - for _, prop in pairs(relative) do - if prop <= -0.5 or prop >= 0.5 then - inside = false - break - end - end - local function level(x, z) - local function distance_squared(corner) - return (x - corner[1]) ^ 2 + (z - corner[3]) ^ 2 - end - local irrelevant_corner, distance = 1, distance_squared(corner_levels[1]) - for index = 2, 4 do - local other_distance = distance_squared(corner_levels[index]) - if other_distance > distance then - irrelevant_corner, distance = index, other_distance - end - end - local function corner(off) - return corner_levels[((irrelevant_corner + off) % 4) + 1] - end - local base = corner(2) - local edge_1, edge_2 = corner(1) - base, corner(3) - base - -- Properly selected edges will have a total length of 2 - assert(math.abs(edge_1[1] + edge_1[3]) + math.abs(edge_2[1] + edge_2[3]) == 2) - if edge_1[1] == 0 then - edge_1, edge_2 = edge_2, edge_1 - end - local level = base[2] + (edge_1[2] * ((x - base[1]) / edge_1[1])) + (edge_2[2] * ((z - base[3]) / edge_2[3])) - assert(level >= -0.5 and level <= 0.5) - return level - end - inside = inside and (relative[2] < level(relative[1], relative[3])) - if inside then - -- pos1 is inside the liquid node - pointed_thing.intersection_point = _pos1 - pointed_thing.intersection_normal = vector.new(0, 0, 0) - return pointed_thing - end - local function intersection_normal(axis, dir) - return {x = 0, y = 0, z = 0, [axis] = dir} - end - local function plane(axis, dir) - local offset = dir * 0.5 - local diff_axis = (relative[axis] - offset) / -direction[axis] - local intersection_point = {} - for plane_axis = 1, 3 do - if plane_axis ~= axis then - local value = direction[plane_axis] * diff_axis + relative[plane_axis] - if value < -0.5 or value > 0.5 then - return - end - intersection_point[plane_axis] = value - end - end - intersection_point[axis] = offset - return intersection_point - end - if direction[2] > 0 then - local intersection_point = plane(2, -1) - if intersection_point then - pointed_thing.intersection_point = (intersection_point + pos):to_minetest() - pointed_thing.intersection_normal = intersection_normal("y", -1) - return pointed_thing - end - end - for coord, other in pairs{[1] = 3, [3] = 1} do - if direction[coord] ~= 0 then - local dir = direction[coord] > 0 and -1 or 1 - local intersection_point = plane(coord, dir) - if intersection_point then - local height = 0 - for _, corner in pairs(corner_levels) do - if corner[coord] == dir * 0.5 then - height = height + (math.abs(intersection_point[other] + corner[other])) * corner[2] - end - end - if intersection_point[2] <= height then - pointed_thing.intersection_point = (intersection_point + pos):to_minetest() - pointed_thing.intersection_normal = intersection_normal(modlib.vector.index_aliases[coord], dir) - return pointed_thing - end - end - end - end - for _, triangle in pairs{ - {corner_levels[3], corner_levels[2], corner_levels[1]}, - {corner_levels[4], corner_levels[3], corner_levels[1]} - } do - local pos_on_ray = modlib.vector.ray_triangle_intersection(relative, direction, triangle) - if pos_on_ray and pos_on_ray <= length then - pointed_thing.intersection_point = (pos1 + modlib.vector.multiply_scalar(direction, pos_on_ray)):to_minetest() - pointed_thing.intersection_normal = modlib.vector.triangle_normal(triangle):to_minetest() - return pointed_thing - end - end - end - end - return setmetatable({next = next}, {__call = next}) + local raycast = minetest.raycast(_pos1, _pos2, objects, liquids) + if not liquids then + return raycast + end + local pos1 = modlib.vector.from_minetest(_pos1) + local _direction = vector.direction(_pos1, _pos2) + local direction = modlib.vector.from_minetest(_direction) + local length = vector.distance(_pos1, _pos2) + local function next() + for pointed_thing in raycast do + if pointed_thing.type ~= "node" then + return pointed_thing + end + local _pos = pointed_thing.under + local pos = modlib.vector.from_minetest(_pos) + local node = minetest.get_node(_pos) + local def = minetest.registered_nodes[node.name] + if not (def and def.drawtype == "flowingliquid") then return pointed_thing end + local corner_levels = get_liquid_corner_levels(_pos) + local full_corner_levels = true + for _, corner_level in pairs(corner_levels) do + if corner_level[2] < 0.5 then + full_corner_levels = false + break + end + end + if full_corner_levels then + return pointed_thing + end + local relative = pos1 - pos + local inside = true + for _, prop in pairs(relative) do + if prop <= -0.5 or prop >= 0.5 then + inside = false + break + end + end + local function level(x, z) + local function distance_squared(corner) + return (x - corner[1]) ^ 2 + (z - corner[3]) ^ 2 + end + local irrelevant_corner, distance = 1, distance_squared(corner_levels[1]) + for index = 2, 4 do + local other_distance = distance_squared(corner_levels[index]) + if other_distance > distance then + irrelevant_corner, distance = index, other_distance + end + end + local function corner(off) + return corner_levels[((irrelevant_corner + off) % 4) + 1] + end + local base = corner(2) + local edge_1, edge_2 = corner(1) - base, corner(3) - base + -- Properly selected edges will have a total length of 2 + assert(math.abs(edge_1[1] + edge_1[3]) + math.abs(edge_2[1] + edge_2[3]) == 2) + if edge_1[1] == 0 then + edge_1, edge_2 = edge_2, edge_1 + end + local level = base[2] + (edge_1[2] * ((x - base[1]) / edge_1[1])) + (edge_2[2] * ((z - base[3]) / edge_2[3])) + assert(level >= -0.5 and level <= 0.5) + return level + end + inside = inside and (relative[2] < level(relative[1], relative[3])) + if inside then + -- pos1 is inside the liquid node + pointed_thing.intersection_point = _pos1 + pointed_thing.intersection_normal = vector.new(0, 0, 0) + return pointed_thing + end + local function intersection_normal(axis, dir) + return {x = 0, y = 0, z = 0, [axis] = dir} + end + local function plane(axis, dir) + local offset = dir * 0.5 + local diff_axis = (relative[axis] - offset) / -direction[axis] + local intersection_point = {} + for plane_axis = 1, 3 do + if plane_axis ~= axis then + local value = direction[plane_axis] * diff_axis + relative[plane_axis] + if value < -0.5 or value > 0.5 then + return + end + intersection_point[plane_axis] = value + end + end + intersection_point[axis] = offset + return intersection_point + end + if direction[2] > 0 then + local intersection_point = plane(2, -1) + if intersection_point then + pointed_thing.intersection_point = (intersection_point + pos):to_minetest() + pointed_thing.intersection_normal = intersection_normal("y", -1) + return pointed_thing + end + end + for coord, other in pairs{[1] = 3, [3] = 1} do + if direction[coord] ~= 0 then + local dir = direction[coord] > 0 and -1 or 1 + local intersection_point = plane(coord, dir) + if intersection_point then + local height = 0 + for _, corner in pairs(corner_levels) do + if corner[coord] == dir * 0.5 then + height = height + (math.abs(intersection_point[other] + corner[other])) * corner[2] + end + end + if intersection_point[2] <= height then + pointed_thing.intersection_point = (intersection_point + pos):to_minetest() + pointed_thing.intersection_normal = intersection_normal(modlib.vector.index_aliases[coord], dir) + return pointed_thing + end + end + end + end + for _, triangle in pairs{ + {corner_levels[3], corner_levels[2], corner_levels[1]}, + {corner_levels[4], corner_levels[3], corner_levels[1]} + } do + local pos_on_ray = modlib.vector.ray_triangle_intersection(relative, direction, triangle) + if pos_on_ray and pos_on_ray <= length then + pointed_thing.intersection_point = (pos1 + modlib.vector.multiply_scalar(direction, pos_on_ray)):to_minetest() + pointed_thing.intersection_normal = modlib.vector.triangle_normal(triangle):to_minetest() + return pointed_thing + end + end + end + end + return setmetatable({next = next}, {__call = next}) end \ No newline at end of file diff --git a/minetest/wielditem_change.lua b/minetest/wielditem_change.lua index 314ad3c..c63b338 100644 --- a/minetest/wielditem_change.lua +++ b/minetest/wielditem_change.lua @@ -1,13 +1,13 @@ players = {} registered_on_wielditem_changes = {function(...) - local _, previous_item, _, item = ... - if previous_item then - ((previous_item:get_definition()._modlib or {}).un_wield or modlib.func.no_op)(...) - end - if item then - ((item:get_definition()._modlib or {}).on_wield or modlib.func.no_op)(...) - end + local _, previous_item, _, item = ... + if previous_item then + ((previous_item:get_definition()._modlib or {}).un_wield or modlib.func.no_op)(...) + end + if item then + ((item:get_definition()._modlib or {}).on_wield or modlib.func.no_op)(...) + end end} --+ Registers an on_wielditem_change callback: function(player, previous_item, previous_index, item) @@ -15,32 +15,32 @@ end} register_on_wielditem_change = modlib.func.curry(table.insert, registered_on_wielditem_changes) minetest.register_on_mods_loaded(function() - -- Other on_joinplayer / on_leaveplayer callbacks should execute first - minetest.register_on_joinplayer(function(player) - local item, index = player:get_wielded_item(), player:get_wield_index() - players[player:get_player_name()] = { - wield = { - item = item, - index = index - } - } - modlib.table.icall(registered_on_wielditem_changes, player, nil, index, item) - end) - minetest.register_on_leaveplayer(function(player) - players[player:get_player_name()] = nil - end) + -- Other on_joinplayer / on_leaveplayer callbacks should execute first + minetest.register_on_joinplayer(function(player) + local item, index = player:get_wielded_item(), player:get_wield_index() + players[player:get_player_name()] = { + wield = { + item = item, + index = index + } + } + modlib.table.icall(registered_on_wielditem_changes, player, nil, index, item) + end) + minetest.register_on_leaveplayer(function(player) + players[player:get_player_name()] = nil + end) end) minetest.register_globalstep(function() - for _, player in pairs(minetest.get_connected_players()) do - local item, index = player:get_wielded_item(), player:get_wield_index() - local playerdata = players[player:get_player_name()] - if not playerdata then return end - local previous_item, previous_index = playerdata.wield.item, playerdata.wield.index - if item:get_name() ~= previous_item or index ~= previous_index then - playerdata.wield.item = item - playerdata.wield.index = index - modlib.table.icall(registered_on_wielditem_changes, player, previous_item, previous_index, item) - end - end + for _, player in pairs(minetest.get_connected_players()) do + local item, index = player:get_wielded_item(), player:get_wield_index() + local playerdata = players[player:get_player_name()] + if not playerdata then return end + local previous_item, previous_index = playerdata.wield.item, playerdata.wield.index + if item:get_name() ~= previous_item or index ~= previous_index then + playerdata.wield.item = item + playerdata.wield.index = index + modlib.table.icall(registered_on_wielditem_changes, player, previous_item, previous_index, item) + end + end end) \ No newline at end of file diff --git a/mod.lua b/mod.lua index fc819fd..3e2d640 100644 --- a/mod.lua +++ b/mod.lua @@ -1,130 +1,130 @@ -- get resource + dofile function include(modname, file) - if not file then - file = modname - modname = minetest.get_current_modname() - end - return dofile(get_resource(modname, file)) + if not file then + file = modname + modname = minetest.get_current_modname() + end + return dofile(get_resource(modname, file)) end function include_env(file_or_string, env, is_string) - setfenv(assert((is_string and loadstring or loadfile)(file_or_string)), env)() + setfenv(assert((is_string and loadstring or loadfile)(file_or_string)), env)() end function create_namespace(namespace_name, parent_namespace) - namespace_name = namespace_name or minetest.get_current_modname() - parent_namespace = parent_namespace or _G - local metatable = {__index = parent_namespace == _G and function(_, key) return rawget(_G, key) end or parent_namespace} - local namespace = {} - namespace = setmetatable(namespace, metatable) - if parent_namespace == _G then - rawset(parent_namespace, namespace_name, namespace) - else - parent_namespace[namespace_name] = namespace - end - return namespace + namespace_name = namespace_name or minetest.get_current_modname() + parent_namespace = parent_namespace or _G + local metatable = {__index = parent_namespace == _G and function(_, key) return rawget(_G, key) end or parent_namespace} + local namespace = {} + namespace = setmetatable(namespace, metatable) + if parent_namespace == _G then + rawset(parent_namespace, namespace_name, namespace) + else + parent_namespace[namespace_name] = namespace + end + return namespace end -- formerly extend_mod function extend(modname, file) - if not file then - file = modname - modname = minetest.get_current_modname() - end - include_env(get_resource(modname, file .. ".lua"), rawget(_G, modname)) + if not file then + file = modname + modname = minetest.get_current_modname() + end + include_env(get_resource(modname, file .. ".lua"), rawget(_G, modname)) end -- runs main.lua in table env -- formerly include_mod function init(modname) - modname = modname or minetest.get_current_modname() - create_namespace(modname) - extend(modname, "main") + modname = modname or minetest.get_current_modname() + create_namespace(modname) + extend(modname, "main") end --! deprecated function extend_string(modname, string) - if not string then - string = modname - modname = minetest.get_current_modname() - end - include_env(string, rawget(_G, modname), true) + if not string then + string = modname + modname = minetest.get_current_modname() + end + include_env(string, rawget(_G, modname), true) end function configuration(modname) - modname = modname or minetest.get_current_modname() - local schema = modlib.schema.new(assert(include(modname, "schema.lua"))) - schema.name = schema.name or modname - assert(schema.type == "table") - local overrides = {} - local conf - local function add(path) - for _, format in ipairs{ - {extension = "lua", read = function(text) - assert(overrides._C == nil) - local additions = setfenv(assert(loadstring(text)), setmetatable(overrides, {__index = {_C = overrides}}))() - setmetatable(overrides, nil) - if additions == nil then - return overrides - end - return additions - end}, - {extension = "luon", read = function(text) - local value = {setfenv(assert(loadstring("return " .. text)), setmetatable(overrides, {}))()} - assert(#value == 1) - value = value[1] - local function check_type(value) - local type = type(value) - if type == "table" then - assert(getmetatable(value) == nil) - for key, value in pairs(value) do - check_type(key) - check_type(value) - end - elseif not (type == "boolean" or type == "number" or type == "string") then - error("disallowed type " .. type) - end - end - check_type(value) - return value - end}, - {extension = "conf", read = function(text) return modlib.conf.build_setting_tree(Settings(text):to_table()) end, convert_strings = true}, - {extension = "json", read = minetest.parse_json} - } do - local content = modlib.file.read(path .. "." .. format.extension) - if content then - overrides = modlib.table.deep_add_all(overrides, format.read(content)) - conf = schema:load(overrides, {convert_strings = format.convert_strings, error_message = true}) - end - end - end - add(minetest.get_worldpath() .. "/conf/" .. modname) - add(get_resource(modname, "conf")) - local minetest_conf = modlib.conf.settings[schema.name] - if minetest_conf then - overrides = modlib.table.deep_add_all(overrides, minetest_conf) - conf = schema:load(overrides, {convert_strings = true, error_message = true}) - end - modlib.file.ensure_content(get_resource(modname, "settingtypes.txt"), schema:generate_settingtypes()) - local readme_path = get_resource(modname, "Readme.md") - local readme = modlib.file.read(readme_path) - if readme then - local modified = false - readme = readme:gsub("" .. "(.-)" .. "", function(level, content) - schema._md_level = assert(tonumber(level)) + 1 - -- HACK: Newline between comment and heading (MD implementations don't handle comments properly) - local markdown = "\n" .. schema:generate_markdown() - if content ~= markdown then - modified = true - return "" .. markdown .. "" - end - end, 1) - if modified then - assert(modlib.file.write(readme_path, readme)) - end - end - if conf == nil then - return schema:load({}, {error_message = true}) - end - return conf -end \ No newline at end of file + modname = modname or minetest.get_current_modname() + local schema = modlib.schema.new(assert(include(modname, "schema.lua"))) + schema.name = schema.name or modname + assert(schema.type == "table") + local overrides = {} + local conf + local function add(path) + for _, format in ipairs{ + {extension = "lua", read = function(text) + assert(overrides._C == nil) + local additions = setfenv(assert(loadstring(text)), setmetatable(overrides, {__index = {_C = overrides}}))() + setmetatable(overrides, nil) + if additions == nil then + return overrides + end + return additions + end}, + {extension = "luon", read = function(text) + local value = {setfenv(assert(loadstring("return " .. text)), setmetatable(overrides, {}))()} + assert(#value == 1) + value = value[1] + local function check_type(value) + local type = type(value) + if type == "table" then + assert(getmetatable(value) == nil) + for key, value in pairs(value) do + check_type(key) + check_type(value) + end + elseif not (type == "boolean" or type == "number" or type == "string") then + error("disallowed type " .. type) + end + end + check_type(value) + return value + end}, + {extension = "conf", read = function(text) return modlib.conf.build_setting_tree(Settings(text):to_table()) end, convert_strings = true}, + {extension = "json", read = minetest.parse_json} + } do + local content = modlib.file.read(path .. "." .. format.extension) + if content then + overrides = modlib.table.deep_add_all(overrides, format.read(content)) + conf = schema:load(overrides, {convert_strings = format.convert_strings, error_message = true}) + end + end + end + add(minetest.get_worldpath() .. "/conf/" .. modname) + add(get_resource(modname, "conf")) + local minetest_conf = modlib.conf.settings[schema.name] + if minetest_conf then + overrides = modlib.table.deep_add_all(overrides, minetest_conf) + conf = schema:load(overrides, {convert_strings = true, error_message = true}) + end + modlib.file.ensure_content(get_resource(modname, "settingtypes.txt"), schema:generate_settingtypes()) + local readme_path = get_resource(modname, "Readme.md") + local readme = modlib.file.read(readme_path) + if readme then + local modified = false + readme = readme:gsub("" .. "(.-)" .. "", function(level, content) + schema._md_level = assert(tonumber(level)) + 1 + -- HACK: Newline between comment and heading (MD implementations don't handle comments properly) + local markdown = "\n" .. schema:generate_markdown() + if content ~= markdown then + modified = true + return "" .. markdown .. "" + end + end, 1) + if modified then + assert(modlib.file.write(readme_path, readme)) + end + end + if conf == nil then + return schema:load({}, {error_message = true}) + end + return conf +end \ No newline at end of file diff --git a/quaternion.lua b/quaternion.lua index 03529f6..b219ef1 100644 --- a/quaternion.lua +++ b/quaternion.lua @@ -5,8 +5,8 @@ function from_euler_rotation(rotation) local cos = vector.apply(rotation, math.cos) local sin = vector.apply(rotation, math.sin) return { - sin.z * cos.x * cos.y - cos.z * sin.x * sin.y, - cos.z * sin.x * cos.y + sin.z * cos.x * sin.y, + sin.z * cos.x * cos.y - cos.z * sin.x * sin.y, + cos.z * sin.x * cos.y + sin.z * cos.x * sin.y, cos.z * cos.x * sin.y - sin.z * sin.x * cos.y, cos.z * cos.x * cos.y + sin.z * sin.x * sin.y } @@ -76,23 +76,23 @@ end --> {x = pitch, y = yaw, z = roll} euler rotation in degrees function to_euler_rotation(self) - local rotation = {} + local rotation = {} - local sinr_cosp = 2 * (self[4] * self[1] + self[2] * self[3]) - local cosr_cosp = 1 - 2 * (self[1] ^ 2 + self[2] ^ 2) - rotation.x = math.atan2(sinr_cosp, cosr_cosp) + local sinr_cosp = 2 * (self[4] * self[1] + self[2] * self[3]) + local cosr_cosp = 1 - 2 * (self[1] ^ 2 + self[2] ^ 2) + rotation.x = math.atan2(sinr_cosp, cosr_cosp) - local sinp = 2 * (self[4] * self[2] - self[3] * self[1]) - if sinp <= -1 then - rotation.z = -math.pi/2 - elseif sinp >= 1 then - rotation.z = math.pi/2 - else - rotation.z = math.asin(sinp) + local sinp = 2 * (self[4] * self[2] - self[3] * self[1]) + if sinp <= -1 then + rotation.z = -math.pi/2 + elseif sinp >= 1 then + rotation.z = math.pi/2 + else + rotation.z = math.asin(sinp) end - local siny_cosp = 2 * (self[4] * self[3] + self[1] * self[2]) - local cosy_cosp = 1 - 2 * (self[2] ^ 2 + self[3] ^ 2) + local siny_cosp = 2 * (self[4] * self[3] + self[1] * self[2]) + local cosy_cosp = 1 - 2 * (self[2] ^ 2 + self[3] ^ 2) rotation.y = math.atan2(siny_cosp, cosy_cosp) return vector.apply(rotation, math.deg) diff --git a/ranked_set.lua b/ranked_set.lua index 1ac2737..2b5208f 100644 --- a/ranked_set.lua +++ b/ranked_set.lua @@ -5,308 +5,308 @@ comparator = modlib.table.default_comparator --+ Uses a weight-balanced binary tree function new(comparator) - return setmetatable({comparator = comparator, root = {total = 0}}, metatable) + return setmetatable({comparator = comparator, root = {total = 0}}, metatable) end function len(self) - return self.root.total + return self.root.total end metatable.__len = len function is_empty(self) - return len(self) == 0 + return len(self) == 0 end local function insert_all(tree, _table) - if tree.left then - insert_all(tree.left, _table) - end - table.insert(_table, tree.key) - if tree.right then - insert_all(tree.right, _table) - end + if tree.left then + insert_all(tree.left, _table) + end + table.insert(_table, tree.key) + if tree.right then + insert_all(tree.right, _table) + end end function to_table(self) - local table = {} - if not is_empty(self) then - insert_all(self.root, table) - end - return table + local table = {} + if not is_empty(self) then + insert_all(self.root, table) + end + return table end --> iterator: function() -> `rank, key` with ascending rank function ipairs(self, min, max) - if is_empty(self) then - return function() end - end - min = min or 1 - local tree = self.root - local current_rank = (tree.left and tree.left.total or 0) + 1 - repeat - if min == current_rank then - break - end - local left, right = tree.left, tree.right - if min < current_rank then - current_rank = current_rank - (left and left.right and left.right.total or 0) - 1 - tree = left - else - current_rank = current_rank + (right and right.left and right.left.total or 0) + 1 - tree = right - end - until not tree - max = max or len(self) - local to_visit = {tree} - tree = nil - local rank = min - 1 - local function next() - if not tree then - local len = #to_visit - if len == 0 then return end - tree = to_visit[len] - to_visit[len] = nil - else - while tree.left do - table.insert(to_visit, tree) - tree = tree.left - end - end - local key = tree.key - tree = tree.right - return key - end - return function() - if rank >= max then - return - end - local key = next() - if key == nil then - return - end - rank = rank + 1 - return rank, key - end + if is_empty(self) then + return function() end + end + min = min or 1 + local tree = self.root + local current_rank = (tree.left and tree.left.total or 0) + 1 + repeat + if min == current_rank then + break + end + local left, right = tree.left, tree.right + if min < current_rank then + current_rank = current_rank - (left and left.right and left.right.total or 0) - 1 + tree = left + else + current_rank = current_rank + (right and right.left and right.left.total or 0) + 1 + tree = right + end + until not tree + max = max or len(self) + local to_visit = {tree} + tree = nil + local rank = min - 1 + local function next() + if not tree then + local len = #to_visit + if len == 0 then return end + tree = to_visit[len] + to_visit[len] = nil + else + while tree.left do + table.insert(to_visit, tree) + tree = tree.left + end + end + local key = tree.key + tree = tree.right + return key + end + return function() + if rank >= max then + return + end + local key = next() + if key == nil then + return + end + rank = rank + 1 + return rank, key + end end local function _right_rotation(parent, right, left) - local new_parent = parent[left] - parent[left] = new_parent[right] - new_parent[right] = parent - parent.total = (parent[left] and parent[left].total or 0) + (parent[right] and parent[right].total or 0) + 1 - assert(parent.total > 0 or (parent.left == nil and parent.right == nil)) - new_parent.total = (new_parent[left] and new_parent[left].total or 0) + parent.total + 1 - return new_parent + local new_parent = parent[left] + parent[left] = new_parent[right] + new_parent[right] = parent + parent.total = (parent[left] and parent[left].total or 0) + (parent[right] and parent[right].total or 0) + 1 + assert(parent.total > 0 or (parent.left == nil and parent.right == nil)) + new_parent.total = (new_parent[left] and new_parent[left].total or 0) + parent.total + 1 + return new_parent end local function right_rotation(parent) - return _right_rotation(parent, "right", "left") + return _right_rotation(parent, "right", "left") end local function left_rotation(parent) - return _right_rotation(parent, "left", "right") + return _right_rotation(parent, "left", "right") end local function _rebalance(parent) - local left_count, right_count = (parent.left and parent.left.total or 0), (parent.right and parent.right.total or 0) - if right_count > 1 and left_count * 2 < right_count then - return left_rotation(parent) - end - if left_count > 1 and right_count * 2 < left_count then - return right_rotation(parent) - end - return parent + local left_count, right_count = (parent.left and parent.left.total or 0), (parent.right and parent.right.total or 0) + if right_count > 1 and left_count * 2 < right_count then + return left_rotation(parent) + end + if left_count > 1 and right_count * 2 < left_count then + return right_rotation(parent) + end + return parent end -- Rebalances a parent chain local function rebalance(self, len, parents, sides) - if len <= 1 then - return - end - for i = len, 2, -1 do - parents[i] = _rebalance(parents[i]) - parents[i - 1][sides[i - 1]] = parents[i] - end - self.root = parents[1] + if len <= 1 then + return + end + for i = len, 2, -1 do + parents[i] = _rebalance(parents[i]) + parents[i - 1][sides[i - 1]] = parents[i] + end + self.root = parents[1] end local function _insert(self, key, replace) - assert(key ~= nil) - if is_empty(self) then - self.root = {key = key, total = 1} - return - end - local comparator = self.comparator - local parents, sides = {}, {} - local tree = self.root - repeat - local tree_key = tree.key - local compared = comparator(key, tree_key) - if compared == 0 then - if replace then - tree.key = key - return tree_key - end - return - end - table.insert(parents, tree) - local side = compared < 0 and "left" or "right" - table.insert(sides, side) - tree = tree[side] - until not tree - local len = #parents - parents[len][sides[len]] = {key = key, total = 1} - for _, parent in pairs(parents) do - parent.total = parent.total + 1 - end - rebalance(self, len, parents, sides) + assert(key ~= nil) + if is_empty(self) then + self.root = {key = key, total = 1} + return + end + local comparator = self.comparator + local parents, sides = {}, {} + local tree = self.root + repeat + local tree_key = tree.key + local compared = comparator(key, tree_key) + if compared == 0 then + if replace then + tree.key = key + return tree_key + end + return + end + table.insert(parents, tree) + local side = compared < 0 and "left" or "right" + table.insert(sides, side) + tree = tree[side] + until not tree + local len = #parents + parents[len][sides[len]] = {key = key, total = 1} + for _, parent in pairs(parents) do + parent.total = parent.total + 1 + end + rebalance(self, len, parents, sides) end function insert(self, key) - return _insert(self, key) + return _insert(self, key) end function insert_or_replace(self, key) - return _insert(self, key, true) + return _insert(self, key, true) end local function _delete(self, key, is_rank) - assert(key ~= nil) - if is_empty(self) then - return - end - local comparator = self.comparator - local parents, sides = {}, {} - local tree = self.root - local rank = (tree.left and tree.left.total or 0) + 1 - repeat - local tree_key = tree.key - local compared - if is_rank then - if key == rank then - compared = 0 - elseif key < rank then - rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1 - compared = -1 - else - rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1 - compared = 1 - end - else - compared = comparator(key, tree_key) - end - if compared == 0 then - local len = #parents - local left, right = tree.left, tree.right - if left then - tree.total = tree.total - 1 - if right then - -- Obtain successor - local side = left.total > right.total and "left" or "right" - local other_side = side == "left" and "right" or "left" - local sidemost = tree[side] - while sidemost[other_side] do - sidemost.total = sidemost.total - 1 - table.insert(parents, sidemost) - table.insert(sides, other_side) - sidemost = sidemost[other_side] - end - -- Replace deleted key - tree.key = rightmost.key - -- Replace the successor by it's single child - parents[len][sides[len]] = sidemost[side] - else - if len == 0 then - self.root = left or {total = 0} - else - parents[len][sides[len]] = left - end - end - elseif right then - if len == 0 then - self.root = right or {total = 0} - else - tree.total = tree.total - 1 - parents[len][sides[len]] = right - end - else - if len == 0 then - self.root = {total = 0} - else - parents[len][sides[len]] = nil - end - end - for _, parent in pairs(parents) do - parent.total = parent.total - 1 - end - rebalance(self, len, parents, sides) - if is_rank then - return tree_key - end - return rank, tree_key - end - table.insert(parents, tree) - local side - if compared < 0 then - side = "left" - else - side = "right" - end - table.insert(sides, side) - tree = tree[side] - until not tree + assert(key ~= nil) + if is_empty(self) then + return + end + local comparator = self.comparator + local parents, sides = {}, {} + local tree = self.root + local rank = (tree.left and tree.left.total or 0) + 1 + repeat + local tree_key = tree.key + local compared + if is_rank then + if key == rank then + compared = 0 + elseif key < rank then + rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1 + compared = -1 + else + rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1 + compared = 1 + end + else + compared = comparator(key, tree_key) + end + if compared == 0 then + local len = #parents + local left, right = tree.left, tree.right + if left then + tree.total = tree.total - 1 + if right then + -- Obtain successor + local side = left.total > right.total and "left" or "right" + local other_side = side == "left" and "right" or "left" + local sidemost = tree[side] + while sidemost[other_side] do + sidemost.total = sidemost.total - 1 + table.insert(parents, sidemost) + table.insert(sides, other_side) + sidemost = sidemost[other_side] + end + -- Replace deleted key + tree.key = rightmost.key + -- Replace the successor by it's single child + parents[len][sides[len]] = sidemost[side] + else + if len == 0 then + self.root = left or {total = 0} + else + parents[len][sides[len]] = left + end + end + elseif right then + if len == 0 then + self.root = right or {total = 0} + else + tree.total = tree.total - 1 + parents[len][sides[len]] = right + end + else + if len == 0 then + self.root = {total = 0} + else + parents[len][sides[len]] = nil + end + end + for _, parent in pairs(parents) do + parent.total = parent.total - 1 + end + rebalance(self, len, parents, sides) + if is_rank then + return tree_key + end + return rank, tree_key + end + table.insert(parents, tree) + local side + if compared < 0 then + side = "left" + else + side = "right" + end + table.insert(sides, side) + tree = tree[side] + until not tree end function delete(self, key) - return _delete(self, key) + return _delete(self, key) end delete_by_key = delete function delete_by_rank(self, rank) - return _delete(self, rank, true) + return _delete(self, rank, true) end --> `rank, key` if the key was found --> `rank` the key would have if inserted function get(self, key) - if is_empty(self) then return end - local comparator = self.comparator - local tree = self.root - local rank = (tree.left and tree.left.total or 0) + 1 - while tree do - local compared = comparator(key, tree.key) - if compared == 0 then - return rank, tree.key - end - if compared < 0 then - rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1 - tree = tree.left - else - rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1 - tree = tree.right - end - end - return rank + if is_empty(self) then return end + local comparator = self.comparator + local tree = self.root + local rank = (tree.left and tree.left.total or 0) + 1 + while tree do + local compared = comparator(key, tree.key) + if compared == 0 then + return rank, tree.key + end + if compared < 0 then + rank = rank - (tree.left and tree.left.right and tree.left.right.total or 0) - 1 + tree = tree.left + else + rank = rank + (tree.right and tree.right.left and tree.right.left.total or 0) + 1 + tree = tree.right + end + end + return rank end get_by_key = get --> key function get_by_rank(self, rank) - local tree = self.root - local current_rank = (tree.left and tree.left.total or 0) + 1 - repeat - if rank == current_rank then - return tree.key - end - local left, right = tree.left, tree.right - if rank < current_rank then - current_rank = current_rank - (left and left.right and left.right.total or 0) - 1 - tree = left - else - current_rank = current_rank + (right and right.left and right.left.total or 0) + 1 - tree = right - end - until not tree + local tree = self.root + local current_rank = (tree.left and tree.left.total or 0) + 1 + repeat + if rank == current_rank then + return tree.key + end + local left, right = tree.left, tree.right + if rank < current_rank then + current_rank = current_rank - (left and left.right and left.right.total or 0) - 1 + tree = left + else + current_rank = current_rank + (right and right.left and right.left.total or 0) + 1 + tree = right + end + until not tree end \ No newline at end of file diff --git a/schema.lua b/schema.lua index 95f7575..7a81448 100644 --- a/schema.lua +++ b/schema.lua @@ -1,291 +1,291 @@ local schema = getfenv(1) function new(def) - -- TODO type inference, sanity checking etc. - return setmetatable(def, {__index = schema}) + -- TODO type inference, sanity checking etc. + return setmetatable(def, {__index = schema}) end local function field_name_to_title(name) - local title = modlib.text.split(name, "_") - title[1] = modlib.text.upper_first(title[1]) - return table.concat(title, " ") + local title = modlib.text.split(name, "_") + title[1] = modlib.text.upper_first(title[1]) + return table.concat(title, " ") end function generate_settingtypes(self) - local typ = self.type - local settingtype, type_args - self.title = self.title or field_name_to_title(self.name) - self._level = self._level or 0 - local default = self.default - if typ == "boolean" then - settingtype = "bool" - default = default and "true" or "false" - elseif typ == "string" then - settingtype = "string" - elseif typ == "number" then - settingtype = self.int and "int" or "float" - if self.min or self.max then - -- TODO handle exclusive min/max - type_args = (self.int and "%d %d" or "%f %f"):format(self.min or (2 ^ -30), self.max or (2 ^ 30)) - end - elseif typ == "table" then - local settings = {} - if self._level > 0 then - -- HACK: Minetest automatically adds the modname - -- TODO simple names (not modname.field.other_field) - settings = {"[" .. table.concat(modlib.table.repetition("*", self._level)) .. self.name .. "]"} - end - local function setting(key, value_scheme) - assert(not key:find("[=%.%s]")) - value_scheme.name = self.name .. "." .. key - value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key) - value_scheme._level = self._level + 1 - table.insert(settings, generate_settingtypes(value_scheme)) - end - local keys = {} - for key in pairs(self.entries or {}) do - table.insert(keys, key) - end - table.sort(keys) - for _, key in ipairs(keys) do - setting(key, self.entries[key]) - end - return table.concat(settings, "\n") - end - if not typ then - return "" - end - local description = self.description - -- TODO extend description by range etc.? - -- TODO enum etc. support - if description then - if type(description) ~= "table" then - description = {description} - end - description = "# " .. table.concat(description, "\n# ") .. "\n" - else - description = "" - end - return description .. self.name .. " (" .. self.title .. ") " .. settingtype .. " " .. (default or "") .. (type_args and (" " .. type_args) or "") + local typ = self.type + local settingtype, type_args + self.title = self.title or field_name_to_title(self.name) + self._level = self._level or 0 + local default = self.default + if typ == "boolean" then + settingtype = "bool" + default = default and "true" or "false" + elseif typ == "string" then + settingtype = "string" + elseif typ == "number" then + settingtype = self.int and "int" or "float" + if self.min or self.max then + -- TODO handle exclusive min/max + type_args = (self.int and "%d %d" or "%f %f"):format(self.min or (2 ^ -30), self.max or (2 ^ 30)) + end + elseif typ == "table" then + local settings = {} + if self._level > 0 then + -- HACK: Minetest automatically adds the modname + -- TODO simple names (not modname.field.other_field) + settings = {"[" .. table.concat(modlib.table.repetition("*", self._level)) .. self.name .. "]"} + end + local function setting(key, value_scheme) + assert(not key:find("[=%.%s]")) + value_scheme.name = self.name .. "." .. key + value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key) + value_scheme._level = self._level + 1 + table.insert(settings, generate_settingtypes(value_scheme)) + end + local keys = {} + for key in pairs(self.entries or {}) do + table.insert(keys, key) + end + table.sort(keys) + for _, key in ipairs(keys) do + setting(key, self.entries[key]) + end + return table.concat(settings, "\n") + end + if not typ then + return "" + end + local description = self.description + -- TODO extend description by range etc.? + -- TODO enum etc. support + if description then + if type(description) ~= "table" then + description = {description} + end + description = "# " .. table.concat(description, "\n# ") .. "\n" + else + description = "" + end + return description .. self.name .. " (" .. self.title .. ") " .. settingtype .. " " .. (default or "") .. (type_args and (" " .. type_args) or "") end function generate_markdown(self) - -- TODO address redundancies - local typ = self.type - self.title = self.title or field_name_to_title(self._md_name) - self._md_level = self._md_level or 1 - if typ == "table" then - local settings = {} - local function setting(key, value_scheme) - value_scheme._md_name = key - value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key) - value_scheme._md_level = self._md_level + 1 - table.insert(settings, table.concat(modlib.table.repetition("#", self._md_level)) .. " `" .. key .. "`") - table.insert(settings, "") - table.insert(settings, generate_markdown(value_scheme)) - table.insert(settings, "") - end - local keys = {} - for key in pairs(self.entries or {}) do - table.insert(keys, key) - end - table.sort(keys) - for _, key in ipairs(keys) do - setting(key, self.entries[key]) - end - return table.concat(settings, "\n") - end - if not typ then - return "" - end - local lines = {} - local function line(text) - table.insert(lines, "* " .. text) - end - local description = self.description - if description then - if type(description) ~= "table" then - table.insert(lines, description) - else - modlib.table.append(lines, description) - end - end - table.insert(lines, "") - line("Type: " .. self.type) - if self.default ~= nil then - line("Default: `" .. tostring(self.default) .. "`") - end - if self.int then - line"Integer" - elseif self.list then - line"List" - end - if self.infinity then - line"Infinities allowed" - end - if self.nan then - line"Not-a-Number (NaN) allowed" - end - if self.range then - if self.range.min then - line(">= " .. self.range.min) - elseif self.range.min_exclusive then - line("> " .. self.range.min_exclusive) - end - if self.range.max then - line("<= " .. self.range.max) - elseif self.range.max_exclusive then - line("< " .. self.range.max_exclusive) - end - end - if self.values then - line("Possible values:") - for value in pairs(self.values) do - table.insert(lines, " * " .. value) - end - end - return table.concat(lines, "\n") + -- TODO address redundancies + local typ = self.type + self.title = self.title or field_name_to_title(self._md_name) + self._md_level = self._md_level or 1 + if typ == "table" then + local settings = {} + local function setting(key, value_scheme) + value_scheme._md_name = key + value_scheme.title = value_scheme.title or self.title .. " " .. field_name_to_title(key) + value_scheme._md_level = self._md_level + 1 + table.insert(settings, table.concat(modlib.table.repetition("#", self._md_level)) .. " `" .. key .. "`") + table.insert(settings, "") + table.insert(settings, generate_markdown(value_scheme)) + table.insert(settings, "") + end + local keys = {} + for key in pairs(self.entries or {}) do + table.insert(keys, key) + end + table.sort(keys) + for _, key in ipairs(keys) do + setting(key, self.entries[key]) + end + return table.concat(settings, "\n") + end + if not typ then + return "" + end + local lines = {} + local function line(text) + table.insert(lines, "* " .. text) + end + local description = self.description + if description then + if type(description) ~= "table" then + table.insert(lines, description) + else + modlib.table.append(lines, description) + end + end + table.insert(lines, "") + line("Type: " .. self.type) + if self.default ~= nil then + line("Default: `" .. tostring(self.default) .. "`") + end + if self.int then + line"Integer" + elseif self.list then + line"List" + end + if self.infinity then + line"Infinities allowed" + end + if self.nan then + line"Not-a-Number (NaN) allowed" + end + if self.range then + if self.range.min then + line(">= " .. self.range.min) + elseif self.range.min_exclusive then + line("> " .. self.range.min_exclusive) + end + if self.range.max then + line("<= " .. self.range.max) + elseif self.range.max_exclusive then + line("< " .. self.range.max_exclusive) + end + end + if self.values then + line("Possible values:") + for value in pairs(self.values) do + table.insert(lines, " * " .. value) + end + end + return table.concat(lines, "\n") end function settingtypes(self) - self.settingtypes = self.settingtypes or generate_settingtypes(self) - return self.settingtypes + self.settingtypes = self.settingtypes or generate_settingtypes(self) + return self.settingtypes end function load(self, override, params) - local converted - if params.convert_strings and type(override) == "string" then - converted = true - if self.type == "boolean" then - if override == "true" then - override = true - elseif override == "false" then - override = false - end - elseif self.type == "number" then - override = tonumber(override) - else - converted = false - end - end - if override == nil and not converted then - if self.default ~= nil then - return self.default - elseif self.type == "table" then - override = {} - end - end - local _error = error - local function format_error(typ, ...) - if typ == "type" then - return "mismatched type: expected " .. self.type ..", got " .. type(override) .. (converted and " (converted)" or "") - end - if typ == "range" then - local conditions = {} - local function push(condition, bound) - if self.range[bound] then - table.insert(conditions, " " .. condition .. " " .. minetest.write_json(self.range[bound])) - end - end - push(">", "min_exclusive") - push(">=", "min") - push("<", "max_exclusive") - push("<=", "max") - return "out of range: expected value " .. table.concat(conditions, "and") - end - if typ == "int" then - return "expected integer" - end - if typ == "infinity" then - return "expected no infinity" - end - if typ == "nan" then - return "expected no nan" - end - if typ == "required" then - local key = ... - return "required field " .. minetest.write_json(key) .. " missing" - end - if typ == "additional" then - local key = ... - return "superfluous field " .. minetest.write_json(key) - end - if typ == "list" then - return "not a list" - end - if typ == "values" then - return "expected one of " .. minetest.write_json(modlib.table.keys(self.values)) .. ", got " .. minetest.write_json(override) - end - _error("unknown error type") - end - local function error(type, ...) - if params.error_message then - local formatted = format_error(type, ...) - settingtypes(self) - _error("Invalid value: " .. self.name .. ": " .. formatted) - end - _error{ - type = type, - self = self, - override = override, - converted = converted - } - end - local function assert(value, ...) - if not value then - error(...) - end - return value - end - assert(self.type == type(override), "type") - if self.type == "number" or self.type == "string" then - if self.range then - if self.range.min then - assert(self.range.min <= override, "range") - elseif self.range.min_exclusive then - assert(self.range.min_exclusive < override, "range") - end - if self.range.max then - assert(self.range.max >= override, "range") - elseif self.range.max_exclusive then - assert(self.range.max_exclusive > override, "range") - end - end - if self.type == "number" then - assert((not self.int) or (override % 1 == 0), "int") - assert(self.infinity or math.abs(override) ~= math.huge, "infinity") - assert(self.nan or override == override, "nan") - end - elseif self.type == "table" then - if self.entries then - for key, schema in pairs(self.entries) do - if schema.required and override[key] == nil then - error("required", key) - end - override[key] = load(schema, override[key], params) - end - if self.additional == false then - for key in pairs(override) do - if self.entries[key] == nil then - error("additional", key) - end - end - end - end - if self.keys then - for key, value in pairs(override) do - override[load(self.keys, key, params)], override[key] = value, nil - end - end - if self.values then - for key, value in pairs(override) do - override[key] = load(self.values, value, params) - end - end - assert((not self.list) or modlib.table.count(override) == #override, "list") - else - assert((not self.values) or self.values[override], "values") - end - if self.func then self.func(override) end - return override + local converted + if params.convert_strings and type(override) == "string" then + converted = true + if self.type == "boolean" then + if override == "true" then + override = true + elseif override == "false" then + override = false + end + elseif self.type == "number" then + override = tonumber(override) + else + converted = false + end + end + if override == nil and not converted then + if self.default ~= nil then + return self.default + elseif self.type == "table" then + override = {} + end + end + local _error = error + local function format_error(typ, ...) + if typ == "type" then + return "mismatched type: expected " .. self.type ..", got " .. type(override) .. (converted and " (converted)" or "") + end + if typ == "range" then + local conditions = {} + local function push(condition, bound) + if self.range[bound] then + table.insert(conditions, " " .. condition .. " " .. minetest.write_json(self.range[bound])) + end + end + push(">", "min_exclusive") + push(">=", "min") + push("<", "max_exclusive") + push("<=", "max") + return "out of range: expected value " .. table.concat(conditions, "and") + end + if typ == "int" then + return "expected integer" + end + if typ == "infinity" then + return "expected no infinity" + end + if typ == "nan" then + return "expected no nan" + end + if typ == "required" then + local key = ... + return "required field " .. minetest.write_json(key) .. " missing" + end + if typ == "additional" then + local key = ... + return "superfluous field " .. minetest.write_json(key) + end + if typ == "list" then + return "not a list" + end + if typ == "values" then + return "expected one of " .. minetest.write_json(modlib.table.keys(self.values)) .. ", got " .. minetest.write_json(override) + end + _error("unknown error type") + end + local function error(type, ...) + if params.error_message then + local formatted = format_error(type, ...) + settingtypes(self) + _error("Invalid value: " .. self.name .. ": " .. formatted) + end + _error{ + type = type, + self = self, + override = override, + converted = converted + } + end + local function assert(value, ...) + if not value then + error(...) + end + return value + end + assert(self.type == type(override), "type") + if self.type == "number" or self.type == "string" then + if self.range then + if self.range.min then + assert(self.range.min <= override, "range") + elseif self.range.min_exclusive then + assert(self.range.min_exclusive < override, "range") + end + if self.range.max then + assert(self.range.max >= override, "range") + elseif self.range.max_exclusive then + assert(self.range.max_exclusive > override, "range") + end + end + if self.type == "number" then + assert((not self.int) or (override % 1 == 0), "int") + assert(self.infinity or math.abs(override) ~= math.huge, "infinity") + assert(self.nan or override == override, "nan") + end + elseif self.type == "table" then + if self.entries then + for key, schema in pairs(self.entries) do + if schema.required and override[key] == nil then + error("required", key) + end + override[key] = load(schema, override[key], params) + end + if self.additional == false then + for key in pairs(override) do + if self.entries[key] == nil then + error("additional", key) + end + end + end + end + if self.keys then + for key, value in pairs(override) do + override[load(self.keys, key, params)], override[key] = value, nil + end + end + if self.values then + for key, value in pairs(override) do + override[key] = load(self.values, value, params) + end + end + assert((not self.list) or modlib.table.count(override) == #override, "list") + else + assert((not self.values) or self.values[override], "values") + end + if self.func then self.func(override) end + return override end \ No newline at end of file diff --git a/table.lua b/table.lua index cf7bebb..5169240 100644 --- a/table.lua +++ b/table.lua @@ -1,79 +1,79 @@ -- Table helpers function map_index(table, func) - local mapping_metatable = { - __index = function(table, key) - return rawget(table, func(key)) - end, - __newindex = function(table, key, value) - rawset(table, func(key), value) - end - } - return setmetatable(table, mapping_metatable) + local mapping_metatable = { + __index = function(table, key) + return rawget(table, func(key)) + end, + __newindex = function(table, key, value) + rawset(table, func(key), value) + end + } + return setmetatable(table, mapping_metatable) end function set_case_insensitive_index(table) - return map_index(table, string.lower) + return map_index(table, string.lower) end --+ nilget(a, "b", "c") == a?.b?.c function nilget(value, key, ...) - if value == nil or key == nil then - return value - end - return nilget(value[key], ...) + if value == nil or key == nil then + return value + end + return nilget(value[key], ...) end -- Fisher-Yates function shuffle(table) - for index = 1, #table - 1 do - local index_2 = math.random(index + 1, #table) - table[index], table[index_2] = table[index_2], table[index] - end - return table + for index = 1, #table - 1 do + local index_2 = math.random(index + 1, #table) + table[index], table[index_2] = table[index_2], table[index] + end + return table end local rope_metatable = {__index = { - write = function(self, text) - table.insert(self, text) - end, - to_text = function(self) - return table.concat(self) - end + write = function(self, text) + table.insert(self, text) + end, + to_text = function(self) + return table.concat(self) + end }} --> rope with simple metatable (:write(text) and :to_text()) function rope(table) - return setmetatable(table or {}, rope_metatable) + return setmetatable(table or {}, rope_metatable) end local rope_len_metatable = {__index = { - write = function(self, text) - self.len = self.len + text:len() - end + write = function(self, text) + self.len = self.len + text:len() + end }} --> rope for determining length supporting :write(text), .len being the length function rope_len(len) - return setmetatable({len = len or 0}, rope_len_metatable) + return setmetatable({len = len or 0}, rope_len_metatable) end function is_circular(table) - assert(type(table) == "table") - local known = {} - local function _is_circular(value) - if type(value) ~= "table" then - return false - end - if known[value] then - return true - end - known[value] = true - for key, value in pairs(value) do - if _is_circular(key) or _is_circular(value) then - return true - end - end - end - return _is_circular(table) + assert(type(table) == "table") + local known = {} + local function _is_circular(value) + if type(value) ~= "table" then + return false + end + if known[value] then + return true + end + known[value] = true + for key, value in pairs(value) do + if _is_circular(key) or _is_circular(value) then + return true + end + end + end + return _is_circular(table) end --+ Simple table equality check. Stack overflow if tables are too deep or circular. @@ -81,44 +81,44 @@ end --> Equality of noncircular tables if `table` and `other_table` are tables --> `table == other_table` else function equals_noncircular(table, other_table) - local is_equal = table == other_table - if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then - return is_equal - end - if #table ~= #other_table then - return false - end - local table_keys = {} - for key, value in pairs(table) do - local value_2 = other_table[key] - if not equals_noncircular(value, value_2) then - if type(key) == "table" then - table_keys[key] = value - else - return false - end - end - end - for other_key, other_value in pairs(other_table) do - if type(other_key) == "table" then - local found - for table, value in pairs(table_keys) do - if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then - table_keys[table] = nil - found = true - break - end - end - if not found then - return false - end - else - if table[other_key] == nil then - return false - end - end - end - return true + local is_equal = table == other_table + if is_equal or type(table) ~= "table" or type(other_table) ~= "table" then + return is_equal + end + if #table ~= #other_table then + return false + end + local table_keys = {} + for key, value in pairs(table) do + local value_2 = other_table[key] + if not equals_noncircular(value, value_2) then + if type(key) == "table" then + table_keys[key] = value + else + return false + end + end + end + for other_key, other_value in pairs(other_table) do + if type(other_key) == "table" then + local found + for table, value in pairs(table_keys) do + if equals_noncircular(other_key, table) and equals_noncircular(other_value, value) then + table_keys[table] = nil + found = true + break + end + end + if not found then + return false + end + else + if table[other_key] == nil then + return false + end + end + end + return true end equals = equals_noncircular @@ -127,60 +127,60 @@ equals = equals_noncircular --> Table content equality if `table` and `other_table` are tables --> `table == other_table` else function equals_content(table, other_table) - local equal_tables = {} - local function _equals(table, other_equal_table) - local function set_equal_tables(value) - equal_tables[table] = equal_tables[table] or {} - equal_tables[table][other_equal_table] = value - return value - end - local is_equal = table == other_equal_table - if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then - return is_equal - end - if #table ~= #other_equal_table then - return set_equal_tables(false) - end - local lookup_equal = (equal_tables[table] or {})[other_equal_table] - if lookup_equal ~= nil then - return lookup_equal - end - -- Premise - set_equal_tables(true) - local table_keys = {} - for key, value in pairs(table) do - local other_value = other_equal_table[key] - if not _equals(value, other_value) then - if type(key) == "table" then - table_keys[key] = value - else - return set_equal_tables(false) - end - end - end - for other_key, other_value in pairs(other_equal_table) do - if type(other_key) == "table" then - local found = false - for table_key, value in pairs(table_keys) do - if _equals(table_key, other_key) and _equals(value, other_value) then - table_keys[table_key] = nil - found = true - -- Breaking is fine as per transitivity - break - end - end - if not found then - return set_equal_tables(false) - end - else - if table[other_key] == nil then - return set_equal_tables(false) - end - end - end - return true - end - return _equals(table, other_table) + local equal_tables = {} + local function _equals(table, other_equal_table) + local function set_equal_tables(value) + equal_tables[table] = equal_tables[table] or {} + equal_tables[table][other_equal_table] = value + return value + end + local is_equal = table == other_equal_table + if is_equal or type(table) ~= "table" or type(other_equal_table) ~= "table" then + return is_equal + end + if #table ~= #other_equal_table then + return set_equal_tables(false) + end + local lookup_equal = (equal_tables[table] or {})[other_equal_table] + if lookup_equal ~= nil then + return lookup_equal + end + -- Premise + set_equal_tables(true) + local table_keys = {} + for key, value in pairs(table) do + local other_value = other_equal_table[key] + if not _equals(value, other_value) then + if type(key) == "table" then + table_keys[key] = value + else + return set_equal_tables(false) + end + end + end + for other_key, other_value in pairs(other_equal_table) do + if type(other_key) == "table" then + local found = false + for table_key, value in pairs(table_keys) do + if _equals(table_key, other_key) and _equals(value, other_value) then + table_keys[table_key] = nil + found = true + -- Breaking is fine as per transitivity + break + end + end + if not found then + return set_equal_tables(false) + end + else + if table[other_key] == nil then + return set_equal_tables(false) + end + end + end + return true + end + return _equals(table, other_table) end --+ Table equality check: content has to be equal, relations between tables as well @@ -190,400 +190,400 @@ end --> equality (same tables after table reference substitution) of circular tables if `table` and `other_table` are tables --> `table == other_table` else function equals_references(table, other_table) - local function _equals(table, other_table, equal_refs) - if equal_refs[table] then - return equal_refs[table] == other_table - end - local is_equal = table == other_table - -- this check could be omitted if table key equality is being checked - if type(table) ~= "table" or type(other_table) ~= "table" then - return is_equal - end - if is_equal then - equal_refs[table] = other_table - return true - end - -- Premise: table = other table - equal_refs[table] = other_table - local table_keys = {} - for key, value in pairs(table) do - if type(key) == "table" then - table_keys[key] = value - else - local other_value = other_table[key] - if not _equals(value, other_value, equal_refs) then - return false - end - end - end - local other_table_keys = {} - for other_key, other_value in pairs(other_table) do - if type(other_key) == "table" then - other_table_keys[other_key] = other_value - elseif table[other_key] == nil then - return false - end - end - local function _next(current_key, equal_refs, available_keys) - local key, value = next(table_keys, current_key) - if key == nil then - return true - end - for other_key, other_value in pairs(other_table_keys) do - local copy_equal_refs = shallowcopy(equal_refs) - if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then - local copy_available_keys = shallowcopy(available_keys) - copy_available_keys[other_key] = nil - if _next(key, copy_equal_refs, copy_available_keys) then - return true - end - end - end - return false - end - return _next(nil, equal_refs, other_table_keys) - end - return _equals(table, other_table, {}) + local function _equals(table, other_table, equal_refs) + if equal_refs[table] then + return equal_refs[table] == other_table + end + local is_equal = table == other_table + -- this check could be omitted if table key equality is being checked + if type(table) ~= "table" or type(other_table) ~= "table" then + return is_equal + end + if is_equal then + equal_refs[table] = other_table + return true + end + -- Premise: table = other table + equal_refs[table] = other_table + local table_keys = {} + for key, value in pairs(table) do + if type(key) == "table" then + table_keys[key] = value + else + local other_value = other_table[key] + if not _equals(value, other_value, equal_refs) then + return false + end + end + end + local other_table_keys = {} + for other_key, other_value in pairs(other_table) do + if type(other_key) == "table" then + other_table_keys[other_key] = other_value + elseif table[other_key] == nil then + return false + end + end + local function _next(current_key, equal_refs, available_keys) + local key, value = next(table_keys, current_key) + if key == nil then + return true + end + for other_key, other_value in pairs(other_table_keys) do + local copy_equal_refs = shallowcopy(equal_refs) + if _equals(key, other_key, copy_equal_refs) and _equals(value, other_value, copy_equal_refs) then + local copy_available_keys = shallowcopy(available_keys) + copy_available_keys[other_key] = nil + if _next(key, copy_equal_refs, copy_available_keys) then + return true + end + end + end + return false + end + return _next(nil, equal_refs, other_table_keys) + end + return _equals(table, other_table, {}) end function shallowcopy(table) - local copy = {} - for key, value in pairs(table) do - copy[key] = value - end - return copy + local copy = {} + for key, value in pairs(table) do + copy[key] = value + end + return copy end function deepcopy_noncircular(table) - local function _copy(value) - if type(value) == "table" then - return deepcopy_noncircular(value) - end - return value - end - local copy = {} - for key, value in pairs(table) do - copy[_copy(key)] = _copy(value) - end - return copy + local function _copy(value) + if type(value) == "table" then + return deepcopy_noncircular(value) + end + return value + end + local copy = {} + for key, value in pairs(table) do + copy[_copy(key)] = _copy(value) + end + return copy end function deepcopy(table) - local copies = {} - local function _deepcopy(table) - if copies[table] then - return copies[table] - end - local copy = {} - copies[table] = copy - local function _copy(value) - if type(value) == "table" then - if copies[value] then - return copies[value] - end - return _deepcopy(value) - end - return value - end - for key, value in pairs(table) do - copy[_copy(key)] = _copy(value) - end - return copy - end - return _deepcopy(table) + local copies = {} + local function _deepcopy(table) + if copies[table] then + return copies[table] + end + local copy = {} + copies[table] = copy + local function _copy(value) + if type(value) == "table" then + if copies[value] then + return copies[value] + end + return _deepcopy(value) + end + return value + end + for key, value in pairs(table) do + copy[_copy(key)] = _copy(value) + end + return copy + end + return _deepcopy(table) end tablecopy = deepcopy copy = deepcopy function count(table) - local count = 0 - for _ in pairs(table) do - count = count + 1 - end - return count + local count = 0 + for _ in pairs(table) do + count = count + 1 + end + return count end function is_empty(table) - return next(table) == nil + return next(table) == nil end function foreach(table, func) - for k, v in pairs(table) do - func(k, v) - end + for k, v in pairs(table) do + func(k, v) + end end function foreach_value(table, func) - for _, v in pairs(table) do - func(v) - end + for _, v in pairs(table) do + func(v) + end end function call(table, ...) - for _, func in pairs(table) do - func(...) - end + for _, func in pairs(table) do + func(...) + end end function icall(table, ...) - for _, func in ipairs(table) do - func(...) - end + for _, func in ipairs(table) do + func(...) + end end function foreach_key(table, func) - for key, _ in pairs(table) do - func(key) - end + for key, _ in pairs(table) do + func(key) + end end function map(table, func) - for key, value in pairs(table) do - table[key] = func(value) - end - return table + for key, value in pairs(table) do + table[key] = func(value) + end + return table end function map_keys(table, func) - local new_tab = {} - for key, value in pairs(table) do - new_tab[func(key)] = value - end - return new_tab + local new_tab = {} + for key, value in pairs(table) do + new_tab[func(key)] = value + end + return new_tab end function process(tab, func) - local results = {} - for key, value in pairs(tab) do - table.insert(results, func(key,value)) - end - return results + local results = {} + for key, value in pairs(tab) do + table.insert(results, func(key,value)) + end + return results end function call(funcs, ...) - for _, func in ipairs(funcs) do - func(...) - end + for _, func in ipairs(funcs) do + func(...) + end end function find(list, value) - for index, other_value in pairs(list) do - if value == other_value then - return index - end - end - return + for index, other_value in pairs(list) do + if value == other_value then + return index + end + end + return end contains = find function to_add(table, after_additions) - local additions = {} - for key, value in pairs(after_additions) do - if table[key] ~= value then - additions[key] = value - end - end - return additions + local additions = {} + for key, value in pairs(after_additions) do + if table[key] ~= value then + additions[key] = value + end + end + return additions end difference = to_add function deep_to_add(table, after_additions) - local additions = {} - for key, value in pairs(after_additions) do - if type(table[key]) == "table" and type(value) == "table" then - additions[key] = deep_to_add(table[key], value) - elseif table[key] ~= value then - additions[key] = value - end - end - return additions + local additions = {} + for key, value in pairs(after_additions) do + if type(table[key]) == "table" and type(value) == "table" then + additions[key] = deep_to_add(table[key], value) + elseif table[key] ~= value then + additions[key] = value + end + end + return additions end function add_all(table, additions) - for key, value in pairs(additions) do - table[key] = value - end - return table + for key, value in pairs(additions) do + table[key] = value + end + return table end function deep_add_all(table, additions) - for key, value in pairs(additions) do - if type(table[key]) == "table" and type(value) == "table" then - deep_add_all(table[key], value) - else - table[key] = value - end - end - return table + for key, value in pairs(additions) do + if type(table[key]) == "table" and type(value) == "table" then + deep_add_all(table[key], value) + else + table[key] = value + end + end + return table end function complete(table, completions) - for key, value in pairs(completions) do - if table[key] == nil then - table[key] = value - end - end - return table + for key, value in pairs(completions) do + if table[key] == nil then + table[key] = value + end + end + return table end function deepcomplete(table, completions) - for key, value in pairs(completions) do - if table[key] == nil then - table[key] = value - elseif type(table[key]) == "table" and type(value) == "table" then - deepcomplete(table[key], value) - end - end - return table + for key, value in pairs(completions) do + if table[key] == nil then + table[key] = value + elseif type(table[key]) == "table" and type(value) == "table" then + deepcomplete(table[key], value) + end + end + return table end function merge_tables(table, other_table) - return add_all(copy(table), other_table) + return add_all(copy(table), other_table) end union = merge_tables function intersection(table, other_table) - local result = {} - for key, value in pairs(table) do - if other_table[key] then - result[key] = value - end - end - return result + local result = {} + for key, value in pairs(table) do + if other_table[key] then + result[key] = value + end + end + return result end function append(table, other_table) - local length = #table - for index, value in ipairs(other_table) do - table[length + index] = value - end - return table + local length = #table + for index, value in ipairs(other_table) do + table[length + index] = value + end + return table end function keys(table) - local keys = {} - for key, _ in pairs(table) do - keys[#keys + 1] = key - end - return keys + local keys = {} + for key, _ in pairs(table) do + keys[#keys + 1] = key + end + return keys end function values(table) - local values = {} - for _, value in pairs(table) do - values[#values + 1] = value - end - return values + local values = {} + for _, value in pairs(table) do + values[#values + 1] = value + end + return values end function flip(table) - local flipped = {} - for key, value in pairs(table) do - flipped[value] = key - end - return flipped + local flipped = {} + for key, value in pairs(table) do + flipped[value] = key + end + return flipped end function set(table) - local flipped = {} - for _, value in pairs(table) do - flipped[value] = true - end - return flipped + local flipped = {} + for _, value in pairs(table) do + flipped[value] = true + end + return flipped end function unique(table) - return keys(set(table)) + return keys(set(table)) end function rpairs(table) - local index = #table - return function() - if index >= 1 then - local value = table[index] - index = index - 1 - if value ~= nil then - return index + 1, value - end - end - end + local index = #table + return function() + if index >= 1 then + local value = table[index] + index = index - 1 + if value ~= nil then + return index + 1, value + end + end + end end function best_value(table, is_better_func) - local best = next(table) - if best == nil then - return - end - local candidate = best - while true do - candidate = next(table, candidate) - if candidate == nil then - return best - end - if is_better_func(candidate, best) then - best = candidate - end - end - error() + local best = next(table) + if best == nil then + return + end + local candidate = best + while true do + candidate = next(table, candidate) + if candidate == nil then + return best + end + if is_better_func(candidate, best) then + best = candidate + end + end + error() end function min(table) - return best_value(table, function(value, other_value) return value < other_value end) + return best_value(table, function(value, other_value) return value < other_value end) end function max(table) - return best_value(table, function(value, other_value) return value > other_value end) + return best_value(table, function(value, other_value) return value > other_value end) end function default_comparator(value, other_value) - if value == other_value then - return 0 - end - if value > other_value then - return 1 - end - return -1 + if value == other_value then + return 0 + end + if value > other_value then + return 1 + end + return -1 end --> index if element found --> -index for insertion if not found function binary_search_comparator(comparator) - return function(list, value) - local min, max = 1, #list - while min <= max do - local pivot = min + math.floor((max - min) / 2) - local element = list[pivot] - local compared = comparator(value, element) - if compared == 0 then - return pivot - elseif compared > 0 then - min = pivot + 1 - else - max = pivot - 1 - end - end - return -min - end + return function(list, value) + local min, max = 1, #list + while min <= max do + local pivot = min + math.floor((max - min) / 2) + local element = list[pivot] + local compared = comparator(value, element) + if compared == 0 then + return pivot + elseif compared > 0 then + min = pivot + 1 + else + max = pivot - 1 + end + end + return -min + end end binary_search = binary_search_comparator(default_comparator) function reverse(table) - local l = #table + 1 - for index = 1, math.floor(#table / 2) do - table[l - index], table[index] = table[index], table[l - index] - end - return table + local l = #table + 1 + for index = 1, math.floor(#table / 2) do + table[l - index], table[index] = table[index], table[l - index] + end + return table end function repetition(value, count) - local table = {} - for index = 1, count do - table[index] = value - end - return table + local table = {} + for index = 1, count do + table[index] = value + end + return table end \ No newline at end of file diff --git a/test.lua b/test.lua index f1f3d34..4670494 100644 --- a/test.lua +++ b/test.lua @@ -3,16 +3,16 @@ local random, huge = math.random, math.huge local parent_env = getfenv(1) setfenv(1, setmetatable({}, { - __index = function(_, key) - local value = modlib[key] - if value ~= nil then - return value - end - return parent_env[key] - end, - __newindex = function(_, key, value) - error(dump{key = key, value = value}) - end + __index = function(_, key) + local value = modlib[key] + if value ~= nil then + return value + end + return parent_env[key] + end, + __newindex = function(_, key, value) + error(dump{key = key, value = value}) + end })) -- string @@ -20,104 +20,104 @@ assert(string.escape_magic_chars"%" == "%%") -- table do - local tab = {} - tab[tab] = tab - local table_copy = table.deepcopy(tab) - assert(table_copy[table_copy] == table_copy) - assert(table.is_circular(tab)) - assert(not table.is_circular{a = 1}) - assert(table.equals_noncircular({[{}]={}}, {[{}]={}})) - assert(table.equals_content(tab, table_copy)) - local equals_references = table.equals_references - assert(equals_references(tab, table_copy)) - assert(equals_references({}, {})) - assert(not equals_references({a = 1, b = 2}, {a = 1, b = 3})) - tab = {} - tab.a, tab.b = tab, tab - table_copy = table.deepcopy(tab) - assert(equals_references(tab, table_copy)) - local x, y = {}, {} - assert(not equals_references({[x] = x, [y] = y}, {[x] = y, [y] = x})) - assert(equals_references({[x] = x, [y] = y}, {[x] = x, [y] = y})) - local nilget = table.nilget - assert(nilget({a = {b = {c = 42}}}, "a", "b", "c") == 42) - assert(nilget({a = {}}, "a", "b", "c") == nil) - assert(nilget(nil, "a", "b", "c") == nil) - assert(nilget(nil, "a", nil, "c") == nil) - local rope = table.rope{} - rope:write"hello" - rope:write" " - rope:write"world" - assert(rope:to_text() == "hello world", rope:to_text()) + local tab = {} + tab[tab] = tab + local table_copy = table.deepcopy(tab) + assert(table_copy[table_copy] == table_copy) + assert(table.is_circular(tab)) + assert(not table.is_circular{a = 1}) + assert(table.equals_noncircular({[{}]={}}, {[{}]={}})) + assert(table.equals_content(tab, table_copy)) + local equals_references = table.equals_references + assert(equals_references(tab, table_copy)) + assert(equals_references({}, {})) + assert(not equals_references({a = 1, b = 2}, {a = 1, b = 3})) + tab = {} + tab.a, tab.b = tab, tab + table_copy = table.deepcopy(tab) + assert(equals_references(tab, table_copy)) + local x, y = {}, {} + assert(not equals_references({[x] = x, [y] = y}, {[x] = y, [y] = x})) + assert(equals_references({[x] = x, [y] = y}, {[x] = x, [y] = y})) + local nilget = table.nilget + assert(nilget({a = {b = {c = 42}}}, "a", "b", "c") == 42) + assert(nilget({a = {}}, "a", "b", "c") == nil) + assert(nilget(nil, "a", "b", "c") == nil) + assert(nilget(nil, "a", nil, "c") == nil) + local rope = table.rope{} + rope:write"hello" + rope:write" " + rope:write"world" + assert(rope:to_text() == "hello world", rope:to_text()) end -- heap do - local n = 100 - local list = {} - for index = 1, n do - list[index] = index - end - table.shuffle(list) - local heap = heap.new() - for index = 1, #list do - heap:push(list[index]) - end - for index = 1, #list do - local popped = heap:pop() - assert(popped == index) - end + local n = 100 + local list = {} + for index = 1, n do + list[index] = index + end + table.shuffle(list) + local heap = heap.new() + for index = 1, #list do + heap:push(list[index]) + end + for index = 1, #list do + local popped = heap:pop() + assert(popped == index) + end end -- ranked set do - local n = 100 - local ranked_set = ranked_set.new() - local list = {} - for i = 1, n do - ranked_set:insert(i) - list[i] = i - end + local n = 100 + local ranked_set = ranked_set.new() + local list = {} + for i = 1, n do + ranked_set:insert(i) + list[i] = i + end - assert(table.equals(ranked_set:to_table(), list)) + assert(table.equals(ranked_set:to_table(), list)) - local i = 0 - for rank, key in ranked_set:ipairs() do - i = i + 1 - assert(i == key and i == rank) - assert(ranked_set:get_by_rank(rank) == key) - local rank, key = ranked_set:get(i) - assert(key == i and i == rank) - end - assert(i == n) + local i = 0 + for rank, key in ranked_set:ipairs() do + i = i + 1 + assert(i == key and i == rank) + assert(ranked_set:get_by_rank(rank) == key) + local rank, key = ranked_set:get(i) + assert(key == i and i == rank) + end + assert(i == n) - for i = 1, n do - local _, v = ranked_set:delete(i) - assert(v == i, i) - end - assert(not next(ranked_set:to_table())) + for i = 1, n do + local _, v = ranked_set:delete(i) + assert(v == i, i) + end + assert(not next(ranked_set:to_table())) - local ranked_set = ranked_set.new() - for i = 1, n do - ranked_set:insert(i) - end + local ranked_set = ranked_set.new() + for i = 1, n do + ranked_set:insert(i) + end - for rank, key in ranked_set:ipairs(10, 20) do - assert(rank == key and key >= 10 and key <= 20) - end + for rank, key in ranked_set:ipairs(10, 20) do + assert(rank == key and key >= 10 and key <= 20) + end - for i = n, 1, -1 do - local j = ranked_set:delete_by_rank(i) - assert(j == i) - end + for i = n, 1, -1 do + local j = ranked_set:delete_by_rank(i) + assert(j == i) + end end -- colorspec local colorspec = minetest.colorspec local function test_from_string(string, number) - local spec = colorspec.from_string(string) - local expected = colorspec.from_number(number) - assertdump(table.equals(spec, expected), {expected = expected, actual = spec}) + local spec = colorspec.from_string(string) + local expected = colorspec.from_number(number) + assertdump(table.equals(spec, expected), {expected = expected, actual = spec}) end local spec = colorspec.from_number(0xDDCCBBAA) assertdump(table.equals(spec, {a = 0xAA, b = 0xBB, g = 0xCC, r = 0xDD}), spec) @@ -130,147 +130,147 @@ test_from_string("#11223344", 0x11223344) -- k-d-tree local vectors = {} for _ = 1, 1000 do - _G.table.insert(vectors, {random(), random(), random()}) + _G.table.insert(vectors, {random(), random(), random()}) end local kdtree = kdtree.new(vectors) for _, v in ipairs(vectors) do - local neighbor, distance = kdtree:get_nearest_neighbor(v) - assert(vector.equals(v, neighbor), distance == 0) + local neighbor, distance = kdtree:get_nearest_neighbor(v) + assert(vector.equals(v, neighbor), distance == 0) end for _ = 1, 1000 do - local v = {random(), random(), random()} - local _, distance = kdtree:get_nearest_neighbor(v) - local min_distance = huge - for _, w in ipairs(vectors) do - local other_distance = vector.distance(v, w) - if other_distance < min_distance then - min_distance = other_distance - end - end - assert(distance == min_distance) + local v = {random(), random(), random()} + local _, distance = kdtree:get_nearest_neighbor(v) + local min_distance = huge + for _, w in ipairs(vectors) do + local other_distance = vector.distance(v, w) + if other_distance < min_distance then + min_distance = other_distance + end + end + assert(distance == min_distance) end -- bluon do - local bluon = bluon - local function assert_preserves(object) - local rope = table.rope{} - local written, read, input - local _, err = pcall(function() - bluon:write(object, rope) - written = rope:to_text() - input = text.inputstream(written) - read = bluon:read(input) - local remaining = input:read(1000) - assert(not remaining) - end) - assertdump(table.equals_references(object, read) and not err, { - object = object, - read = read, - written = written and text.hexdump(written), - err = err - }) - end - for _, constant in pairs{true, false, huge, -huge} do - assert_preserves(constant) - end - for i = 1, 1000 do - assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i))) - end - for _ = 1, 1000 do - local int = random(-2^50, 2^50) - assert(int % 1 == 0) - assert_preserves(int) - assert_preserves((random() - 0.5) * 2^random(-20, 20)) - end - assert_preserves{hello = "world", welt = "hallo"} - assert_preserves{"hello", "hello", "hello"} - local a = {} - a[a] = a - a[1] = a - assert_preserves(a) + local bluon = bluon + local function assert_preserves(object) + local rope = table.rope{} + local written, read, input + local _, err = pcall(function() + bluon:write(object, rope) + written = rope:to_text() + input = text.inputstream(written) + read = bluon:read(input) + local remaining = input:read(1000) + assert(not remaining) + end) + assertdump(table.equals_references(object, read) and not err, { + object = object, + read = read, + written = written and text.hexdump(written), + err = err + }) + end + for _, constant in pairs{true, false, huge, -huge} do + assert_preserves(constant) + end + for i = 1, 1000 do + assert_preserves(_G.table.concat(table.repetition(_G.string.char(i % 256), i))) + end + for _ = 1, 1000 do + local int = random(-2^50, 2^50) + assert(int % 1 == 0) + assert_preserves(int) + assert_preserves((random() - 0.5) * 2^random(-20, 20)) + end + assert_preserves{hello = "world", welt = "hallo"} + assert_preserves{"hello", "hello", "hello"} + local a = {} + a[a] = a + a[1] = a + assert_preserves(a) end -- in-game tests & b3d testing local tests = { - -- depends on player_api - b3d = false, - liquid_dir = false, - liquid_raycast = false + -- depends on player_api + b3d = false, + liquid_dir = false, + liquid_raycast = false } if tests.b3d then - local stream = io.open(mod.get_resource("player_api", "models", "character.b3d"), "r") - assert(stream) - local b3d = b3d.read(stream) - --! dirty helper method to create truncate tables with 10+ number keys - local function _b3d_truncate(table) - local count = 1 - for key, value in pairs(table) do - if type(key) == "table" then - _b3d_truncate(key) - end - if type(value) == "table" then - _b3d_truncate(value) - end - count = count + 1 - if type(key) == "number" and count >= 9 and next(table, key) then - if count == 9 then - table[key] = "TRUNCATED" - else - table[key] = nil - end - end - end - return table - end - file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(b3d)))) - stream:close() + local stream = io.open(mod.get_resource("player_api", "models", "character.b3d"), "r") + assert(stream) + local b3d = b3d.read(stream) + --! dirty helper method to create truncate tables with 10+ number keys + local function _b3d_truncate(table) + local count = 1 + for key, value in pairs(table) do + if type(key) == "table" then + _b3d_truncate(key) + end + if type(value) == "table" then + _b3d_truncate(value) + end + count = count + 1 + if type(key) == "number" and count >= 9 and next(table, key) then + if count == 9 then + table[key] = "TRUNCATED" + else + table[key] = nil + end + end + end + return table + end + file.write(mod.get_resource"character.b3d.lua", "return " .. dump(_b3d_truncate(table.copy(b3d)))) + stream:close() end local vector, minetest, ml_mt = _G.vector, _G.minetest, minetest if tests.liquid_dir then - minetest.register_abm{ - label = "get_liquid_corner_levels & get_liquid_direction test", - nodenames = {"group:liquid"}, - interval = 1, - chance = 1, - action = function(pos, node) - assert(type(node) == "table") - for _, corner_level in pairs(ml_mt.get_liquid_corner_levels(pos, node)) do - minetest.add_particle{ - pos = vector.add(pos, corner_level), - size = 2, - texture = "logo.png" - } - end - local direction = ml_mt.get_liquid_flow_direction(pos, node) - local start_pos = pos - start_pos.y = start_pos.y + 1 - for i = 0, 5 do - minetest.add_particle{ - pos = vector.add(start_pos, vector.multiply(direction, i/5)), - size = i/2.5, - texture = "logo.png" - } - end - end - } + minetest.register_abm{ + label = "get_liquid_corner_levels & get_liquid_direction test", + nodenames = {"group:liquid"}, + interval = 1, + chance = 1, + action = function(pos, node) + assert(type(node) == "table") + for _, corner_level in pairs(ml_mt.get_liquid_corner_levels(pos, node)) do + minetest.add_particle{ + pos = vector.add(pos, corner_level), + size = 2, + texture = "logo.png" + } + end + local direction = ml_mt.get_liquid_flow_direction(pos, node) + local start_pos = pos + start_pos.y = start_pos.y + 1 + for i = 0, 5 do + minetest.add_particle{ + pos = vector.add(start_pos, vector.multiply(direction, i/5)), + size = i/2.5, + texture = "logo.png" + } + end + end + } end if tests.liquid_raycast then - minetest.register_globalstep(function() - for _, player in pairs(minetest.get_connected_players()) do - local eye_pos = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0) - local raycast = ml_mt.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), 3)), false, true) - for pointed_thing in raycast do - if pointed_thing.type == "node" and minetest.registered_nodes[minetest.get_node(pointed_thing.under).name].liquidtype == "flowing" then - minetest.add_particle{ - pos = vector.add(pointed_thing.intersection_point, vector.multiply(pointed_thing.intersection_normal, 0.1)), - size = 0.5, - texture = "object_marker_red.png", - expirationtime = 3 - } - end - end - end - end) + minetest.register_globalstep(function() + for _, player in pairs(minetest.get_connected_players()) do + local eye_pos = vector.offset(player:get_pos(), 0, player:get_properties().eye_height, 0) + local raycast = ml_mt.raycast(eye_pos, vector.add(eye_pos, vector.multiply(player:get_look_dir(), 3)), false, true) + for pointed_thing in raycast do + if pointed_thing.type == "node" and minetest.registered_nodes[minetest.get_node(pointed_thing.under).name].liquidtype == "flowing" then + minetest.add_particle{ + pos = vector.add(pointed_thing.intersection_point, vector.multiply(pointed_thing.intersection_normal, 0.1)), + size = 0.5, + texture = "object_marker_red.png", + expirationtime = 3 + } + end + end + end + end) end \ No newline at end of file diff --git a/vector.lua b/vector.lua index 830625c..3e579db 100644 --- a/vector.lua +++ b/vector.lua @@ -2,106 +2,106 @@ local mt_vector = vector local vector = getfenv(1) index_aliases = { - x = 1, - y = 2, - z = 3, - w = 4 + x = 1, + y = 2, + z = 3, + w = 4 } modlib.table.add_all(index_aliases, modlib.table.flip(index_aliases)) metatable = { - __index = function(table, key) - local index = index_aliases[key] - if index ~= nil then - return table[index] - end - return vector[key] - end, - __newindex = function(table, key, value) - local index = letters[key] - if index ~= nil then - return rawset(table, index, value) - end - end + __index = function(table, key) + local index = index_aliases[key] + if index ~= nil then + return table[index] + end + return vector[key] + end, + __newindex = function(table, key, value) + local index = letters[key] + if index ~= nil then + return rawset(table, index, value) + end + end } function new(v) - return setmetatable(v, metatable) + return setmetatable(v, metatable) end function from_xyzw(v) - return new{v.x, v.y, v.z, v.w} + return new{v.x, v.y, v.z, v.w} end function from_minetest(v) - return new{v.x, v.y, v.z} + return new{v.x, v.y, v.z} end function to_xyzw(v) - return {x = v[1], y = v[2], z = v[3], w = v[4]} + return {x = v[1], y = v[2], z = v[3], w = v[4]} end --+ not necessarily required, as Minetest respects the metatable function to_minetest(v) - return mt_vector.new(unpack(v)) + return mt_vector.new(unpack(v)) end function equals(v, w) - for k, v in pairs(v) do - if v ~= w[k] then return false end - end - return true + for k, v in pairs(v) do + if v ~= w[k] then return false end + end + return true end metatable.__eq = equals function less_than(v, w) - for k, v in pairs(v) do - if v >= w[k] then return false end - end - return true + for k, v in pairs(v) do + if v >= w[k] then return false end + end + return true end metatable.__lt = less_than function less_or_equal(v, w) - for k, v in pairs(v) do - if v > w[k] then return false end - end - return true + for k, v in pairs(v) do + if v > w[k] then return false end + end + return true end metatable.__le = less_or_equal function combine(v, w, f) - local new_vector = {} - for key, value in pairs(v) do - new_vector[key] = f(value, w[key]) - end - return new(new_vector) + local new_vector = {} + for key, value in pairs(v) do + new_vector[key] = f(value, w[key]) + end + return new(new_vector) end function apply(v, f, ...) - local new_vector = {} - for key, value in pairs(v) do - new_vector[key] = f(value, ...) - end - return new(new_vector) + local new_vector = {} + for key, value in pairs(v) do + new_vector[key] = f(value, ...) + end + return new(new_vector) end function combinator(f) - return function(v, w) - return combine(v, w, f) - end, function(v, ...) - return apply(v, f, ...) - end + return function(v, w) + return combine(v, w, f) + end, function(v, ...) + return apply(v, f, ...) + end end function invert(v) - for key, value in pairs(v) do - v[key] = -value - end + for key, value in pairs(v) do + v[key] = -value + end end add, add_scalar = combinator(function(v, w) return v + w end) @@ -119,107 +119,107 @@ metatable.__div = divide --+ linear interpolation --: ratio number from 0 (all the first vector) to 1 (all the second vector) function interpolate(v, w, ratio) - return add(multiply(v, 1 - ratio), multiply(w, ratio)) + return add(multiply(v, 1 - ratio), multiply(w, ratio)) end function norm(v) - local sum = 0 - for _, value in pairs(v) do - sum = sum + value ^ 2 - end - return sum + local sum = 0 + for _, value in pairs(v) do + sum = sum + value ^ 2 + end + return sum end function length(v) - return math.sqrt(norm(v)) + return math.sqrt(norm(v)) end -- Minor code duplication for the sake of performance function distance(v, w) - local sum = 0 - for key, value in pairs(v) do - sum = sum + (value - w[key]) ^ 2 - end - return math.sqrt(sum) + local sum = 0 + for key, value in pairs(v) do + sum = sum + (value - w[key]) ^ 2 + end + return math.sqrt(sum) end function normalize(v) - return divide_scalar(v, length(v)) + return divide_scalar(v, length(v)) end function floor(v) - return apply(v, math.floor) + return apply(v, math.floor) end function ceil(v) - return apply(v, math.ceil) + return apply(v, math.ceil) end function clamp(v, min, max) - return apply(apply(v, math.max, min), math.min, max) + return apply(apply(v, math.max, min), math.min, max) end function cross3(v, w) - assert(#v == 3 and #w == 3) - return new{ - v[2] * w[3] - v[3] * w[2], - v[3] * w[1] - v[1] * w[3], - v[1] * w[2] - v[2] * w[1] - } + assert(#v == 3 and #w == 3) + return new{ + v[2] * w[3] - v[3] * w[2], + v[3] * w[1] - v[1] * w[3], + v[1] * w[2] - v[2] * w[1] + } end function dot(v, w) - local sum = 0 - for i, c in pairs(v) do - sum = sum + c * w[i] - end - return sum + local sum = 0 + for i, c in pairs(v) do + sum = sum + c * w[i] + end + return sum end --+ Angle between two vectors --> Signed angle in radians function angle(v, w) - -- Based on dot(v, w) = |v| * |w| * cos(x) - return math.acos(dot(v, w) / length(v) / length(w)) + -- Based on dot(v, w) = |v| * |w| * cos(x) + return math.acos(dot(v, w) / length(v) / length(w)) end function box_box_collision(diff, box, other_box) - for index, diff in pairs(diff) do - if box[index] + diff > other_box[index + 3] or other_box[index] > box[index + 3] + diff then - return false - end - end - return true + for index, diff in pairs(diff) do + if box[index] + diff > other_box[index + 3] or other_box[index] > box[index + 3] + diff then + return false + end + end + return true end --+ Möller-Trumbore function ray_triangle_intersection(origin, direction, triangle) - local point_1, point_2, point_3 = unpack(triangle) - local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1) - local h = cross3(direction, edge_2) - local a = dot(edge_1, h) - if math.abs(a) < 1e-9 then - return - end - local f = 1 / a - local diff = subtract(origin, point_1) - local u = f * dot(diff, h) - if u < 0 or u > 1 then - return - end - local q = cross3(diff, edge_1) - local v = f * dot(direction, q) - if v < 0 or u + v > 1 then - return - end - local pos_on_line = f * dot(edge_2, q) - if pos_on_line >= 0 then - return pos_on_line - end + local point_1, point_2, point_3 = unpack(triangle) + local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1) + local h = cross3(direction, edge_2) + local a = dot(edge_1, h) + if math.abs(a) < 1e-9 then + return + end + local f = 1 / a + local diff = subtract(origin, point_1) + local u = f * dot(diff, h) + if u < 0 or u > 1 then + return + end + local q = cross3(diff, edge_1) + local v = f * dot(direction, q) + if v < 0 or u + v > 1 then + return + end + local pos_on_line = f * dot(edge_2, q) + if pos_on_line >= 0 then + return pos_on_line + end end function triangle_normal(triangle) - local point_1, point_2, point_3 = unpack(triangle) - local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1) - return normalize(cross3(edge_1, edge_2)) + local point_1, point_2, point_3 = unpack(triangle) + local edge_1, edge_2 = subtract(point_2, point_1), subtract(point_3, point_1) + return normalize(cross3(edge_1, edge_2)) end \ No newline at end of file