--[[ ITB (insidethebox) minetest game - Copyright (C) 2017-2018 sofar & nore This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ]]-- -- Functions to handle binary data encoding the boxes, to save and load them local function bytes_to_string(bytes) local s = {} for i = 1, #bytes do s[i] = string.char(bytes[i]) end return table.concat(s) end function boxes.save(minp, maxp) local t1 = os.clock() --[[ TODO: save the box in incremental steps to avoid stalling the server. The problem is that we have to iterate over all positions since there is no way to get the metadata of all nodes in an area, although the engine can. However, we still use a voxelmanip to load node bulk data. ]] local vm = minetest.get_voxel_manip(minp, maxp) local emin, emax = vm:get_emerged_area() local data = vm:get_data() local param2 = vm:get_param2_data() local flat_data = {} local flat_index = 1 local function add_u8(x) flat_data[flat_index] = x flat_index = flat_index + 1 end local function add_u16(x) add_u8(math.floor(x / 256)) add_u8(x % 256) end local function add_string(s) add_u16(string.len(s)) for i = 1, string.len(s) do add_u8(string.byte(s, i)) end end -- Version add_u8(2) local sx = maxp.x - minp.x + 1 local sy = maxp.y - minp.y + 1 local sz = maxp.z - minp.z + 1 -- Size add_u16(sx) add_u16(sy) add_u16(sz) local cid_mapping = {} local cid_index = 0 local cid_rmapping = {} local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} for z = minp.z, maxp.z do for y = minp.y, maxp.y do local index = va:index(minp.x, y, z) for x = minp.x, maxp.x do local cid = data[index] if cid_rmapping[cid] == nil then cid_rmapping[cid] = cid_index cid_mapping[cid_index] = minetest.get_name_from_content_id(cid) cid_index = cid_index + 1 end index = index + 1 end end end -- Write cid mapping add_u16(cid_index) for i = 0, cid_index - 1 do add_string(cid_mapping[i]) end -- Write bulk node data for z = minp.z, maxp.z do for y = minp.y, maxp.y do local index = va:index(minp.x, y, z) for x = minp.x, maxp.x do add_u16(cid_rmapping[data[index]]) add_u8(param2[index]) index = index + 1 end end end local meta_at_pos = {} for _, v in pairs(minetest.find_nodes_with_meta(minp, maxp)) do meta_at_pos[minetest.pos_to_string(v)] = 1 end local meta_to_save = {} local meta_save_index = 1 for z = minp.z, maxp.z do for y = minp.y, maxp.y do for x = minp.x, maxp.x do --FIXME don't save meta if cid==air local meta_table if meta_at_pos[minetest.pos_to_string({x = x, y = y, z = z})] then local meta = minetest.get_meta({x = x, y = y, z = z}) meta_table = meta:to_table() else meta_table = {fields = {}, inventory = {}} end local nodetimer = minetest.get_node_timer({x = x, y = y, z = z}) if next(meta_table.fields) ~= nil or next(meta_table.inventory) ~= nil or nodetimer:is_started() then local inv = {} for inv_name, inv_list in pairs(meta_table.inventory) do local inv_list_2 = {} for i, stack in ipairs(inv_list) do inv_list_2[i] = stack:to_string() end inv[inv_name] = inv_list_2 end local sv = {x = x - minp.x, y = y - minp.y, z = z - minp.z} if next(meta_table.fields) ~= nil then sv.fields = meta_table.fields end if next(inv) ~= nil then sv.inventory = inv end if nodetimer:is_started() then sv.timer = {to = nodetimer:get_timeout(), el = nodetimer:get_elapsed()} end meta_to_save[meta_save_index] = sv meta_save_index = meta_save_index + 1 end end end end add_u16(meta_save_index - 1) for i = 1, meta_save_index - 1 do local m = meta_to_save[i] add_u16(m.x) add_u16(m.y) add_u16(m.z) if m.fields then add_string(minetest.serialize(m.fields)) else add_string("") end if m.inventory then add_string(minetest.serialize(m.inventory)) else add_string("") end if m.timer then add_string(minetest.serialize(m.timer)) else add_string("") end end local raw_data = bytes_to_string(flat_data) local t2 = os.clock() local cc = minetest.compress(raw_data, "deflate") local t3 = os.clock() minetest.log("action", "boxes.save: " .. (t2 - t1) .. ", " .. (t3 - t2)) return cc end function boxes.load(minp, box_id, player) local compressed = db.box_get_data(box_id) local raw_data = minetest.decompress(compressed, "deflate") local raw_data_index = 1 local function read_u8() local c = string.byte(raw_data, raw_data_index) raw_data_index = raw_data_index + 1 return c end local function read_u16() local a = read_u8() local b = read_u8() return 256 * a + b end local function read_string() local len = read_u16() local r = string.sub(raw_data, raw_data_index, raw_data_index + len - 1) raw_data_index = raw_data_index + len return r end local version = read_u8() assert (version <= 2) local sx = read_u16() local sy = read_u16() local sz = read_u16() -- Read cid mapping local cid_mapping = {} local callbacks = {} local cid_index = read_u16() for i = 0, cid_index - 1 do local name = read_string() callbacks[i] = minetest.registered_nodes[name].after_box_construct cid_mapping[i] = minetest.get_content_id(name) end local maxp = {x = minp.x + sx - 1, y = minp.y + sy - 1, z = minp.z + sz - 1} local vm = minetest.get_voxel_manip(minp, maxp) local emin, emax = vm:get_emerged_area() local va = VoxelArea:new{MinEdge=emin,MaxEdge=emax} local vmdata = vm:get_data() local param2 = vm:get_param2_data() local to_call = {} local to_call_index = 1 -- Read bulk node data for z = minp.z, maxp.z do for y = minp.y, maxp.y do local index = va:index(minp.x, y, z) for x = minp.x, maxp.x do local cid = read_u16() vmdata[index] = cid_mapping[cid] param2[index] = read_u8() index = index + 1 if callbacks[cid] then to_call[to_call_index] = {x = x, y = y, z = z, callback = callbacks[cid]} to_call_index = to_call_index + 1 end end end end vm:set_data(vmdata) vm:set_param2_data(param2) vm:update_liquids() vm:write_to_map() vm:update_map() minetest.after(0.2, function() minetest.fix_light(vector.add(minp, -16), vector.add(maxp, 16)) end) -- Clear existing metadata local meta_positions = minetest.find_nodes_with_meta(minp, maxp) for _, pos in ipairs(meta_positions) do minetest.get_meta(pos):from_table() end -- Finally, read metadata local nmeta = read_u16() for _ = 1, nmeta do local x = read_u16() local y = read_u16() local z = read_u16() local p = {x = minp.x + x, y = minp.y + y, z = minp.z + z} local meta = minetest.get_meta(p) local fields = minetest.deserialize(read_string()) or {} local inv = minetest.deserialize(read_string()) or {} if version >= 2 then local timer = minetest.deserialize(read_string()) or {} if timer.to then minetest.get_node_timer(p):set(timer.to, timer.el) end end for inv_name, inv_list in pairs(inv) do for i, stack in ipairs(inv_list) do inv_list[i] = ItemStack(inv_list[i]) end end meta:from_table({fields = fields, inventory = inv}) end -- And call callbacks for nodes which define them for i = 1, to_call_index - 1 do local tc = to_call[i] tc.callback({x = tc.x, y = tc.y, z = tc.z}, box_id, player) end end -- Although that function can be quite useful in some cases, -- it implies decompressing the whole compressed data just to -- know the extent of a box. Thus, it is commented out and while -- it can be useful for quick prototyping, it should be avoided to -- use it in production code. --[[ function boxes.extent(compressed) local raw_data = minetest.decompress(compressed, "deflate") local raw_data_index = 1 local function read_u8() local c = string.byte(raw_data, raw_data_index) raw_data_index = raw_data_index + 1 return c end local function read_u16() local a = read_u8() local b = read_u8() return 256 * a + b end local version = read_u8() assert (version == 1) local sx = read_u16() local sy = read_u16() local sz = read_u16() return {x = sx, y = sy, z = sz} end ]]