331 lines
8.6 KiB
Lua
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
|
|
]]
|