forked from minetest-mods/MoreMesecons
383 lines
9.8 KiB
Lua
383 lines
9.8 KiB
Lua
moremesecons = {}
|
|
|
|
function moremesecons.setting(modname, settingname, default, min)
|
|
local setting = "moremesecons_" .. modname .. "." .. settingname
|
|
|
|
if type(default) == "boolean" then
|
|
local ret = minetest.settings:get_bool(setting)
|
|
if ret == nil then
|
|
ret = default
|
|
end
|
|
return ret
|
|
elseif type(default) == "string" then
|
|
return minetest.settings:get(setting) or default
|
|
elseif type(default) == "number" then
|
|
local ret = tonumber(minetest.settings:get(setting)) or default
|
|
if not ret then
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' must be a number. Set to default value ("..tostring(default)..").")
|
|
ret = default
|
|
elseif ret ~= ret then -- NaN
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' is NaN. Set to default value ("..tostring(default)..").")
|
|
ret = default
|
|
end
|
|
if min and ret < min then
|
|
minetest.log("warning", "[moremesecons_"..modname.."]: setting '"..setting.."' is under minimum value "..tostring(min)..". Set to minimum value ("..tostring(min)..").")
|
|
ret = min
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
|
|
-- Storage helpers
|
|
|
|
function moremesecons.get_storage_data(storage, name)
|
|
return {
|
|
tab = minetest.deserialize(storage:get_string(name)) or {},
|
|
name = name,
|
|
storage = storage
|
|
}
|
|
end
|
|
|
|
function moremesecons.set_data_to_pos(sto, pos, data)
|
|
sto.tab[minetest.hash_node_position(pos)] = data
|
|
sto.storage:set_string(sto.name, minetest.serialize(sto.tab))
|
|
end
|
|
|
|
function moremesecons.get_data_from_pos(sto, pos)
|
|
return sto.tab[minetest.hash_node_position(pos)]
|
|
end
|
|
|
|
function moremesecons.remove_data_from_pos(sto, pos)
|
|
sto.tab[minetest.hash_node_position(pos)] = nil
|
|
sto.storage:set_string(sto.name, minetest.serialize(sto.tab))
|
|
end
|
|
|
|
-- Some additional vector helpers
|
|
|
|
-- The same as minetest.hash_node_position; I copied it to ensure backwards
|
|
-- compatibility and used hexadecimal number notation
|
|
local function node_position_key(pos)
|
|
return (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
+ (pos.y + 0x8000) * 0x10000
|
|
+ pos.x + 0x8000
|
|
end
|
|
|
|
local MapDataStorage = {}
|
|
setmetatable(MapDataStorage, {__call = function()
|
|
local obj = {}
|
|
setmetatable(obj, MapDataStorage)
|
|
return obj
|
|
end})
|
|
MapDataStorage.__index = {
|
|
getAt = function(self, pos)
|
|
return self[node_position_key(pos)]
|
|
end,
|
|
setAt = function(self, pos, data)
|
|
-- If x, y or z is omitted, the key corresponds to a position outside
|
|
-- of the map (hopefully), so it can be used to skip lines and planes
|
|
local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000
|
|
local vi = vi_zy + pos.x + 0x8000
|
|
local is_new = self[vi] == nil
|
|
self[vi] = data
|
|
if is_new then
|
|
self[vi_z] = (self[vi_z] or 0) + 1
|
|
self[vi_zy] = (self[vi_zy] or 0) + 1
|
|
end
|
|
end,
|
|
setAtI = function(self, vi, data)
|
|
local vi_zy = vi - vi % 0x10000
|
|
local vi_z = vi - vi % (0x10000 * 0x10000)
|
|
local is_new = self[vi] == nil
|
|
self[vi] = data
|
|
if is_new then
|
|
self[vi_z] = (self[vi_z] or 0) + 1
|
|
self[vi_zy] = (self[vi_zy] or 0) + 1
|
|
end
|
|
end,
|
|
removeAt = function(self, pos)
|
|
local vi_z = (pos.z + 0x8000) * 0x10000 * 0x10000
|
|
local vi_zy = vi_z + (pos.y + 0x8000) * 0x10000
|
|
local vi = vi_zy + pos.x + 0x8000
|
|
if self[vi] == nil then
|
|
-- Nothing to remove
|
|
return
|
|
end
|
|
self[vi] = nil
|
|
-- Update existence information for the xy plane and x line
|
|
self[vi_z] = self[vi_z] - 1
|
|
if self[vi_z] == 0 then
|
|
self[vi_z] = nil
|
|
self[vi_zy] = nil
|
|
return
|
|
end
|
|
self[vi_zy] = self[vi_zy] - 1
|
|
if self[vi_zy] == 0 then
|
|
self[vi_zy] = nil
|
|
end
|
|
end,
|
|
iter = function(self, pos1, pos2)
|
|
local ystride = 0x10000
|
|
local zstride = 0x10000 * 0x10000
|
|
|
|
-- Skip z values where no data can be found
|
|
pos1 = vector.new(pos1)
|
|
local vi_z = (pos1.z + 0x8000) * 0x10000 * 0x10000
|
|
while not self[vi_z] do
|
|
pos1.z = pos1.z + 1
|
|
vi_z = vi_z + zstride
|
|
if pos1.z > pos2.z then
|
|
-- There are no values to iterate through
|
|
return function() return end
|
|
end
|
|
end
|
|
-- Skipping y values is not yet implemented and may require much code
|
|
|
|
local xrange = pos2.x - pos1.x + 1
|
|
local yrange = pos2.y - pos1.y + 1
|
|
local zrange = pos2.z - pos1.z + 1
|
|
|
|
-- x-only and y-only parts of the vector index of pos1
|
|
local vi_y = (pos1.y + 0x8000) * 0x10000
|
|
local vi_x = pos1.x + 0x8000
|
|
|
|
local y = 0
|
|
local z = 0
|
|
|
|
local vi = node_position_key(pos1)
|
|
local pos = vector.new(pos1)
|
|
local nextaction = vi + xrange
|
|
pos.x = pos.x - 1
|
|
vi = vi - 1
|
|
local function iterfunc()
|
|
-- continue along x until it needs to jump
|
|
vi = vi + 1
|
|
pos.x = pos.x + 1
|
|
if vi ~= nextaction then
|
|
local v = self[vi]
|
|
if v == nil then
|
|
-- No data here
|
|
return iterfunc()
|
|
end
|
|
-- The returned position must not be changed
|
|
return pos, v
|
|
end
|
|
|
|
-- Reset x position
|
|
vi = vi - xrange
|
|
-- Go along y until pos2.y is exceeded
|
|
while true do
|
|
y = y + 1
|
|
pos.y = pos.y + 1
|
|
-- Set vi to index(pos1.x, pos1.y + y, pos1.z + z)
|
|
vi = vi + ystride
|
|
if y == yrange then
|
|
break
|
|
end
|
|
if self[vi - vi_x] then
|
|
nextaction = vi + xrange
|
|
|
|
vi = vi - 1
|
|
pos.x = pos1.x - 1
|
|
return iterfunc()
|
|
end
|
|
-- Nothing along this x line, so increase y again
|
|
end
|
|
|
|
-- Go back along y
|
|
vi = vi - yrange * ystride
|
|
y = 0
|
|
pos.y = pos1.y
|
|
-- Go along z until pos2.z is exceeded
|
|
while true do
|
|
z = z + 1
|
|
pos.z = pos.z + 1
|
|
vi = vi + zstride
|
|
if z == zrange then
|
|
-- Cuboid finished, return nil
|
|
return
|
|
end
|
|
if self[vi - vi_x - vi_y] then
|
|
y = 0
|
|
nextaction = vi + xrange
|
|
|
|
vi = vi - 1
|
|
pos.x = pos1.x - 1
|
|
return iterfunc()
|
|
end
|
|
-- Nothing in this xy plane, so increase z again
|
|
end
|
|
end
|
|
return iterfunc
|
|
end,
|
|
iterAll = function(self)
|
|
local previous_vi = nil
|
|
local function iterfunc()
|
|
local vi, v = next(self, previous_vi)
|
|
previous_vi = vi
|
|
if not vi then
|
|
return
|
|
end
|
|
local z = math.floor(vi / (0x10000 * 0x10000))
|
|
vi = vi - z * 0x10000 * 0x10000
|
|
local y = math.floor(vi / 0x10000)
|
|
if y == 0 or z == 0 then
|
|
-- The index does not refer to a position inside the map
|
|
return iterfunc()
|
|
end
|
|
local x = vi - y * 0x10000 - 0x8000
|
|
y = y - 0x8000
|
|
z = z - 0x8000
|
|
return {x=x, y=y, z=z}, v
|
|
end
|
|
return iterfunc
|
|
end,
|
|
serialize = function(self)
|
|
local indices = {}
|
|
local values = {}
|
|
local i = 1
|
|
for pos, v in self:iterAll() do
|
|
local vi = node_position_key(pos)
|
|
-- Convert the double reversible to a string;
|
|
-- minetest.serialize does not (yet) do this
|
|
indices[i] = ("%.17g"):format(vi)
|
|
values[i] = v
|
|
end
|
|
return minetest.serialize({
|
|
version = "MapDataStorage_v1",
|
|
indices = "return {" .. table.concat(indices, ",") .. "}",
|
|
values = minetest.serialize(values),
|
|
})
|
|
end,
|
|
}
|
|
MapDataStorage.deserialize = function(txtdata)
|
|
local data = minetest.deserialize(txtdata)
|
|
if data.version ~= "MapDataStorage_v1" then
|
|
minetest.log("error", "Unknown MapDataStorage version: " ..
|
|
data.version)
|
|
end
|
|
-- I assume that minetest.deserialize correctly deserializes the indices,
|
|
-- which are in the %a format
|
|
local indices = minetest.deserialize(data.indices)
|
|
local values = minetest.deserialize(data.values)
|
|
if not indices or not values then
|
|
return MapDataStorage()
|
|
end
|
|
data = MapDataStorage()
|
|
for i = 1,#indices do
|
|
local vi = indices[i]
|
|
local v = values[i]
|
|
data:setAtI(vi, v)
|
|
end
|
|
return data
|
|
end
|
|
moremesecons.MapDataStorage = MapDataStorage
|
|
|
|
|
|
-- Legacy
|
|
|
|
-- vector_extras there: https://github.com/HybridDog/vector_extras
|
|
-- Creates a MapDataStorage object from old vector_extras generated table
|
|
function moremesecons.load_old_data_from_pos(t)
|
|
local data = MapDataStorage()
|
|
for z, yxv in pairs(t) do
|
|
for y, xv in pairs(yxv) do
|
|
for x, v in pairs(xv) do
|
|
data:setAt({x=x, y=y, z=z}, v)
|
|
end
|
|
end
|
|
end
|
|
return data
|
|
end
|
|
|
|
function moremesecons.load_old_dfp_storage(modstorage, name)
|
|
local data = minetest.deserialize(modstorage:get_string(name))
|
|
if not data then
|
|
return
|
|
end
|
|
return moremesecons.load_old_data_from_pos(data)
|
|
end
|
|
|
|
function moremesecons.load_MapDataStorage_legacy(modstorage, name, oldname)
|
|
local t_old = moremesecons.load_old_dfp_storage(modstorage, oldname)
|
|
local t
|
|
if t_old and t_old ~= "" then
|
|
t = t_old
|
|
modstorage:set_string(name, t:serialize())
|
|
modstorage:set_string(oldname, nil)
|
|
return t
|
|
end
|
|
t = modstorage:get_string(name)
|
|
if t and t ~= "" then
|
|
return MapDataStorage.deserialize(t)
|
|
end
|
|
return MapDataStorage()
|
|
end
|
|
|
|
|
|
|
|
--[[
|
|
-- This testing code shows an example usage of the MapDataStorage code
|
|
local function do_test()
|
|
print("Test if iter returns correct positions when a lot is set")
|
|
local data = MapDataStorage()
|
|
local k = 0
|
|
for x = -5, 3 do
|
|
for y = -5, 3 do
|
|
for z = -5, 3 do
|
|
k = k + 1
|
|
data:setAt({x=x, y=y, z=z}, k)
|
|
end
|
|
end
|
|
end
|
|
local expected_positions = {}
|
|
for z = -4, 2 do
|
|
for y = -4, 2 do
|
|
for x = -4, 2 do
|
|
expected_positions[#expected_positions+1] = {x=x, y=y, z=z}
|
|
end
|
|
end
|
|
end
|
|
local i = 0
|
|
for pos in data:iter({x=-4, y=-4, z=-4}, {x=2, y=2, z=2}) do
|
|
i = i + 1
|
|
assert(vector.equals(pos, expected_positions[i]))
|
|
end
|
|
|
|
print("Test if iter works correctly on a corner")
|
|
local found = false
|
|
for pos in data:iter({x=-8, y=-7, z=-80}, {x=-5, y=-5, z=-5}) do
|
|
assert(not found)
|
|
found = true
|
|
assert(vector.equals(pos, {x=-5, y=-5, z=-5}))
|
|
end
|
|
assert(found)
|
|
|
|
print("Test if iter finds all corners")
|
|
local expected_positions = {}
|
|
local k = 1
|
|
for _, z in ipairs({-9, -6}) do
|
|
for _, y in ipairs({-9, -6}) do
|
|
for _, x in ipairs({-8, -6}) do
|
|
local pos = {x=x, y=y, z=z}
|
|
expected_positions[#expected_positions+1] = pos
|
|
data:setAt(pos, k)
|
|
k = k + 1
|
|
end
|
|
end
|
|
end
|
|
local i = 1
|
|
for pos, v in data:iter({x=-8, y=-9, z=-9}, {x=-6, y=-6, z=-6}) do
|
|
assert(v == i)
|
|
assert(vector.equals(pos, expected_positions[i]))
|
|
i = i + 1
|
|
--~ print("found " .. minetest.pos_to_string(pos))
|
|
end
|
|
assert(i == 8 + 1, "Not enough or too many corners found")
|
|
|
|
--~ data:iterAll()
|
|
end
|
|
do_test()
|
|
--]]
|
|
minetest.log("action", "[moremesecons_utils] loaded.")
|