-- If true, the Perlin test nodes will support color -- (set to false in case of performance problems) local COLORIZE_NODES = true -- The number of available test node colors is divided by this number. -- A number between 1 to 256 -- * 1 = Full 256 palette. -- * 2 = 128 colors. -- * 4 = 64 colors. -- etc. -- Higher values lead to less colors but increased performance. -- This value is only used for performance reason, because Minetest -- has a sharp performance drop when there are many different node colors on screen. -- Consider removing this one when Minetest's performance problem has been solved. local COLOR_PRECISION = 4 -- Time to wait in seconds before checking and generating new nodes in autobuild mode. local AUTOBUILD_UPDATE_TIME = 1.0 -- x/y/z size of chunks to generate in autobuild mode. local AUTOBUILD_SIZE = 16 -- Amount of chunks to generate around player local AUTOBUILD_CHUNKDIST = 2 -- Color of the formspec box[] element local FORMSPEC_BOX_COLOR = "#00000080" -- Color for the section titles in the formspec local FORMSPEC_HEADER_COLOR = "#000000FF" local S = minetest.get_translator("perlin_explorer") local F = minetest.formspec_escape -- Per-player formspec states (mostly for remembering checkbox states) local formspec_states = {} -- List of noise parameters profiles local np_profiles = {} local default_noiseparams = { offset = 0.0, scale = 1.0, spread = vector.new(10, 10, 10), seed = 0, octaves = 2, persistence = 0.5, lacunarity = 2.0, flags = "noeased,noabsvalue", } -- Holds the currently used Perlin noise local current_perlin = {} -- holds the current PerlinNoise object current_perlin.noise = nil current_perlin.noiseparams = table.copy(default_noiseparams) table.insert(np_profiles, current_perlin.noiseparams) -- Side length of calculated perlin area current_perlin.size = 64 -- Theoretical min and max values for Perlin noise (for colorization) current_perlin.min = -1 current_perlin.max = 1 -- dimensions of current Perlin noise (2 or 3) current_perlin.dimensions = 2 -- If greater than 1, the Perlin noise values are "pixelized". Noise values at -- coordinates not divisible by sidelen will be set equal to the noise value -- of the nearest number (counting downwards) that is divisible by sidelen. -- This is (kind of) analogous to the "sidelen" parameter of mapgen decorations. current_perlin.sidelen = 1 current_perlin.pos = {} current_perlin.auto_build = true current_perlin.loaded_areas = {} ------------ -- Helper functions -- Reduce the pos coordinates down to the closest numbers divisible by sidelen local sidelen_pos = function(pos, sidelen) local newpos = table.copy(pos) if sidelen <= 1 then return newpos end newpos.x = newpos.x - newpos.x % sidelen newpos.y = newpos.y - newpos.y % sidelen newpos.z = newpos.z - newpos.z % sidelen return newpos end -- Sets the currently active Perlin noise. -- * noiseparams: NoiseParams table (see Minetest's Lua API documentation) local set_perlin_noise = function(noiseparams) current_perlin.noise = PerlinNoise(noiseparams) current_perlin.noiseparams = noiseparams end -- Test nodes to generate a map based on a perlin noise local paramtype2 if COLORIZE_NODES then paramtype2 = "color" end minetest.register_node("perlin_explorer:node", { description = S("Perlin Test Node"), paramtype = "light", sunlight_propagates = true, paramtype2 = paramtype2, tiles = {"perlin_explorer_node.png"}, palette = "perlin_explorer_node_palette.png", groups = { dig_immediate = 3 }, -- Force-drop without metadata to avoid spamming the inventory drop = "perlin_explorer:node", }) minetest.register_node("perlin_explorer:node_negative", { description = S("Negative Perlin Test Node"), paramtype = "light", sunlight_propagates = true, paramtype2 = paramtype2, tiles = {"perlin_explorer_node_neg.png"}, palette = "perlin_explorer_node_palette_neg.png", groups = { dig_immediate = 3 }, -- Force-drop without metadata to avoid spamming the inventory drop = "perlin_explorer:node_negative", }) minetest.register_tool("perlin_explorer:getter", { description = S("Perlin Value Getter"), _tt_help = S("Punch a node to display the Perlin noise value at this position"), inventory_image = "perlin_explorer_getter.png", wield_image = "perlin_explorer_getter.png", groups = { disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) if not user then return end local privs = minetest.get_player_privs(user:get_player_name()) if not privs.server then minetest.chat_send_player(user:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server")) return end if current_perlin.noise then if pointed_thing.type ~= "node" then -- No-op for non-nodes return end local pos = pointed_thing.under local getpos = sidelen_pos(pos, current_perlin.sidelen) local val if current_perlin.dimensions == 2 then val = current_perlin.noise:get_2d({x=getpos.x, y=getpos.z}) elseif current_perlin.dimensions == 3 then val = current_perlin.noise:get_3d(getpos) else minetest.chat_send_player(user:get_player_name(), S("Unknown/invalid number of Perlin noise dimensions. Use /generate_perlin first!")) end minetest.chat_send_player(user:get_player_name(), S("pos=@1, value=@2", minetest.pos_to_string(pos), val)) else local msg = S("No Perlin noise set. Set one first!") minetest.chat_send_player(user:get_player_name(), msg) end end, }) minetest.register_tool("perlin_explorer:seeder", { description = S("Random Perlin seed setter"), _tt_help = S("Set a random Perlin seed for the current Perlin noise params"), inventory_image = "perlin_explorer_seeder.png", wield_image = "perlin_explorer_seeder.png", groups = { disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) if not user then return end local privs = minetest.get_player_privs(user:get_player_name()) if not privs.server then minetest.chat_send_player(user:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server")) return end if current_perlin.noise then local noiseparams = table.copy(current_perlin.noiseparams) noiseparams.seed = math.random(0, 2^32-1) set_perlin_noise(noiseparams) current_perlin.loaded_areas = {} local msg = S("New random seed set!") minetest.chat_send_player(user:get_player_name(), msg) else local msg = S("No Perlin noise set. Set one first!") minetest.chat_send_player(user:get_player_name(), msg) end end, }) local CONTENT_TEST_NODE = minetest.get_content_id("perlin_explorer:node") local CONTENT_TEST_NODE_NEGATIVE = minetest.get_content_id("perlin_explorer:node_negative") local update_map = function(pos, set_nodes) local stats if not current_perlin.noise then return end local size_v = vector.new(current_perlin.size, current_perlin.size, current_perlin.size) local startpos = pos local endpos = vector.add(startpos, current_perlin.size-1) local y_max = endpos.y - startpos.y if current_perlin.dimensions == 2 then y_max = 0 startpos.y = pos.y endpos.y = pos.y end local vmanip, emin, emax, vdata, vdata2, varea if set_nodes then vmanip = VoxelManip(startpos, endpos) emin, emax = vmanip:get_emerged_area() vdata = vmanip:get_data() vdata2 = vmanip:get_param2_data() varea = VoxelArea:new({MinEdge = emin, MaxEdge = emax}) end stats = {} stats.avg = 0 local sum_of_values = 0 local value_count = 0 for x=0, endpos.x - startpos.x do for y=0, y_max do for z=0, endpos.z - startpos.z do -- Get Perlin value at current pos local relpos = vector.new(x,y,z) local abspos = vector.add(startpos, relpos) local abspos_get = sidelen_pos(abspos, current_perlin.sidelen) local perlin_value if current_perlin.dimensions == 2 then perlin_value = current_perlin.noise:get_2d({x=abspos_get.x, y=abspos_get.z}) elseif current_perlin.dimensions == 3 then perlin_value = current_perlin.noise:get_3d(abspos_get) else error("[perlin_explorer] Unknown/invalid number of Perlin noise dimensions!") return end -- Statistics if not stats.min then stats.min = perlin_value elseif perlin_value < stats.min then stats.min = perlin_value end if not stats.max then stats.max = perlin_value elseif perlin_value > stats.max then stats.max = perlin_value end sum_of_values = sum_of_values + perlin_value value_count = value_count + 1 -- Calculate color (param2) for node local zeropoint = 0 local min_size = zeropoint - current_perlin.min local max_size = current_perlin.max - zeropoint local node_param2 if perlin_value >= zeropoint then node_param2 = (math.abs(perlin_value) / max_size) * 255 else node_param2 = (math.abs(perlin_value) / min_size) * 255 end node_param2 = math.floor(math.abs(node_param2)) node_param2 = math.max(0, math.min(255, node_param2)) if node_param2 < 255 then node_param2 = node_param2 - (node_param2 % COLOR_PRECISION) end if set_nodes then -- Get vmanip index local index = varea:indexp(abspos) if not index then return end -- Set node and param2 if perlin_value >= zeropoint then vdata[index] = CONTENT_TEST_NODE vdata2[index] = node_param2 else if current_perlin.show_negative == true then vdata[index] = CONTENT_TEST_NODE_NEGATIVE vdata2[index] = node_param2 else vdata[index] = minetest.CONTENT_AIR vdata2[index] = 0 end end end end end end stats.avg = sum_of_values / value_count if set_nodes then -- Set vmanip, return stats vmanip:set_data(vdata) vmanip:set_param2_data(vdata2) vmanip:write_to_map() end return stats end -- Creates and demonstrates a Perlin noise. -- * pos: Where the Perlin noise starts -- * options: table with: -- * dimensions: number of Perlin noise dimensions (2 or 3) -- * size: side length of area/volume to calculate) -- * show_negative: if true, places nodes for negative Perlin values (default: true for 2 dimensions and false for 3 dimensions) -- * set_nodes: if true, will set nodes, otherwise it's a "dry run" local create_perlin = function(pos, options) if not current_perlin.noise then return false end current_perlin.dimensions = options.dimensions current_perlin.size = options.size current_perlin.show_negative = options.show_negative if current_perlin.show_negative == nil then if current_perlin.dimensions == 2 then current_perlin.show_negative = true elseif current_perlin.dimensions == 3 then current_perlin.show_negative = false end end local cpos = table.copy(pos) local mpos = vector.round(cpos) current_perlin.pos = mpos local set_nodes = options.set_nodes ~= false local stats = update_map(mpos, set_nodes) -- Show a particle in the center of the newly generated area local center = vector.new() center.x = mpos.x + options.size/2 center.y = mpos.y + options.size/2 center.z = mpos.z + options.size/2 minetest.add_particle({ pos = center, expirationtime = 4, size = 16, texture = "perlin_explorer_new_chunk_particle.png", glow = minetest.LIGHT_MAX, }) if stats then return string.format("Perlin noise created! Stats: min. value=%.3f, max. value=%.3f, avg. value=%.3f", stats.min, stats.max, stats.avg) end end minetest.register_chatcommand("perlin_set_options", { privs = { server = true }, description = S("Set Perlin options"), params = S(" "), func = function(name, param) local dimensions, sidelen, min, max = string.match(param, "([23]) ([0-9]+) ([0-9.-]+) ([0-9.-]+)") if not dimensions then return false end dimensions = tonumber(dimensions) sidelen = tonumber(sidelen) min = tonumber(min) max = tonumber(max) if not dimensions or not sidelen or not min or not max then return false, S("Invalid parameter type.") end current_perlin.dimensions = dimensions current_perlin.sidelen = sidelen current_perlin.min = min current_perlin.max = max current_perlin.loaded_areas = {} return true, S("Perlin options set!") end, }) minetest.register_chatcommand("perlin_set_noise", { privs = { server = true }, description = S("Set Perlin noise parameters"), params = S(" "), func = function(name, param) local octaves, offset, scale, sx, sy, sz, persistence, lacunarity, seed = string.match(param, string.rep("([0-9.-]+) ", 8) .. "([0-9.-]+)") if not octaves then return false end octaves = tonumber(octaves) offset = tonumber(offset) sx = tonumber(sx) sy = tonumber(sy) sz = tonumber(sz) persistence = tonumber(persistence) lacunarity = tonumber(lacunarity) seed = tonumber(seed) if not octaves or not offset or not sx or not sy or not sz or not persistence or not lacunarity or not seed then return false, S("Invalid parameter type.") end set_perlin_noise({ octaves = octaves, offset = offset, scale = scale, spread = { x = sx, y = sy, z = sz }, persistence = persistence, lacunarity = lacunarity, seed = seed, }) current_perlin.loaded_areas = {} return true, S("Perlin noise set!") end, }) minetest.register_chatcommand("perlin_generate", { privs = { server = true }, description = S("Generate Perlin noise"), params = S(" "), func = function(name, param) local x, y, z, size = string.match(param, "([0-9.-]+) ([0-9.-]+) ([0-9.-]+) ([0-9]+)") if not x then return false end x = tonumber(x) y = tonumber(y) z = tonumber(z) size = tonumber(size) if not x or not y or not z or not size then return false end if not x or not y or not z or not size then return false, S("Invalid parameter type.") end local pos = vector.new(x, y, z) minetest.chat_send_player(name, S("Creating Perlin noise, please wait …")) local msg = create_perlin(pos, {dimensions=current_perlin.dimensions, size=size}) if msg == false then return false, S("No Perlin noise set. Set one with '/perlin_set_noise' first!") end return true, msg end, }) local build_flags_string = function(eased, absvalue) local flags = "" if eased then flags = "eased" else flags = "noeased" end if absvalue then flags = flags .. ",absvalue" else flags = flags .. ",noabsvalue" end return flags end local parse_flags_string = function(flags) local ftable = string.split(flags, ",") local eased, absvalue = false, false for f=1, #ftable do local s = string.trim(ftable[f]) if s == "eased" then eased = true elseif s == "absvalue" then absvalue = true end end return { eased = eased, absvalue = absvalue } end local show_formspec = function(player, noiseparams, profile_id) local np if noiseparams then np = noiseparams else np = current_perlin.noiseparams end if not profile_id then profile_id = 1 end local offset = tostring(np.offset or "") local scale = tostring(np.scale or "") local seed = tostring(np.seed or "") local sx, sy, sz = "", "", "" if np.spread then sx = tostring(np.spread.x or "") sy = tostring(np.spread.y or "") sz = tostring(np.spread.z or "") end local octaves = tostring(np.octaves or "") local persistence = tostring(np.persistence or "") local lacunarity = tostring(np.lacunarity or "") local size = tostring(current_perlin.size or "") local sidelen = tostring(current_perlin.sidelen or "") local pos_x = tostring(current_perlin.pos.x or "") local pos_y = tostring(current_perlin.pos.y or "") local pos_z = tostring(current_perlin.pos.z or "") local value_min = tostring(current_perlin.min or "") local value_max = tostring(current_perlin.max or "") local flags = np.flags local flags_table = parse_flags_string(flags) local eased = tostring(flags_table.eased) local absvalue = tostring(flags_table.absvalue) local noiseparams_list = {} for i=1, #np_profiles do table.insert(noiseparams_list, S("Profile @1", i)) end local noiseparams_list_str = table.concat(noiseparams_list, ",") local dimensions_index = (current_perlin.dimensions or 2) - 1 local delete_btn = "" if #np_profiles > 1 then delete_btn = "button[7.25,0.5;2.0,0.5;delete_np_profile;"..F(S("Delete")).."]" end local form = [[ formspec_version[4]size[10,12.5] container[0.25,0.25] box[0,0;9.5,5.5;]]..FORMSPEC_BOX_COLOR..[[] box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR..[[] label[0.15,0.2;]]..F(S("Noise parameters"))..[[] container[0.0,0.5] dropdown[0.25,0;2,0.5;np_profiles;]]..noiseparams_list_str..[[;]]..profile_id..[[;true] button[3.25,0;2.0,0.5;add_np_profile;]]..F(S("Add"))..[[] button[5.25,0;2.0,0.5;load_np_profile;]]..F(S("Load"))..[[] ]]..delete_btn..[[ container_end[] container[0.0,1.5] field[0.25,0;2,0.75;offset;]]..F(S("Offset"))..[[;]]..offset..[[] field[3.25,0;2,0.75;scale;]]..F(S("Scale"))..[[;]]..scale..[[] field[6.25,0;2,0.75;seed;]]..F(S("Seed"))..[[;]]..seed..[[] image_button[8.35,0.0;0.75,0.75;perlin_explorer_seeder.png;set_random_seed;] field[0.25,1.2;2,0.75;spread_x;]]..F(S("X Spread"))..[[;]]..sx..[[] field[3.25,1.2;2,0.75;spread_y;]]..F(S("Y Spread"))..[[;]]..sy..[[] field[6.25,1.2;2,0.75;spread_z;]]..F(S("Z Spread"))..[[;]]..sz..[[] field[0.25,2.4;2,0.75;octaves;]]..F(S("Octaves"))..[[;]]..octaves..[[] field[3.25,2.4;2,0.75;persistence;]]..F(S("Persistence"))..[[;]]..persistence..[[] field[6.25,2.4;2,0.75;lacunarity;]]..F(S("Lacunarity"))..[[;]]..lacunarity..[[] checkbox[0.25,3.55;eased;]]..F(S("eased"))..[[;]]..eased..[[] checkbox[3.25,3.55;absvalue;]]..F(S("absvalue"))..[[;]]..absvalue..[[] container_end[] field_close_on_enter[offset;false] field_close_on_enter[scale;false] field_close_on_enter[seed;false] field_close_on_enter[spread_x;false] field_close_on_enter[spread_y;false] field_close_on_enter[spread_z;false] field_close_on_enter[octaves;false] field_close_on_enter[persistence;false] field_close_on_enter[lacunarity;false] field_close_on_enter[sidelen;false] field_close_on_enter[pos_x;false] field_close_on_enter[pos_y;false] field_close_on_enter[pos_z;false] field_close_on_enter[value_min;false] field_close_on_enter[value_max;false] tooltip[set_random_seed;]]..F(S("Random seed"))..[[] container_end[] container[0.25,6.0] box[0,0;9.5,1.6;]]..FORMSPEC_BOX_COLOR..[[] box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR..[[] label[0.15,0.2;]]..F(S("Noise options"))..[[] dropdown[0.25,0.7;1,0.75;dimensions;]]..F(S("2D"))..[[,]]..F(S("3D"))..[[;]]..dimensions_index..[[;true] field[2.25,0.7;2,0.75;sidelen;]]..F(S("Pixelization"))..[[;]]..sidelen..[[] tooltip[sidelen;]]..F(S("If higher than 1, Perlin values will be repeated along all axes every x nodes, for a ‘pixelized’ effect."))..[[] container_end[] container[0.25,7.85] box[0,0;9.5,2.9;]]..FORMSPEC_BOX_COLOR..[[] box[0,0;9.5,0.4;]]..FORMSPEC_HEADER_COLOR..[[] label[0.15,0.2;]]..F(S("Node generation"))..[[] field[0.25,0.75;2,0.75;value_min;]]..F(S("Min. color at"))..[[;]]..value_min..[[] field[2.35,0.75;2,0.75;value_max;]]..F(S("Max. color at"))..[[;]]..value_max..[[] field[0.25,1.95;2,0.75;pos_x;]]..F(S("X"))..[[;]]..pos_x..[[] field[2.35,1.95;2,0.75;pos_y;]]..F(S("Y"))..[[;]]..pos_y..[[] field[4.45,1.95;2,0.75;pos_z;]]..F(S("Z"))..[[;]]..pos_z..[[] field[6.55,1.95;2,0.75;size;]]..F(S("Size"))..[[;]]..size..[[] tooltip[value_min;]]..F(S("The Perlin value at which the node color gradient begins. Must be lower than 0."))..[[] tooltip[value_max;]]..F(S("The Perlin value at which the node color gradient ends. Must be higher than 0."))..[[] container_end[] container[0,10.95] button[0.5,0;3,1;apply;]]..F(S("Apply"))..[[] button[3.5,0;3,1;create;]]..F(S("Apply and create"))..[[] button_exit[6.5,0;3,1;close;]]..F(S("Close"))..[[] container_end[] ]] minetest.show_formspec(player:get_player_name(), "perlin_explorer:creator", form) end minetest.register_on_player_receive_fields(function(player, formname, fields) if formname ~= "perlin_explorer:creator" then return end -- Require 'server' priv local privs = minetest.get_player_privs(player:get_player_name()) if not privs.server then return end -- Handle checkboxes local name = player:get_player_name() local flags_touched = false if fields.eased == "true" then formspec_states[name].eased = true return elseif fields.eased == "false" then formspec_states[name].eased = false return end if fields.absvalue == "true" then formspec_states[name].absvalue = true return elseif fields.absvalue == "false" then formspec_states[name].absvalue = false return end -- Deleting a profile does not require any other field if fields.delete_np_profile then if #np_profiles <= 1 then return end local profile_to_delete = tonumber(fields.np_profiles) table.remove(np_profiles, profile_to_delete) local new_id = math.max(1, profile_to_delete - 1) show_formspec(player, default_noiseparams, new_id) return end -- Handle other fields local do_apply = fields.apply ~= nil local do_create = fields.create ~= nil or fields.key_enter_field ~= nil if (do_create or do_apply or fields.add_np_profile or fields.np_profiles) then if fields.offset and fields.scale and fields.seed and fields.spread_x and fields.spread_y and fields.spread_z and fields.octaves and fields.persistence and fields.lacunarity then current_perlin.loaded_areas = {} local offset = tonumber(fields.offset) local scale = tonumber(fields.scale) local seed = tonumber(fields.seed) local sx = tonumber(fields.spread_x) local sy = tonumber(fields.spread_y) local sz = tonumber(fields.spread_z) if not sx or not sy or not sz then return end local spread = vector.new(sx, sy, sz) local octaves = tonumber(fields.octaves) local persistence = tonumber(fields.persistence) local lacunarity = tonumber(fields.lacunarity) local dimensions = tonumber(fields.dimensions) local sidelen = tonumber(fields.sidelen) local px = tonumber(fields.pos_x) local py = tonumber(fields.pos_y) local pz = tonumber(fields.pos_z) local size = tonumber(fields.size) local value_min = tonumber(fields.value_min) local value_max = tonumber(fields.value_max) if (offset and scale and spread and octaves and persistence) then local eased = formspec_states[name].eased local absvalue = formspec_states[name].absvalue local noiseparams = { offset = offset, scale = scale, seed = seed, spread = spread, octaves = octaves, persistence = persistence, lacunarity = lacunarity, flags = build_flags_string(eased, absvalue), } -- Change NP profile selection if fields.load_np_profile and fields.np_profiles then local profile = tonumber(fields.np_profiles) local loaded_np = np_profiles[profile] -- Load new profile show_formspec(player, loaded_np, profile) minetest.log("action", "[perlin_explorer] Loaded perlin noise profile "..profile) return -- Add new profile and save current noiseparams to it elseif fields.add_np_profile then table.insert(np_profiles, noiseparams) local new_profile = #np_profiles minetest.log("action", "[perlin_explorer] Perlin noise profile "..new_profile.." added!") show_formspec(player, noiseparams, new_profile) return elseif fields.set_random_seed then -- Randomize seed local profile = tonumber(fields.np_profiles) noiseparams.seed = math.random(0, 2^32-1) show_formspec(player, noiseparams, profile) return end if not (dimensions and sidelen and size and value_min and value_max) then return end -- Convert dropdown index to actual dimensions number dimensions = dimensions + 1 -- Spread is used differently in 2D if dimensions == 2 then spread.y = spread.z end set_perlin_noise(noiseparams) minetest.log("action", "[perlin_explorer] Perlin noise set!") current_perlin.dimensions = dimensions current_perlin.size = size current_perlin.sidelen = sidelen current_perlin.min = value_min current_perlin.max = value_max if do_create then if not px or not py or not pz then return end local place_pos = vector.new(px, py, pz) local msg = S("Creating Perlin noise, please wait …") minetest.chat_send_player(name, msg) msg = create_perlin(place_pos, {dimensions=dimensions, size=size}) if msg then minetest.chat_send_player(name, msg) elseif msg == false then minetest.log("error", "[perlin_explorer] Error generating Perlin noise nodes!") end end end end end end) minetest.register_tool("perlin_explorer:creator", { description = S("Perlin Noise Creator"), _tt_help = S("Punch to open the Perlin noise creation menu"), inventory_image = "perlin_explorer_creator.png", wield_image = "perlin_explorer_creator.png", groups = { disable_repair = 1 }, on_use = function(itemstack, user, pointed_thing) if not user then return end local privs = minetest.get_player_privs(user:get_player_name()) if not privs.server then minetest.chat_send_player(user:get_player_name(), S("Insufficient privileges! You need the @1 privilege to use this tool.", "server")) return end show_formspec(user) end, }) local timer = 0 minetest.register_globalstep(function(dtime) timer = timer + dtime if timer < AUTOBUILD_UPDATE_TIME then return end timer = 0 if current_perlin.noise and current_perlin.auto_build then local player = minetest.get_player_by_name("singleplayer") if not player then return end local build = function(pos, player_name) if not pos or not pos.x or not pos.y or not pos.z then minetest.log("error", "[perlin_explorer] build(): Invalid pos!") return end local hash = minetest.hash_node_position(pos) if not current_perlin.loaded_areas[hash] then local msg = create_perlin(pos, { dimensions = current_perlin.dimensions, size = AUTOBUILD_SIZE, }) minetest.chat_send_player(player_name, msg) current_perlin.loaded_areas[hash] = true end end local pos = vector.round(player:get_pos()) pos = sidelen_pos(pos, AUTOBUILD_SIZE) local neighbors = { vector.new(0, 0, 0) } local c = AUTOBUILD_CHUNKDIST local cc = c if current_perlin.dimensions == 2 then cc = 0 end for cx=-c, c do for cy=-cc, cc do for cz=-c, c do table.insert(neighbors, vector.new(cx, cy, cz)) end end end for n=1, #neighbors do local offset = vector.multiply(neighbors[n], AUTOBUILD_SIZE) local npos = vector.add(pos, offset) build(npos, player:get_player_name()) end end end) minetest.register_on_joinplayer(function(player) local name = player:get_player_name() formspec_states[name] = { eased = false, } end) minetest.register_on_leaveplayer(function(player) local name = player:get_player_name() formspec_states[name] = nil end)