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