2021-06-11 20:47:29 +02:00
|
|
|
-- Localize globals
|
2021-07-13 20:26:51 +02:00
|
|
|
local VoxelArea, ItemStack, assert, error, io, ipairs, math, minetest, modlib, next, pairs, setmetatable, string, table, type, vector
|
2021-07-14 11:51:47 +02:00
|
|
|
= VoxelArea, ItemStack, assert, error, io, ipairs, math, minetest, modlib, next, pairs, setmetatable, string, table, type, vector
|
2021-06-11 20:47:29 +02:00
|
|
|
|
2021-06-17 19:45:08 +02:00
|
|
|
|
2022-01-25 19:51:46 +01:00
|
|
|
local schematic = {}
|
2021-05-08 21:17:09 +02:00
|
|
|
local metatable = {__index = schematic}
|
|
|
|
|
|
|
|
function schematic.setmetatable(self)
|
2021-07-14 11:51:47 +02:00
|
|
|
return setmetatable(self, metatable)
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
2023-06-01 13:21:29 +02:00
|
|
|
function schematic.create(params, pos_min, pos_max)
|
|
|
|
pos_min, pos_max = vector.sort(pos_min, pos_max)
|
|
|
|
local size = vector.add(vector.subtract(pos_max, pos_min), 1)
|
2021-07-14 11:51:47 +02:00
|
|
|
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
|
|
|
|
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
|
|
|
|
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
|
2023-06-01 13:21:29 +02:00
|
|
|
local nodes, light_values, param2s = {}, params.light_values and {}, {}
|
2021-07-14 11:51:47 +02:00
|
|
|
local vm_nodes, vm_light_values, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
|
|
|
|
local node_names, node_ids = {}, {}
|
|
|
|
local i = 0
|
|
|
|
for index in voxelarea:iterp(pos_min, pos_max) do
|
|
|
|
if nodes[index] == minetest.CONTENT_UNKNOWN or nodes[index] == minetest.CONTENT_IGNORE then
|
|
|
|
error("unknown or ignore node at " .. minetest.pos_to_string(voxelarea:position(index)))
|
|
|
|
end
|
|
|
|
local name = minetest.get_name_from_content_id(vm_nodes[index])
|
|
|
|
local id = node_ids[name]
|
|
|
|
if not id then
|
|
|
|
table.insert(node_names, name)
|
|
|
|
id = #node_names
|
|
|
|
node_ids[name] = id
|
|
|
|
end
|
|
|
|
i = i + 1
|
|
|
|
nodes[i] = id
|
2023-06-01 13:21:29 +02:00
|
|
|
if params.light_values then
|
2021-07-14 11:51:47 +02:00
|
|
|
light_values[i] = vm_light_values[index]
|
|
|
|
end
|
|
|
|
param2s[i] = vm_param2s[index]
|
|
|
|
end
|
2023-06-01 13:21:29 +02:00
|
|
|
local metas
|
|
|
|
if params.metas or params.metas == nil then
|
2021-07-14 11:51:47 +02:00
|
|
|
metas = {}
|
|
|
|
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
|
|
|
|
local meta = minetest.get_meta(pos):to_table()
|
|
|
|
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
|
|
|
|
local relative = vector.subtract(pos, pos_min)
|
2023-06-01 13:21:29 +02:00
|
|
|
metas[((relative.z * size.y) + relative.y) * size.x + relative.x] = meta
|
2021-07-14 11:51:47 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2023-06-01 13:21:29 +02:00
|
|
|
return schematic.setmetatable({
|
|
|
|
size = size,
|
|
|
|
node_names = node_names,
|
|
|
|
nodes = nodes,
|
|
|
|
light_values = light_values,
|
|
|
|
param2s = param2s,
|
|
|
|
metas = metas,
|
|
|
|
})
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function schematic:write_to_voxelmanip(voxelmanip, pos_min)
|
2023-06-01 13:21:29 +02:00
|
|
|
local size = self.size
|
|
|
|
local pos_max = vector.subtract(vector.add(pos_min, size), 1) -- `pos_max` is inclusive
|
2021-07-14 11:51:47 +02:00
|
|
|
local emin, emax = voxelmanip:read_from_map(pos_min, pos_max)
|
|
|
|
local voxelarea = VoxelArea:new{ MinEdge = emin, MaxEdge = emax }
|
|
|
|
local nodes, light_values, param2s, metas = self.nodes, self.light_values, self.param2s, self.metas
|
|
|
|
local vm_nodes, vm_lights, vm_param2s = voxelmanip:get_data(), light_values and voxelmanip:get_light_data(), voxelmanip:get_param2_data()
|
|
|
|
for _, pos in ipairs(minetest.find_nodes_with_meta(pos_min, pos_max)) do
|
|
|
|
-- Clear all metadata. Due to an engine bug, nodes will actually have empty metadata.
|
|
|
|
minetest.get_meta(pos):from_table{}
|
|
|
|
end
|
|
|
|
local content_ids = {}
|
|
|
|
for index, name in ipairs(self.node_names) do
|
|
|
|
content_ids[index] = assert(minetest.get_content_id(name), ("unknown node %q"):format(name))
|
|
|
|
end
|
|
|
|
local i = 0
|
|
|
|
for index in voxelarea:iterp(pos_min, pos_max) do
|
|
|
|
i = i + 1
|
|
|
|
vm_nodes[index] = content_ids[nodes[i]]
|
|
|
|
if light_values then
|
|
|
|
vm_lights[index] = light_values[i]
|
|
|
|
end
|
|
|
|
vm_param2s[index] = param2s[i]
|
|
|
|
end
|
|
|
|
voxelmanip:set_data(vm_nodes)
|
|
|
|
if light_values then
|
|
|
|
voxelmanip:set_light_data(vm_lights)
|
|
|
|
end
|
|
|
|
voxelmanip:set_param2_data(vm_param2s)
|
|
|
|
if metas then
|
|
|
|
for index, meta in pairs(metas) do
|
2023-06-01 13:21:29 +02:00
|
|
|
local floored = math.floor(index / size.x)
|
2021-07-14 11:51:47 +02:00
|
|
|
local relative = {
|
2023-06-01 13:21:29 +02:00
|
|
|
x = index % size.x,
|
|
|
|
y = floored % size.y,
|
|
|
|
z = math.floor(floored / size.y)
|
2021-07-14 11:51:47 +02:00
|
|
|
}
|
|
|
|
minetest.get_meta(vector.add(relative, pos_min)):from_table(meta)
|
|
|
|
end
|
|
|
|
end
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function schematic:place(pos_min)
|
2023-06-01 13:21:29 +02:00
|
|
|
local pos_max = vector.subtract(vector.add(pos_min, self.size), 1) -- `pos_max` is inclusive
|
2021-07-14 11:51:47 +02:00
|
|
|
local voxelmanip = minetest.get_voxel_manip(pos_min, pos_max)
|
|
|
|
self:write_to_voxelmanip(voxelmanip, pos_min)
|
|
|
|
voxelmanip:write_to_map(not self.light_values)
|
|
|
|
return voxelmanip
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
2021-07-13 20:26:51 +02:00
|
|
|
local function table_to_byte_string(tab)
|
2021-07-14 11:51:47 +02:00
|
|
|
if not tab then return end
|
|
|
|
return table.concat(modlib.table.map(tab, string.char))
|
2021-07-13 20:26:51 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function write_bluon(self, stream)
|
2023-06-01 13:21:29 +02:00
|
|
|
local metas = modlib.table.copy(self.metas)
|
|
|
|
for _, meta in pairs(metas) do
|
2021-07-14 11:51:47 +02:00
|
|
|
for _, list in pairs(meta.inventory) do
|
|
|
|
for index, stack in pairs(list) do
|
|
|
|
list[index] = stack:to_string()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2023-06-01 13:21:29 +02:00
|
|
|
modlib.bluon:write({
|
|
|
|
size = self.size,
|
|
|
|
node_names = self.node_names,
|
|
|
|
nodes = self.nodes,
|
|
|
|
light_values = table_to_byte_string(self.light_values),
|
|
|
|
param2s = table_to_byte_string(self.param2s),
|
|
|
|
metas = metas,
|
|
|
|
}, stream)
|
2021-07-13 20:26:51 +02:00
|
|
|
end
|
|
|
|
|
2021-05-08 21:17:09 +02:00
|
|
|
function schematic:write_bluon(path)
|
2021-07-14 11:51:47 +02:00
|
|
|
local file = io.open(path, "wb")
|
|
|
|
-- Header, short for "ModLib Bluon Schematic"
|
|
|
|
file:write"MLBS"
|
|
|
|
write_bluon(self, file)
|
|
|
|
file:close()
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
2021-07-13 20:26:51 +02:00
|
|
|
local function byte_string_to_table(self, field)
|
2021-07-14 11:51:47 +02:00
|
|
|
local byte_string = self[field]
|
|
|
|
if not byte_string then return end
|
|
|
|
local tab = {}
|
|
|
|
for i = 1, #byte_string do
|
|
|
|
tab[i] = byte_string:byte(i)
|
|
|
|
end
|
|
|
|
self[field] = tab
|
2021-07-13 20:26:51 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
local function read_bluon(file)
|
2021-07-14 11:51:47 +02:00
|
|
|
local self = modlib.bluon:read(file)
|
|
|
|
assert(not file:read(1), "expected EOF")
|
|
|
|
for _, meta in pairs(self.metas) do
|
|
|
|
for _, list in pairs(meta.inventory) do
|
|
|
|
for index, itemstring in pairs(list) do
|
|
|
|
assert(type(itemstring) == "string")
|
|
|
|
list[index] = ItemStack(itemstring)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
byte_string_to_table(self, "light_values")
|
|
|
|
byte_string_to_table(self, "param2s")
|
|
|
|
return self
|
2021-07-13 20:26:51 +02:00
|
|
|
end
|
|
|
|
|
2021-05-08 21:17:09 +02:00
|
|
|
function schematic.read_bluon(path)
|
2021-07-14 11:51:47 +02:00
|
|
|
local file = io.open(path, "rb")
|
|
|
|
assert(file:read(4) == "MLBS", "not a modlib bluon schematic")
|
|
|
|
return schematic.setmetatable(read_bluon(file))
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function schematic:write_zlib_bluon(path, compression)
|
2021-07-14 11:51:47 +02:00
|
|
|
local file = io.open(path, "wb")
|
|
|
|
-- Header, short for "ModLib Zlib-compressed-bluon Schematic"
|
|
|
|
file:write"MLZS"
|
|
|
|
local rope = modlib.table.rope{}
|
|
|
|
write_bluon(self, rope)
|
|
|
|
local text = rope:to_text()
|
|
|
|
file:write(minetest.compress(text, "deflate", compression or 9))
|
|
|
|
file:close()
|
2021-05-08 21:17:09 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
function schematic.read_zlib_bluon(path)
|
2021-07-14 11:51:47 +02:00
|
|
|
local file = io.open(path, "rb")
|
|
|
|
assert(file:read(4) == "MLZS", "not a modlib zlib compressed bluon schematic")
|
|
|
|
return schematic.setmetatable(read_bluon(modlib.text.inputstream(minetest.decompress(file:read"*a", "deflate"))))
|
2022-01-25 19:51:46 +01:00
|
|
|
end
|
|
|
|
|
2023-06-01 13:21:29 +02:00
|
|
|
return schematic
|