2022-04-16 03:24:41 +02:00

892 lines
29 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- 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
current_perlin.nodetype = 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
local nodetypes = {
{ S("Solid Nodes"), "perlin_explorer:node", "perlin_explorer:node_negative" },
{ S("Grid Nodes"), "perlin_explorer:grid", "perlin_explorer:grid_negative" },
{ S("Minibox Nodes"), "perlin_explorer:mini", "perlin_explorer:mini_negative" },
}
minetest.register_node("perlin_explorer:node", {
description = S("Solid 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("Solid 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_node("perlin_explorer:grid", {
description = S("Grid Perlin Test Node"),
paramtype = "light",
drawtype = "allfaces",
use_texture_alpha = "clip",
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_grid.png"},
palette = "perlin_explorer_node_palette.png",
groups = { dig_immediate = 3 },
drop = "perlin_explorer:grid",
})
minetest.register_node("perlin_explorer:grid_negative", {
description = S("Grid Negative Perlin Test Node"),
paramtype = "light",
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_grid_neg.png"},
use_texture_alpha = "clip",
palette = "perlin_explorer_node_palette_neg.png",
groups = { dig_immediate = 3 },
drop = "perlin_explorer:grid_negative",
})
minetest.register_node("perlin_explorer:mini", {
description = S("Minibox Perlin Test Node"),
paramtype = "light",
drawtype = "nodebox",
climbable = true,
walkable = false,
node_box = {
type = "fixed",
fixed = { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
},
use_texture_alpha = "clip",
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_mini.png"},
palette = "perlin_explorer_node_palette.png",
groups = { dig_immediate = 3 },
drop = "perlin_explorer:mini",
})
minetest.register_node("perlin_explorer:mini_negative", {
description = S("Minibox Negative Perlin Test Node"),
paramtype = "light",
drawtype = "nodebox",
climbable = true,
walkable = false,
node_box = {
type = "fixed",
fixed = { -2/16, -2/16, -2/16, 2/16, 2/16, 2/16 },
},
sunlight_propagates = true,
paramtype2 = paramtype2,
tiles = {"perlin_explorer_mini_neg.png"},
use_texture_alpha = "clip",
palette = "perlin_explorer_node_palette_neg.png",
groups = { dig_immediate = 3 },
drop = "perlin_explorer:mini_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 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
local content_test_node = minetest.get_content_id(nodetypes[current_perlin.nodetype][2])
local content_test_node_negative = minetest.get_content_id(nodetypes[current_perlin.nodetype][3])
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("<dimensions> <sidelen> <minval> <maxval>"),
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("<octaves> <offset> <scale> <spread_x> <spread_y> <spread_z> <persistence> <lacunarity> <seed>"),
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("<pos> <size>"),
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 nodetype_index = (current_perlin.nodetype)
local nodetypes_list = {}
for i=1, #nodetypes do
table.insert(nodetypes_list, F(nodetypes[i][1]))
end
local nodetypes_list_str = table.concat(nodetypes_list, ",")
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..[[]
dropdown[6.55,0.75;2,0.75;nodetype;]]..nodetypes_list_str..[[;]]..nodetype_index..[[;true]
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)
local nodetype = tonumber(fields.nodetype)
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 and nodetype) 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
current_perlin.nodetype = nodetype
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)