-worldedit = worldedit or {}\r
-local minetest = minetest -- Local copy of global\r
+--- Schematic serialization and deserialiation.\r
+-- @module worldedit.serialization\r
\r
worldedit.LATEST_SERIALIZATION_VERSION = 5\r
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"\r
-- @return The serialized data.\r
-- @return The number of nodes serialized.\r
function worldedit.serialize(pos1, pos2)\r
- -- Keep area loaded\r
- local manip = minetest.get_voxel_manip()\r
- manip:read_from_map(pos1, pos2)\r
+ pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
+\r
+ worldedit.keep_loaded(pos1, pos2)\r
+\r
+ local get_node, get_meta, hash_node_position =\r
+ minetest.get_node, minetest.get_meta, minetest.hash_node_position\r
+\r
+ -- Find the positions which have metadata\r
+ local has_meta = {}\r
+ local meta_positions = minetest.find_nodes_with_meta(pos1, pos2)\r
+ for i = 1, #meta_positions do\r
+ has_meta[hash_node_position(meta_positions[i])] = true\r
+ end\r
\r
- local pos1, pos2 = worldedit.sort_pos(pos1, pos2)\r
local pos = {x=pos1.x, y=0, z=0}\r
local count = 0\r
local result = {}\r
- local get_node, get_meta = minetest.get_node, minetest.get_meta\r
while pos.x <= pos2.x do\r
pos.y = pos1.y\r
while pos.y <= pos2.y do\r
local node = get_node(pos)\r
if node.name ~= "air" and node.name ~= "ignore" then\r
count = count + 1\r
- local meta = get_meta(pos):to_table()\r
-\r
- local meta_empty = true\r
- -- Convert metadata item stacks to item strings\r
- for name, inventory in pairs(meta.inventory) do\r
- for index, stack in ipairs(inventory) do\r
- meta_empty = false\r
- inventory[index] = stack.to_string and stack:to_string() or stack\r
- end\r
- end\r
- for k in pairs(meta) do\r
- if k ~= "inventory" then\r
- meta_empty = false\r
- break\r
+\r
+ local meta\r
+ if has_meta[hash_node_position(pos)] then\r
+ meta = get_meta(pos):to_table()\r
+\r
+ -- Convert metadata item stacks to item strings\r
+ for _, invlist in pairs(meta.inventory) do\r
+ for index = 1, #invlist do\r
+ local itemstack = invlist[index]\r
+ if itemstack.to_string then\r
+ invlist[index] = itemstack:to_string()\r
+ end\r
+ end\r
end\r
end\r
\r
name = node.name,\r
param1 = node.param1 ~= 0 and node.param1 or nil,\r
param2 = node.param2 ~= 0 and node.param2 or nil,\r
- meta = not meta_empty and meta or nil,\r
+ meta = meta,\r
}\r
end\r
pos.z = pos.z + 1\r
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)\r
-- by ChillCode, available under the MIT license.\r
-- @return A node list in the latest format, or nil on failure.\r
-function worldedit.load_schematic(value)\r
+local function load_schematic(value)\r
local version, header, content = worldedit.read_header(value)\r
local nodes = {}\r
if version == 1 or version == 2 then -- Original flat table format\r
"([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do\r
param1, param2 = tonumber(param1), tonumber(param2)\r
table.insert(nodes, {\r
- x = originx + tonumber(x),\r
- y = originy + tonumber(y),\r
- z = originz + tonumber(z),\r
+ x = tonumber(x),\r
+ y = tonumber(y),\r
+ z = tonumber(z),\r
name = name,\r
param1 = param1 ~= 0 and param1 or nil,\r
param2 = param2 ~= 0 and param2 or nil,\r
else\r
-- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit\r
nodes = {}\r
- value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data\r
- local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)\r
+ content = content:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data\r
+ local escaped = content:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end)\r
local startpos, startpos1, endpos = 1, 1\r
while true do -- go through each individual node entry (except the last)\r
startpos, endpos = escaped:find("},%s*{", startpos)\r
if not startpos then\r
break\r
end\r
- local current = value:sub(startpos1, startpos)\r
+ local current = content:sub(startpos1, startpos)\r
local entry = minetest.deserialize("return " .. current)\r
table.insert(nodes, entry)\r
startpos, startpos1 = endpos, endpos\r
end\r
- local entry = minetest.deserialize("return " .. value:sub(startpos1)) -- process the last entry\r
+ local entry = minetest.deserialize("return " .. content:sub(startpos1)) -- process the last entry\r
table.insert(nodes, entry)\r
end\r
else\r
-- @return Low corner position.\r
-- @return High corner position.\r
-- @return The number of nodes.\r
-worldedit.allocate = function(origin_pos, value)\r
- local nodes = worldedit.load_schematic(value)\r
+function worldedit.allocate(origin_pos, value)\r
+ local nodes = load_schematic(value)\r
if not nodes then return nil end\r
return worldedit.allocate_with_nodes(origin_pos, nodes)\r
end\r
\r
\r
-- Internal\r
-worldedit.allocate_with_nodes = function(origin_pos, nodes)\r
+function worldedit.allocate_with_nodes(origin_pos, nodes)\r
local huge = math.huge\r
local pos1x, pos1y, pos1z = huge, huge, huge\r
local pos2x, pos2y, pos2z = -huge, -huge, -huge\r
\r
--- Loads the nodes represented by string `value` at position `origin_pos`.\r
-- @return The number of nodes deserialized.\r
-worldedit.deserialize = function(origin_pos, value)\r
- local nodes = worldedit.load_schematic(value)\r
+function worldedit.deserialize(origin_pos, value)\r
+ local nodes = load_schematic(value)\r
if not nodes then return nil end\r
+ if #nodes == 0 then return #nodes end\r
\r
- -- Make area stay loaded\r
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)\r
- local manip = minetest.get_voxel_manip()\r
- manip:read_from_map(pos1, pos2)\r
+ worldedit.keep_loaded(pos1, pos2)\r
\r
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z\r
local count = 0\r