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, 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 pairsfunc = pairs(self) local function iterfunc() local vi, v = pairsfunc() 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 end, } moremesecons.MapDataStorage = MapDataStorage -- 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, v 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, v 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() if not vector.get_data_from_pos then function vector.get_data_from_pos(tab, z,y,x) local data = tab[z] if data then data = data[y] if data then return data[x] end end end end if not vector.set_data_to_pos then function vector.set_data_to_pos(tab, z,y,x, data) if tab[z] then if tab[z][y] then tab[z][y][x] = data return end tab[z][y] = {[x] = data} return end tab[z] = {[y] = {[x] = data}} end end if not vector.remove_data_from_pos then function vector.remove_data_from_pos(tab, z,y,x) if vector.get_data_from_pos(tab, z,y,x) == nil then return end tab[z][y][x] = nil if not next(tab[z][y]) then tab[z][y] = nil end if not next(tab[z]) then tab[z] = nil end end end if not vector.unpack then function vector.unpack(pos) return pos.z, pos.y, pos.x end end