2017-04-08 19:17:25 +02:00

286 lines
7.3 KiB
Lua

-- 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)
--[[ 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_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
local meta = minetest.get_meta({x = x, y = y, z = z})
local meta_table = meta:to_table()
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)
return minetest.compress(raw_data, "deflate")
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()
-- 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
]]