2017-04-19 11:08:58 +02:00
moremesecons = { }
2017-04-19 18:33:36 +02:00
function moremesecons . setting ( modname , settingname , default , min )
2017-04-19 11:08:58 +02:00
local setting = " moremesecons_ " .. modname .. " . " .. settingname
2017-04-19 13:27:06 +02:00
if type ( default ) == " boolean " then
2017-05-13 10:52:42 +02:00
local ret = minetest.settings : get_bool ( setting )
2017-04-19 11:08:58 +02:00
if ret == nil then
ret = default
end
return ret
elseif type ( default ) == " string " then
2017-05-13 10:52:42 +02:00
return minetest.settings : get ( setting ) or default
2017-04-19 11:08:58 +02:00
elseif type ( default ) == " number " then
2017-05-13 10:52:42 +02:00
local ret = tonumber ( minetest.settings : get ( setting ) ) or default
2017-05-21 19:11:12 +02:00
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
2017-04-19 18:33:36 +02:00
minetest.log ( " warning " , " [moremesecons_ " .. modname .. " ]: setting ' " .. setting .. " ' is NaN. Set to default value ( " .. tostring ( default ) .. " ). " )
2017-04-19 11:08:58 +02:00
ret = default
end
2017-04-19 18:33:36 +02:00
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
2017-04-19 11:08:58 +02:00
end
return ret
end
end
2017-04-19 21:41:10 +02:00
2020-04-09 16:13:51 +02:00
-- 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
2020-04-13 18:11:34 +02:00
-- 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 ,
2020-04-13 19:30:15 +02:00
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 ,
2020-04-13 18:11:34 +02:00
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 )
2020-04-13 19:30:15 +02:00
local previous_vi = nil
2020-04-13 18:11:34 +02:00
local function iterfunc ( )
2020-04-13 19:30:15 +02:00
local vi , v = next ( self , previous_vi )
previous_vi = vi
2020-04-13 18:11:34 +02:00
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
2020-04-13 19:30:15 +02:00
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
2020-04-29 18:06:17 +02:00
indices [ i ] = ( " %.17g " ) : format ( vi )
2020-04-29 12:43:29 +02:00
values [ i ] = v
2020-04-13 19:30:15 +02:00
end
2021-03-08 19:34:21 +01:00
return minetest.serialize ( {
2020-04-13 19:30:15 +02:00
version = " MapDataStorage_v1 " ,
indices = " return { " .. table.concat ( indices , " , " ) .. " } " ,
2020-04-29 12:43:29 +02:00
values = minetest.serialize ( values ) ,
2021-03-08 19:34:21 +01:00
} )
2020-04-13 18:11:34 +02:00
end ,
}
2020-04-13 19:30:15 +02:00
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
2020-04-29 12:43:29 +02:00
local indices = minetest.deserialize ( data.indices )
local values = minetest.deserialize ( data.values )
if not indices or not values then
return MapDataStorage ( )
end
2020-04-13 19:30:15 +02:00
data = MapDataStorage ( )
for i = 1 , # indices do
local vi = indices [ i ]
local v = values [ i ]
data : setAtI ( vi , v )
end
return data
end
2020-04-13 18:11:34 +02:00
moremesecons.MapDataStorage = MapDataStorage
2020-04-13 19:30:15 +02:00
-- 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
2020-04-29 12:43:29 +02:00
t = modstorage : get_string ( name )
2020-04-13 19:30:15 +02:00
if t and t ~= " " then
return MapDataStorage.deserialize ( t )
end
return MapDataStorage ( )
end
2021-03-08 19:34:21 +01:00
--[[
2020-04-13 18:11:34 +02:00
-- 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
2021-03-07 19:19:00 +01:00
for pos in data : iter ( { x =- 4 , y =- 4 , z =- 4 } , { x = 2 , y = 2 , z = 2 } ) do
2020-04-13 18:11:34 +02:00
i = i + 1
assert ( vector.equals ( pos , expected_positions [ i ] ) )
end
print ( " Test if iter works correctly on a corner " )
local found = false
2021-03-07 19:19:00 +01:00
for pos in data : iter ( { x =- 8 , y =- 7 , z =- 80 } , { x =- 5 , y =- 5 , z =- 5 } ) do
2020-04-13 18:11:34 +02:00
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
2021-03-08 19:34:21 +01:00
do_test ( )
--]]