2019-09-15 21:07:48 -07:00

331 lines
8.6 KiB
Lua

--[[
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
]]