From 3cf1a2318f361eb7a235376cf75b91f6748d6c1e Mon Sep 17 00:00:00 2001 From: Ombridride Date: Mon, 2 Mar 2015 23:59:23 +0100 Subject: [PATCH] Update Worldedit --- mods/WorldEdit/.gitignore | 2 + .../{Chat Commands.md => ChatCommands.md} | 6 + mods/WorldEdit/README.md | 36 +- mods/WorldEdit/Tutorial.md | 4 +- mods/WorldEdit/WorldEdit API.md | 71 +- mods/WorldEdit/config.ld | 12 + mods/WorldEdit/worldedit/code.lua | 122 +- mods/WorldEdit/worldedit/common.lua | 114 + mods/WorldEdit/worldedit/compatibility.lua | 97 +- mods/WorldEdit/worldedit/init.lua | 69 +- mods/WorldEdit/worldedit/manipulations.lua | 1328 +++++----- mods/WorldEdit/worldedit/primitives.lua | 743 ++---- mods/WorldEdit/worldedit/serialization.lua | 512 ++-- mods/WorldEdit/worldedit/visualization.lua | 273 +- mods/WorldEdit/worldedit_commands/init.lua | 2249 +++++++++-------- mods/WorldEdit/worldedit_commands/mark.lua | 323 +-- mods/WorldEdit/worldedit_commands/safe.lua | 130 +- .../WorldEdit/worldedit_gui/functionality.lua | 1314 +++++----- mods/WorldEdit/worldedit_gui/init.lua | 5 +- mods/WorldEdit/worldedit_infinity/depends.txt | 1 + mods/WorldEdit/worldedit_infinity/init.lua | 206 +- .../worldedit_shortcommands/depends.txt | 2 +- .../worldedit_shortcommands/init.lua | 98 +- 23 files changed, 3770 insertions(+), 3947 deletions(-) create mode 100755 mods/WorldEdit/.gitignore rename mods/WorldEdit/{Chat Commands.md => ChatCommands.md} (92%) create mode 100755 mods/WorldEdit/config.ld create mode 100755 mods/WorldEdit/worldedit/common.lua create mode 100755 mods/WorldEdit/worldedit_infinity/depends.txt diff --git a/mods/WorldEdit/.gitignore b/mods/WorldEdit/.gitignore new file mode 100755 index 00000000..5236e1e4 --- /dev/null +++ b/mods/WorldEdit/.gitignore @@ -0,0 +1,2 @@ +*~ + diff --git a/mods/WorldEdit/Chat Commands.md b/mods/WorldEdit/ChatCommands.md similarity index 92% rename from mods/WorldEdit/Chat Commands.md rename to mods/WorldEdit/ChatCommands.md index 07bcfd61..0c8b3a11 100755 --- a/mods/WorldEdit/Chat Commands.md +++ b/mods/WorldEdit/ChatCommands.md @@ -98,6 +98,12 @@ Display the volume of the current WorldEdit region. //volume +### `//deleteblocks` + +Delete the MapBlocks (16x16x16 units) that contain the selected region. This means that mapgen will be invoked for that area. As only whole MapBlocks get removed, the deleted area is usually larger than the selected one. Also, mapgen can trigger mechanisms like mud reflow or cavegen, which affects nodes (up to 112 nodes away) outside the MapBlock, so dont use this near buildings. + + //deleteblocks + ### `//set ` Set the current WorldEdit region to ``. diff --git a/mods/WorldEdit/README.md b/mods/WorldEdit/README.md index 224059e8..22dd7c74 100755 --- a/mods/WorldEdit/README.md +++ b/mods/WorldEdit/README.md @@ -1,4 +1,4 @@ -WorldEdit v1.0 for Minetest 0.4.8+ +WorldEdit v1.1 for Minetest 0.4.8+ ================================== The ultimate in-game world editing tool for [Minetest](http://minetest.net/)! Tons of functionality to help with building, fixing, and more. @@ -41,9 +41,9 @@ Interface --------- WorldEdit is accessed in-game in two main ways. -The GUI adds a screen to each player's inventory that gives access to various WorldEdit functions. The [tutorial](Tutorial.md) and the [Chat Commands Reference](Chat Commands.md) may be helpful in learning to use it. +The GUI adds a screen to each player's inventory that gives access to various WorldEdit functions. The [tutorial](Tutorial.md) and the [Chat Commands Reference](ChatCommands.md) may be helpful in learning to use it. -The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](Chat Commands.md). +The chat interface adds many chat commands that perform various WorldEdit powered tasks. It is documented in the [Chat Commands Reference](ChatCommands.md). Compatibility ------------- @@ -109,20 +109,24 @@ WorldEdit supports two different types of schematics. The first is the WorldEdit Schematic format, with the file extension ".we", and in some older versions, ".wem". There have been several previous versions of the WorldEdit Schematic format, but WorldEdit is capable of loading any past versions, and will always support them - there is no need to worry about schematics becoming obselete. -The current version of the WorldEdit Schematic format, internally known as version 4, is essentially an array of node data tables in Lua 5.2 table syntax. Specifically: +As of version 5, WorldEdit schematics include a header. The header is seperated from the content by a colon (`:`). It contains fields seperated by commas (`,`). Currently only one field is used, which contains the version in ASCII decimal. + +The current version of the WorldEdit Schematic format is essentially an array of node data tables in Lua 5.1 table syntax preceded by a header. +Specifically it looks like this: + + 5:return { + { + y = , + x = , + z = , + name = , + param1 = , + param2 = , + meta = , + }, + <...> + } - return { - { - ["y"] = , - ["x"] = , - ["name"] = , - ["z"] = , - ["meta"] = , - ["param2"] = , - ["param1"] = , - }, - <...> - } The ordering of the values and minor aspects of the syntax, such as trailing commas or newlines, are not guaranteed to stay the same in future versions. diff --git a/mods/WorldEdit/Tutorial.md b/mods/WorldEdit/Tutorial.md index abe554ad..1ed39983 100755 --- a/mods/WorldEdit/Tutorial.md +++ b/mods/WorldEdit/Tutorial.md @@ -107,7 +107,7 @@ Step 4: Other commands ---------------------- ### Chat Commands -There are many more commands than what is shown here. See the [Chat Commands Reference](Chat Commands.md) for a detailed list of them, along with descriptions and examples for every single one. +There are many more commands than what is shown here. See the [Chat Commands Reference](ChatCommands.md) for a detailed list of them, along with descriptions and examples for every single one. If you're in-game and forgot how a command works, just use the `/help ` command, without the first forward slash. For example, to see some information about the `//set ` command mentioned earlier, simply use `/help /set`. @@ -115,6 +115,6 @@ A very useful command to check out is the `//save ` command, which ca ### WorldEdit GUI -This only scratches the surface of what WorldEdit is capable of. Most of the functions in the WorldEdit GUI correspond to chat commands, and so the [Chat Commands Reference](Chat Commands.md) may be useful if you get stuck. +This only scratches the surface of what WorldEdit is capable of. Most of the functions in the WorldEdit GUI correspond to chat commands, and so the [Chat Commands Reference](ChatCommands.md) may be useful if you get stuck. It is helpful to explore the various buttons in the interface and check out what they do. Learning the chat command interface is also useful if you use WorldEdit intensively - an experienced chat command user can usually work faster than an experienced WorldEdit GUI user. \ No newline at end of file diff --git a/mods/WorldEdit/WorldEdit API.md b/mods/WorldEdit/WorldEdit API.md index 8f4b7d23..f50b5062 100755 --- a/mods/WorldEdit/WorldEdit API.md +++ b/mods/WorldEdit/WorldEdit API.md @@ -21,9 +21,9 @@ Manipulations ------------- Contained in manipulations.lua, this module allows several node operations to be applied over a region. -### count = worldedit.set(pos1, pos2, nodename) +### count = worldedit.set(pos1, pos2, node_name) -Sets a region defined by positions `pos1` and `pos2` to `nodename`. To clear a region, use "air" as the value of `nodename`. +Sets a region defined by positions `pos1` and `pos2` to `node_name`. To clear a region, use "air" as the value of `node_name`. Returns the number of nodes set. @@ -109,51 +109,33 @@ Primitives ---------- Contained in primitives.lua, this module allows the creation of several geometric primitives. -### count = worldedit.hollow_sphere(pos, radius, nodename) +### count = worldedit.sphere(pos, radius, node_name, hollow) -Adds a hollow sphere centered at `pos` with radius `radius`, composed of `nodename`. +Adds a sphere centered at `pos` with radius `radius`, composed of `node_name`. Returns the number of nodes added. -### count = worldedit.sphere(pos, radius, nodename) +### count = worldedit.dome(pos, radius, node_name, hollow) -Adds a sphere centered at `pos` with radius `radius`, composed of `nodename`. +Adds a dome centered at `pos` with radius `radius`, composed of `node_name`. Returns the number of nodes added. -### count = worldedit.hollow_dome(pos, radius, nodename) +### count = worldedit.cylinder(pos, axis, length, radius, node_name, hollow) -Adds a hollow dome centered at `pos` with radius `radius`, composed of `nodename`. +Adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `node_name`. Returns the number of nodes added. -### count = worldedit.dome(pos, radius, nodename) - -Adds a dome centered at `pos` with radius `radius`, composed of `nodename`. - -Returns the number of nodes added. - -### count = worldedit.hollow_cylinder(pos, axis, length, radius, nodename) - -Adds a hollow cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`. - -Returns the number of nodes added. - -### count = worldedit.cylinder(pos, axis, length, radius, nodename) - -Adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`. - -Returns the number of nodes added. - -### count = worldedit.pyramid(pos, axis, height, nodename) +### count = worldedit.pyramid(pos, axis, height, node_name) Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`. Returns the number of nodes added. -### count = worldedit.spiral(pos, length, height, spacer, nodename) +### count = worldedit.spiral(pos, length, height, spacer, node_name) -Adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `nodename`. +Adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `node_name`. Returns the number of nodes added. @@ -173,15 +155,15 @@ Hides all nodes in a region defined by positions `pos1` and `pos2` by non-destru Returns the number of nodes hidden. -### count = worldedit.suppress(pos1, pos2, nodename) +### count = worldedit.suppress(pos1, pos2, node_name) -Suppresses all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes. +Suppresses all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes. Returns the number of nodes suppressed. -### count = worldedit.highlight(pos1, pos2, nodename) +### count = worldedit.highlight(pos1, pos2, node_name) -Highlights all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes. +Highlights all instances of `node_name` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes. Returns the number of nodes found. @@ -195,29 +177,30 @@ Serialization ------------- Contained in serialization.lua, this module allows regions of nodes to be serialized and deserialized to formats suitable for use outside MineTest. -### version = worldedit.valueversion(value) +### version, extra_fields, content = worldedit.read_header(value) -Determines the version of serialized data `value`. +Reads the header from serialized data `value`. -Returns the version as a positive integer or 0 for unknown versions. +Returns the version as a positive integer (nil for unknown versions), +extra header fields (nil if not supported), and the content after the header. ### data, count = worldedit.serialize(pos1, pos2) Converts the region defined by positions `pos1` and `pos2` into a single string. -Returns the serialized data and the number of nodes serialized. +Returns the serialized data and the number of nodes serialized, or nil. -### pos1, pos2, count = worldedit.allocate(originpos, value) +### pos1, pos2, count = worldedit.allocate(origin_pos, value) -Determines the volume the nodes represented by string `value` would occupy if deserialized at `originpos`. +Determines the volume the nodes represented by string `value` would occupy if deserialized at `origin_pos`. -Returns the two corner positions and the number of nodes. +Returns the two corner positions and the number of nodes, or nil. -### count = worldedit.deserialize(originpos, value) +### count = worldedit.deserialize(origin_pos, value) -Loads the nodes represented by string `value` at position `originpos`. +Loads the nodes represented by string `value` at position `origin_pos`. -Returns the number of nodes deserialized. +Returns the number of nodes deserialized or nil. Code ---- @@ -233,4 +216,4 @@ Returns an error if the code fails or nil otherwise. Executes `code` as a Lua chunk in the global namespace with the variable `pos` available, for each node in a region defined by positions `pos1` and `pos2`. -Returns an error if the code fails or nil otherwise. \ No newline at end of file +Returns an error if the code fails or nil otherwise. diff --git a/mods/WorldEdit/config.ld b/mods/WorldEdit/config.ld new file mode 100755 index 00000000..69be2243 --- /dev/null +++ b/mods/WorldEdit/config.ld @@ -0,0 +1,12 @@ +project = "WorldEdit" +title = "WorldEdit API Documentation" +description = "Minetest mod to mass-modify nodes" +format = "markdown" +file = {"worldedit"} +topics = { + "README.md", + "Tutorial.md", + "ChatCommands.md", + "LICENSE.txt" +} + diff --git a/mods/WorldEdit/worldedit/code.lua b/mods/WorldEdit/worldedit/code.lua index 1aa90b38..a939debb 100755 --- a/mods/WorldEdit/worldedit/code.lua +++ b/mods/WorldEdit/worldedit/code.lua @@ -1,70 +1,52 @@ -worldedit = worldedit or {} -local minetest = minetest -- local copy of global - --- Copies and modifies positions `pos1` and `pos2` so that each component of --- `pos1` is less than or equal to the corresponding component of `pos2`. --- Returns the new positions. -worldedit.sort_pos = function(pos1, pos2) - pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} - pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} - if pos1.x > pos2.x then - pos2.x, pos1.x = pos1.x, pos2.x - end - if pos1.y > pos2.y then - pos2.y, pos1.y = pos1.y, pos2.y - end - if pos1.z > pos2.z then - pos2.z, pos1.z = pos1.z, pos2.z - end - return pos1, pos2 -end - --- Executes `code` as a Lua chunk in the global namespace, --- returning an error if the code fails, or nil otherwise. -worldedit.lua = function(code) - local func, err = loadstring(code) - if not func then -- Syntax error - return err - end - local good, err = pcall(operation) - if not good then -- Runtime error - return err - end - return nil -end - --- Executes `code` as a Lua chunk in the global namespace with the variable --- pos available, for each node in a region defined by positions `pos1` and --- `pos2`, returning an error if the code fails, or nil otherwise -worldedit.luatransform = function(pos1, pos2, code) - pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - local factory, err = loadstring("return function(pos) " .. code .. " end") - if not factory then -- Syntax error - return err - end - local func = factory() - - -- Keep area loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos = {x=pos1.x, y=0, z=0} - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local good, err = pcall(func, pos) - if not good then -- Runtime error - return err - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return nil -end - +--- Lua code execution functions. +-- @module worldedit.code + +--- Executes `code` as a Lua chunk in the global namespace. +-- @return An error message if the code fails, or nil on success. +function worldedit.lua(code) + local func, err = loadstring(code) + if not func then -- Syntax error + return err + end + local good, err = pcall(func) + if not good then -- Runtime error + return err + end + return nil +end + + +--- Executes `code` as a Lua chunk in the global namespace with the variable +-- pos available, for each node in a region defined by positions `pos1` and +-- `pos2`. +-- @return An error message if the code fails, or nil on success. +function worldedit.luatransform(pos1, pos2, code) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local factory, err = loadstring("return function(pos) " .. code .. " end") + if not factory then -- Syntax error + return err + end + local func = factory() + + worldedit.keep_loaded(pos1, pos2) + + local pos = {x=pos1.x, y=0, z=0} + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local good, err = pcall(func, pos) + if not good then -- Runtime error + return err + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return nil +end + diff --git a/mods/WorldEdit/worldedit/common.lua b/mods/WorldEdit/worldedit/common.lua new file mode 100755 index 00000000..be9a2c9c --- /dev/null +++ b/mods/WorldEdit/worldedit/common.lua @@ -0,0 +1,114 @@ +--- Common functions [INTERNAL]. All of these functions are internal! +-- @module worldedit.common + +--- Copies and modifies positions `pos1` and `pos2` so that each component of +-- `pos1` is less than or equal to the corresponding component of `pos2`. +-- Returns the new positions. +function worldedit.sort_pos(pos1, pos2) + pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} + pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} + if pos1.x > pos2.x then + pos2.x, pos1.x = pos1.x, pos2.x + end + if pos1.y > pos2.y then + pos2.y, pos1.y = pos1.y, pos2.y + end + if pos1.z > pos2.z then + pos2.z, pos1.z = pos1.z, pos2.z + end + return pos1, pos2 +end + + +--- Determines the volume of the region defined by positions `pos1` and `pos2`. +-- @return The volume. +function worldedit.volume(pos1, pos2) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + return (pos2.x - pos1.x + 1) * + (pos2.y - pos1.y + 1) * + (pos2.z - pos1.z + 1) +end + + +--- Gets other axes given an axis. +-- @raise Axis must be x, y, or z! +function worldedit.get_axis_others(axis) + if axis == "x" then + return "y", "z" + elseif axis == "y" then + return "x", "z" + elseif axis == "z" then + return "x", "y" + else + error("Axis must be x, y, or z!") + end +end + + +function worldedit.keep_loaded(pos1, pos2) + local manip = minetest.get_voxel_manip() + manip:read_from_map(pos1, pos2) +end + + +local mh = {} +worldedit.manip_helpers = mh + + +--- Generates an empty VoxelManip data table for an area. +-- @return The empty data table. +function mh.get_empty_data(area) + -- Fill emerged area with ignore so that blocks in the area that are + -- only partially modified aren't overwriten. + local data = {} + local c_ignore = minetest.get_content_id("ignore") + for i = 1, worldedit.volume(area.MinEdge, area.MaxEdge) do + data[i] = c_ignore + end + return data +end + + +function mh.init(pos1, pos2) + local manip = minetest.get_voxel_manip() + local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) + local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) + return manip, area +end + + +function mh.init_radius(pos, radius) + local pos1 = vector.subtract(pos, radius) + local pos2 = vector.add(pos, radius) + return mh.init(pos1, pos2) +end + + +function mh.init_axis_radius(base_pos, axis, radius) + return mh.init_axis_radius_length(base_pos, axis, radius, radius) +end + + +function mh.init_axis_radius_length(base_pos, axis, radius, length) + local other1, other2 = worldedit.get_axis_others(axis) + local pos1 = { + [axis] = base_pos[axis], + [other1] = base_pos[other1] - radius, + [other2] = base_pos[other2] - radius + } + local pos2 = { + [axis] = base_pos[axis] + length, + [other1] = base_pos[other1] + radius, + [other2] = base_pos[other2] + radius + } + return mh.init(pos1, pos2) +end + + +function mh.finish(manip, data) + -- Update map + manip:set_data(data) + manip:write_to_map() + manip:update_map() +end + diff --git a/mods/WorldEdit/worldedit/compatibility.lua b/mods/WorldEdit/worldedit/compatibility.lua index b2a76011..c989a058 100755 --- a/mods/WorldEdit/worldedit/compatibility.lua +++ b/mods/WorldEdit/worldedit/compatibility.lua @@ -1,23 +1,74 @@ -worldedit = worldedit or {} -local minetest = minetest --local copy of global - -worldedit.allocate_old = worldedit.allocate -worldedit.deserialize_old = worldedit.deserialize -worldedit.metasave = function(pos1, pos2, filename) - local file, err = io.open(filename, "wb") - if err then return 0 end - local data, count = worldedit.serialize(pos1, pos2) - file:write(data) - file:close() - return count -end -worldedit.metaload = function(originpos, filename) - filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" - local file, err = io.open(filename, "wb") - if err then return 0 end - local data = file:read("*a") - return worldedit.deserialize(originpos, data) -end -worldedit.scale = function(pos1, pos2, factor) - return worldedit.stretch(pos1, pos2, factor, factor, factor) -end \ No newline at end of file +--- Compatibility functions. +-- @module worldedit.compatibility + +local function deprecated(new_func) + local info = debug.getinfo(1, "n") + local msg = "worldedit." .. info.name .. "() is deprecated." + if new_func then + msg = msg .. " Use worldedit." .. new_func .. "() instead." + end + minetest.log("deprecated", msg) +end + +worldedit.allocate_old = worldedit.allocate + +worldedit.deserialize_old = worldedit.deserialize + +function worldedit.metasave(pos1, pos2, filename) + deprecated("save") + local file, err = io.open(filename, "wb") + if err then return 0 end + local data, count = worldedit.serialize(pos1, pos2) + file:write(data) + file:close() + return count +end + +function worldedit.metaload(originpos, filename) + deprecated("load") + filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" + local file, err = io.open(filename, "wb") + if err then return 0 end + local data = file:read("*a") + return worldedit.deserialize(originpos, data) +end + +function worldedit.scale(pos1, pos2, factor) + deprecated("stretch") + return worldedit.stretch(pos1, pos2, factor, factor, factor) +end + +function worldedit.valueversion(value) + deprecated("read_header") + local version = worldedit.read_header(value) + if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then + return 0 + end + return version +end + +function worldedit.replaceinverse(pos1, pos2, search_node, replace_node) + deprecated("replace") + return worldedit.replace(pos1, pos2, search_node, replace_node, true) +end + +function worldedit.clearobjects(...) + deprecated("clear_objects") + return worldedit.clear_objects(...) +end + +function worldedit.hollow_sphere(pos, radius, node_name) + deprecated("sphere") + return worldedit.sphere(pos, radius, node_name, true) +end + +function worldedit.hollow_dome(pos, radius, node_name) + deprecated("dome") + return worldedit.dome(pos, radius, node_name, true) +end + +function worldedit.hollow_cylinder(pos, axis, length, radius, node_name) + deprecated("cylinder") + return worldedit.cylinder(pos, axis, length, radius, node_name, true) +end + diff --git a/mods/WorldEdit/worldedit/init.lua b/mods/WorldEdit/worldedit/init.lua index c60b50c9..34c98200 100755 --- a/mods/WorldEdit/worldedit/init.lua +++ b/mods/WorldEdit/worldedit/init.lua @@ -1,25 +1,44 @@ -worldedit = worldedit or {} -worldedit.version = {major=1, minor=0} -worldedit.version_string = "1.0" - -assert(minetest.get_voxel_manip, string.rep(">", 300) .. "HEY YOU! YES, YOU OVER THERE. THIS VERSION OF WORLDEDIT REQUIRES MINETEST 0.4.8 OR LATER! YOU HAVE AN OLD VERSION." .. string.rep("<", 300)) - -local path = minetest.get_modpath(minetest.get_current_modname()) - -local loadmodule = function(path) - local file = io.open(path) - if not file then - return - end - file:close() - return dofile(path) -end - -loadmodule(path .. "/manipulations.lua") -loadmodule(path .. "/primitives.lua") -loadmodule(path .. "/visualization.lua") -loadmodule(path .. "/serialization.lua") -loadmodule(path .. "/code.lua") -loadmodule(path .. "/compatibility.lua") - -print("[MOD] WorldEdit loaded!") +--- Worldedit. +-- @module worldedit +-- @release 1.1 +-- @copyright 2013 sfan5, Anthony Zhang (Uberi/Temperest), and Brett O'Donnell (cornernote). +-- @license GNU Affero General Public License version 3 (AGPLv3) +-- @author sfan5 +-- @author Anthony Zang (Uberi/Temperest) +-- @author Bret O'Donnel (cornernote) +-- @author ShadowNinja + +worldedit = {} +worldedit.version = {1, 1, major=1, minor=1} +worldedit.version_string = table.concat(worldedit.version, ".") + +if not minetest.get_voxel_manip then + local err_msg = "This version of WorldEdit requires Minetest 0.4.8 or later! You have an old version." + minetest.log("error", string.rep("#", 128)) + minetest.log("error", err_msg) + minetest.log("error", string.rep("#", 128)) + error(err_msg) +end + +local path = minetest.get_modpath(minetest.get_current_modname()) + +local function load_module(path) + local file = io.open(path) + if not file then return end + file:close() + return dofile(path) +end + +dofile(path .. "/common.lua") +load_module(path .. "/manipulations.lua") +load_module(path .. "/primitives.lua") +load_module(path .. "/visualization.lua") +load_module(path .. "/serialization.lua") +load_module(path .. "/code.lua") +load_module(path .. "/compatibility.lua") + + +if minetest.setting_getbool("log_mods") then + print("[WorldEdit] Loaded!") +end + diff --git a/mods/WorldEdit/worldedit/manipulations.lua b/mods/WorldEdit/worldedit/manipulations.lua index c2ada12a..e76cf701 100755 --- a/mods/WorldEdit/worldedit/manipulations.lua +++ b/mods/WorldEdit/worldedit/manipulations.lua @@ -1,731 +1,597 @@ -worldedit = worldedit or {} -local minetest = minetest --local copy of global - --- Copies and modifies positions `pos1` and `pos2` so that each component of --- `pos1` is less than or equal to the corresponding component of `pos2`. --- Returns the new positions. -worldedit.sort_pos = function(pos1, pos2) - pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} - pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} - if pos1.x > pos2.x then - pos2.x, pos1.x = pos1.x, pos2.x - end - if pos1.y > pos2.y then - pos2.y, pos1.y = pos1.y, pos2.y - end - if pos1.z > pos2.z then - pos2.z, pos1.z = pos1.z, pos2.z - end - return pos1, pos2 -end - ---determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume -worldedit.volume = function(pos1, pos2) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1) -end - ---sets a region defined by positions `pos1` and `pos2` to `nodename`, returning the number of nodes filled -worldedit.set = function(pos1, pos2, nodenames) - if type(nodenames) == "string" then - nodenames = {nodenames} - end - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_ids = {} - for i,v in ipairs(nodenames) do - node_ids[i] = minetest.get_content_id(nodenames[i]) - end - if #node_ids == 1 then --only one type of node - local id = node_ids[1] - for i in area:iterp(pos1, pos2) do nodes[i] = id end --fill area with node - else --several types of nodes specified - local id_count, rand = #node_ids, math.random - for i in area:iterp(pos1, pos2) do nodes[i] = node_ids[rand(id_count)] end --fill randomly with all types of specified nodes - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return worldedit.volume(pos1, pos2) -end - ---replaces all instances of `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced -worldedit.replace = function(pos1, pos2, searchnode, replacenode) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - local nodes = manip:get_data() - local searchnode_id = minetest.get_content_id(searchnode) - local replacenode_id = minetest.get_content_id(replacenode) - local count = 0 - for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode - if nodes[i] == searchnode_id then - nodes[i] = replacenode_id - count = count + 1 - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---replaces all nodes other than `searchnode` with `replacenode` in a region defined by positions `pos1` and `pos2`, returning the number of nodes replaced -worldedit.replaceinverse = function(pos1, pos2, searchnode, replacenode) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - local nodes = manip:get_data() - local searchnode_id = minetest.get_content_id(searchnode) - local replacenode_id = minetest.get_content_id(replacenode) - local count = 0 - for i in area:iterp(pos1, pos2) do --replace anything that is not searchnode with replacenode - if nodes[i] ~= searchnode_id then - nodes[i] = replacenode_id - count = count + 1 - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied -worldedit.copy = function(pos1, pos2, axis, amount) --wip: replace the old version below - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - if amount == 0 then - return - end - - local other1, other2 - if axis == "x" then - other1, other2 = "y", "z" - elseif axis == "y" then - other1, other2 = "x", "z" - else --axis == "z" - other1, other2 = "x", "y" - end - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - --prepare slice along axis - local extent = { - [axis] = 1, - [other1]=pos2[other1] - pos1[other1] + 1, - [other2]=pos2[other2] - pos1[other2] + 1, - } - local nodes = {} - local schematic = {size=extent, data=nodes} - - local currentpos = {x=pos1.x, y=pos1.y, z=pos1.z} - local stride = {x=1, y=extent.x, z=extent.x * extent.y} - local get_node = minetest.get_node - for index1 = 1, extent[axis] do --go through each slice - --copy slice into schematic - local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed - for index2 = 1, extent[other1] do - local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1] - for index3 = 1, extent[other2] do - local i = newindex2 + (index3 + offset[other2]) * stride[other2] - local node = get_node(pos) - node.param1 = 255 --node will always appear - nodes[i] = node - end - end - - --copy schematic to target - currentpos[axis] = currentpos[axis] + amount - place_schematic(currentpos, schematic) - - --wip: copy meta - - currentpos[axis] = currentpos[axis] + 1 - end - return worldedit.volume(pos1, pos2) -end - -worldedit.copy2 = function(pos1, pos2, direction, volume) - -- the overlap shouldn't matter as long as we - -- 1) start at the furthest separated corner - -- 2) complete an edge before moving inward, either edge works - -- 3) complete a face before moving inward, similarly - -- - -- to do this I - -- 1) find the furthest destination in the direction, of each axis - -- 2) call those the furthest separated corner - -- 3) make sure to iterate inward from there - -- 4) nested loop to make sure complete edge, complete face, then complete cube. - - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - local somemeta = get_meta(pos1) -- hax lol - local to_table = somemeta.to_table - local from_table = somemeta.from_table - somemeta = nil - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local sx, sy, sz -- direction sign - local ix, iy, iz -- initial destination - local ex, ey, ez -- final destination - local originalx, originaly, originalz -- source - -- vim -> :'<,'>s/\<\([ioes]\?\)x\>/\1y/g - if direction.x > 0 then - originalx = pos2.x - ix = originalx + direction.x - ex = pos1.x + direction.x - sx = -1 - elseif direction.x < 0 then - originalx = pos1.x - ix = originalx + direction.x - ex = pos2.x + direction.x - sx = 1 - else - originalx = pos1.x - ix = originalx -- whatever - ex = pos2.x - sx = 1 - end - - if direction.y > 0 then - originaly = pos2.y - iy = originaly + direction.y - ey = pos1.y + direction.y - sy = -1 - elseif direction.y < 0 then - originaly = pos1.y - iy = originaly + direction.y - ey = pos2.y + direction.y - sy = 1 - else - originaly = pos1.y - iy = originaly -- whatever - ey = pos2.y - sy = 1 - end - - if direction.z > 0 then - originalz = pos2.z - iz = originalz + direction.z - ez = pos1.z + direction.z - sz = -1 - elseif direction.z < 0 then - originalz = pos1.z - iz = originalz + direction.z - ez = pos2.z + direction.z - sz = 1 - else - originalz = pos1.z - iz = originalz -- whatever - ez = pos2.z - sz = 1 - end - -- print('copy',originalx,ix,ex,sx,originaly,iy,ey,sy,originalz,iz,ez,sz) - - local ox,oy,oz - - ox = originalx - for x = ix, ex, sx do - oy = originaly - for y = iy, ey, sy do - oz = originalz - for z = iz, ez, sz do - -- reusing pos1/pos2 as source/dest here - pos1.x, pos1.y, pos1.z = ox, oy, oz - pos2.x, pos2.y, pos2.z = x, y, z - local node = get_node(pos1) - local meta = to_table(get_meta(pos1)) --get meta of current node - add_node(pos2,node) - from_table(get_meta(pos2),meta) - oz = oz + sz - end - oy = oy + sy - end - ox = ox + sx - end -end - ---duplicates the region defined by positions `pos1` and `pos2` `amount` times with offset vector `direction`, returning the number of nodes stacked -worldedit.stack2 = function(pos1, pos2, direction, amount, finished) - local i = 0 - local translated = {x=0,y=0,z=0} - local function nextone() - if i <= amount then - i = i + 1 - translated.x = translated.x + direction.x - translated.y = translated.y + direction.y - translated.z = translated.z + direction.z - worldedit.copy2(pos1, pos2, translated, volume) - minetest.after(0, nextone) - else - if finished then - finished() - end - end - end - nextone() - return worldedit.volume(pos1, pos2) * amount -end - ---copies the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes copied -worldedit.copy = function(pos1, pos2, axis, amount) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - if amount < 0 then - local pos = {x=pos1.x, y=0, z=0} - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) --obtain current node - local meta = get_meta(pos):to_table() --get meta of current node - local value = pos[axis] --store current position - pos[axis] = value + amount --move along axis - add_node(pos, node) --copy node to new position - get_meta(pos):from_table(meta) --set metadata of new node - pos[axis] = value --restore old position - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - else - local pos = {x=pos2.x, y=0, z=0} - while pos.x >= pos1.x do - pos.y = pos2.y - while pos.y >= pos1.y do - pos.z = pos2.z - while pos.z >= pos1.z do - local node = get_node(pos) --obtain current node - local meta = get_meta(pos):to_table() --get meta of current node - local value = pos[axis] --store current position - pos[axis] = value + amount --move along axis - add_node(pos, node) --copy node to new position - get_meta(pos):from_table(meta) --set metadata of new node - pos[axis] = value --restore old position - pos.z = pos.z - 1 - end - pos.y = pos.y - 1 - end - pos.x = pos.x - 1 - end - end - return worldedit.volume(pos1, pos2) -end - ---moves the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") by `amount` nodes, returning the number of nodes moved -worldedit.move = function(pos1, pos2, axis, amount) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - --wip: move slice by slice using schematic method in the move axis and transfer metadata in separate loop (and if the amount is greater than the length in the axis, copy whole thing at a time and erase original after, using schematic method) - local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node - if amount < 0 then - local pos = {x=pos1.x, y=0, z=0} - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) --obtain current node - local meta = get_meta(pos):to_table() --get metadata of current node - remove_node(pos) - local value = pos[axis] --store current position - pos[axis] = value + amount --move along axis - add_node(pos, node) --move node to new position - get_meta(pos):from_table(meta) --set metadata of new node - pos[axis] = value --restore old position - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - else - local pos = {x=pos2.x, y=0, z=0} - while pos.x >= pos1.x do - pos.y = pos2.y - while pos.y >= pos1.y do - pos.z = pos2.z - while pos.z >= pos1.z do - local node = get_node(pos) --obtain current node - local meta = get_meta(pos):to_table() --get metadata of current node - remove_node(pos) - local value = pos[axis] --store current position - pos[axis] = value + amount --move along axis - add_node(pos, node) --move node to new position - get_meta(pos):from_table(meta) --set metadata of new node - pos[axis] = value --restore old position - pos.z = pos.z - 1 - end - pos.y = pos.y - 1 - end - pos.x = pos.x - 1 - end - end - return worldedit.volume(pos1, pos2) -end - ---duplicates the region defined by positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z") `count` times, returning the number of nodes stacked -worldedit.stack = function(pos1, pos2, axis, count) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local length = pos2[axis] - pos1[axis] + 1 - if count < 0 then - count = -count - length = -length - end - local amount = 0 - local copy = worldedit.copy - local i = 1 - function nextone() - if i <= count then - i = i + 1 - amount = amount + length - copy(pos1, pos2, axis, amount) - minetest.after(0, nextone) - end - end - nextone() - return worldedit.volume(pos1, pos2) * count -end - ---stretches the region defined by positions `pos1` and `pos2` by an factor of positive integers `stretchx`, `stretchy`. and `stretchz` along the X, Y, and Z axes, respectively, with `pos1` as the origin, returning the number of nodes scaled, the new scaled position 1, and the new scaled position 2 -worldedit.stretch = function(pos1, pos2, stretchx, stretchy, stretchz) --wip: test this - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --prepare schematic of large node - local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic - local placeholder_node = {name="", param1=255, param2=0} - local nodes = {} - for i = 1, stretchx * stretchy * stretchz do - nodes[i] = placeholder_node - end - local schematic = {size={x=stretchx, y=stretchy, z=stretchz}, data=nodes} - - local sizex, sizey, sizez = stretchx - 1, stretchy - 1, stretchz - 1 - - --make area stay loaded - local manip = minetest.get_voxel_manip() - local new_pos2 = { - x=pos1.x + (pos2.x - pos1.x) * stretchx + sizex, - y=pos1.y + (pos2.y - pos1.y) * stretchy + sizey, - z=pos1.z + (pos2.z - pos1.z) * stretchz + sizez, - } - manip:read_from_map(pos1, new_pos2) - - local pos = {x=pos2.x, y=0, z=0} - local bigpos = {x=0, y=0, z=0} - while pos.x >= pos1.x do - pos.y = pos2.y - while pos.y >= pos1.y do - pos.z = pos2.z - while pos.z >= pos1.z do - local node = get_node(pos) --obtain current node - local meta = get_meta(pos):to_table() --get meta of current node - - --calculate far corner of the big node - local posx = pos1.x + (pos.x - pos1.x) * stretchx - local posy = pos1.y + (pos.y - pos1.y) * stretchy - local posz = pos1.z + (pos.z - pos1.z) * stretchz - - --create large node - placeholder_node.name = node.name - placeholder_node.param2 = node.param2 - bigpos.x, bigpos.y, bigpos.z = posx, posy, posz - place_schematic(bigpos, schematic) - - --fill in large node meta - if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then --node has meta fields - for x = 0, sizex do - for y = 0, sizey do - for z = 0, sizez do - bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z - get_meta(bigpos):from_table(meta) --set metadata of new node - end - end - end - end - pos.z = pos.z - 1 - end - pos.y = pos.y - 1 - end - pos.x = pos.x - 1 - end - return worldedit.volume(pos1, pos2) * stretchx * stretchy * stretchz, pos1, new_pos2 -end - ---transposes a region defined by the positions `pos1` and `pos2` between the `axis1` and `axis2` axes, returning the number of nodes transposed, the new transposed position 1, and the new transposed position 2 -worldedit.transpose = function(pos1, pos2, axis1, axis2) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - local compare - local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2] - - if extent1 > extent2 then - compare = function(extent1, extent2) - return extent1 > extent2 - end - else - compare = function(extent1, extent2) - return extent1 < extent2 - end - end - - --calculate the new position 2 after transposition - local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} - new_pos2[axis1] = pos1[axis1] + extent2 - new_pos2[axis2] = pos1[axis2] + extent1 - - --make area stay loaded - local manip = minetest.get_voxel_manip() - local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z} - if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end - if upperbound[axis2] < new_pos2[axis2] then upperbound[axis2] = new_pos2[axis2] end - manip:read_from_map(pos1, upperbound) - - local pos = {x=pos1.x, y=0, z=0} - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] - if compare(extent1, extent2) then --transpose only if below the diagonal - local node1 = get_node(pos) - local meta1 = get_meta(pos):to_table() - local value1, value2 = pos[axis1], pos[axis2] --save position values - pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents - local node2 = get_node(pos) - local meta2 = get_meta(pos):to_table() - add_node(pos, node1) - get_meta(pos):from_table(meta1) - pos[axis1], pos[axis2] = value1, value2 --restore position values - add_node(pos, node2) - get_meta(pos):from_table(meta2) - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return worldedit.volume(pos1, pos2), pos1, new_pos2 -end - ---flips a region defined by the positions `pos1` and `pos2` along the `axis` axis ("x" or "y" or "z"), returning the number of nodes flipped -worldedit.flip = function(pos1, pos2, axis) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - --wip: flip the region slice by slice along the flip axis using schematic method - local pos = {x=pos1.x, y=0, z=0} - local start = pos1[axis] + pos2[axis] - pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node1 = get_node(pos) - local meta1 = get_meta(pos):to_table() - local value = pos[axis] - pos[axis] = start - value - local node2 = get_node(pos) - local meta2 = get_meta(pos):to_table() - add_node(pos, node1) - get_meta(pos):from_table(meta1) - pos[axis] = value - add_node(pos, node2) - get_meta(pos):from_table(meta2) - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return worldedit.volume(pos1, pos2) -end - ---rotates a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise around axis `axis` (90 degree increment), returning the number of nodes rotated -worldedit.rotate = function(pos1, pos2, axis, angle) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - local axis1, axis2 - if axis == "x" then - axis1, axis2 = "z", "y" - elseif axis == "y" then - axis1, axis2 = "x", "z" - else --axis == "z" - axis1, axis2 = "y", "x" - end - angle = angle % 360 - - local count - if angle == 90 then - worldedit.flip(pos1, pos2, axis1) - count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) - elseif angle == 180 then - worldedit.flip(pos1, pos2, axis1) - count = worldedit.flip(pos1, pos2, axis2) - elseif angle == 270 then - worldedit.flip(pos1, pos2, axis2) - count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) - end - return count, pos1, pos2 -end - ---rotates all oriented nodes in a region defined by the positions `pos1` and `pos2` by `angle` degrees clockwise (90 degree increment) around the Y axis, returning the number of nodes oriented -worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotation along arbitrary axis - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local registered_nodes = minetest.registered_nodes - - local wallmounted = { - [90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3}, - [180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4}, - [270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2} - } - local facedir = { - [90]={[0]=1, [1]=2, [2]=3, [3]=0}, - [180]={[0]=2, [1]=3, [2]=0, [3]=1}, - [270]={[0]=3, [1]=0, [2]=1, [3]=2} - } - - angle = angle % 360 - if angle == 0 then - return 0 - end - local wallmounted_substitution = wallmounted[angle] - local facedir_substitution = facedir[angle] - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local count = 0 - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - local pos = {x=pos1.x, y=0, z=0} - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) - local def = registered_nodes[node.name] - if def then - if def.paramtype2 == "wallmounted" then - node.param2 = wallmounted_substitution[node.param2] - local meta = get_meta(pos):to_table() - add_node(pos, node) - get_meta(pos):from_table(meta) - count = count + 1 - elseif def.paramtype2 == "facedir" then - node.param2 = facedir_substitution[node.param2] - local meta = get_meta(pos):to_table() - add_node(pos, node) - get_meta(pos):from_table(meta) - count = count + 1 - end - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return count -end - ---fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated -worldedit.fixlight = function(pos1, pos2) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local nodes = minetest.find_nodes_in_area(pos1, pos2, "air") - local dig_node = minetest.dig_node - for _, pos in ipairs(nodes) do - dig_node(pos) - end - return #nodes -end - ---clears all objects in a region defined by the positions `pos1` and `pos2`, returning the number of objects cleared -worldedit.clearobjects = function(pos1, pos2) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z - local pos2x, pos2y, pos2z = pos2.x + 1, pos2.y + 1, pos2.z + 1 - local center = {x=(pos1x + pos2x) / 2, y=(pos1y + pos2y) / 2, z=(pos1z + pos2z) / 2} --center of region - local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius - local count = 0 - for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere - local entity = obj:get_luaentity() - if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities - local pos = obj:getpos() - if pos.x >= pos1x and pos.x <= pos2x - and pos.y >= pos1y and pos.y <= pos2y - and pos.z >= pos1z and pos.z <= pos2z then --inside region - obj:remove() - count = count + 1 - end - end - end - return count -end +--- Generic node manipulations. +-- @module worldedit.manipulations + +local mh = worldedit.manip_helpers + + +--- Sets a region to `node_names`. +-- @param pos1 +-- @param pos2 +-- @param node_names Node name or list of node names. +-- @return The number of nodes set. +function worldedit.set(pos1, pos2, node_names) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local manip, area = mh.init(pos1, pos2) + local data = mh.get_empty_data(area) + + if type(node_names) == "string" then -- Only one type of node + local id = minetest.get_content_id(node_names) + -- Fill area with node + for i in area:iterp(pos1, pos2) do + data[i] = id + end + else -- Several types of nodes specified + local node_ids = {} + for i, v in ipairs(node_names) do + node_ids[i] = minetest.get_content_id(v) + end + -- Fill area randomly with nodes + local id_count, rand = #node_ids, math.random + for i in area:iterp(pos1, pos2) do + data[i] = node_ids[rand(id_count)] + end + end + + mh.finish(manip, data) + + return worldedit.volume(pos1, pos2) +end + + +--- Replaces all instances of `search_node` with `replace_node` in a region. +-- When `inverse` is `true`, replaces all instances that are NOT `search_node`. +-- @return The number of nodes replaced. +function worldedit.replace(pos1, pos2, search_node, replace_node, inverse) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local manip, area = mh.init(pos1, pos2) + local data = manip:get_data() + + local search_id = minetest.get_content_id(search_node) + local replace_id = minetest.get_content_id(replace_node) + + local count = 0 + + --- TODO: This could be shortened by checking `inverse` in the loop, + -- but that would have a speed penalty. Is the penalty big enough + -- to matter? + if not inverse then + for i in area:iterp(pos1, pos2) do + if data[i] == search_id then + data[i] = replace_id + count = count + 1 + end + end + else + for i in area:iterp(pos1, pos2) do + if data[i] ~= search_id then + data[i] = replace_id + count = count + 1 + end + end + end + + mh.finish(manip, data) + + return count +end + + +--- Duplicates a region `amount` times with offset vector `direction`. +-- Stacking is spread across server steps, one copy per step. +-- @return The number of nodes stacked. +function worldedit.stack2(pos1, pos2, direction, amount, finished) + local i = 0 + local translated = {x=0, y=0, z=0} + local function next_one() + if i < amount then + i = i + 1 + translated.x = translated.x + direction.x + translated.y = translated.y + direction.y + translated.z = translated.z + direction.z + worldedit.copy2(pos1, pos2, translated, volume) + minetest.after(0, next_one) + else + if finished then + finished() + end + end + end + next_one() + return worldedit.volume(pos1, pos2) * amount +end + + +--- Copies a region along `axis` by `amount` nodes. +-- @param pos1 +-- @param pos2 +-- @param axis Axis ("x", "y", or "z") +-- @param amount +-- @return The number of nodes copied. +function worldedit.copy(pos1, pos2, axis, amount) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local get_node, get_meta, set_node = minetest.get_node, + minetest.get_meta, minetest.set_node + -- Copy things backwards when negative to avoid corruption. + -- FIXME: Lots of code duplication here. + if amount < 0 then + local pos = {} + pos.x = pos1.x + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) -- Obtain current node + local meta = get_meta(pos):to_table() -- Get meta of current node + local value = pos[axis] -- Store current position + pos[axis] = value + amount -- Move along axis + set_node(pos, node) -- Copy node to new position + get_meta(pos):from_table(meta) -- Set metadata of new node + pos[axis] = value -- Restore old position + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + else + local pos = {} + pos.x = pos2.x + while pos.x >= pos1.x do + pos.y = pos2.y + while pos.y >= pos1.y do + pos.z = pos2.z + while pos.z >= pos1.z do + local node = get_node(pos) -- Obtain current node + local meta = get_meta(pos):to_table() -- Get meta of current node + local value = pos[axis] -- Store current position + pos[axis] = value + amount -- Move along axis + set_node(pos, node) -- Copy node to new position + get_meta(pos):from_table(meta) -- Set metadata of new node + pos[axis] = value -- Restore old position + pos.z = pos.z - 1 + end + pos.y = pos.y - 1 + end + pos.x = pos.x - 1 + end + end + return worldedit.volume(pos1, pos2) +end + + +--- Moves a region along `axis` by `amount` nodes. +-- @return The number of nodes moved. +function worldedit.move(pos1, pos2, axis, amount) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + --- TODO: Move slice by slice using schematic method in the move axis + -- and transfer metadata in separate loop (and if the amount is + -- greater than the length in the axis, copy whole thing at a time and + -- erase original after, using schematic method). + local get_node, get_meta, set_node, remove_node = minetest.get_node, + minetest.get_meta, minetest.set_node, minetest.remove_node + -- Copy things backwards when negative to avoid corruption. + --- FIXME: Lots of code duplication here. + if amount < 0 then + local pos = {} + pos.x = pos1.x + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) -- Obtain current node + local meta = get_meta(pos):to_table() -- Get metadata of current node + remove_node(pos) -- Remove current node + local value = pos[axis] -- Store current position + pos[axis] = value + amount -- Move along axis + set_node(pos, node) -- Move node to new position + get_meta(pos):from_table(meta) -- Set metadata of new node + pos[axis] = value -- Restore old position + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + else + local pos = {} + pos.x = pos2.x + while pos.x >= pos1.x do + pos.y = pos2.y + while pos.y >= pos1.y do + pos.z = pos2.z + while pos.z >= pos1.z do + local node = get_node(pos) -- Obtain current node + local meta = get_meta(pos):to_table() -- Get metadata of current node + remove_node(pos) -- Remove current node + local value = pos[axis] -- Store current position + pos[axis] = value + amount -- Move along axis + set_node(pos, node) -- Move node to new position + get_meta(pos):from_table(meta) -- Set metadata of new node + pos[axis] = value -- Restore old position + pos.z = pos.z - 1 + end + pos.y = pos.y - 1 + end + pos.x = pos.x - 1 + end + end + return worldedit.volume(pos1, pos2) +end + + +--- Duplicates a region along `axis` `amount` times. +-- Stacking is spread across server steps, one copy per step. +-- @param pos1 +-- @param pos2 +-- @param axis Axis direction, "x", "y", or "z". +-- @param count +-- @return The number of nodes stacked. +function worldedit.stack(pos1, pos2, axis, count) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + local length = pos2[axis] - pos1[axis] + 1 + if count < 0 then + count = -count + length = -length + end + local amount = 0 + local copy = worldedit.copy + local i = 1 + function next_one() + if i <= count then + i = i + 1 + amount = amount + length + copy(pos1, pos2, axis, amount) + minetest.after(0, next_one) + end + end + next_one() + return worldedit.volume(pos1, pos2) * count +end + + +--- Stretches a region by a factor of positive integers along the X, Y, and Z +-- axes, respectively, with `pos1` as the origin. +-- @param pos1 +-- @param pos2 +-- @param stretch_x Amount to stretch along X axis. +-- @param stretch_y Amount to stretch along Y axis. +-- @param stretch_z Amount to stretch along Z axis. +-- @return The number of nodes scaled. +-- @return The new scaled position 1. +-- @return The new scaled position 2. +function worldedit.stretch(pos1, pos2, stretch_x, stretch_y, stretch_z) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + -- Prepare schematic of large node + local get_node, get_meta, place_schematic = minetest.get_node, + minetest.get_meta, minetest.place_schematic + local placeholder_node = {name="", param1=255, param2=0} + local nodes = {} + for i = 1, stretch_x * stretch_y * stretch_z do + nodes[i] = placeholder_node + end + local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes} + + local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1 + + local new_pos2 = { + x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x, + y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y, + z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z, + } + worldedit.keep_loaded(pos1, new_pos2) + + local pos = {x=pos2.x, y=0, z=0} + local big_pos = {x=0, y=0, z=0} + while pos.x >= pos1.x do + pos.y = pos2.y + while pos.y >= pos1.y do + pos.z = pos2.z + while pos.z >= pos1.z do + local node = get_node(pos) -- Get current node + local meta = get_meta(pos):to_table() -- Get meta of current node + + -- Calculate far corner of the big node + local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x + local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y + local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z + + -- Create large node + placeholder_node.name = node.name + placeholder_node.param2 = node.param2 + big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z + place_schematic(big_pos, schematic) + + -- Fill in large node meta + if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then + -- Node has meta fields + for x = 0, size_x do + for y = 0, size_y do + for z = 0, size_z do + big_pos.x = pos_x + x + big_pos.y = pos_y + y + big_pos.z = pos_z + z + -- Set metadata of new node + get_meta(big_pos):from_table(meta) + end + end + end + end + pos.z = pos.z - 1 + end + pos.y = pos.y - 1 + end + pos.x = pos.x - 1 + end + return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2 +end + + +--- Transposes a region between two axes. +-- @return The number of nodes transposed. +-- @return The new transposed position 1. +-- @return The new transposed position 2. +function worldedit.transpose(pos1, pos2, axis1, axis2) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local compare + local extent1, extent2 = pos2[axis1] - pos1[axis1], pos2[axis2] - pos1[axis2] + + if extent1 > extent2 then + compare = function(extent1, extent2) + return extent1 > extent2 + end + else + compare = function(extent1, extent2) + return extent1 < extent2 + end + end + + -- Calculate the new position 2 after transposition + local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} + new_pos2[axis1] = pos1[axis1] + extent2 + new_pos2[axis2] = pos1[axis2] + extent1 + + local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z} + if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end + if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end + worldedit.keep_loaded(pos1, upper_bound) + + local pos = {x=pos1.x, y=0, z=0} + local get_node, get_meta, set_node = minetest.get_node, + minetest.get_meta, minetest.set_node + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] + if compare(extent1, extent2) then -- Transpose only if below the diagonal + local node1 = get_node(pos) + local meta1 = get_meta(pos):to_table() + local value1, value2 = pos[axis1], pos[axis2] -- Save position values + pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents + local node2 = get_node(pos) + local meta2 = get_meta(pos):to_table() + set_node(pos, node1) + get_meta(pos):from_table(meta1) + pos[axis1], pos[axis2] = value1, value2 -- Restore position values + set_node(pos, node2) + get_meta(pos):from_table(meta2) + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return worldedit.volume(pos1, pos2), pos1, new_pos2 +end + + +--- Flips a region along `axis`. +-- @return The number of nodes flipped. +function worldedit.flip(pos1, pos2, axis) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + --- TODO: Flip the region slice by slice along the flip axis using schematic method. + local pos = {x=pos1.x, y=0, z=0} + local start = pos1[axis] + pos2[axis] + pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) + local get_node, get_meta, set_node = minetest.get_node, + minetest.get_meta, minetest.set_node + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node1 = get_node(pos) + local meta1 = get_meta(pos):to_table() + local value = pos[axis] -- Save position + pos[axis] = start - value -- Shift position + local node2 = get_node(pos) + local meta2 = get_meta(pos):to_table() + set_node(pos, node1) + get_meta(pos):from_table(meta1) + pos[axis] = value -- Restore position + set_node(pos, node2) + get_meta(pos):from_table(meta2) + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return worldedit.volume(pos1, pos2) +end + + +--- Rotates a region clockwise around an axis. +-- @param pos1 +-- @param pos2 +-- @param axis Axis ("x", "y", or "z"). +-- @param angle Angle in degrees (90 degree increments only). +-- @return The number of nodes rotated. +-- @return The new first position. +-- @return The new second position. +function worldedit.rotate(pos1, pos2, axis, angle) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + local other1, other2 = worldedit.get_axis_others(axis) + angle = angle % 360 + + local count + if angle == 90 then + worldedit.flip(pos1, pos2, other1) + count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) + elseif angle == 180 then + worldedit.flip(pos1, pos2, other1) + count = worldedit.flip(pos1, pos2, other2) + elseif angle == 270 then + worldedit.flip(pos1, pos2, other2) + count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2) + else + error("Only 90 degree increments are supported!") + end + return count, pos1, pos2 +end + + +--- Rotates all oriented nodes in a region clockwise around the Y axis. +-- @param pos1 +-- @param pos2 +-- @param angle Angle in degrees (90 degree increments only). +-- @return The number of nodes oriented. +-- TODO: Support 6D facedir rotation along arbitrary axis. +function worldedit.orient(pos1, pos2, angle) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + local registered_nodes = minetest.registered_nodes + + local wallmounted = { + [90] = {[0]=0, 1, 5, 4, 2, 3}, + [180] = {[0]=0, 1, 3, 2, 5, 4}, + [270] = {[0]=0, 1, 4, 5, 3, 2} + } + local facedir = { + [90] = {[0]=1, 2, 3, 0}, + [180] = {[0]=2, 3, 0, 1}, + [270] = {[0]=3, 0, 1, 2} + } + + angle = angle % 360 + if angle == 0 then + return 0 + end + if angle % 90 ~= 0 then + error("Only 90 degree increments are supported!") + end + local wallmounted_substitution = wallmounted[angle] + local facedir_substitution = facedir[angle] + + worldedit.keep_loaded(pos1, pos2) + + local count = 0 + local get_node, get_meta, swap_node = minetest.get_node, + minetest.get_meta, minetest.swap_node + local pos = {x=pos1.x, y=0, z=0} + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) + local def = registered_nodes[node.name] + if def then + if def.paramtype2 == "wallmounted" then + node.param2 = wallmounted_substitution[node.param2] + local meta = get_meta(pos):to_table() + set_node(pos, node) + get_meta(pos):from_table(meta) + count = count + 1 + elseif def.paramtype2 == "facedir" then + node.param2 = facedir_substitution[node.param2] + local meta = get_meta(pos):to_table() + set_node(pos, node) + get_meta(pos):from_table(meta) + count = count + 1 + end + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return count +end + + +--- Attempts to fix the lighting in a region. +-- @return The number of nodes updated. +function worldedit.fixlight(pos1, pos2) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local nodes = minetest.find_nodes_in_area(pos1, pos2, "air") + local dig_node = minetest.dig_node + for _, pos in ipairs(nodes) do + dig_node(pos) + end + return #nodes +end + + +--- Clears all objects in a region. +-- @return The number of objects cleared. +function worldedit.clear_objects(pos1, pos2) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + -- Offset positions to include full nodes (positions are in the center of nodes) + local pos1x, pos1y, pos1z = pos1.x - 0.5, pos1.y - 0.5, pos1.z - 0.5 + local pos2x, pos2y, pos2z = pos2.x + 0.5, pos2.y + 0.5, pos2.z + 0.5 + + -- Center of region + local center = { + x = pos1x + ((pos2x - pos1x) / 2), + y = pos1y + ((pos2y - pos1y) / 2), + z = pos1z + ((pos2z - pos1z) / 2) + } + -- Bounding sphere radius + local radius = math.sqrt( + (center.x - pos1x) ^ 2 + + (center.y - pos1y) ^ 2 + + (center.z - pos1z) ^ 2) + local count = 0 + for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do + local entity = obj:get_luaentity() + -- Avoid players and WorldEdit entities + if not obj:is_player() and (not entity or + not entity.name:find("^worldedit:")) then + local pos = obj:getpos() + if pos.x >= pos1x and pos.x <= pos2x and + pos.y >= pos1y and pos.y <= pos2y and + pos.z >= pos1z and pos.z <= pos2z then + -- Inside region + obj:remove() + count = count + 1 + end + end + end + return count +end + diff --git a/mods/WorldEdit/worldedit/primitives.lua b/mods/WorldEdit/worldedit/primitives.lua index 9655b3f9..6d3b026e 100755 --- a/mods/WorldEdit/worldedit/primitives.lua +++ b/mods/WorldEdit/worldedit/primitives.lua @@ -1,470 +1,273 @@ -worldedit = worldedit or {} -local minetest = minetest --local copy of global - ---adds a hollow sphere centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.hollow_sphere = function(pos, radius, nodename) - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius} - local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius} - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) - local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z - local zstride, ystride = area.zstride, area.ystride - local count = 0 - for z = -radius, radius do - local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed - for y = -radius, radius do - local newy = newz + (y + offsety) * ystride - for x = -radius, radius do - local squared = x * x + y * y + z * z - if squared >= min_radius and squared <= max_radius then --position is on surface of sphere - local i = newy + (x + offsetx) - nodes[i] = node_id - count = count + 1 - end - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a sphere centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.sphere = function(pos, radius, nodename) - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = {x=pos.x - radius, y=pos.y - radius, z=pos.z - radius} - local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius} - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local max_radius = radius * (radius + 1) - local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z - local zstride, ystride = area.zstride, area.ystride - local count = 0 - for z = -radius, radius do - local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed - for y = -radius, radius do - local newy = newz + (y + offsety) * ystride - for x = -radius, radius do - if x * x + y * y + z * z <= max_radius then --position is inside sphere - local i = newy + (x + offsetx) - nodes[i] = node_id - count = count + 1 - end - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a hollow dome centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.hollow_dome = function(pos, radius, nodename) - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = {x=pos.x - radius, y=pos.y, z=pos.z - radius} - local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius} - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - local miny, maxy = 0, radius - if radius < 0 then - radius = -radius - miny, maxy = -radius, 0 - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) - local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z - local zstride, ystride = area.zstride, area.ystride - local count = 0 - for z = -radius, radius do - local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed - for y = miny, maxy do - local newy = newz + (y + offsety) * ystride - for x = -radius, radius do - local squared = x * x + y * y + z * z - if squared >= min_radius and squared <= max_radius then --position is on surface of sphere - local i = newy + (x + offsetx) - nodes[i] = node_id - count = count + 1 - end - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a dome centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.dome = function(pos, radius, nodename) - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = {x=pos.x - radius, y=pos.y, z=pos.z - radius} - local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius} - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - local miny, maxy = 0, radius - if radius < 0 then - radius = -radius - miny, maxy = -radius, 0 - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local max_radius = radius * (radius + 1) - local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z - local zstride, ystride = area.zstride, area.ystride - local count = 0 - for z = -radius, radius do - local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed - for y = miny, maxy do - local newy = newz + (y + offsety) * ystride - for x = -radius, radius do - if x * x + y * y + z * z <= max_radius then --position is inside sphere - local i = newy + (x + offsetx) - nodes[i] = node_id - count = count + 1 - end - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a hollow cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.hollow_cylinder = function(pos, axis, length, radius, nodename) - local other1, other2 - if axis == "x" then - other1, other2 = "y", "z" - elseif axis == "y" then - other1, other2 = "x", "z" - else --axis == "z" - other1, other2 = "x", "y" - end - - --handle negative lengths - local currentpos = {x=pos.x, y=pos.y, z=pos.z} - if length < 0 then - length = -length - currentpos[axis] = currentpos[axis] - length - end - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = { - [axis]=currentpos[axis], - [other1]=currentpos[other1] - radius, - [other2]=currentpos[other2] - radius - } - local pos2 = { - [axis]=currentpos[axis] + length - 1, - [other1]=currentpos[other1] + radius, - [other2]=currentpos[other2] + radius - } - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) - local stride = {x=1, y=area.ystride, z=area.zstride} - local offset = {x=currentpos.x - emerged_pos1.x, y=currentpos.y - emerged_pos1.y, z=currentpos.z - emerged_pos1.z} - local min_slice, max_slice = offset[axis], offset[axis] + length - 1 - local count = 0 - for index2 = -radius, radius do - local newindex2 = (index2 + offset[other1]) * stride[other1] + 1 --offset contributed by other axis 1 plus 1 to make it 1-indexed - for index3 = -radius, radius do - local newindex3 = newindex2 + (index3 + offset[other2]) * stride[other2] - local squared = index2 * index2 + index3 * index3 - if squared >= min_radius and squared <= max_radius then --position is on surface of cylinder - for index1 = min_slice, max_slice do --add column along axis - local i = newindex3 + index1 * stride[axis] - nodes[i] = node_id - end - count = count + length - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a cylinder at `pos` along the `axis` axis ("x" or "y" or "z") with length `length` and radius `radius`, composed of `nodename`, returning the number of nodes added -worldedit.cylinder = function(pos, axis, length, radius, nodename) - local other1, other2 - if axis == "x" then - other1, other2 = "y", "z" - elseif axis == "y" then - other1, other2 = "x", "z" - else --axis == "z" - other1, other2 = "x", "y" - end - - --handle negative lengths - local currentpos = {x=pos.x, y=pos.y, z=pos.z} - if length < 0 then - length = -length - currentpos[axis] = currentpos[axis] - length - end - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local pos1 = { - [axis]=currentpos[axis], - [other1]=currentpos[other1] - radius, - [other2]=currentpos[other2] - radius - } - local pos2 = { - [axis]=currentpos[axis] + length - 1, - [other1]=currentpos[other1] + radius, - [other2]=currentpos[other2] + radius - } - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local max_radius = radius * (radius + 1) - local stride = {x=1, y=area.ystride, z=area.zstride} - local offset = {x=currentpos.x - emerged_pos1.x, y=currentpos.y - emerged_pos1.y, z=currentpos.z - emerged_pos1.z} - local min_slice, max_slice = offset[axis], offset[axis] + length - 1 - local count = 0 - for index2 = -radius, radius do - local newindex2 = (index2 + offset[other1]) * stride[other1] + 1 --offset contributed by other axis 1 plus 1 to make it 1-indexed - for index3 = -radius, radius do - local newindex3 = newindex2 + (index3 + offset[other2]) * stride[other2] - if index2 * index2 + index3 * index3 <= max_radius then --position is within cylinder - for index1 = min_slice, max_slice do --add column along axis - local i = newindex3 + index1 * stride[axis] - nodes[i] = node_id - end - count = count + length - end - end - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a pyramid centered at `pos` with height `height`, composed of `nodename`, returning the number of nodes added -worldedit.pyramid = function(pos, axis, height, nodename) - local other1, other2 - if axis == "x" then - other1, other2 = "y", "z" - elseif axis == "y" then - other1, other2 = "x", "z" - else --axis == "z" - other1, other2 = "x", "y" - end - - local pos1 = {x=pos.x - height, y=pos.y - height, z=pos.z - height} - local pos2 = {x=pos.x + height, y=pos.y + height, z=pos.z + height} - - --handle inverted pyramids - local startaxis, endaxis, step - if height > 0 then - height = height - 1 - step = 1 - pos1[axis] = pos[axis] --upper half of box - else - height = height + 1 - step = -1 - pos2[axis] = pos[axis] --lower half of box - end - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --fill selected area with node - local node_id = minetest.get_content_id(nodename) - local stride = {x=1, y=area.ystride, z=area.zstride} - local offset = {x=pos.x - emerged_pos1.x, y=pos.y - emerged_pos1.y, z=pos.z - emerged_pos1.z} - local size = height * step - local count = 0 - for index1 = 0, height, step do --go through each level of the pyramid - local newindex1 = (index1 + offset[axis]) * stride[axis] + 1 --offset contributed by axis plus 1 to make it 1-indexed - for index2 = -size, size do - local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1] - for index3 = -size, size do - local i = newindex2 + (index3 + offset[other2]) * stride[other2] - nodes[i] = node_id - end - end - count = count + (size * 2 + 1) ^ 2 - size = size - 1 - end - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end - ---adds a spiral centered at `pos` with side length `length`, height `height`, space between walls `spacer`, composed of `nodename`, returning the number of nodes added -worldedit.spiral = function(pos, length, height, spacer, nodename) - local extent = math.ceil(length / 2) - local pos1 = {x=pos.x - extent, y=pos.y, z=pos.z - extent} - local pos2 = {x=pos.x + extent, y=pos.y + height, z=pos.z + extent} - - --set up voxel manipulator - local manip = minetest.get_voxel_manip() - local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) - local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) - - --fill emerged area with ignore - local nodes = {} - local ignore = minetest.get_content_id("ignore") - for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do - nodes[i] = ignore - end - - --set up variables - local node_id = minetest.get_content_id(nodename) - local stride = {x=1, y=area.ystride, z=area.zstride} - local offsetx, offsety, offsetz = pos.x - emerged_pos1.x, pos.y - emerged_pos1.y, pos.z - emerged_pos1.z - local i = offsetz * stride.z + offsety * stride.y + offsetx + 1 - - --add first column - local count = height - local column = i - for y = 1, height do - nodes[column] = node_id - column = column + stride.y - end - - --add spiral segments - local strideaxis, strideother = stride.x, stride.z - local sign = -1 - local segment_length = 0 - spacer = spacer + 1 - for segment = 1, math.floor(length / spacer) * 2 do --go through each segment except the last - if segment % 2 == 1 then --change sign and length every other turn starting with the first - sign = -sign - segment_length = segment_length + spacer - end - for index = 1, segment_length do --fill segment - i = i + strideaxis * sign --move along the direction of the segment - local column = i - for y = 1, height do --add column - nodes[column] = node_id - column = column + stride.y - end - end - count = count + segment_length * height - strideaxis, strideother = strideother, strideaxis --swap axes - end - - --add shorter final segment - sign = -sign - for index = 1, segment_length do - i = i + strideaxis * sign - local column = i - for y = 1, height do --add column - nodes[column] = node_id - column = column + stride.y - end - end - count = count + segment_length * height - - --update map nodes - manip:set_data(nodes) - manip:write_to_map() - manip:update_map() - - return count -end \ No newline at end of file +--- Functions for creating primitive shapes. +-- @module worldedit.primitives + +local mh = worldedit.manip_helpers + + +--- Adds a sphere of `node_name` centered at `pos`. +-- @param pos Position to center sphere at. +-- @param radius Sphere radius. +-- @param node_name Name of node to make shere of. +-- @param hollow Whether the sphere should be hollow. +-- @return The number of nodes added. +function worldedit.sphere(pos, radius, node_name, hollow) + local manip, area = mh.init_radius(pos, radius) + + local data = mh.get_empty_data(area) + + -- Fill selected area with node + local node_id = minetest.get_content_id(node_name) + local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) + local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z + local stride_z, stride_y = area.zstride, area.ystride + local count = 0 + for z = -radius, radius do + -- Offset contributed by z plus 1 to make it 1-indexed + local new_z = (z + offset_z) * stride_z + 1 + for y = -radius, radius do + local new_y = new_z + (y + offset_y) * stride_y + for x = -radius, radius do + local squared = x * x + y * y + z * z + if squared <= max_radius and (not hollow or squared >= min_radius) then + -- Position is on surface of sphere + local i = new_y + (x + offset_x) + data[i] = node_id + count = count + 1 + end + end + end + end + + mh.finish(manip, data) + + return count +end + + +--- Adds a dome. +-- @param pos Position to center dome at. +-- @param radius Dome radius. Negative for concave domes. +-- @param node_name Name of node to make dome of. +-- @param hollow Whether the dome should be hollow. +-- @return The number of nodes added. +-- TODO: Add axis option. +function worldedit.dome(pos, radius, node_name, hollow) + local min_y, max_y = 0, radius + if radius < 0 then + radius = -radius + min_y, max_y = -radius, 0 + end + + local manip, area = mh.init_axis_radius(pos, "y", radius) + local data = mh.get_empty_data(area) + + -- Add dome + local node_id = minetest.get_content_id(node_name) + local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) + local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z + local stride_z, stride_y = area.zstride, area.ystride + local count = 0 + for z = -radius, radius do + local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed + for y = min_y, max_y do + local new_y = new_z + (y + offset_y) * stride_y + for x = -radius, radius do + local squared = x * x + y * y + z * z + if squared <= max_radius and (not hollow or squared >= min_radius) then + -- Position is in dome + local i = new_y + (x + offset_x) + data[i] = node_id + count = count + 1 + end + end + end + end + + mh.finish(manip, data) + + return count +end + +--- Adds a cylinder. +-- @param pos Position to center base of cylinder at. +-- @param axis Axis ("x", "y", or "z") +-- @param length Cylinder length. +-- @param radius Cylinder radius. +-- @param node_name Name of node to make cylinder of. +-- @param hollow Whether the cylinder should be hollow. +-- @return The number of nodes added. +function worldedit.cylinder(pos, axis, length, radius, node_name, hollow) + local other1, other2 = worldedit.get_axis_others(axis) + + -- Handle negative lengths + local current_pos = {x=pos.x, y=pos.y, z=pos.z} + if length < 0 then + length = -length + current_pos[axis] = current_pos[axis] - length + end + + -- Set up voxel manipulator + local manip, area = mh.init_axis_radius_length(current_pos, axis, radius, length) + local data = mh.get_empty_data(area) + + -- Add cylinder + local node_id = minetest.get_content_id(node_name) + local min_radius, max_radius = radius * (radius - 1), radius * (radius + 1) + local stride = {x=1, y=area.ystride, z=area.zstride} + local offset = { + x = current_pos.x - area.MinEdge.x, + y = current_pos.y - area.MinEdge.y, + z = current_pos.z - area.MinEdge.z, + } + local min_slice, max_slice = offset[axis], offset[axis] + length - 1 + local count = 0 + for index2 = -radius, radius do + -- Offset contributed by other axis 1 plus 1 to make it 1-indexed + local new_index2 = (index2 + offset[other1]) * stride[other1] + 1 + for index3 = -radius, radius do + local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2] + local squared = index2 * index2 + index3 * index3 + if squared <= max_radius and (not hollow or squared >= min_radius) then + -- Position is in cylinder + -- Add column along axis + for index1 = min_slice, max_slice do + local vi = new_index3 + index1 * stride[axis] + data[vi] = node_id + end + count = count + length + end + end + end + + mh.finish(manip, data) + + return count +end + + +--- Adds a pyramid. +-- @param pos Position to center base of pyramid at. +-- @param axis Axis ("x", "y", or "z") +-- @param height Pyramid height. +-- @param node_name Name of node to make pyramid of. +-- @return The number of nodes added. +function worldedit.pyramid(pos, axis, height, node_name) + local other1, other2 = worldedit.get_axis_others(axis) + + -- Set up voxel manipulator + local manip, area = mh.init_axis_radius(pos, axis, + height >= 0 and height or -height) + local data = mh.get_empty_data() + + -- Handle inverted pyramids + local start_axis, end_axis, step + if height > 0 then + height = height - 1 + step = 1 + else + height = height + 1 + step = -1 + end + + -- Add pyramid + local node_id = minetest.get_content_id(node_name) + local stride = {x=1, y=area.ystride, z=area.zstride} + local offset = { + x = pos.x - area.MinEdge.x, + y = pos.y - area.MinEdge.y, + z = pos.z - area.MinEdge.z, + } + local size = height * step + local count = 0 + -- For each level of the pyramid + for index1 = 0, height, step do + -- Offset contributed by axis plus 1 to make it 1-indexed + local new_index1 = (index1 + offset[axis]) * stride[axis] + 1 + for index2 = -size, size do + local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1] + for index3 = -size, size do + local i = new_index2 + (index3 + offset[other2]) * stride[other2] + data[i] = node_id + end + end + count = count + (size * 2 + 1) ^ 2 + size = size - 1 + end + + mh.finish(manip, data) + + return count +end + +--- Adds a spiral. +-- @param pos Position to center spiral at. +-- @param length Spral length. +-- @param height Spiral height. +-- @param spacer Space between walls. +-- @param node_name Name of node to make spiral of. +-- @return Number of nodes added. +-- TODO: Add axis option. +function worldedit.spiral(pos, length, height, spacer, node_name) + local extent = math.ceil(length / 2) + + local manip, area = mh.init_axis_radius_length(pos, "y", extent, height) + local data = mh.get_empty_data(area) + + -- Set up variables + local node_id = minetest.get_content_id(node_name) + local stride = {x=1, y=area.ystride, z=area.zstride} + local offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z + local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1 + + -- Add first column + local count = height + local column = i + for y = 1, height do + data[column] = node_id + column = column + stride.y + end + + -- Add spiral segments + local stride_axis, stride_other = stride.x, stride.z + local sign = -1 + local segment_length = 0 + spacer = spacer + 1 + -- Go through each segment except the last + for segment = 1, math.floor(length / spacer) * 2 do + -- Change sign and length every other turn starting with the first + if segment % 2 == 1 then + sign = -sign + segment_length = segment_length + spacer + end + -- Fill segment + for index = 1, segment_length do + -- Move along the direction of the segment + i = i + stride_axis * sign + local column = i + -- Add column + for y = 1, height do + data[column] = node_id + column = column + stride.y + end + end + count = count + segment_length * height + stride_axis, stride_other = stride_other, stride_axis -- Swap axes + end + + -- Add shorter final segment + sign = -sign + for index = 1, segment_length do + i = i + stride_axis * sign + local column = i + -- Add column + for y = 1, height do + data[column] = node_id + column = column + stride.y + end + end + count = count + segment_length * height + + mh.finish(manip, data) + + return count +end diff --git a/mods/WorldEdit/worldedit/serialization.lua b/mods/WorldEdit/worldedit/serialization.lua index c7be315a..bec7e090 100755 --- a/mods/WorldEdit/worldedit/serialization.lua +++ b/mods/WorldEdit/worldedit/serialization.lua @@ -1,273 +1,239 @@ -worldedit = worldedit or {} -local minetest = minetest --local copy of global - ---modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions -worldedit.sort_pos = function(pos1, pos2) - pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} - pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} - if pos1.x > pos2.x then - pos2.x, pos1.x = pos1.x, pos2.x - end - if pos1.y > pos2.y then - pos2.y, pos1.y = pos1.y, pos2.y - end - if pos1.z > pos2.z then - pos2.z, pos1.z = pos1.z, pos2.z - end - return pos1, pos2 -end - ---determines the version of serialized data `value`, returning the version as a positive integer or 0 for unknown versions -worldedit.valueversion = function(value) - if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then --previous list format - return 3 - elseif value:find("^[^\"']+%{%d+%}") then - if value:find("%[\"meta\"%]") then --previous meta flat table format - return 2 - end - return 1 --original flat table format - elseif value:find("%{") then --current nested table format - return 4 - end - return 0 --unknown format -end - ---converts the region defined by positions `pos1` and `pos2` into a single string, returning the serialized data and the number of nodes serialized -worldedit.serialize = function(pos1, pos2) - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local pos = {x=pos1.x, y=0, z=0} - local count = 0 - local result = {} - local get_node, get_meta = minetest.get_node, minetest.get_meta - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) - if node.name ~= "air" and node.name ~= "ignore" then - count = count + 1 - local meta = get_meta(pos):to_table() - - --convert metadata itemstacks to itemstrings - for name, inventory in pairs(meta.inventory) do - for index, stack in ipairs(inventory) do - inventory[index] = stack.to_string and stack:to_string() or stack - end - end - - result[count] = { - x = pos.x - pos1.x, - y = pos.y - pos1.y, - z = pos.z - pos1.z, - name = node.name, - param1 = node.param1, - param2 = node.param2, - meta = meta, - } - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - result = minetest.serialize(result) --convert entries to a string - return result, count -end - ---determines the volume the nodes represented by string `value` would occupy if deserialized at `originpos`, returning the two corner positions and the number of nodes ---contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible) -worldedit.allocate = function(originpos, value) - local huge = math.huge - local pos1x, pos1y, pos1z = huge, huge, huge - local pos2x, pos2y, pos2z = -huge, -huge, -huge - local originx, originy, originz = originpos.x, originpos.y, originpos.z - local count = 0 - local version = worldedit.valueversion(value) - if version == 1 or version == 2 then --flat table format - --obtain the node table - local get_tables = loadstring(value) - if get_tables then --error loading value - return originpos, originpos, count - end - local tables = get_tables() - - --transform the node table into an array of nodes - for i = 1, #tables do - for j, v in pairs(tables[i]) do - if type(v) == "table" then - tables[i][j] = tables[v[1]] - end - end - end - local nodes = tables[1] - - --check the node array - count = #nodes - if version == 1 then --original flat table format - for index = 1, count do - local entry = nodes[index] - local pos = entry[1] - local x, y, z = originx - pos.x, originy - pos.y, originz - pos.z - if x < pos1x then pos1x = x end - if y < pos1y then pos1y = y end - if z < pos1z then pos1z = z end - if x > pos2x then pos2x = x end - if y > pos2y then pos2y = y end - if z > pos2z then pos2z = z end - end - else --previous meta flat table format - for index = 1, count do - local entry = nodes[index] - local x, y, z = originx - entry.x, originy - entry.y, originz - entry.z - if x < pos1x then pos1x = x end - if y < pos1y then pos1y = y end - if z < pos1z then pos1z = z end - if x > pos2x then pos2x = x end - if y > pos2y then pos2y = y end - if z > pos2z then pos2z = z end - end - end - elseif version == 3 then --previous list format - for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries - x, y, z = originx + tonumber(x), originy + tonumber(y), originz + tonumber(z) - if x < pos1x then pos1x = x end - if y < pos1y then pos1y = y end - if z < pos1z then pos1z = z end - if x > pos2x then pos2x = x end - if y > pos2y then pos2y = y end - if z > pos2z then pos2z = z end - count = count + 1 - end - elseif version == 4 then --current nested table format - --wip: this is a filthy hack that works surprisingly well - value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) - local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) - local startpos, startpos1, endpos = 1, 1 - local nodes = {} - while true do - startpos, endpos = escaped:find("},%s*{", startpos) - if not startpos then - break - end - local current = value:sub(startpos1, startpos) - table.insert(nodes, minetest.deserialize("return " .. current)) - startpos, startpos1 = endpos, endpos - end - table.insert(nodes, minetest.deserialize("return " .. value:sub(startpos1))) - - --local nodes = minetest.deserialize(value) --wip: this is broken for larger tables in the current version of LuaJIT - - count = #nodes - for index = 1, count do - local entry = nodes[index] - x, y, z = originx + entry.x, originy + entry.y, originz + entry.z - if x < pos1x then pos1x = x end - if y < pos1y then pos1y = y end - if z < pos1z then pos1z = z end - if x > pos2x then pos2x = x end - if y > pos2y then pos2y = y end - if z > pos2z then pos2z = z end - end - end - local pos1 = {x=pos1x, y=pos1y, z=pos1z} - local pos2 = {x=pos2x, y=pos2y, z=pos2z} - return pos1, pos2, count -end - ---loads the nodes represented by string `value` at position `originpos`, returning the number of nodes deserialized ---contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) by ChillCode, available under the MIT license (GPL compatible) -worldedit.deserialize = function(originpos, value) - --make area stay loaded - local pos1, pos2 = worldedit.allocate(originpos, value) - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local originx, originy, originz = originpos.x, originpos.y, originpos.z - local count = 0 - local add_node, get_meta = minetest.add_node, minetest.get_meta - local version = worldedit.valueversion(value) - if version == 1 or version == 2 then --original flat table format - --obtain the node table - local get_tables = loadstring(value) - if not get_tables then --error loading value - return count - end - local tables = get_tables() - - --transform the node table into an array of nodes - for i = 1, #tables do - for j, v in pairs(tables[i]) do - if type(v) == "table" then - tables[i][j] = tables[v[1]] - end - end - end - local nodes = tables[1] - - --load the node array - count = #nodes - if version == 1 then --original flat table format - for index = 1, count do - local entry = nodes[index] - local pos = entry[1] - pos.x, pos.y, pos.z = originx - pos.x, originy - pos.y, originz - pos.z - add_node(pos, entry[2]) - end - else --previous meta flat table format - for index = 1, #nodes do - local entry = nodes[index] - entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z - add_node(entry, entry) --entry acts both as position and as node - get_meta(entry):from_table(entry.meta) - end - end - elseif version == 3 then --previous list format - local pos = {x=0, y=0, z=0} - local node = {name="", param1=0, param2=0} - for x, y, z, name, param1, param2 in value:gmatch("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do --match node entries - pos.x, pos.y, pos.z = originx + tonumber(x), originy + tonumber(y), originz + tonumber(z) - node.name, node.param1, node.param2 = name, param1, param2 - add_node(pos, node) - count = count + 1 - end - elseif version == 4 then --current nested table format - --wip: this is a filthy hack that works surprisingly well - value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) - local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) - local startpos, startpos1, endpos = 1, 1 - local nodes = {} - while true do - startpos, endpos = escaped:find("},%s*{", startpos) - if not startpos then - break - end - local current = value:sub(startpos1, startpos) - table.insert(nodes, minetest.deserialize("return " .. current)) - startpos, startpos1 = endpos, endpos - end - table.insert(nodes, minetest.deserialize("return " .. value:sub(startpos1))) - - --local nodes = minetest.deserialize(value) --wip: this is broken for larger tables in the current version of LuaJIT - - --load the nodes - count = #nodes - for index = 1, count do - local entry = nodes[index] - entry.x, entry.y, entry.z = originx + entry.x, originy + entry.y, originz + entry.z - add_node(entry, entry) --entry acts both as position and as node - end - - --load the metadata - for index = 1, count do - local entry = nodes[index] - get_meta(entry):from_table(entry.meta) - end - end - return count -end +--- Schematic serialization and deserialiation. +-- @module worldedit.serialization + +worldedit.LATEST_SERIALIZATION_VERSION = 5 +local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":" + + +--[[ +Serialization version history: + 1: Original format. Serialized Lua table with a weird linked format... + 2: Position and node seperated into sub-tables in fields `1` and `2`. + 3: List of nodes, one per line, with fields seperated by spaces. + Format: + 4: Serialized Lua table containing a list of nodes with `x`, `y`, `z`, + `name`, `param1`, `param2`, and `meta` fields. + 5: Added header and made `param1`, `param2`, and `meta` fields optional. + Header format: ,,...: +--]] + + +--- Reads the header of serialized data. +-- @param value Serialized WorldEdit data. +-- @return The version as a positive natural number, or 0 for unknown versions. +-- @return Extra header fields as a list of strings, or nil if not supported. +-- @return Content (data after header). +function worldedit.read_header(value) + if value:find("^[0-9]+[%-:]") then + local header_end = value:find(":", 1, true) + local header = value:sub(1, header_end - 1):split(",") + local version = tonumber(header[1]) + table.remove(header, 1) + local content = value:sub(header_end + 1) + return version, header, content + end + -- Old versions that didn't include a header with a version number + if value:find("([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)") and not value:find("%{") then -- List format + return 3, nil, value + elseif value:find("^[^\"']+%{%d+%}") then + if value:find("%[\"meta\"%]") then -- Meta flat table format + return 2, nil, value + end + return 1, nil, value -- Flat table format + elseif value:find("%{") then -- Raw nested table format + return 4, nil, value + end + return nil +end + + +--- Converts the region defined by positions `pos1` and `pos2` +-- into a single string. +-- @return The serialized data. +-- @return The number of nodes serialized. +function worldedit.serialize(pos1, pos2) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local pos = {x=pos1.x, y=0, z=0} + local count = 0 + local result = {} + local get_node, get_meta = minetest.get_node, minetest.get_meta + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) + if node.name ~= "air" and node.name ~= "ignore" then + count = count + 1 + local meta = get_meta(pos):to_table() + + local meta_empty = true + -- Convert metadata item stacks to item strings + for name, inventory in pairs(meta.inventory) do + for index, stack in ipairs(inventory) do + meta_empty = false + inventory[index] = stack.to_string and stack:to_string() or stack + end + end + for k in pairs(meta) do + if k ~= "inventory" then + meta_empty = false + break + end + end + + result[count] = { + x = pos.x - pos1.x, + y = pos.y - pos1.y, + z = pos.z - pos1.z, + name = node.name, + param1 = node.param1 ~= 0 and node.param1 or nil, + param2 = node.param2 ~= 0 and node.param2 or nil, + meta = not meta_empty and meta or nil, + } + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + -- Serialize entries + result = minetest.serialize(result) + return LATEST_SERIALIZATION_HEADER .. result, count +end + + +--- Loads the schematic in `value` into a node list in the latest format. +-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) +-- by ChillCode, available under the MIT license. +-- @return A node list in the latest format, or nil on failure. +local function load_schematic(value) + local version, header, content = worldedit.read_header(value) + local nodes = {} + if version == 1 or version == 2 then -- Original flat table format + local tables = minetest.deserialize(content) + if not tables then return nil end + + -- Transform the node table into an array of nodes + for i = 1, #tables do + for j, v in pairs(tables[i]) do + if type(v) == "table" then + tables[i][j] = tables[v[1]] + end + end + end + nodes = tables[1] + + if version == 1 then --original flat table format + for i, entry in ipairs(nodes) do + local pos = entry[1] + entry.x, entry.y, entry.z = pos.x, pos.y, pos.z + entry[1] = nil + local node = entry[2] + entry.name, entry.param1, entry.param2 = node.name, node.param1, node.param2 + entry[2] = nil + end + end + elseif version == 3 then -- List format + for x, y, z, name, param1, param2 in content:gmatch( + "([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)%s+" .. + "([^%s]+)%s+(%d+)%s+(%d+)[^\r\n]*[\r\n]*") do + param1, param2 = tonumber(param1), tonumber(param2) + table.insert(nodes, { + x = originx + tonumber(x), + y = originy + tonumber(y), + z = originz + tonumber(z), + name = name, + param1 = param1 ~= 0 and param1 or nil, + param2 = param2 ~= 0 and param2 or nil, + }) + end + elseif version == 4 or version == 5 then -- Nested table format + if not jit then + -- This is broken for larger tables in the current version of LuaJIT + nodes = minetest.deserialize(content) + else + -- XXX: This is a filthy hack that works surprisingly well - in LuaJIT, `minetest.deserialize` will fail due to the register limit + nodes = {} + value = value:gsub("return%s*{", "", 1):gsub("}%s*$", "", 1) -- remove the starting and ending values to leave only the node data + local escaped = value:gsub("\\\\", "@@"):gsub("\\\"", "@@"):gsub("(\"[^\"]*\")", function(s) return string.rep("@", #s) end) + local startpos, startpos1, endpos = 1, 1 + while true do -- go through each individual node entry (except the last) + startpos, endpos = escaped:find("},%s*{", startpos) + if not startpos then + break + end + local current = value:sub(startpos1, startpos) + local entry = minetest.deserialize("return " .. current) + table.insert(nodes, entry) + startpos, startpos1 = endpos, endpos + end + local entry = minetest.deserialize("return " .. value:sub(startpos1)) -- process the last entry + table.insert(nodes, entry) + end + else + return nil + end + return nodes +end + +--- Determines the volume the nodes represented by string `value` would occupy +-- if deserialized at `origin_pos`. +-- @return Low corner position. +-- @return High corner position. +-- @return The number of nodes. +function worldedit.allocate(origin_pos, value) + local nodes = load_schematic(value) + if not nodes then return nil end + return worldedit.allocate_with_nodes(origin_pos, nodes) +end + + +-- Internal +function worldedit.allocate_with_nodes(origin_pos, nodes) + local huge = math.huge + local pos1x, pos1y, pos1z = huge, huge, huge + local pos2x, pos2y, pos2z = -huge, -huge, -huge + local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z + for i, entry in ipairs(nodes) do + local x, y, z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z + if x < pos1x then pos1x = x end + if y < pos1y then pos1y = y end + if z < pos1z then pos1z = z end + if x > pos2x then pos2x = x end + if y > pos2y then pos2y = y end + if z > pos2z then pos2z = z end + end + local pos1 = {x=pos1x, y=pos1y, z=pos1z} + local pos2 = {x=pos2x, y=pos2y, z=pos2z} + return pos1, pos2, #nodes +end + + +--- Loads the nodes represented by string `value` at position `origin_pos`. +-- @return The number of nodes deserialized. +function worldedit.deserialize(origin_pos, value) + local nodes = load_schematic(value) + if not nodes then return nil end + + local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes) + worldedit.keep_loaded(pos1, pos2) + + local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z + local count = 0 + local add_node, get_meta = minetest.add_node, minetest.get_meta + for i, entry in ipairs(nodes) do + entry.x, entry.y, entry.z = origin_x + entry.x, origin_y + entry.y, origin_z + entry.z + -- Entry acts as both position and node + add_node(entry, entry) + if entry.meta then + get_meta(entry):from_table(entry.meta) + end + end + return #nodes +end + diff --git a/mods/WorldEdit/worldedit/visualization.lua b/mods/WorldEdit/worldedit/visualization.lua index a3a4ca3e..5ac49f3b 100755 --- a/mods/WorldEdit/worldedit/visualization.lua +++ b/mods/WorldEdit/worldedit/visualization.lua @@ -1,142 +1,131 @@ -worldedit = worldedit or {} -local minetest = minetest --local copy of global - ---modifies positions `pos1` and `pos2` so that each component of `pos1` is less than or equal to its corresponding conent of `pos2`, returning two new positions -worldedit.sort_pos = function(pos1, pos2) - pos1 = {x=pos1.x, y=pos1.y, z=pos1.z} - pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} - if pos1.x > pos2.x then - pos2.x, pos1.x = pos1.x, pos2.x - end - if pos1.y > pos2.y then - pos2.y, pos1.y = pos1.y, pos2.y - end - if pos1.z > pos2.z then - pos2.z, pos1.z = pos1.z, pos2.z - end - return pos1, pos2 -end - ---determines the volume of the region defined by positions `pos1` and `pos2`, returning the volume -worldedit.volume = function(pos1, pos2) - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - return (pos2.x - pos1.x + 1) * (pos2.y - pos1.y + 1) * (pos2.z - pos1.z + 1) -end - -minetest.register_node("worldedit:placeholder", { - drawtype = "airlike", - paramtype = "light", - sunlight_propagates = true, - diggable = false, - groups = {not_in_creative_inventory=1}, -}) - ---hides all nodes in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes, returning the number of nodes hidden -worldedit.hide = function(pos1, pos2) - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local pos = {x=pos1.x, y=0, z=0} - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) - if node.name ~= "worldedit:placeholder" then - local data = get_meta(pos):to_table() --obtain metadata of original node - data.fields.worldedit_placeholder = node.name --add the node's name - node.name = "worldedit:placeholder" --set node name - add_node(pos, node) --add placeholder node - get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return worldedit.volume(pos1, pos2) -end - ---suppresses all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively replacing them with invisible nodes, returning the number of nodes suppressed -worldedit.suppress = function(pos1, pos2, nodename) - --ignore placeholder supression - if nodename == "worldedit:placeholder" then - return 0 - end - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local nodes = minetest.find_nodes_in_area(pos1, pos2, nodename) - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - for _, pos in ipairs(nodes) do - local node = get_node(pos) - local data = get_meta(pos):to_table() --obtain metadata of original node - data.fields.worldedit_placeholder = node.name --add the node's name - node.name = "worldedit:placeholder" --set node name - add_node(pos, node) --add placeholder node - get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata - end - return #nodes -end - ---highlights all instances of `nodename` in a region defined by positions `pos1` and `pos2` by non-destructively hiding all other nodes, returning the number of nodes found -worldedit.highlight = function(pos1, pos2, nodename) - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local pos = {x=pos1.x, y=0, z=0} - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - local count = 0 - while pos.x <= pos2.x do - pos.y = pos1.y - while pos.y <= pos2.y do - pos.z = pos1.z - while pos.z <= pos2.z do - local node = get_node(pos) - if node.name == nodename then --node found - count = count + 1 - elseif node.name ~= "worldedit:placeholder" then --hide other nodes - local data = get_meta(pos):to_table() --obtain metadata of original node - data.fields.worldedit_placeholder = node.name --add the node's name - node.name = "worldedit:placeholder" --set node name - add_node(pos, node) --add placeholder node - get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata - end - pos.z = pos.z + 1 - end - pos.y = pos.y + 1 - end - pos.x = pos.x + 1 - end - return count -end - ---restores all nodes hidden with WorldEdit functions in a region defined by positions `pos1` and `pos2`, returning the number of nodes restored -worldedit.restore = function(pos1, pos2) - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local nodes = minetest.find_nodes_in_area(pos1, pos2, "worldedit:placeholder") - local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node - for _, pos in ipairs(nodes) do - local node = get_node(pos) - local data = get_meta(pos):to_table() --obtain node metadata - node.name = data.fields.worldedit_placeholder --set node name - data.fields.worldedit_placeholder = nil --delete old nodename - add_node(pos, node) --add original node - get_meta(pos):from_table(data) --set original node metadata - end - return #nodes -end +--- Functions for visibly hiding nodes +-- @module worldedit.visualization + +minetest.register_node("worldedit:placeholder", { + drawtype = "airlike", + paramtype = "light", + sunlight_propagates = true, + diggable = false, + walkable = false, + groups = {not_in_creative_inventory=1}, +}) + +--- Hides all nodes in a region defined by positions `pos1` and `pos2` by +-- non-destructively replacing them with invisible nodes. +-- @return The number of nodes hidden. +function worldedit.hide(pos1, pos2) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local pos = {x=pos1.x, y=0, z=0} + local get_node, get_meta, swap_node = minetest.get_node, + minetest.get_meta, minetest.swap_node + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) + if node.name ~= "air" and node.name ~= "worldedit:placeholder" then + -- Save the node's original name + get_meta(pos):set_string("worldedit_placeholder", node.name) + -- Swap in placeholder node + node.name = "worldedit:placeholder" + swap_node(pos, node) + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return worldedit.volume(pos1, pos2) +end + +--- Suppresses all instances of `node_name` in a region defined by positions +-- `pos1` and `pos2` by non-destructively replacing them with invisible nodes. +-- @return The number of nodes suppressed. +function worldedit.suppress(pos1, pos2, node_name) + -- Ignore placeholder supression + if node_name == "worldedit:placeholder" then + return 0 + end + + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local nodes = minetest.find_nodes_in_area(pos1, pos2, node_name) + local get_node, get_meta, swap_node = minetest.get_node, + minetest.get_meta, minetest.swap_node + for _, pos in ipairs(nodes) do + local node = get_node(pos) + -- Save the node's original name + get_meta(pos):set_string("worldedit_placeholder", node.name) + -- Swap in placeholder node + node.name = "worldedit:placeholder" + swap_node(pos, node) + end + return #nodes +end + +--- Highlights all instances of `node_name` in a region defined by positions +-- `pos1` and `pos2` by non-destructively hiding all other nodes. +-- @return The number of nodes found. +function worldedit.highlight(pos1, pos2, node_name) + pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local pos = {x=pos1.x, y=0, z=0} + local get_node, get_meta, swap_node = minetest.get_node, + minetest.get_meta, minetest.swap_node + local count = 0 + while pos.x <= pos2.x do + pos.y = pos1.y + while pos.y <= pos2.y do + pos.z = pos1.z + while pos.z <= pos2.z do + local node = get_node(pos) + if node.name == node_name then -- Node found + count = count + 1 + elseif node.name ~= "worldedit:placeholder" then -- Hide other nodes + -- Save the node's original name + get_meta(pos):set_string("worldedit_placeholder", node.name) + -- Swap in placeholder node + node.name = "worldedit:placeholder" + swap_node(pos, node) + end + pos.z = pos.z + 1 + end + pos.y = pos.y + 1 + end + pos.x = pos.x + 1 + end + return count +end + +-- Restores all nodes hidden with WorldEdit functions in a region defined +-- by positions `pos1` and `pos2`. +-- @return The number of nodes restored. +function worldedit.restore(pos1, pos2) + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + + worldedit.keep_loaded(pos1, pos2) + + local nodes = minetest.find_nodes_in_area(pos1, pos2, "worldedit:placeholder") + local get_node, get_meta, swap_node = minetest.get_node, + minetest.get_meta, minetest.swap_node + for _, pos in ipairs(nodes) do + local node = get_node(pos) + local meta = get_meta(pos) + local data = meta:to_table() + node.name = data.fields.worldedit_placeholder + data.fields.worldedit_placeholder = nil + meta:from_table(data) + swap_node(pos, node) + end + return #nodes +end + diff --git a/mods/WorldEdit/worldedit_commands/init.lua b/mods/WorldEdit/worldedit_commands/init.lua index 3d089e19..e97f304b 100755 --- a/mods/WorldEdit/worldedit_commands/init.lua +++ b/mods/WorldEdit/worldedit_commands/init.lua @@ -1,1114 +1,1135 @@ -minetest.register_privilege("worldedit", "Can use WorldEdit commands") - ---wip: fold the hollow stuff into the main functions and add a hollow flag at the end, then add the compatibility stuff - -worldedit.set_pos = {} -worldedit.inspect = {} - -worldedit.pos1 = {} -worldedit.pos2 = {} -if minetest.place_schematic then - worldedit.prob_pos = {} - worldedit.prob_list = {} -end - -dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua") -dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua") - -local get_position = function(name) --position 1 retrieval function for when not using `safe_region` - local pos1 = worldedit.pos1[name] - if pos1 == nil then - worldedit.player_notify(name, "no position 1 selected") - end - return pos1 -end - -local get_node = function(name, nodename) - local node = worldedit.normalize_nodename(nodename) - if not node then - worldedit.player_notify(name, "invalid node name: " .. nodename) - return nil - end - return node -end - -worldedit.player_notify = function(name, message) - minetest.chat_send_player(name, "WorldEdit -!- " .. message, false) -end - ---determines whether `nodename` is a valid node name, returning a boolean -worldedit.normalize_nodename = function(nodename) - nodename = nodename:gsub("^%s*(.-)%s*$", "%1") - if nodename == "" then return nil end - local fullname = ItemStack({name=nodename}):get_name() --resolve aliases of node names to full names - if minetest.registered_nodes[fullname] or fullname == "air" then --directly found node name or alias of nodename - return fullname - end - for key, value in pairs(minetest.registered_nodes) do - if key:find(":" .. nodename, 1, true) then --found in mod - return key - end - end - nodename = nodename:lower() --lowercase both for case insensitive comparison - for key, value in pairs(minetest.registered_nodes) do - if value.description:lower() == nodename then --found in description - return key - end - end - return nil -end - ---determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1) -worldedit.player_axis = function(name) - local dir = minetest.get_player_by_name(name):get_look_dir() - local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z) - if x > y then - if x > z then - return "x", dir.x > 0 and 1 or -1 - end - elseif y > z then - return "y", dir.y > 0 and 1 or -1 - end - return "z", dir.z > 0 and 1 or -1 -end - -minetest.register_chatcommand("/about", { - params = "", - description = "Get information about the mod", - func = function(name, param) - worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string .. " is available on this server. Type /help to get a list of commands, or get more information at https://github.com/Uberi/MineTest-WorldEdit/") - end, -}) - -minetest.register_chatcommand("/inspect", { - params = "on/off/1/0/true/false/yes/no/enable/disable/", - description = "Enable or disable node inspection", - privs = {worldedit=true}, - func = function(name, param) - if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then - worldedit.inspect[name] = true - local axis, sign = worldedit.player_axis(name) - worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis", - name, axis .. (sign > 0 and "+" or "-"))) - elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then - worldedit.inspect[name] = nil - worldedit.player_notify(name, "inspector: inspection disabled") - else - worldedit.player_notify(name, "invalid usage: " .. param) - end - end, -}) - -minetest.register_on_punchnode(function(pos, node, puncher) - local name = puncher:get_player_name() - if worldedit.inspect[name] then - if minetest.check_player_privs(name, {worldedit=true}) then - local axis, sign = worldedit.player_axis(name) - message = string.format("inspector: %s at %s (param1=%d, param2=%d) punched by %s facing the %s axis", - node.name, minetest.pos_to_string(pos), node.param1, node.param2, name, axis .. (sign > 0 and "+" or "-")) - else - message = "inspector: worldedit privileges required" - end - worldedit.player_notify(name, message) - end -end) - -minetest.register_chatcommand("/reset", { - params = "", - description = "Reset the region so that it is empty", - privs = {worldedit=true}, - func = function(name, param) - worldedit.pos1[name] = nil - worldedit.pos2[name] = nil - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - worldedit.set_pos[name] = nil - worldedit.player_notify(name, "region reset") - end, -}) - -minetest.register_chatcommand("/mark", { - params = "", - description = "Show markers at the region positions", - privs = {worldedit=true}, - func = function(name, param) - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - worldedit.player_notify(name, "region marked") - end, -}) - -minetest.register_chatcommand("/unmark", { - params = "", - description = "Hide markers if currently shown", - privs = {worldedit=true}, - func = function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - worldedit.pos1[name] = nil - worldedit.pos2[name] = nil - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - worldedit.pos1[name] = pos1 - worldedit.pos2[name] = pos2 - worldedit.player_notify(name, "region unmarked") - end, -}) - -minetest.register_chatcommand("/pos1", { - params = "", - description = "Set WorldEdit region position 1 to the player's location", - privs = {worldedit=true}, - func = function(name, param) - local pos = minetest.get_player_by_name(name):getpos() - pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) - worldedit.pos1[name] = pos - worldedit.mark_pos1(name) - worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) - end, -}) - -minetest.register_chatcommand("/pos2", { - params = "", - description = "Set WorldEdit region position 2 to the player's location", - privs = {worldedit=true}, - func = function(name, param) - local pos = minetest.get_player_by_name(name):getpos() - pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) - worldedit.pos2[name] = pos - worldedit.mark_pos2(name) - worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) - end, -}) - -minetest.register_chatcommand("/p", { - params = "set/set1/set2/get", - description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region", - privs = {worldedit=true}, - func = function(name, param) - if param == "set" then --set both WorldEdit positions - worldedit.set_pos[name] = "pos1" - worldedit.player_notify(name, "select positions by punching two nodes") - elseif param == "set1" then --set WorldEdit position 1 - worldedit.set_pos[name] = "pos1only" - worldedit.player_notify(name, "select position 1 by punching a node") - elseif param == "set2" then --set WorldEdit position 2 - worldedit.set_pos[name] = "pos2" - worldedit.player_notify(name, "select position 2 by punching a node") - elseif param == "get" then --display current WorldEdit positions - if worldedit.pos1[name] ~= nil then - worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name])) - else - worldedit.player_notify(name, "position 1 not set") - end - if worldedit.pos2[name] ~= nil then - worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name])) - else - worldedit.player_notify(name, "position 2 not set") - end - else - worldedit.player_notify(name, "unknown subcommand: " .. param) - end - end, -}) - -minetest.register_chatcommand("/fixedpos", { - params = "set1/set2 x y z", - description = "Set a WorldEdit region position to the position at (, , )", - privs = {worldedit=true}, - func = function(name, param) - local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - local pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)} - if flag == "set1" then - worldedit.pos1[name] = pos - worldedit.mark_pos1(name) - worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) - else --flag == "set2" - worldedit.pos2[name] = pos - worldedit.mark_pos2(name) - worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) - end - end, -}) - -minetest.register_on_punchnode(function(pos, node, puncher) - local name = puncher:get_player_name() - if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position - if worldedit.set_pos[name] == "pos1" then --setting position 1 - worldedit.pos1[name] = pos - worldedit.mark_pos1(name) - worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation - worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) - elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only - worldedit.pos1[name] = pos - worldedit.mark_pos1(name) - worldedit.set_pos[name] = nil --finished setting positions - worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) - elseif worldedit.set_pos[name] == "pos2" then --setting position 2 - worldedit.pos2[name] = pos - worldedit.mark_pos2(name) - worldedit.set_pos[name] = nil --finished setting positions - worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) - elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities - worldedit.prob_pos[name] = pos - minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]") - end - end -end) - -minetest.register_chatcommand("/volume", { - params = "", - description = "Display the volume of the current WorldEdit region", - privs = {worldedit=true}, - func = function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - if pos1 == nil or pos2 == nil then - worldedit.player_notify(name, "no region selected") - return nil - end - - local volume = worldedit.volume(pos1, pos2) - local abs = math.abs - worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes (" - .. abs(pos2.x - pos1.x) + 1 .. "*" - .. abs(pos2.y - pos1.y) + 1 .. "*" - .. abs(pos2.z - pos1.z) + 1 .. ")") - end, -}) - -minetest.register_chatcommand("/set", { - params = "", - description = "Set the current WorldEdit region to ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local node = get_node(name, param) - if not node then - worldedit.player_notify(name, "Could not identify node \"" .. param .. "\"") - return - end - - local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node) - worldedit.player_notify(name, count .. " nodes set") - end, check_region), -}) - -minetest.register_chatcommand("/mix", { - params = " ...", - description = "Fill the current WorldEdit region with a random mix of , ...", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local nodes = {} - for nodename in param:gmatch("[^%s]+") do - local node = get_node(name, nodename) - if not node then - worldedit.player_notify(name, "Could not identify node \"" .. name .. "\"") - return - end - nodes[#nodes + 1] = node - end - - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - local count = worldedit.set(pos1, pos2, nodes) - worldedit.player_notify(name, count .. " nodes set") - end, check_region), -}) - -local check_replace = function(name, param) - local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local newsearchnode = worldedit.normalize_nodename(searchnode) - if not newsearchnode then - worldedit.player_notify(name, "invalid search node name: " .. searchnode) - return nil - end - local newreplacenode = worldedit.normalize_nodename(replacenode) - if not newreplacenode then - worldedit.player_notify(name, "invalid replace node name: " .. replacenode) - return nil - end - return check_region(name, param) -end - -minetest.register_chatcommand("/replace", { - params = " ", - description = "Replace all instances of with in the current WorldEdit region", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") - local newsearchnode = worldedit.normalize_nodename(searchnode) - local newreplacenode = worldedit.normalize_nodename(replacenode) - local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], newsearchnode, newreplacenode) - worldedit.player_notify(name, count .. " nodes replaced") - end, check_replace), -}) - -minetest.register_chatcommand("/replaceinverse", { - params = " ", - description = "Replace all nodes other than with in the current WorldEdit region", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") - local newsearchnode = worldedit.normalize_nodename(searchnode) - local newreplacenode = worldedit.normalize_nodename(replacenode) - local count = worldedit.replaceinverse(worldedit.pos1[name], worldedit.pos2[name], searchnode, replacenode) - worldedit.player_notify(name, count .. " nodes replaced") - end, check_replace), -}) - -local check_sphere = function(name, param) - if worldedit.pos1[name] == nil then - worldedit.player_notify(name, "no position 1 selected") - return nil - end - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local node = get_node(name, nodename) - if not node then return nil end - return math.ceil((4 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of sphere -end - -minetest.register_chatcommand("/hollowsphere", { - params = " ", - description = "Add hollow sphere centered at WorldEdit position 1 with radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - local node = get_node(name, nodename) - local count = worldedit.hollow_sphere(worldedit.pos1[name], tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_sphere), -}) - -minetest.register_chatcommand("/sphere", { - params = " ", - description = "Add sphere centered at WorldEdit position 1 with radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - local node = get_node(name, nodename) - local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_sphere), -}) - -local check_dome = function(name, param) - if worldedit.pos1[name] == nil then - worldedit.player_notify(name, "no position 1 selected") - return nil - end - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local node = get_node(name, nodename) - if not node then return nil end - return math.ceil((2 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of dome -end - -minetest.register_chatcommand("/hollowdome", { - params = " ", - description = "Add hollow dome centered at WorldEdit position 1 with radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - local node = get_node(name, nodename) - local count = worldedit.hollow_dome(worldedit.pos1[name], tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_dome), -}) - -minetest.register_chatcommand("/dome", { - params = " ", - description = "Add dome centered at WorldEdit position 1 with radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") - local node = get_node(name, nodename) - local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_dome), -}) - -local check_cylinder = function(name, param) - if worldedit.pos1[name] == nil then - worldedit.player_notify(name, "no position 1 selected") - return nil - end - local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local node = get_node(name, nodename) - if not node then return nil end - return math.ceil(math.pi * (tonumber(radius) ^ 2) * tonumber(length)) -end - -minetest.register_chatcommand("/hollowcylinder", { - params = "x/y/z/? ", - description = "Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length and radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") - length = tonumber(length) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - length = length * sign - end - local node = get_node(name, nodename) - local count = worldedit.hollow_cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_cylinder), -}) - -minetest.register_chatcommand("/cylinder", { - params = "x/y/z/? ", - description = "Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length and radius , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") - length = tonumber(length) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - length = length * sign - end - local node = get_node(name, nodename) - local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node) - worldedit.player_notify(name, count .. " nodes added") - end, check_cylinder), -}) - -minetest.register_chatcommand("/pyramid", { - params = "x/y/z/? ", - description = "Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") - height = tonumber(height) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - height = height * sign - end - local node = get_node(name, nodename) - local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node) - worldedit.player_notify(name, count .. " nodes added") - end, - function(name, param) - if worldedit.pos1[name] == nil then - worldedit.player_notify(name, "no position 1 selected") - return nil - end - local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local node = get_node(name, nodename) - if not node then return nil end - height = tonumber(height) - return math.ceil(((height * 2 + 1) ^ 2) * height / 3) - end), -}) - -minetest.register_chatcommand("/spiral", { - params = " ", - description = "Add spiral centered at WorldEdit position 1 with side length , height , space between walls , composed of ", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") - local node = get_node(name, nodename) - local count = worldedit.spiral(worldedit.pos1[name], tonumber(length), tonumber(height), tonumber(space), node) - worldedit.player_notify(name, count .. " nodes added") - end, - function(name, param) - if worldedit.pos1[name] == nil then - worldedit.player_notify(name, "no position 1 selected") - return nil - end - local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - local node = get_node(name, nodename) - if not node then return nil end - return check_region(name, param) - end), -}) - -minetest.register_chatcommand("/copy", { - params = "x/y/z/? ", - description = "Copy the current WorldEdit region along the x/y/z/? axis by nodes", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - amount = tonumber(amount) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - amount = amount * sign - end - - local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount) - worldedit.player_notify(name, count .. " nodes copied") - end, - function(name, param) - local volume = check_region(name, param) - return volume and volume * 2 or volume - end), -}) - -minetest.register_chatcommand("/move", { - params = "x/y/z/? ", - description = "Move the current WorldEdit region along the x/y/z/? axis by nodes", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - amount = tonumber(amount) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - amount = amount * sign - end - - local count = worldedit.move(pos1, pos2, axis, amount) - - pos1[axis] = pos1[axis] + amount - pos2[axis] = pos2[axis] + amount - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - worldedit.player_notify(name, count .. " nodes moved") - end, check_region), -}) - -minetest.register_chatcommand("/stack", { - params = "x/y/z/? ", - description = "Stack the current WorldEdit region along the x/y/z/? axis times", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") - repetitions = tonumber(repetitions) - if axis == "?" then - axis, sign = worldedit.player_axis(name) - repetitions = repetitions * sign - end - local count = worldedit.stack(worldedit.pos1[name], worldedit.pos2[name], axis, repetitions) - worldedit.player_notify(name, count .. " nodes stacked") - end, - function(name, param) - local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - end - local count = check_region(name, param) - if count then return (tonumber(repetitions) + 1) * count end - return nil - end), -}) - -minetest.register_chatcommand("/stack2", { - params = " ", - description = "Stack the current WorldEdit region times by offset , , ", - privs = {worldedit=true}, - func = function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - if pos1 == nil or pos2 == nil then - worldedit.player_notify(name, "Select a position first!") - return - end - local repetitions, incs = param:match("(%d+)%s*(.+)") - if repetitions == nil then - worldedit.player_notify(name, "invalid count: " .. param) - return - end - repetitions = tonumber(repetitions) - - local x, y, z = incs:match("([+-]?%d+) ([+-]%d+) ([+-]%d+)") - if x == nil then - worldedit.player_notify(name, "invalid increments: " .. param) - return - end - x, y, z = tonumber(x), tonumber(y), tonumber(z) - - local count = worldedit.volume(pos1, pos2) * repetitions - - return safe_region(function() - worldedit.stack2(pos1, pos2, {x=x, y=y, z=z}, repetitions, - function() worldedit.player_notify(name, count .. " nodes stacked") end) - end, function() - return count - end)(name,param) -- more hax --wip: clean this up a little bit - end -}) - - -minetest.register_chatcommand("/stretch", { - params = " ", - description = "Scale the current WorldEdit positions and region by a factor of , , along the X, Y, and Z axes, repectively, with position 1 as the origin", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") - stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) - local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz) - - --reset markers to scaled positions - worldedit.pos1[name] = pos1 - worldedit.pos2[name] = pos2 - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - - worldedit.player_notify(name, count .. " nodes stretched") - end, - function(name, param) - local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) - if stretchx == 0 or stretchy == 0 or stretchz == 0 then - worldedit.player_notify(name, "invalid scaling factors: " .. param) - end - local count = check_region(name, param) - if count then return tonumber(stretchx) * tonumber(stretchy) * tonumber(stretchz) * count end - return nil - end), -}) - -minetest.register_chatcommand("/transpose", { - params = "x/y/z/? x/y/z/?", - description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") - if axis1 == "?" then axis1 = worldedit.player_axis(name) end - if axis2 == "?" then axis2 = worldedit.player_axis(name) end - local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) - - --reset markers to transposed positions - worldedit.pos1[name] = pos1 - worldedit.pos2[name] = pos2 - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - - worldedit.player_notify(name, count .. " nodes transposed") - end, - function(name, param) - local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - if axis1 == axis2 then - worldedit.player_notify(name, "invalid usage: axes must be different") - return nil - end - return check_region(name, param) - end), -}) - -minetest.register_chatcommand("/flip", { - params = "x/y/z/?", - description = "Flip the current WorldEdit region along the x/y/z/? axis", - privs = {worldedit=true}, - func = safe_region(function(name, param) - if param == "?" then param = worldedit.player_axis(name) end - local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param) - worldedit.player_notify(name, count .. " nodes flipped") - end, - function(name, param) - if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - return check_region(name, param) - end), -}) - -minetest.register_chatcommand("/rotate", { - params = " ", - description = "Rotate the current WorldEdit region around the axis by angle (90 degree increment)", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") - if axis == "?" then axis = worldedit.player_axis(name) end - local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle) - - --reset markers to rotated positions - worldedit.pos1[name] = pos1 - worldedit.pos2[name] = pos2 - worldedit.mark_pos1(name) - worldedit.mark_pos2(name) - - worldedit.player_notify(name, count .. " nodes rotated") - end, - function(name, param) - local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - if angle % 90 ~= 0 then - worldedit.player_notify(name, "invalid usage: angle must be multiple of 90") - return nil - end - return check_region(name, param) - end), -}) - -minetest.register_chatcommand("/orient", { - params = "", - description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle (90 degree increment)", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local found, _, angle = param:find("^([+-]?%d+)$") - local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle) - worldedit.player_notify(name, count .. " nodes oriented") - end, - function(name, param) - local found, _, angle = param:find("^([+-]?%d+)$") - if found == nil then - worldedit.player_notify(name, "invalid usage: " .. param) - return nil - end - if angle % 90 ~= 0 then - worldedit.player_notify(name, "invalid usage: angle must be multiple of 90") - return nil - end - return check_region(name, param) - end), -}) - -minetest.register_chatcommand("/fixlight", { - params = "", - description = "Fix the lighting in the current WorldEdit region", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name]) - worldedit.player_notify(name, count .. " nodes updated") - end), -}) - -minetest.register_chatcommand("/hide", { - params = "", - description = "Hide all nodes in the current WorldEdit region non-destructively", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name]) - worldedit.player_notify(name, count .. " nodes hidden") - end), -}) - -local check_set -- Actual garbage for an unknown global variable - -minetest.register_chatcommand("/suppress", { - params = "", - description = "Suppress all in the current WorldEdit region non-destructively", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local node = get_node(name, param) - local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node) - worldedit.player_notify(name, count .. " nodes suppressed") - end, check_set), -}) - -minetest.register_chatcommand("/highlight", { - params = "", - description = "Highlight in the current WorldEdit region by hiding everything else non-destructively", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local node = get_node(name, param) - local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node) - worldedit.player_notify(name, count .. " nodes highlighted") - end, check_set), -}) - -minetest.register_chatcommand("/restore", { - params = "", - description = "Restores nodes hidden with WorldEdit in the current WorldEdit region", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name]) - worldedit.player_notify(name, count .. " nodes restored") - end), -}) - -minetest.register_chatcommand("/save", { - params = "", - description = "Save the current WorldEdit region to \"(world folder)/schems/.we\"", - privs = {worldedit=true}, - func = safe_region(function(name, param) - if param == "" then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then - worldedit.player_notify(name, "invalid file name: " .. param) - return - end - - local result, count = worldedit.serialize(worldedit.pos1[name], worldedit.pos2[name]) - - local path = minetest.get_worldpath() .. "/schems" - local filename = path .. "/" .. param .. ".we" - filename = filename:gsub("\"", "\\\""):gsub("\\", "\\\\") --escape any nasty characters - os.execute("mkdir \"" .. path .. "\"") --create directory if it does not already exist - local file, err = io.open(filename, "wb") - if err ~= nil then - worldedit.player_notify(name, "could not save file to \"" .. filename .. "\"") - return - end - file:write(result) - file:flush() - file:close() - - worldedit.player_notify(name, count .. " nodes saved") - end), -}) - -minetest.register_chatcommand("/allocate", { - params = "", - description = "Set the region defined by nodes from \"(world folder)/schems/.we\" as the current WorldEdit region", - privs = {worldedit=true}, - func = function(name, param) - local pos = get_position(name) - if pos == nil then return end - - if param == "" then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then - worldedit.player_notify(name, "invalid file name: " .. param) - return - end - - local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we" - local file, err = io.open(filename, "rb") - if err ~= nil then - worldedit.player_notify(name, "could not open file \"" .. filename .. "\"") - return - end - local value = file:read("*a") - file:close() - - if worldedit.valueversion(value) == 0 then --unknown version - worldedit.player_notify(name, "invalid file: file is invalid or created with newer version of WorldEdit") - return - end - local nodepos1, nodepos2, count = worldedit.allocate(pos, value) - - worldedit.pos1[name] = nodepos1 - worldedit.mark_pos1(name) - worldedit.pos2[name] = nodepos2 - worldedit.mark_pos2(name) - - worldedit.player_notify(name, count .. " nodes allocated") - end, -}) - -minetest.register_chatcommand("/load", { - params = "", - description = "Load nodes from \"(world folder)/schems/[.we[m]]\" with position 1 of the current WorldEdit region as the origin", - privs = {worldedit=true}, - func = function(name, param) - local pos = get_position(name) - if pos == nil then return end - - if param == "" then - worldedit.player_notify(name, "invalid usage: " .. param) - return - end - if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then - worldedit.player_notify(name, "invalid file name: " .. param) - return - end - - --find the file in the world path - local testpaths = { - minetest.get_worldpath() .. "/schems/" .. param, - minetest.get_worldpath() .. "/schems/" .. param .. ".we", - minetest.get_worldpath() .. "/schems/" .. param .. ".wem", - } - local file, err - for index, path in ipairs(testpaths) do - file, err = io.open(path, "rb") - if not err then - break - end - end - if err then - worldedit.player_notify(name, "could not open file \"" .. param .. "\"") - return - end - local value = file:read("*a") - file:close() - - if worldedit.valueversion(value) == 0 then --unknown version - worldedit.player_notify(name, "invalid file: file is invalid or created with newer version of WorldEdit") - return - end - - local count = worldedit.deserialize(pos, value) - - worldedit.player_notify(name, count .. " nodes loaded") - end, -}) - -minetest.register_chatcommand("/lua", { - params = "", - description = "Executes as a Lua chunk in the global namespace", - privs = {worldedit=true, server=true}, - func = function(name, param) - local admin = minetest.setting_get("name") - if not admin or not name == admin then - worldedit.player_notify(name, "this command can only be run by the server administrator") - return - end - local err = worldedit.lua(param) - if err then - worldedit.player_notify(name, "code error: " .. err) - else - worldedit.player_notify(name, "code successfully executed", false) - end - end, -}) - -minetest.register_chatcommand("/luatransform", { - params = "", - description = "Executes as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region", - privs = {worldedit=true, server=true}, - func = safe_region(function(name, param) - local admin = minetest.setting_get("name") - if not admin or not name == admin then - worldedit.player_notify(name, "this command can only be run by the server administrator") - return - end - - local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param) - if err then - worldedit.player_notify(name, "code error: " .. err, false) - else - worldedit.player_notify(name, "code successfully executed", false) - end - end), -}) - -minetest.register_chatcommand("/mtschemcreate", { - params = "", - description = "Save the current WorldEdit region using the Minetest Schematic format to \"(world folder)/schems/.mts\"", - privs = {worldedit=true}, - func = safe_region(function(name, param) - if param == nil then - worldedit.player_notify(name, "No filename specified") - return - end - - local path = minetest.get_worldpath() .. "/schems" - local filename = path .. "/" .. param .. ".mts" - filename = filename:gsub("\"", "\\\""):gsub("\\", "\\\\") --escape any nasty characters - os.execute("mkdir \"" .. path .. "\"") --create directory if it does not already exist - - local ret = minetest.create_schematic(worldedit.pos1[name], worldedit.pos2[name], worldedit.prob_list[name], filename) - if ret == nil then - worldedit.player_notify(name, "failed to create Minetest schematic", false) - else - worldedit.player_notify(name, "saved Minetest schematic to " .. param, false) - end - worldedit.prob_list[name] = {} - end), -}) - -minetest.register_chatcommand("/mtschemplace", { - params = "", - description = "Load nodes from \"(world folder)/schems/.mts\" with position 1 of the current WorldEdit region as the origin", - privs = {worldedit=true}, - func = function(name, param) - if param == nil then - worldedit.player_notify(name, "no filename specified") - return - end - - local pos = get_position(name) - if pos == nil then return end - - local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts" - if minetest.place_schematic(pos, path) == nil then - worldedit.player_notify(name, "failed to place Minetest schematic", false) - else - worldedit.player_notify(name, "placed Minetest schematic " .. param .. - " at " .. minetest.pos_to_string(pos), false) - end - end, -}) - -minetest.register_chatcommand("/mtschemprob", { - params = "start/finish/get", - description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry", - privs = {worldedit=true}, - func = function(name, param) - if param == "start" then --start probability setting - worldedit.set_pos[name] = "prob" - worldedit.prob_list[name] = {} - worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes") - elseif param == "finish" then --finish probability setting - worldedit.set_pos[name] = nil - worldedit.player_notify(name, "finished Minetest schematic probability selection") - elseif param == "get" then --get all nodes that had probabilities set on them - local text = "" - local problist = worldedit.prob_list[name] - if problist == nil then - return - end - for k,v in pairs(problist) do - local prob = math.floor(((v["prob"] / 256) * 100) * 100 + 0.5) / 100 - text = text .. minetest.pos_to_string(v["pos"]) .. ": " .. prob .. "% | " - end - worldedit.player_notify(name, "currently set node probabilities:") - worldedit.player_notify(name, text) - else - worldedit.player_notify(name, "unknown subcommand: " .. param) - end - end, -}) - -minetest.register_on_player_receive_fields( - function(player, formname, fields) - if (formname == "prob_val_enter") and (fields.text ~= "") then - local name = player:get_player_name() - local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)} - local index = table.getn(worldedit.prob_list[name]) + 1 - worldedit.prob_list[name][index] = prob_entry - end - end -) - -minetest.register_chatcommand("/clearobjects", { - params = "", - description = "Clears all objects within the WorldEdit region", - privs = {worldedit=true}, - func = safe_region(function(name, param) - local count = worldedit.clearobjects(worldedit.pos1[name], worldedit.pos2[name]) - worldedit.player_notify(name, count .. " objects cleared") - end), -}) +minetest.register_privilege("worldedit", "Can use WorldEdit commands") + +worldedit.set_pos = {} +worldedit.inspect = {} + +worldedit.pos1 = {} +worldedit.pos2 = {} +if minetest.place_schematic then + worldedit.prob_pos = {} + worldedit.prob_list = {} +end + +dofile(minetest.get_modpath("worldedit_commands") .. "/mark.lua") +dofile(minetest.get_modpath("worldedit_commands") .. "/safe.lua"); safe_region = safe_region or function(callback) return callback end + +local get_position = function(name) --position 1 retrieval function for when not using `safe_region` + local pos1 = worldedit.pos1[name] + if pos1 == nil then + worldedit.player_notify(name, "no position 1 selected") + end + return pos1 +end + +local get_node = function(name, nodename) + local node = worldedit.normalize_nodename(nodename) + if not node then + worldedit.player_notify(name, "invalid node name: " .. nodename) + return nil + end + return node +end + +worldedit.player_notify = function(name, message) + minetest.chat_send_player(name, "WorldEdit -!- " .. message, false) +end + +--determines whether `nodename` is a valid node name, returning a boolean +worldedit.normalize_nodename = function(nodename) + nodename = nodename:gsub("^%s*(.-)%s*$", "%1") + if nodename == "" then return nil end + local fullname = ItemStack({name=nodename}):get_name() --resolve aliases of node names to full names + if minetest.registered_nodes[fullname] or fullname == "air" then --directly found node name or alias of nodename + return fullname + end + for key, value in pairs(minetest.registered_nodes) do + if key:find(":" .. nodename, 1, true) then --found in mod + return key + end + end + nodename = nodename:lower() --lowercase both for case insensitive comparison + for key, value in pairs(minetest.registered_nodes) do + if value.description:lower() == nodename then --found in description + return key + end + end + return nil +end + +--determines the axis in which a player is facing, returning an axis ("x", "y", or "z") and the sign (1 or -1) +worldedit.player_axis = function(name) + local dir = minetest.get_player_by_name(name):get_look_dir() + local x, y, z = math.abs(dir.x), math.abs(dir.y), math.abs(dir.z) + if x > y then + if x > z then + return "x", dir.x > 0 and 1 or -1 + end + elseif y > z then + return "y", dir.y > 0 and 1 or -1 + end + return "z", dir.z > 0 and 1 or -1 +end + +minetest.register_chatcommand("/about", { + params = "", + description = "Get information about the mod", + func = function(name, param) + worldedit.player_notify(name, "WorldEdit " .. worldedit.version_string .. " is available on this server. Type /help to get a list of commands, or get more information at https://github.com/Uberi/MineTest-WorldEdit/") + end, +}) + +minetest.register_chatcommand("/inspect", { + params = "on/off/1/0/true/false/yes/no/enable/disable/", + description = "Enable or disable node inspection", + privs = {worldedit=true}, + func = function(name, param) + if param == "on" or param == "1" or param == "true" or param == "yes" or param == "enable" or param == "" then + worldedit.inspect[name] = true + local axis, sign = worldedit.player_axis(name) + worldedit.player_notify(name, string.format("inspector: inspection enabled for %s, currently facing the %s axis", + name, axis .. (sign > 0 and "+" or "-"))) + elseif param == "off" or param == "0" or param == "false" or param == "no" or param == "disable" then + worldedit.inspect[name] = nil + worldedit.player_notify(name, "inspector: inspection disabled") + else + worldedit.player_notify(name, "invalid usage: " .. param) + end + end, +}) + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if worldedit.inspect[name] then + if minetest.check_player_privs(name, {worldedit=true}) then + local axis, sign = worldedit.player_axis(name) + message = string.format("inspector: %s at %s (param1=%d, param2=%d) punched by %s facing the %s axis", + node.name, minetest.pos_to_string(pos), node.param1, node.param2, name, axis .. (sign > 0 and "+" or "-")) + else + message = "inspector: worldedit privileges required" + end + worldedit.player_notify(name, message) + end +end) + +minetest.register_chatcommand("/reset", { + params = "", + description = "Reset the region so that it is empty", + privs = {worldedit=true}, + func = function(name, param) + worldedit.pos1[name] = nil + worldedit.pos2[name] = nil + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + worldedit.set_pos[name] = nil + worldedit.player_notify(name, "region reset") + end, +}) + +minetest.register_chatcommand("/mark", { + params = "", + description = "Show markers at the region positions", + privs = {worldedit=true}, + func = function(name, param) + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + worldedit.player_notify(name, "region marked") + end, +}) + +minetest.register_chatcommand("/unmark", { + params = "", + description = "Hide markers if currently shown", + privs = {worldedit=true}, + func = function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + worldedit.pos1[name] = nil + worldedit.pos2[name] = nil + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + worldedit.pos1[name] = pos1 + worldedit.pos2[name] = pos2 + worldedit.player_notify(name, "region unmarked") + end, +}) + +minetest.register_chatcommand("/pos1", { + params = "", + description = "Set WorldEdit region position 1 to the player's location", + privs = {worldedit=true}, + func = function(name, param) + local pos = minetest.get_player_by_name(name):getpos() + pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) + worldedit.pos1[name] = pos + worldedit.mark_pos1(name) + worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) + end, +}) + +minetest.register_chatcommand("/pos2", { + params = "", + description = "Set WorldEdit region position 2 to the player's location", + privs = {worldedit=true}, + func = function(name, param) + local pos = minetest.get_player_by_name(name):getpos() + pos.x, pos.y, pos.z = math.floor(pos.x + 0.5), math.floor(pos.y + 0.5), math.floor(pos.z + 0.5) + worldedit.pos2[name] = pos + worldedit.mark_pos2(name) + worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) + end, +}) + +minetest.register_chatcommand("/p", { + params = "set/set1/set2/get", + description = "Set WorldEdit region, WorldEdit position 1, or WorldEdit position 2 by punching nodes, or display the current WorldEdit region", + privs = {worldedit=true}, + func = function(name, param) + if param == "set" then --set both WorldEdit positions + worldedit.set_pos[name] = "pos1" + worldedit.player_notify(name, "select positions by punching two nodes") + elseif param == "set1" then --set WorldEdit position 1 + worldedit.set_pos[name] = "pos1only" + worldedit.player_notify(name, "select position 1 by punching a node") + elseif param == "set2" then --set WorldEdit position 2 + worldedit.set_pos[name] = "pos2" + worldedit.player_notify(name, "select position 2 by punching a node") + elseif param == "get" then --display current WorldEdit positions + if worldedit.pos1[name] ~= nil then + worldedit.player_notify(name, "position 1: " .. minetest.pos_to_string(worldedit.pos1[name])) + else + worldedit.player_notify(name, "position 1 not set") + end + if worldedit.pos2[name] ~= nil then + worldedit.player_notify(name, "position 2: " .. minetest.pos_to_string(worldedit.pos2[name])) + else + worldedit.player_notify(name, "position 2 not set") + end + else + worldedit.player_notify(name, "unknown subcommand: " .. param) + end + end, +}) + +minetest.register_chatcommand("/fixedpos", { + params = "set1/set2 x y z", + description = "Set a WorldEdit region position to the position at (, , )", + privs = {worldedit=true}, + func = function(name, param) + local found, _, flag, x, y, z = param:find("^(set[12])%s+([+-]?%d+)%s+([+-]?%d+)%s+([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + local pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)} + if flag == "set1" then + worldedit.pos1[name] = pos + worldedit.mark_pos1(name) + worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) + else --flag == "set2" + worldedit.pos2[name] = pos + worldedit.mark_pos2(name) + worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) + end + end, +}) + +minetest.register_on_punchnode(function(pos, node, puncher) + local name = puncher:get_player_name() + if name ~= "" and worldedit.set_pos[name] ~= nil then --currently setting position + if worldedit.set_pos[name] == "pos1" then --setting position 1 + worldedit.pos1[name] = pos + worldedit.mark_pos1(name) + worldedit.set_pos[name] = "pos2" --set position 2 on the next invocation + worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) + elseif worldedit.set_pos[name] == "pos1only" then --setting position 1 only + worldedit.pos1[name] = pos + worldedit.mark_pos1(name) + worldedit.set_pos[name] = nil --finished setting positions + worldedit.player_notify(name, "position 1 set to " .. minetest.pos_to_string(pos)) + elseif worldedit.set_pos[name] == "pos2" then --setting position 2 + worldedit.pos2[name] = pos + worldedit.mark_pos2(name) + worldedit.set_pos[name] = nil --finished setting positions + worldedit.player_notify(name, "position 2 set to " .. minetest.pos_to_string(pos)) + elseif worldedit.set_pos[name] == "prob" then --setting Minetest schematic node probabilities + worldedit.prob_pos[name] = pos + minetest.show_formspec(puncher:get_player_name(), "prob_val_enter", "field[text;;]") + end + end +end) + +minetest.register_chatcommand("/volume", { + params = "", + description = "Display the volume of the current WorldEdit region", + privs = {worldedit=true}, + func = function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + if pos1 == nil or pos2 == nil then + worldedit.player_notify(name, "no region selected") + return nil + end + + local volume = worldedit.volume(pos1, pos2) + local abs = math.abs + worldedit.player_notify(name, "current region has a volume of " .. volume .. " nodes (" + .. abs(pos2.x - pos1.x) + 1 .. "*" + .. abs(pos2.y - pos1.y) + 1 .. "*" + .. abs(pos2.z - pos1.z) + 1 .. ")") + end, +}) + +minetest.register_chatcommand("/deleteblocks", { + params = "", + description = "remove all MapBlocks (16x16x16) containing the selected area from the map", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local success = minetest.delete_area(pos1, pos2) + if success then + worldedit.player_notify(name, "Area deleted.") + else + worldedit.player_notify(name, "There was an error during deletion of the area.") + end + end), +}) + +minetest.register_chatcommand("/set", { + params = "", + description = "Set the current WorldEdit region to ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local node = get_node(name, param) + if not node then + worldedit.player_notify(name, "Could not identify node \"" .. param .. "\"") + return + end + + local count = worldedit.set(worldedit.pos1[name], worldedit.pos2[name], node) + worldedit.player_notify(name, count .. " nodes set") + end, check_region), +}) + +minetest.register_chatcommand("/mix", { + params = " ...", + description = "Fill the current WorldEdit region with a random mix of , ...", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local nodes = {} + for nodename in param:gmatch("[^%s]+") do + local node = get_node(name, nodename) + if not node then + worldedit.player_notify(name, "Could not identify node \"" .. name .. "\"") + return + end + nodes[#nodes + 1] = node + end + + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local count = worldedit.set(pos1, pos2, nodes) + worldedit.player_notify(name, count .. " nodes set") + end, check_region), +}) + +local check_replace = function(name, param) + local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local newsearchnode = worldedit.normalize_nodename(searchnode) + if not newsearchnode then + worldedit.player_notify(name, "invalid search node name: " .. searchnode) + return nil + end + local newreplacenode = worldedit.normalize_nodename(replacenode) + if not newreplacenode then + worldedit.player_notify(name, "invalid replace node name: " .. replacenode) + return nil + end + return check_region(name, param) +end + +minetest.register_chatcommand("/replace", { + params = " ", + description = "Replace all instances of with in the current WorldEdit region", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$") + local norm_search_node = worldedit.normalize_nodename(search_node) + local norm_replace_node = worldedit.normalize_nodename(replace_node) + local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], + norm_search_node, norm_replace_node) + worldedit.player_notify(name, count .. " nodes replaced") + end, check_replace), +}) + +minetest.register_chatcommand("/replaceinverse", { + params = " ", + description = "Replace all nodes other than with in the current WorldEdit region", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$") + local norm_search_node = worldedit.normalize_nodename(search_node) + local norm_replace_node = worldedit.normalize_nodename(replace_node) + local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], + norm_search_node, norm_replace_node, true) + worldedit.player_notify(name, count .. " nodes replaced") + end, check_replace), +}) + +local check_sphere = function(name, param) + if worldedit.pos1[name] == nil then + worldedit.player_notify(name, "no position 1 selected") + return nil + end + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local node = get_node(name, nodename) + if not node then return nil end + return math.ceil((4 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of sphere +end + +minetest.register_chatcommand("/hollowsphere", { + params = " ", + description = "Add hollow sphere centered at WorldEdit position 1 with radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + local node = get_node(name, nodename) + local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node, true) + worldedit.player_notify(name, count .. " nodes added") + end, check_sphere), +}) + +minetest.register_chatcommand("/sphere", { + params = " ", + description = "Add sphere centered at WorldEdit position 1 with radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + local node = get_node(name, nodename) + local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node) + worldedit.player_notify(name, count .. " nodes added") + end, check_sphere), +}) + +local check_dome = function(name, param) + if worldedit.pos1[name] == nil then + worldedit.player_notify(name, "no position 1 selected") + return nil + end + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local node = get_node(name, nodename) + if not node then return nil end + return math.ceil((2 * math.pi * (tonumber(radius) ^ 3)) / 3) --volume of dome +end + +minetest.register_chatcommand("/hollowdome", { + params = " ", + description = "Add hollow dome centered at WorldEdit position 1 with radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + local node = get_node(name, nodename) + local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node, true) + worldedit.player_notify(name, count .. " nodes added") + end, check_dome), +}) + +minetest.register_chatcommand("/dome", { + params = " ", + description = "Add dome centered at WorldEdit position 1 with radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") + local node = get_node(name, nodename) + local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node) + worldedit.player_notify(name, count .. " nodes added") + end, check_dome), +}) + +local check_cylinder = function(name, param) + if worldedit.pos1[name] == nil then + worldedit.player_notify(name, "no position 1 selected") + return nil + end + local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local node = get_node(name, nodename) + if not node then return nil end + return math.ceil(math.pi * (tonumber(radius) ^ 2) * tonumber(length)) +end + +minetest.register_chatcommand("/hollowcylinder", { + params = "x/y/z/? ", + description = "Add hollow cylinder at WorldEdit position 1 along the x/y/z/? axis with length and radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") + length = tonumber(length) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + length = length * sign + end + local node = get_node(name, nodename) + local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node, true) + worldedit.player_notify(name, count .. " nodes added") + end, check_cylinder), +}) + +minetest.register_chatcommand("/cylinder", { + params = "x/y/z/? ", + description = "Add cylinder at WorldEdit position 1 along the x/y/z/? axis with length and radius , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, axis, length, radius, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(%d+)%s+(.+)$") + length = tonumber(length) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + length = length * sign + end + local node = get_node(name, nodename) + local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node) + worldedit.player_notify(name, count .. " nodes added") + end, check_cylinder), +}) + +minetest.register_chatcommand("/pyramid", { + params = "x/y/z/? ", + description = "Add pyramid centered at WorldEdit position 1 along the x/y/z/? axis with height , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") + height = tonumber(height) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + height = height * sign + end + local node = get_node(name, nodename) + local count = worldedit.pyramid(worldedit.pos1[name], axis, height, node) + worldedit.player_notify(name, count .. " nodes added") + end, + function(name, param) + if worldedit.pos1[name] == nil then + worldedit.player_notify(name, "no position 1 selected") + return nil + end + local found, _, axis, height, nodename = param:find("^([xyz%?])%s+([+-]?%d+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local node = get_node(name, nodename) + if not node then return nil end + height = tonumber(height) + return math.ceil(((height * 2 + 1) ^ 2) * height / 3) + end), +}) + +minetest.register_chatcommand("/spiral", { + params = " ", + description = "Add spiral centered at WorldEdit position 1 with side length , height , space between walls , composed of ", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") + local node = get_node(name, nodename) + local count = worldedit.spiral(worldedit.pos1[name], tonumber(length), tonumber(height), tonumber(space), node) + worldedit.player_notify(name, count .. " nodes added") + end, + function(name, param) + if worldedit.pos1[name] == nil then + worldedit.player_notify(name, "no position 1 selected") + return nil + end + local found, _, length, height, space, nodename = param:find("^(%d+)%s+(%d+)%s+(%d+)%s+(.+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + local node = get_node(name, nodename) + if not node then return nil end + return check_region(name, param) + end), +}) + +minetest.register_chatcommand("/copy", { + params = "x/y/z/? ", + description = "Copy the current WorldEdit region along the x/y/z/? axis by nodes", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + amount = tonumber(amount) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + amount = amount * sign + end + + local count = worldedit.copy(worldedit.pos1[name], worldedit.pos2[name], axis, amount) + worldedit.player_notify(name, count .. " nodes copied") + end, + function(name, param) + local volume = check_region(name, param) + return volume and volume * 2 or volume + end), +}) + +minetest.register_chatcommand("/move", { + params = "x/y/z/? ", + description = "Move the current WorldEdit region along the x/y/z/? axis by nodes", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local found, _, axis, amount = param:find("^([xyz%?])%s+([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + amount = tonumber(amount) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + amount = amount * sign + end + + local count = worldedit.move(pos1, pos2, axis, amount) + + pos1[axis] = pos1[axis] + amount + pos2[axis] = pos2[axis] + amount + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + worldedit.player_notify(name, count .. " nodes moved") + end, check_region), +}) + +minetest.register_chatcommand("/stack", { + params = "x/y/z/? ", + description = "Stack the current WorldEdit region along the x/y/z/? axis times", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") + repetitions = tonumber(repetitions) + if axis == "?" then + axis, sign = worldedit.player_axis(name) + repetitions = repetitions * sign + end + local count = worldedit.stack(worldedit.pos1[name], worldedit.pos2[name], axis, repetitions) + worldedit.player_notify(name, count .. " nodes stacked") + end, + function(name, param) + local found, _, axis, repetitions = param:find("^([xyz%?])%s+([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + local count = check_region(name, param) + if count then return (tonumber(repetitions) + 1) * count end + return nil + end), +}) + +minetest.register_chatcommand("/stack2", { + params = " ", + description = "Stack the current WorldEdit region times by offset , , ", + privs = {worldedit=true}, + func = function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + if pos1 == nil or pos2 == nil then + worldedit.player_notify(name, "Select a position first!") + return + end + local repetitions, incs = param:match("(%d+)%s*(.+)") + if repetitions == nil then + worldedit.player_notify(name, "invalid count: " .. param) + return + end + repetitions = tonumber(repetitions) + + local x, y, z = incs:match("([+-]?%d+) ([+-]?%d+) ([+-]?%d+)") + if x == nil then + worldedit.player_notify(name, "invalid increments: " .. param) + return + end + x, y, z = tonumber(x), tonumber(y), tonumber(z) + + local count = worldedit.volume(pos1, pos2) * repetitions + + return safe_region(function() + worldedit.stack2(pos1, pos2, {x=x, y=y, z=z}, repetitions, + function() worldedit.player_notify(name, count .. " nodes stacked") end) + end, function() + return count + end)(name,param) -- more hax --wip: clean this up a little bit + end +}) + + +minetest.register_chatcommand("/stretch", { + params = " ", + description = "Scale the current WorldEdit positions and region by a factor of , , along the X, Y, and Z axes, repectively, with position 1 as the origin", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") + stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) + local count, pos1, pos2 = worldedit.stretch(pos1, pos2, stretchx, stretchy, stretchz) + + --reset markers to scaled positions + worldedit.pos1[name] = pos1 + worldedit.pos2[name] = pos2 + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + + worldedit.player_notify(name, count .. " nodes stretched") + end, + function(name, param) + local found, _, stretchx, stretchy, stretchz = param:find("^(%d+)%s+(%d+)%s+(%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + stretchx, stretchy, stretchz = tonumber(stretchx), tonumber(stretchy), tonumber(stretchz) + if stretchx == 0 or stretchy == 0 or stretchz == 0 then + worldedit.player_notify(name, "invalid scaling factors: " .. param) + end + local count = check_region(name, param) + if count then return tonumber(stretchx) * tonumber(stretchy) * tonumber(stretchz) * count end + return nil + end), +}) + +minetest.register_chatcommand("/transpose", { + params = "x/y/z/? x/y/z/?", + description = "Transpose the current WorldEdit region along the x/y/z/? and x/y/z/? axes", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") + if axis1 == "?" then axis1 = worldedit.player_axis(name) end + if axis2 == "?" then axis2 = worldedit.player_axis(name) end + local count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) + + --reset markers to transposed positions + worldedit.pos1[name] = pos1 + worldedit.pos2[name] = pos2 + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + + worldedit.player_notify(name, count .. " nodes transposed") + end, + function(name, param) + local found, _, axis1, axis2 = param:find("^([xyz%?])%s+([xyz%?])$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + if axis1 == axis2 then + worldedit.player_notify(name, "invalid usage: axes must be different") + return nil + end + return check_region(name, param) + end), +}) + +minetest.register_chatcommand("/flip", { + params = "x/y/z/?", + description = "Flip the current WorldEdit region along the x/y/z/? axis", + privs = {worldedit=true}, + func = safe_region(function(name, param) + if param == "?" then param = worldedit.player_axis(name) end + local count = worldedit.flip(worldedit.pos1[name], worldedit.pos2[name], param) + worldedit.player_notify(name, count .. " nodes flipped") + end, + function(name, param) + if param ~= "x" and param ~= "y" and param ~= "z" and param ~= "?" then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + return check_region(name, param) + end), +}) + +minetest.register_chatcommand("/rotate", { + params = " ", + description = "Rotate the current WorldEdit region around the axis by angle (90 degree increment)", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") + if axis == "?" then axis = worldedit.player_axis(name) end + local count, pos1, pos2 = worldedit.rotate(pos1, pos2, axis, angle) + + --reset markers to rotated positions + worldedit.pos1[name] = pos1 + worldedit.pos2[name] = pos2 + worldedit.mark_pos1(name) + worldedit.mark_pos2(name) + + worldedit.player_notify(name, count .. " nodes rotated") + end, + function(name, param) + local found, _, axis, angle = param:find("^([xyz%?])%s+([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + if angle % 90 ~= 0 then + worldedit.player_notify(name, "invalid usage: angle must be multiple of 90") + return nil + end + return check_region(name, param) + end), +}) + +minetest.register_chatcommand("/orient", { + params = "", + description = "Rotate oriented nodes in the current WorldEdit region around the Y axis by angle (90 degree increment)", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local found, _, angle = param:find("^([+-]?%d+)$") + local count = worldedit.orient(worldedit.pos1[name], worldedit.pos2[name], angle) + worldedit.player_notify(name, count .. " nodes oriented") + end, + function(name, param) + local found, _, angle = param:find("^([+-]?%d+)$") + if found == nil then + worldedit.player_notify(name, "invalid usage: " .. param) + return nil + end + if angle % 90 ~= 0 then + worldedit.player_notify(name, "invalid usage: angle must be multiple of 90") + return nil + end + return check_region(name, param) + end), +}) + +minetest.register_chatcommand("/fixlight", { + params = "", + description = "Fix the lighting in the current WorldEdit region", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local count = worldedit.fixlight(worldedit.pos1[name], worldedit.pos2[name]) + worldedit.player_notify(name, count .. " nodes updated") + end), +}) + +minetest.register_chatcommand("/hide", { + params = "", + description = "Hide all nodes in the current WorldEdit region non-destructively", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local count = worldedit.hide(worldedit.pos1[name], worldedit.pos2[name]) + worldedit.player_notify(name, count .. " nodes hidden") + end), +}) + +minetest.register_chatcommand("/suppress", { + params = "", + description = "Suppress all in the current WorldEdit region non-destructively", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local node = get_node(name, param) + local count = worldedit.suppress(worldedit.pos1[name], worldedit.pos2[name], node) + worldedit.player_notify(name, count .. " nodes suppressed") + end, check_region), +}) + +minetest.register_chatcommand("/highlight", { + params = "", + description = "Highlight in the current WorldEdit region by hiding everything else non-destructively", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local node = get_node(name, param) + local count = worldedit.highlight(worldedit.pos1[name], worldedit.pos2[name], node) + worldedit.player_notify(name, count .. " nodes highlighted") + end, check_region), +}) + +minetest.register_chatcommand("/restore", { + params = "", + description = "Restores nodes hidden with WorldEdit in the current WorldEdit region", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local count = worldedit.restore(worldedit.pos1[name], worldedit.pos2[name]) + worldedit.player_notify(name, count .. " nodes restored") + end), +}) + +minetest.register_chatcommand("/save", { + params = "", + description = "Save the current WorldEdit region to \"(world folder)/schems/.we\"", + privs = {worldedit=true}, + func = safe_region(function(name, param) + if param == "" then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then + worldedit.player_notify(name, "invalid file name: " .. param) + return + end + + local result, count = worldedit.serialize(worldedit.pos1[name], worldedit.pos2[name]) + + local path = minetest.get_worldpath() .. "/schems" + local filename = path .. "/" .. param .. ".we" + filename = filename:gsub("\"", "\\\""):gsub("\\", "\\\\") --escape any nasty characters + os.execute("mkdir \"" .. path .. "\"") --create directory if it does not already exist + local file, err = io.open(filename, "wb") + if err ~= nil then + worldedit.player_notify(name, "could not save file to \"" .. filename .. "\"") + return + end + file:write(result) + file:flush() + file:close() + + worldedit.player_notify(name, count .. " nodes saved") + end), +}) + +minetest.register_chatcommand("/allocate", { + params = "", + description = "Set the region defined by nodes from \"(world folder)/schems/.we\" as the current WorldEdit region", + privs = {worldedit=true}, + func = function(name, param) + local pos = get_position(name) + if pos == nil then return end + + if param == "" then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then + worldedit.player_notify(name, "invalid file name: " .. param) + return + end + + local filename = minetest.get_worldpath() .. "/schems/" .. param .. ".we" + local file, err = io.open(filename, "rb") + if err ~= nil then + worldedit.player_notify(name, "could not open file \"" .. filename .. "\"") + return + end + local value = file:read("*a") + file:close() + + local version = worldedit.read_header(value) + if version == 0 then + worldedit.player_notify(name, "File is invalid!") + return + elseif version > worldedit.LATEST_SERIALIZATION_VERSION then + worldedit.player_notify(name, "File was created with newer version of WorldEdit!") + end + local nodepos1, nodepos2, count = worldedit.allocate(pos, value) + + worldedit.pos1[name] = nodepos1 + worldedit.mark_pos1(name) + worldedit.pos2[name] = nodepos2 + worldedit.mark_pos2(name) + + worldedit.player_notify(name, count .. " nodes allocated") + end, +}) + +minetest.register_chatcommand("/load", { + params = "", + description = "Load nodes from \"(world folder)/schems/[.we[m]]\" with position 1 of the current WorldEdit region as the origin", + privs = {worldedit=true}, + func = function(name, param) + local pos = get_position(name) + if pos == nil then return end + + if param == "" then + worldedit.player_notify(name, "invalid usage: " .. param) + return + end + if not string.find(param, "^[%w \t.,+-_=!@#$%%^&*()%[%]{};'\"]+$") then + worldedit.player_notify(name, "invalid file name: " .. param) + return + end + + --find the file in the world path + local testpaths = { + minetest.get_worldpath() .. "/schems/" .. param, + minetest.get_worldpath() .. "/schems/" .. param .. ".we", + minetest.get_worldpath() .. "/schems/" .. param .. ".wem", + } + local file, err + for index, path in ipairs(testpaths) do + file, err = io.open(path, "rb") + if not err then + break + end + end + if err then + worldedit.player_notify(name, "could not open file \"" .. param .. "\"") + return + end + local value = file:read("*a") + file:close() + + local version = worldedit.read_header(value) + if version == 0 then + worldedit.player_notify(name, "File is invalid!") + return + elseif version > worldedit.LATEST_SERIALIZATION_VERSION then + worldedit.player_notify(name, "File was created with newer version of WorldEdit!") + return + end + + local count = worldedit.deserialize(pos, value) + + worldedit.player_notify(name, count .. " nodes loaded") + end, +}) + +minetest.register_chatcommand("/lua", { + params = "", + description = "Executes as a Lua chunk in the global namespace", + privs = {worldedit=true, server=true}, + func = function(name, param) + local admin = minetest.setting_get("name") + if not admin or not name == admin then + worldedit.player_notify(name, "this command can only be run by the server administrator") + return + end + local err = worldedit.lua(param) + if err then + worldedit.player_notify(name, "code error: " .. err) + else + worldedit.player_notify(name, "code successfully executed", false) + end + end, +}) + +minetest.register_chatcommand("/luatransform", { + params = "", + description = "Executes as a Lua chunk in the global namespace with the variable pos available, for each node in the current WorldEdit region", + privs = {worldedit=true, server=true}, + func = safe_region(function(name, param) + local admin = minetest.setting_get("name") + if not admin or not name == admin then + worldedit.player_notify(name, "this command can only be run by the server administrator") + return + end + + local err = worldedit.luatransform(worldedit.pos1[name], worldedit.pos2[name], param) + if err then + worldedit.player_notify(name, "code error: " .. err, false) + else + worldedit.player_notify(name, "code successfully executed", false) + end + end), +}) + +minetest.register_chatcommand("/mtschemcreate", { + params = "", + description = "Save the current WorldEdit region using the Minetest Schematic format to \"(world folder)/schems/.mts\"", + privs = {worldedit=true}, + func = safe_region(function(name, param) + if param == nil then + worldedit.player_notify(name, "No filename specified") + return + end + + local path = minetest.get_worldpath() .. "/schems" + local filename = path .. "/" .. param .. ".mts" + filename = filename:gsub("\"", "\\\""):gsub("\\", "\\\\") --escape any nasty characters + os.execute("mkdir \"" .. path .. "\"") --create directory if it does not already exist + + local ret = minetest.create_schematic(worldedit.pos1[name], worldedit.pos2[name], worldedit.prob_list[name], filename) + if ret == nil then + worldedit.player_notify(name, "failed to create Minetest schematic", false) + else + worldedit.player_notify(name, "saved Minetest schematic to " .. param, false) + end + worldedit.prob_list[name] = {} + end), +}) + +minetest.register_chatcommand("/mtschemplace", { + params = "", + description = "Load nodes from \"(world folder)/schems/.mts\" with position 1 of the current WorldEdit region as the origin", + privs = {worldedit=true}, + func = function(name, param) + if param == nil then + worldedit.player_notify(name, "no filename specified") + return + end + + local pos = get_position(name) + if pos == nil then return end + + local path = minetest.get_worldpath() .. "/schems/" .. param .. ".mts" + if minetest.place_schematic(pos, path) == nil then + worldedit.player_notify(name, "failed to place Minetest schematic", false) + else + worldedit.player_notify(name, "placed Minetest schematic " .. param .. + " at " .. minetest.pos_to_string(pos), false) + end + end, +}) + +minetest.register_chatcommand("/mtschemprob", { + params = "start/finish/get", + description = "Begins node probability entry for Minetest schematics, gets the nodes that have probabilities set, or ends node probability entry", + privs = {worldedit=true}, + func = function(name, param) + if param == "start" then --start probability setting + worldedit.set_pos[name] = "prob" + worldedit.prob_list[name] = {} + worldedit.player_notify(name, "select Minetest schematic probability values by punching nodes") + elseif param == "finish" then --finish probability setting + worldedit.set_pos[name] = nil + worldedit.player_notify(name, "finished Minetest schematic probability selection") + elseif param == "get" then --get all nodes that had probabilities set on them + local text = "" + local problist = worldedit.prob_list[name] + if problist == nil then + return + end + for k,v in pairs(problist) do + local prob = math.floor(((v["prob"] / 256) * 100) * 100 + 0.5) / 100 + text = text .. minetest.pos_to_string(v["pos"]) .. ": " .. prob .. "% | " + end + worldedit.player_notify(name, "currently set node probabilities:") + worldedit.player_notify(name, text) + else + worldedit.player_notify(name, "unknown subcommand: " .. param) + end + end, +}) + +minetest.register_on_player_receive_fields( + function(player, formname, fields) + if (formname == "prob_val_enter") and (fields.text ~= "") then + local name = player:get_player_name() + local prob_entry = {pos=worldedit.prob_pos[name], prob=tonumber(fields.text)} + local index = table.getn(worldedit.prob_list[name]) + 1 + worldedit.prob_list[name][index] = prob_entry + end + end +) + +minetest.register_chatcommand("/clearobjects", { + params = "", + description = "Clears all objects within the WorldEdit region", + privs = {worldedit=true}, + func = safe_region(function(name, param) + local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name]) + worldedit.player_notify(name, count .. " objects cleared") + end), +}) diff --git a/mods/WorldEdit/worldedit_commands/mark.lua b/mods/WorldEdit/worldedit_commands/mark.lua index ffa907bd..4062cae0 100755 --- a/mods/WorldEdit/worldedit_commands/mark.lua +++ b/mods/WorldEdit/worldedit_commands/mark.lua @@ -1,161 +1,162 @@ -worldedit.marker1 = {} -worldedit.marker2 = {} -worldedit.marker_region = {} - ---marks worldedit region position 1 -worldedit.mark_pos1 = function(name) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - - if pos1 ~= nil then - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos1) - end - if worldedit.marker1[name] ~= nil then --marker already exists - worldedit.marker1[name]:remove() --remove marker - worldedit.marker1[name] = nil - end - if pos1 ~= nil then - --add marker - worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1") - if worldedit.marker1[name] ~= nil then - worldedit.marker1[name]:get_luaentity().name = name - end - end - worldedit.mark_region(name) -end - ---marks worldedit region position 2 -worldedit.mark_pos2 = function(name) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - - if pos2 ~= nil then - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos2, pos2) - end - if worldedit.marker2[name] ~= nil then --marker already exists - worldedit.marker2[name]:remove() --remove marker - worldedit.marker2[name] = nil - end - if pos2 ~= nil then - --add marker - worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2") - if worldedit.marker2[name] ~= nil then - worldedit.marker2[name]:get_luaentity().name = name - end - end - worldedit.mark_region(name) -end - -worldedit.mark_region = function(name) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - - if worldedit.marker_region[name] ~= nil then --marker already exists - --wip: make the area stay loaded somehow - for _, entity in ipairs(worldedit.marker_region[name]) do - entity:remove() - end - worldedit.marker_region[name] = nil - end - if pos1 ~= nil and pos2 ~= nil then - local pos1, pos2 = worldedit.sort_pos(pos1, pos2) - local thickness = 0.2 - local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2 - - --make area stay loaded - local manip = minetest.get_voxel_manip() - manip:read_from_map(pos1, pos2) - - local markers = {} - - --XY plane markers - for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do - local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "worldedit:region_cube") - marker:set_properties({ - visual_size={x=sizex * 2, y=sizey * 2}, - collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness}, - }) - marker:get_luaentity().name = name - table.insert(markers, marker) - end - - --YZ plane markers - for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do - local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "worldedit:region_cube") - marker:set_properties({ - visual_size={x=sizez * 2, y=sizey * 2}, - collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez}, - }) - marker:setyaw(math.pi / 2) - marker:get_luaentity().name = name - table.insert(markers, marker) - end - - worldedit.marker_region[name] = markers - end -end - -minetest.register_entity(":worldedit:pos1", { - initial_properties = { - visual = "cube", - visual_size = {x=1.1, y=1.1}, - textures = {"worldedit_pos1.png", "worldedit_pos1.png", - "worldedit_pos1.png", "worldedit_pos1.png", - "worldedit_pos1.png", "worldedit_pos1.png"}, - collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, - physical = false, - }, - on_step = function(self, dtime) - if worldedit.marker1[self.name] == nil then - self.object:remove() - end - end, - on_punch = function(self, hitter) - self.object:remove() - worldedit.marker1[self.name] = nil - end, -}) - -minetest.register_entity(":worldedit:pos2", { - initial_properties = { - visual = "cube", - visual_size = {x=1.1, y=1.1}, - textures = {"worldedit_pos2.png", "worldedit_pos2.png", - "worldedit_pos2.png", "worldedit_pos2.png", - "worldedit_pos2.png", "worldedit_pos2.png"}, - collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, - physical = false, - }, - on_step = function(self, dtime) - if worldedit.marker2[self.name] == nil then - self.object:remove() - end - end, - on_punch = function(self, hitter) - self.object:remove() - worldedit.marker2[self.name] = nil - end, -}) - -minetest.register_entity(":worldedit:region_cube", { - initial_properties = { - visual = "upright_sprite", - visual_size = {x=1.1, y=1.1}, - textures = {"worldedit_cube.png"}, - visual_size = {x=10, y=10}, - physical = false, - }, - on_step = function(self, dtime) - if worldedit.marker_region[self.name] == nil then - self.object:remove() - return - end - end, - on_punch = function(self, hitter) - for _, entity in ipairs(worldedit.marker_region[self.name]) do - entity:remove() - end - worldedit.marker_region[self.name] = nil - end, -}) \ No newline at end of file +worldedit.marker1 = {} +worldedit.marker2 = {} +worldedit.marker_region = {} + +--marks worldedit region position 1 +worldedit.mark_pos1 = function(name) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + + if pos1 ~= nil then + --make area stay loaded + local manip = minetest.get_voxel_manip() + manip:read_from_map(pos1, pos1) + end + if worldedit.marker1[name] ~= nil then --marker already exists + worldedit.marker1[name]:remove() --remove marker + worldedit.marker1[name] = nil + end + if pos1 ~= nil then + --add marker + worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1") + if worldedit.marker1[name] ~= nil then + worldedit.marker1[name]:get_luaentity().player_name = name + end + end + worldedit.mark_region(name) +end + +--marks worldedit region position 2 +worldedit.mark_pos2 = function(name) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + + if pos2 ~= nil then + --make area stay loaded + local manip = minetest.get_voxel_manip() + manip:read_from_map(pos2, pos2) + end + if worldedit.marker2[name] ~= nil then --marker already exists + worldedit.marker2[name]:remove() --remove marker + worldedit.marker2[name] = nil + end + if pos2 ~= nil then + --add marker + worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2") + if worldedit.marker2[name] ~= nil then + worldedit.marker2[name]:get_luaentity().player_name = name + end + end + worldedit.mark_region(name) +end + +worldedit.mark_region = function(name) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + + if worldedit.marker_region[name] ~= nil then --marker already exists + --wip: make the area stay loaded somehow + for _, entity in ipairs(worldedit.marker_region[name]) do + entity:remove() + end + worldedit.marker_region[name] = nil + end + if pos1 ~= nil and pos2 ~= nil then + local pos1, pos2 = worldedit.sort_pos(pos1, pos2) + local thickness = 0.2 + local sizex, sizey, sizez = (1 + pos2.x - pos1.x) / 2, (1 + pos2.y - pos1.y) / 2, (1 + pos2.z - pos1.z) / 2 + + --make area stay loaded + local manip = minetest.get_voxel_manip() + manip:read_from_map(pos1, pos2) + + local markers = {} + + --XY plane markers + for _, z in ipairs({pos1.z - 0.5, pos2.z + 0.5}) do + local marker = minetest.add_entity({x=pos1.x + sizex - 0.5, y=pos1.y + sizey - 0.5, z=z}, "worldedit:region_cube") + marker:set_properties({ + visual_size={x=sizex * 2, y=sizey * 2}, + collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness}, + }) + marker:get_luaentity().player_name = name + table.insert(markers, marker) + end + + --YZ plane markers + for _, x in ipairs({pos1.x - 0.5, pos2.x + 0.5}) do + local marker = minetest.add_entity({x=x, y=pos1.y + sizey - 0.5, z=pos1.z + sizez - 0.5}, "worldedit:region_cube") + marker:set_properties({ + visual_size={x=sizez * 2, y=sizey * 2}, + collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez}, + }) + marker:setyaw(math.pi / 2) + marker:get_luaentity().player_name = name + table.insert(markers, marker) + end + + worldedit.marker_region[name] = markers + end +end + +minetest.register_entity(":worldedit:pos1", { + initial_properties = { + visual = "cube", + visual_size = {x=1.1, y=1.1}, + textures = {"worldedit_pos1.png", "worldedit_pos1.png", + "worldedit_pos1.png", "worldedit_pos1.png", + "worldedit_pos1.png", "worldedit_pos1.png"}, + collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + physical = false, + }, + on_step = function(self, dtime) + if worldedit.marker1[self.player_name] == nil then + self.object:remove() + end + end, + on_punch = function(self, hitter) + self.object:remove() + worldedit.marker1[self.player_name] = nil + end, +}) + +minetest.register_entity(":worldedit:pos2", { + initial_properties = { + visual = "cube", + visual_size = {x=1.1, y=1.1}, + textures = {"worldedit_pos2.png", "worldedit_pos2.png", + "worldedit_pos2.png", "worldedit_pos2.png", + "worldedit_pos2.png", "worldedit_pos2.png"}, + collisionbox = {-0.55, -0.55, -0.55, 0.55, 0.55, 0.55}, + physical = false, + }, + on_step = function(self, dtime) + if worldedit.marker2[self.player_name] == nil then + self.object:remove() + end + end, + on_punch = function(self, hitter) + self.object:remove() + worldedit.marker2[self.player_name] = nil + end, +}) + +minetest.register_entity(":worldedit:region_cube", { + initial_properties = { + visual = "upright_sprite", + visual_size = {x=1.1, y=1.1}, + textures = {"worldedit_cube.png"}, + visual_size = {x=10, y=10}, + physical = false, + }, + on_step = function(self, dtime) + if worldedit.marker_region[self.player_name] == nil then + self.object:remove() + return + end + end, + on_punch = function(self, hitter) + for _, entity in ipairs(worldedit.marker_region[self.player_name]) do + entity:remove() + end + worldedit.marker_region[self.player_name] = nil + end, +}) + diff --git a/mods/WorldEdit/worldedit_commands/safe.lua b/mods/WorldEdit/worldedit_commands/safe.lua index 0da5e333..c6751c13 100755 --- a/mods/WorldEdit/worldedit_commands/safe.lua +++ b/mods/WorldEdit/worldedit_commands/safe.lua @@ -1,65 +1,65 @@ -local safe_region_callback = {} -local safe_region_param = {} - -check_region = function(name, param) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] --obtain positions - if pos1 == nil or pos2 == nil then - worldedit.player_notify(name, "no region selected") - return nil - end - return worldedit.volume(pos1, pos2) -end - ---`callback` is a callback to run when the user confirms ---`nodes_needed` is a function accepting `param`, `pos1`, and `pos2` to calculate the number of nodes needed -safe_region = function(callback, nodes_needed) - --default node volume calculation - nodes_needed = nodes_needed or check_region - - return function(name, param) - --check if the operation applies to a safe number of nodes - local count = nodes_needed(name, param) - if count == nil then return end --invalid command - if count < 10000 then - return callback(name, param) - end - - --save callback to call later - safe_region_callback[name], safe_region_param[name] = callback, param - worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel") - end -end - -minetest.register_chatcommand("/y", { - params = "", - description = "Confirm a pending operation", - func = function(name) - local callback, param = safe_region_callback[name], safe_region_param[name] - if not callback then - worldedit.player_notify(name, "no operation pending") - return - end - - --obtain positions - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - if pos1 == nil or pos2 == nil then - worldedit.player_notify(name, "no region selected") - return - end - - safe_region_callback[name], safe_region_param[name] = nil, nil --reset pending operation - callback(name, param, pos1, pos2) - end, -}) - -minetest.register_chatcommand("/n", { - params = "", - description = "Confirm a pending operation", - func = function(name) - if not safe_region_callback[name] then - worldedit.player_notify(name, "no operation pending") - return - end - safe_region_callback[name], safe_region_param[name] = nil, nil - end, -}) +local safe_region_callback = {} +local safe_region_param = {} + +check_region = function(name, param) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] --obtain positions + if pos1 == nil or pos2 == nil then + worldedit.player_notify(name, "no region selected") + return nil + end + return worldedit.volume(pos1, pos2) +end + +--`callback` is a callback to run when the user confirms +--`nodes_needed` is a function accepting `param`, `pos1`, and `pos2` to calculate the number of nodes needed +safe_region = function(callback, nodes_needed) + --default node volume calculation + nodes_needed = nodes_needed or check_region + + return function(name, param) + --check if the operation applies to a safe number of nodes + local count = nodes_needed(name, param) + if count == nil then return end --invalid command + if count < 10000 then + return callback(name, param) + end + + --save callback to call later + safe_region_callback[name], safe_region_param[name] = callback, param + worldedit.player_notify(name, "WARNING: this operation could affect up to " .. count .. " nodes; type //y to continue or //n to cancel") + end +end + +minetest.register_chatcommand("/y", { + params = "", + description = "Confirm a pending operation", + func = function(name) + local callback, param = safe_region_callback[name], safe_region_param[name] + if not callback then + worldedit.player_notify(name, "no operation pending") + return + end + + --obtain positions + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + if pos1 == nil or pos2 == nil then + worldedit.player_notify(name, "no region selected") + return + end + + safe_region_callback[name], safe_region_param[name] = nil, nil --reset pending operation + callback(name, param, pos1, pos2) + end, +}) + +minetest.register_chatcommand("/n", { + params = "", + description = "Confirm a pending operation", + func = function(name) + if not safe_region_callback[name] then + worldedit.player_notify(name, "no operation pending") + return + end + safe_region_callback[name], safe_region_param[name] = nil, nil + end, +}) diff --git a/mods/WorldEdit/worldedit_gui/functionality.lua b/mods/WorldEdit/worldedit_gui/functionality.lua index b4e6ee66..620f6c4a 100755 --- a/mods/WorldEdit/worldedit_gui/functionality.lua +++ b/mods/WorldEdit/worldedit_gui/functionality.lua @@ -1,657 +1,657 @@ ---saved state for each player -local gui_nodename1 = {} --mapping of player names to node names (arbitrary strings may also appear as values) -local gui_nodename2 = {} --mapping of player names to node names (arbitrary strings may also appear as values) -local gui_axis1 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) -local gui_axis2 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) -local gui_distance1 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) -local gui_distance2 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) -local gui_distance3 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) -local gui_count1 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) -local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) -local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) -local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise) -local gui_filename = {} --mapping of player names to file names (arbitrary strings may also appear as values) -local gui_formspec = {} --mapping of player names to formspecs -local gui_code = {} --mapping of player names to formspecs - ---set default values -setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end}) -setmetatable(gui_nodename2, {__index = function() return "Stone" end}) -setmetatable(gui_axis1, {__index = function() return 4 end}) -setmetatable(gui_axis2, {__index = function() return 1 end}) -setmetatable(gui_distance1, {__index = function() return "10" end}) -setmetatable(gui_distance2, {__index = function() return "5" end}) -setmetatable(gui_distance3, {__index = function() return "2" end}) -setmetatable(gui_count1, {__index = function() return "3" end}) -setmetatable(gui_count2, {__index = function() return "6" end}) -setmetatable(gui_count3, {__index = function() return "4" end}) -setmetatable(gui_angle, {__index = function() return 90 end}) -setmetatable(gui_filename, {__index = function() return "building" end}) -setmetatable(gui_formspec, {__index = function() return "size[5,5]\nlabel[0,0;Hello, world!]" end}) -setmetatable(gui_code, {__index = function() return "minetest.chat_send_player(\"singleplayer\", \"Hello, world!\")" end}) - -local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4} -local axis_values = {"x", "y", "z", "?"} -setmetatable(axis_indices, {__index = function () return 4 end}) -setmetatable(axis_values, {__index = function () return "?" end}) - -local angle_indices = {["90 degrees"]=1, ["180 degrees"]=2, ["270 degrees"]=3} -local angle_values = {90, 180, 270} -setmetatable(angle_indices, {__index = function () return 1 end}) -setmetatable(angle_values, {__index = function () return 90 end}) - ---given multiple sets of privileges, produces a single set of privs that would have the same effect as requiring all of them at the same time -local combine_privs = function(...) - local result = {} - for i, privs in ipairs({...}) do - for name, value in pairs(privs) do - if result[name] ~= nil and result[name] ~= value then --the priv must be both true and false, which can never happen - return {__fake_priv_that_nobody_has__=true} --priviledge table that can never be satisfied - end - result[name] = value - end - end - return result -end - -worldedit.register_gui_function("worldedit_gui_about", { - name = "About", privs = minetest.chatcommands["/about"].privs, - on_select = function(name) - minetest.chatcommands["/about"].func(name, "") - end, -}) - -worldedit.register_gui_function("worldedit_gui_inspect", { - name = "Toggle Inspect", privs = minetest.chatcommands["/inspect"].privs, - on_select = function(name) - minetest.chatcommands["/inspect"].func(name, worldedit.inspect[name] and "disable" or "enable") - end, -}) - -worldedit.register_gui_function("worldedit_gui_region", { - name = "Get/Set Region", privs = combine_privs(minetest.chatcommands["/p"].privs, minetest.chatcommands["/pos1"].privs, minetest.chatcommands["/pos2"].privs, minetest.chatcommands["/reset"].privs, minetest.chatcommands["/mark"].privs, minetest.chatcommands["/unmark"].privs, minetest.chatcommands["/volume"].privs, minetest.chatcommands["/fixedpos"].privs), - get_formspec = function(name) - local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] - return "size[9,7]" .. worldedit.get_formspec_header("worldedit_gui_region") .. - "button_exit[0,1;3,0.8;worldedit_gui_p_get;Get Positions]" .. - "button_exit[3,1;3,0.8;worldedit_gui_p_set1;Choose Position 1]" .. - "button_exit[6,1;3,0.8;worldedit_gui_p_set2;Choose Position 2]" .. - "button_exit[0,2;3,0.8;worldedit_gui_pos1;Position 1 Here]" .. - "button_exit[3,2;3,0.8;worldedit_gui_pos2;Position 2 Here]" .. - "button_exit[6,2;3,0.8;worldedit_gui_reset;Reset Region]" .. - "button_exit[0,3;3,0.8;worldedit_gui_mark;Mark Region]" .. - "button_exit[3,3;3,0.8;worldedit_gui_unmark;Unmark Region]" .. - "button_exit[6,3;3,0.8;worldedit_gui_volume;Region Volume]" .. - "label[0,4.7;Position 1]" .. - string.format("field[2,5;1.5,0.8;worldedit_gui_fixedpos_pos1x;X ;%s]", pos1 and pos1.x or "") .. - string.format("field[3.5,5;1.5,0.8;worldedit_gui_fixedpos_pos1y;Y ;%s]", pos1 and pos1.y or "") .. - string.format("field[5,5;1.5,0.8;worldedit_gui_fixedpos_pos1z;Z ;%s]", pos1 and pos1.z or "") .. - "button_exit[6.5,4.68;2.5,0.8;worldedit_gui_fixedpos_pos1_submit;Set Position 1]" .. - "label[0,6.2;Position 2]" .. - string.format("field[2,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2x;X ;%s]", pos2 and pos2.x or "") .. - string.format("field[3.5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2y;Y ;%s]", pos2 and pos2.y or "") .. - string.format("field[5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2z;Z ;%s]", pos2 and pos2.z or "") .. - "button_exit[6.5,6.18;2.5,0.8;worldedit_gui_fixedpos_pos2_submit;Set Position 2]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_region", function(name, fields) - if fields.worldedit_gui_p_get then - minetest.chatcommands["/p"].func(name, "get") - return true - elseif fields.worldedit_gui_p_set1 then - minetest.chatcommands["/p"].func(name, "set1") - return true - elseif fields.worldedit_gui_p_set2 then - minetest.chatcommands["/p"].func(name, "set2") - return true - elseif fields.worldedit_gui_pos1 then - minetest.chatcommands["/pos1"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_pos2 then - minetest.chatcommands["/pos2"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_reset then - minetest.chatcommands["/reset"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_mark then - minetest.chatcommands["/mark"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_unmark then - minetest.chatcommands["/unmark"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_volume then - minetest.chatcommands["/volume"].func(name, "") - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_fixedpos_pos1_submit then - minetest.chatcommands["/fixedpos"].func(name, string.format("set1 %s %s %s", - tostring(fields.worldedit_gui_fixedpos_pos1x), - tostring(fields.worldedit_gui_fixedpos_pos1y), - tostring(fields.worldedit_gui_fixedpos_pos1z))) - worldedit.show_page(name, "worldedit_gui_region") - return true - elseif fields.worldedit_gui_fixedpos_pos2_submit then - minetest.chatcommands["/fixedpos"].func(name, string.format("set2 %s %s %s", - tostring(fields.worldedit_gui_fixedpos_pos2x), - tostring(fields.worldedit_gui_fixedpos_pos2y), - tostring(fields.worldedit_gui_fixedpos_pos2z))) - worldedit.show_page(name, "worldedit_gui_region") - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_set", { - name = "Set Nodes", privs = minetest.chatcommands["/set"].privs, - get_formspec = function(name) - local node = gui_nodename1[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_set") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_set_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_set_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - "button_exit[0,2.5;3,0.8;worldedit_gui_set_submit;Set Nodes]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_set", function(name, fields) - if fields.worldedit_gui_set_search or fields.worldedit_gui_set_submit then - gui_nodename1[name] = tostring(fields.worldedit_gui_set_node) - worldedit.show_page(name, "worldedit_gui_set") - if fields.worldedit_gui_set_submit then - minetest.chatcommands["/set"].func(name, gui_nodename1[name]) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_replace", { - name = "Replace Nodes", privs = combine_privs(minetest.chatcommands["/replace"].privs, minetest.chatcommands["/replaceinverse"].privs), - get_formspec = function(name) - local search, replace = gui_nodename1[name], gui_nodename2[name] - local search_nodename, replace_nodename = worldedit.normalize_nodename(search), worldedit.normalize_nodename(replace) - return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_replace") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_replace_search;Name;%s]", minetest.formspec_escape(search)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_replace_search_search;Search]" .. - (search_nodename and string.format("item_image[5.5,1.1;1,1;%s]", search_nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_replace_replace;Name;%s]", minetest.formspec_escape(replace)) .. - "button[4,2.18;1.5,0.8;worldedit_gui_replace_replace_search;Search]" .. - (replace_nodename and string.format("item_image[5.5,2.1;1,1;%s]", replace_nodename) - or "image[5.5,2.1;1,1;unknown_node.png]") .. - "button_exit[0,3.5;3,0.8;worldedit_gui_replace_submit;Replace Nodes]" .. - "button_exit[3.5,3.5;3,0.8;worldedit_gui_replace_submit_inverse;Replace Inverse]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields) - if fields.worldedit_gui_replace_search_search or fields.worldedit_gui_replace_replace_search - or fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then - gui_nodename1[name] = tostring(fields.worldedit_gui_replace_search) - gui_nodename2[name] = tostring(fields.worldedit_gui_replace_replace) - worldedit.show_page(name, "worldedit_gui_replace") - if fields.worldedit_gui_replace_submit then - minetest.chatcommands["/replace"].func(name, string.format("%s %s", gui_nodename1[name], gui_nodename2[name])) - elseif fields.worldedit_gui_replace_submit_inverse then - minetest.chatcommands["/replaceinverse"].func(name, string.format("%s %s", gui_nodename1[name], gui_nodename2[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_sphere_dome", { - name = "Sphere/Dome", privs = combine_privs(minetest.chatcommands["/hollowsphere"].privs, minetest.chatcommands["/sphere"].privs, minetest.chatcommands["/hollowdome"].privs, minetest.chatcommands["/dome"].privs), - get_formspec = function(name) - local node, radius = gui_nodename1[name], gui_distance2[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_sphere_dome") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_sphere_dome_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_sphere_dome_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_sphere_dome_radius;Radius;%s]", minetest.formspec_escape(radius)) .. - "button_exit[0,3.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow;Hollow Sphere]" .. - "button_exit[3.5,3.5;3,0.8;worldedit_gui_sphere_dome_submit_solid;Solid Sphere]" .. - "button_exit[0,4.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow_dome;Hollow Dome]" .. - "button_exit[3.5,4.5;3,0.8;worldedit_gui_sphere_dome_submit_solid_dome;Solid Dome]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields) - if fields.worldedit_gui_sphere_dome_search - or fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid - or fields.worldedit_gui_sphere_dome_submit_hollow_dome or fields.worldedit_gui_sphere_dome_submit_solid_dome then - gui_nodename1[name] = tostring(fields.worldedit_gui_sphere_dome_node) - gui_distance2[name] = tostring(fields.worldedit_gui_sphere_dome_radius) - worldedit.show_page(name, "worldedit_gui_sphere_dome") - if fields.worldedit_gui_sphere_dome_submit_hollow then - minetest.chatcommands["/hollowsphere"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) - elseif fields.worldedit_gui_sphere_dome_submit_solid then - minetest.chatcommands["/sphere"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) - elseif fields.worldedit_gui_sphere_dome_submit_hollow_dome then - minetest.chatcommands["/hollowdome"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) - elseif fields.worldedit_gui_sphere_dome_submit_solid_dome then - minetest.chatcommands["/dome"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_cylinder", { - name = "Cylinder", privs = combine_privs(minetest.chatcommands["/hollowcylinder"].privs, minetest.chatcommands["/cylinder"].privs), - get_formspec = function(name) - local node, axis, length, radius = gui_nodename1[name], gui_axis1[name], gui_distance1[name], gui_distance2[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_cylinder") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_cylinder_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_cylinder_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_cylinder_length;Length;%s]", minetest.formspec_escape(length)) .. - string.format("dropdown[4,2.18;2.5;worldedit_gui_cylinder_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - string.format("field[0.5,3.5;4,0.8;worldedit_gui_cylinder_radius;Radius;%s]", minetest.formspec_escape(radius)) .. - "button_exit[0,4.5;3,0.8;worldedit_gui_cylinder_submit_hollow;Hollow Cylinder]" .. - "button_exit[3.5,4.5;3,0.8;worldedit_gui_cylinder_submit_solid;Solid Cylinder]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields) - if fields.worldedit_gui_cylinder_search - or fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then - gui_nodename1[name] = tostring(fields.worldedit_gui_cylinder_node) - gui_axis1[name] = axis_indices[fields.worldedit_gui_cylinder_axis] - gui_distance1[name] = tostring(fields.worldedit_gui_cylinder_length) - gui_distance2[name] = tostring(fields.worldedit_gui_cylinder_radius) - worldedit.show_page(name, "worldedit_gui_cylinder") - if fields.worldedit_gui_cylinder_submit_hollow then - minetest.chatcommands["/hollowcylinder"].func(name, string.format("%s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_nodename1[name])) - elseif fields.worldedit_gui_cylinder_submit_solid then - minetest.chatcommands["/cylinder"].func(name, string.format("%s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_nodename1[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_pyramid", { - name = "Pyramid", privs = minetest.chatcommands["/pyramid"].privs, - get_formspec = function(name) - local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_pyramid") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_pyramid_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_pyramid_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_pyramid_length;Length;%s]", minetest.formspec_escape(length)) .. - string.format("dropdown[4,2.18;2.5;worldedit_gui_pyramid_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - "button_exit[0,3.5;3,0.8;worldedit_gui_pyramid_submit;Pyramid]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_pyramid", function(name, fields) - if fields.worldedit_gui_pyramid_search or fields.worldedit_gui_pyramid_submit then - gui_nodename1[name] = tostring(fields.worldedit_gui_pyramid_node) - gui_axis1[name] = axis_indices[fields.worldedit_gui_pyramid_axis] - gui_distance1[name] = tostring(fields.worldedit_gui_pyramid_length) - worldedit.show_page(name, "worldedit_gui_pyramid") - if fields.worldedit_gui_pyramid_submit then - minetest.chatcommands["/pyramid"].func(name, string.format("%s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_nodename1[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_spiral", { - name = "Spiral", privs = minetest.chatcommands["/spiral"].privs, - get_formspec = function(name) - local node, length, height, space = gui_nodename1[name], gui_distance1[name], gui_distance2[name], gui_distance3[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_spiral") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_spiral_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_spiral_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_spiral_length;Side Length;%s]", minetest.formspec_escape(length)) .. - string.format("field[0.5,3.5;4,0.8;worldedit_gui_spiral_height;Height;%s]", minetest.formspec_escape(height)) .. - string.format("field[0.5,4.5;4,0.8;worldedit_gui_spiral_space;Wall Spacing;%s]", minetest.formspec_escape(space)) .. - "button_exit[0,5.5;3,0.8;worldedit_gui_spiral_submit;Spiral]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_spiral", function(name, fields) - if fields.worldedit_gui_spiral_search or fields.worldedit_gui_spiral_submit then - gui_nodename1[name] = fields.worldedit_gui_spiral_node - gui_distance1[name] = tostring(fields.worldedit_gui_spiral_length) - gui_distance2[name] = tostring(fields.worldedit_gui_spiral_height) - gui_distance3[name] = tostring(fields.worldedit_gui_spiral_space) - worldedit.show_page(name, "worldedit_gui_spiral") - if fields.worldedit_gui_spiral_submit then - minetest.chatcommands["/spiral"].func(name, string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], gui_nodename1[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_copy_move", { - name = "Copy/Move", privs = combine_privs(minetest.chatcommands["/copy"].privs, minetest.chatcommands["/move"].privs), - get_formspec = function(name) - local axis = gui_axis1[name] or 4 - local amount = gui_distance1[name] or "10" - return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_copy_move") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_copy_move_amount;Amount;%s]", minetest.formspec_escape(amount)) .. - string.format("dropdown[4,1.18;2.5;worldedit_gui_copy_move_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_copy_move_copy;Copy Region]" .. - "button_exit[3.5,2.5;3,0.8;worldedit_gui_copy_move_move;Move Region]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_copy_move", function(name, fields) - if fields.worldedit_gui_copy_move_copy or fields.worldedit_gui_copy_move_move then - gui_axis1[name] = axis_indices[fields.worldedit_gui_cylinder_axis] or 4 - gui_distance1[name] = tostring(fields.worldedit_gui_copy_move_amount) - worldedit.show_page(name, "worldedit_gui_copy_move") - if fields.worldedit_gui_copy_move_copy then - minetest.chatcommands["/copy"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name])) - else --fields.worldedit_gui_copy_move_move - minetest.chatcommands["/move"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name])) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_stack", { - name = "Stack", privs = minetest.chatcommands["/stack"].privs, - get_formspec = function(name) - local axis, count = gui_axis1[name], gui_count1[name] - return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_stack") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_stack_count;Count;%s]", minetest.formspec_escape(count)) .. - string.format("dropdown[4,1.18;2.5;worldedit_gui_stack_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_stack_submit;Stack]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_stack", function(name, fields) - if fields.worldedit_gui_stack_submit then - gui_axis1[name] = axis_indices[fields.worldedit_gui_stack_axis] - gui_count1[name] = tostring(fields.worldedit_gui_stack_count) - worldedit.show_page(name, "worldedit_gui_stack") - minetest.chatcommands["/stack"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_count1[name])) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_stretch", { - name = "Stretch", privs = minetest.chatcommands["/stretch"].privs, - get_formspec = function(name) - local stretchx, stretchy, stretchz = gui_count1[name], gui_count2[name], gui_count3[name] - return "size[5,5]" .. worldedit.get_formspec_header("worldedit_gui_stretch") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_stretch_x;Stretch X;%s]", minetest.formspec_escape(stretchx)) .. - string.format("field[0.5,2.5;4,0.8;worldedit_gui_stretch_y;Stretch Y;%s]", minetest.formspec_escape(stretchy)) .. - string.format("field[0.5,3.5;4,0.8;worldedit_gui_stretch_z;Stretch Z;%s]", minetest.formspec_escape(stretchz)) .. - "button_exit[0,4.5;3,0.8;worldedit_gui_stretch_submit;Stretch]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_stretch", function(name, fields) - if fields.worldedit_gui_stretch_submit then - gui_count1[name] = tostring(fields.worldedit_gui_stretch_x) - gui_count2[name] = tostring(fields.worldedit_gui_stretch_y) - gui_count3[name] = tostring(fields.worldedit_gui_stretch_z) - worldedit.show_page(name, "worldedit_gui_stretch") - minetest.chatcommands["/stretch"].func(name, string.format("%s %s %s", gui_count1[name], gui_count2[name], gui_count3[name])) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_transpose", { - name = "Transpose", privs = minetest.chatcommands["/transpose"].privs, - get_formspec = function(name) - local axis1, axis2 = gui_axis1[name], gui_axis2[name] - return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_transpose") .. - string.format("dropdown[0,1;2.5;worldedit_gui_transpose_axis1;X axis,Y axis,Z axis,Look direction;%d]", axis1) .. - string.format("dropdown[3,1;2.5;worldedit_gui_transpose_axis2;X axis,Y axis,Z axis,Look direction;%d]", axis2) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_transpose_submit;Transpose]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_transpose", function(name, fields) - if fields.worldedit_gui_transpose_submit then - gui_axis1[name] = axis_indices[fields.worldedit_gui_transpose_axis1] - gui_axis2[name] = axis_indices[fields.worldedit_gui_transpose_axis2] - worldedit.show_page(name, "worldedit_gui_transpose") - minetest.chatcommands["/transpose"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], axis_values[gui_axis2[name]])) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_flip", { - name = "Flip", privs = minetest.chatcommands["/flip"].privs, - get_formspec = function(name) - local axis = gui_axis2[name] - return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_flip") .. - string.format("dropdown[0,1;2.5;worldedit_gui_flip_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_flip_submit;Flip]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_flip", function(name, fields) - if fields.worldedit_gui_flip_submit then - gui_axis2[name] = axis_indices[fields.worldedit_gui_flip_axis] - worldedit.show_page(name, "worldedit_gui_flip") - minetest.chatcommands["/flip"].func(name, axis_values[gui_axis2[name]]) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_rotate", { - name = "Rotate", privs = minetest.chatcommands["/rotate"].privs, - get_formspec = function(name) - local axis, angle = gui_axis1[name], gui_angle[name] - return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_rotate") .. - string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. - string.format("dropdown[3,1;2.5;worldedit_gui_rotate_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_rotate_submit;Rotate]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_rotate", function(name, fields) - if fields.worldedit_gui_rotate_submit then - gui_axis1[name] = axis_indices[fields.worldedit_gui_rotate_axis] - gui_angle[name] = angle_indices[fields.worldedit_gui_rotate_angle] - worldedit.show_page(name, "worldedit_gui_rotate") - minetest.chatcommands["/rotate"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], angle_values[gui_angle[name]])) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_orient", { - name = "Orient", privs = minetest.chatcommands["/orient"].privs, - get_formspec = function(name) - local angle = gui_angle[name] - return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_orient") .. - string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_orient_submit;Orient]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_orient", function(name, fields) - if fields.worldedit_gui_orient_submit then - gui_angle[name] = angle_indices[fields.worldedit_gui_orient_angle] - worldedit.show_page(name, "worldedit_gui_orient") - minetest.chatcommands["/orient"].func(name, angle_values[gui_angle[name]]) - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_fixlight", { - name = "Fix Lighting", privs = minetest.chatcommands["/fixlight"].privs, - on_select = function(name) - minetest.chatcommands["/fixlight"].func(name, "") - end, -}) - -worldedit.register_gui_function("worldedit_gui_hide", { - name = "Hide Region", privs = minetest.chatcommands["/hide"].privs, - on_select = function(name) - minetest.chatcommands["/hide"].func(name, "") - end, -}) - -worldedit.register_gui_function("worldedit_gui_suppress", { - name = "Suppress Nodes", privs = minetest.chatcommands["/suppress"].privs, - get_formspec = function(name) - local node = gui_nodename1[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_suppress") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_suppress_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_suppress_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - "button_exit[0,2.5;3,0.8;worldedit_gui_suppress_submit;Suppress Nodes]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields) - if fields.worldedit_gui_suppress_search or fields.worldedit_gui_suppress_submit then - gui_nodename1[name] = tostring(fields.worldedit_gui_suppress_node) - worldedit.show_page(name, "worldedit_gui_suppress") - if fields.worldedit_gui_suppress_submit then - minetest.chatcommands["/suppress"].func(name, gui_nodename1[name]) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_highlight", { - name = "Highlight Nodes", privs = minetest.chatcommands["/highlight"].privs, - get_formspec = function(name) - local node = gui_nodename1[name] - local nodename = worldedit.normalize_nodename(node) - return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_highlight") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_highlight_node;Name;%s]", minetest.formspec_escape(node)) .. - "button[4,1.18;1.5,0.8;worldedit_gui_highlight_search;Search]" .. - (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) - or "image[5.5,1.1;1,1;unknown_node.png]") .. - "button_exit[0,2.5;3,0.8;worldedit_gui_highlight_submit;Highlight Nodes]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields) - if fields.worldedit_gui_highlight_search or fields.worldedit_gui_highlight_submit then - gui_nodename1[name] = tostring(fields.worldedit_gui_highlight_node) - worldedit.show_page(name, "worldedit_gui_highlight") - if fields.worldedit_gui_highlight_submit then - minetest.chatcommands["/highlight"].func(name, gui_nodename1[name]) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_restore", { - name = "Restore Region", privs = minetest.chatcommands["/restore"].privs, - on_select = function(name) - minetest.chatcommands["/restore"].func(name, "") - end, -}) - -worldedit.register_gui_function("worldedit_gui_save_load", { - name = "Save/Load", privs = combine_privs(minetest.chatcommands["/save"].privs, minetest.chatcommands["/allocate"].privs, minetest.chatcommands["/load"].privs), - get_formspec = function(name) - local filename = gui_filename[name] - return "size[6,4]" .. worldedit.get_formspec_header("worldedit_gui_save_load") .. - string.format("field[0.5,1.5;4,0.8;worldedit_gui_save_filename;Filename;%s]", minetest.formspec_escape(filename)) .. - "button_exit[0,2.5;3,0.8;worldedit_gui_save_load_submit_save;Save]" .. - "button_exit[3,2.5;3,0.8;worldedit_gui_save_load_submit_allocate;Allocate]" .. - "button_exit[0,3.5;3,0.8;worldedit_gui_save_load_submit_load;Load]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_save", function(name, fields) - if fields.worldedit_gui_save_load_submit_save or worldedit_gui_save_load_submit_allocate or worldedit_gui_save_load_submit_load then - gui_filename[name] = tostring(fields.worldedit_gui_save_axis) - worldedit.show_page(name, "worldedit_gui_save_load") - if fields.worldedit_gui_save_load_submit_save then - minetest.chatcommands["/save"].func(name, gui_filename[name]) - elseif fields.worldedit_gui_save_load_submit_allocate then - minetest.chatcommands["/allocate"].func(name, gui_filename[name]) - else --fields.worldedit_gui_save_load_submit_load - minetest.chatcommands["/load"].func(name, gui_filename[name]) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_lua", { - name = "Run Lua", - get_formspec = function(name) - local code = gui_code[name] - return "size[8,6.5]" .. worldedit.get_formspec_header("worldedit_gui_lua") .. - string.format("textarea[0.5,1;7.5,5.5;worldedit_gui_lua_code;Lua Code;%s]", minetest.formspec_escape(code)) .. - "button_exit[0,6;3,0.8;worldedit_gui_lua_run;Run Lua]" .. - "button_exit[5,6;3,0.8;worldedit_gui_lua_transform;Lua Transform]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_lua", function(name, fields) - if fields.worldedit_gui_lua_run or fields.worldedit_gui_lua_transform then - gui_code[name] = fields.worldedit_gui_lua_value - worldedit.show_page(name, "worldedit_gui_lua") - if fields.worldedit_gui_lua_run then - minetest.chatcommands["/lua"].func(name, gui_code[name]) - else --fields.worldedit_gui_lua_transform - minetest.chatcommands["/luatransform"].func(name, gui_code[name]) - end - return true - end - return false -end) - -worldedit.register_gui_function("worldedit_gui_clearobjects", { - name = "Clear Objects", privs = minetest.chatcommands["/clearobjects"].privs, - on_select = function(name) - minetest.chatcommands["/clearobjects"].func(name, "") - end, -}) - -worldedit.register_gui_function("worldedit_gui_formspec_tester", { - name = "Formspec Tester", - get_formspec = function(name) - local value = gui_formspec[name] - return "size[8,6.5]" .. worldedit.get_formspec_header("worldedit_gui_formspec_tester") .. - string.format("textarea[0.5,1;7.5,5.5;worldedit_gui_formspec_tester_value;Formspec Code;%s]", minetest.formspec_escape(value)) .. - "button_exit[0,6;3,0.8;worldedit_gui_formspec_tester_show;Show Formspec]" - end, -}) - -worldedit.register_gui_handler("worldedit_gui_formspec_tester", function(name, fields) - if fields.worldedit_gui_formspec_tester_show then - gui_formspec[name] = fields.worldedit_gui_formspec_tester_value or "" - worldedit.show_page(name, "worldedit_gui_formspec_tester") - minetest.show_formspec(name, "worldedit:formspec_tester", gui_formspec[name]) - return true - end - return false -end) +--saved state for each player +local gui_nodename1 = {} --mapping of player names to node names (arbitrary strings may also appear as values) +local gui_nodename2 = {} --mapping of player names to node names (arbitrary strings may also appear as values) +local gui_axis1 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) +local gui_axis2 = {} --mapping of player names to axes (one of 1, 2, 3, or 4, representing the axes in the `axis_indices` table below) +local gui_distance1 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) +local gui_distance2 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) +local gui_distance3 = {} --mapping of player names to a distance (arbitrary strings may also appear as values) +local gui_count1 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) +local gui_count2 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) +local gui_count3 = {} --mapping of player names to a quantity (arbitrary strings may also appear as values) +local gui_angle = {} --mapping of player names to an angle (one of 90, 180, 270, representing the angle in degrees clockwise) +local gui_filename = {} --mapping of player names to file names (arbitrary strings may also appear as values) +local gui_formspec = {} --mapping of player names to formspecs +local gui_code = {} --mapping of player names to formspecs + +--set default values +setmetatable(gui_nodename1, {__index = function() return "Cobblestone" end}) +setmetatable(gui_nodename2, {__index = function() return "Stone" end}) +setmetatable(gui_axis1, {__index = function() return 4 end}) +setmetatable(gui_axis2, {__index = function() return 1 end}) +setmetatable(gui_distance1, {__index = function() return "10" end}) +setmetatable(gui_distance2, {__index = function() return "5" end}) +setmetatable(gui_distance3, {__index = function() return "2" end}) +setmetatable(gui_count1, {__index = function() return "3" end}) +setmetatable(gui_count2, {__index = function() return "6" end}) +setmetatable(gui_count3, {__index = function() return "4" end}) +setmetatable(gui_angle, {__index = function() return 90 end}) +setmetatable(gui_filename, {__index = function() return "building" end}) +setmetatable(gui_formspec, {__index = function() return "size[5,5]\nlabel[0,0;Hello, world!]" end}) +setmetatable(gui_code, {__index = function() return "minetest.chat_send_player(\"singleplayer\", \"Hello, world!\")" end}) + +local axis_indices = {["X axis"]=1, ["Y axis"]=2, ["Z axis"]=3, ["Look direction"]=4} +local axis_values = {"x", "y", "z", "?"} +setmetatable(axis_indices, {__index = function () return 4 end}) +setmetatable(axis_values, {__index = function () return "?" end}) + +local angle_indices = {["90 degrees"]=1, ["180 degrees"]=2, ["270 degrees"]=3} +local angle_values = {90, 180, 270} +setmetatable(angle_indices, {__index = function () return 1 end}) +setmetatable(angle_values, {__index = function () return 90 end}) + +--given multiple sets of privileges, produces a single set of privs that would have the same effect as requiring all of them at the same time +local combine_privs = function(...) + local result = {} + for i, privs in ipairs({...}) do + for name, value in pairs(privs) do + if result[name] ~= nil and result[name] ~= value then --the priv must be both true and false, which can never happen + return {__fake_priv_that_nobody_has__=true} --priviledge table that can never be satisfied + end + result[name] = value + end + end + return result +end + +worldedit.register_gui_function("worldedit_gui_about", { + name = "About", privs = minetest.chatcommands["/about"].privs, + on_select = function(name) + minetest.chatcommands["/about"].func(name, "") + end, +}) + +worldedit.register_gui_function("worldedit_gui_inspect", { + name = "Toggle Inspect", privs = minetest.chatcommands["/inspect"].privs, + on_select = function(name) + minetest.chatcommands["/inspect"].func(name, worldedit.inspect[name] and "disable" or "enable") + end, +}) + +worldedit.register_gui_function("worldedit_gui_region", { + name = "Get/Set Region", privs = combine_privs(minetest.chatcommands["/p"].privs, minetest.chatcommands["/pos1"].privs, minetest.chatcommands["/pos2"].privs, minetest.chatcommands["/reset"].privs, minetest.chatcommands["/mark"].privs, minetest.chatcommands["/unmark"].privs, minetest.chatcommands["/volume"].privs, minetest.chatcommands["/fixedpos"].privs), + get_formspec = function(name) + local pos1, pos2 = worldedit.pos1[name], worldedit.pos2[name] + return "size[9,7]" .. worldedit.get_formspec_header("worldedit_gui_region") .. + "button_exit[0,1;3,0.8;worldedit_gui_p_get;Get Positions]" .. + "button_exit[3,1;3,0.8;worldedit_gui_p_set1;Choose Position 1]" .. + "button_exit[6,1;3,0.8;worldedit_gui_p_set2;Choose Position 2]" .. + "button_exit[0,2;3,0.8;worldedit_gui_pos1;Position 1 Here]" .. + "button_exit[3,2;3,0.8;worldedit_gui_pos2;Position 2 Here]" .. + "button_exit[6,2;3,0.8;worldedit_gui_reset;Reset Region]" .. + "button_exit[0,3;3,0.8;worldedit_gui_mark;Mark Region]" .. + "button_exit[3,3;3,0.8;worldedit_gui_unmark;Unmark Region]" .. + "button_exit[6,3;3,0.8;worldedit_gui_volume;Region Volume]" .. + "label[0,4.7;Position 1]" .. + string.format("field[2,5;1.5,0.8;worldedit_gui_fixedpos_pos1x;X ;%s]", pos1 and pos1.x or "") .. + string.format("field[3.5,5;1.5,0.8;worldedit_gui_fixedpos_pos1y;Y ;%s]", pos1 and pos1.y or "") .. + string.format("field[5,5;1.5,0.8;worldedit_gui_fixedpos_pos1z;Z ;%s]", pos1 and pos1.z or "") .. + "button_exit[6.5,4.68;2.5,0.8;worldedit_gui_fixedpos_pos1_submit;Set Position 1]" .. + "label[0,6.2;Position 2]" .. + string.format("field[2,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2x;X ;%s]", pos2 and pos2.x or "") .. + string.format("field[3.5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2y;Y ;%s]", pos2 and pos2.y or "") .. + string.format("field[5,6.5;1.5,0.8;worldedit_gui_fixedpos_pos2z;Z ;%s]", pos2 and pos2.z or "") .. + "button_exit[6.5,6.18;2.5,0.8;worldedit_gui_fixedpos_pos2_submit;Set Position 2]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_region", function(name, fields) + if fields.worldedit_gui_p_get then + minetest.chatcommands["/p"].func(name, "get") + return true + elseif fields.worldedit_gui_p_set1 then + minetest.chatcommands["/p"].func(name, "set1") + return true + elseif fields.worldedit_gui_p_set2 then + minetest.chatcommands["/p"].func(name, "set2") + return true + elseif fields.worldedit_gui_pos1 then + minetest.chatcommands["/pos1"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_pos2 then + minetest.chatcommands["/pos2"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_reset then + minetest.chatcommands["/reset"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_mark then + minetest.chatcommands["/mark"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_unmark then + minetest.chatcommands["/unmark"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_volume then + minetest.chatcommands["/volume"].func(name, "") + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_fixedpos_pos1_submit then + minetest.chatcommands["/fixedpos"].func(name, string.format("set1 %s %s %s", + tostring(fields.worldedit_gui_fixedpos_pos1x), + tostring(fields.worldedit_gui_fixedpos_pos1y), + tostring(fields.worldedit_gui_fixedpos_pos1z))) + worldedit.show_page(name, "worldedit_gui_region") + return true + elseif fields.worldedit_gui_fixedpos_pos2_submit then + minetest.chatcommands["/fixedpos"].func(name, string.format("set2 %s %s %s", + tostring(fields.worldedit_gui_fixedpos_pos2x), + tostring(fields.worldedit_gui_fixedpos_pos2y), + tostring(fields.worldedit_gui_fixedpos_pos2z))) + worldedit.show_page(name, "worldedit_gui_region") + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_set", { + name = "Set Nodes", privs = minetest.chatcommands["/set"].privs, + get_formspec = function(name) + local node = gui_nodename1[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_set") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_set_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_set_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + "button_exit[0,2.5;3,0.8;worldedit_gui_set_submit;Set Nodes]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_set", function(name, fields) + if fields.worldedit_gui_set_search or fields.worldedit_gui_set_submit then + gui_nodename1[name] = tostring(fields.worldedit_gui_set_node) + worldedit.show_page(name, "worldedit_gui_set") + if fields.worldedit_gui_set_submit then + minetest.chatcommands["/set"].func(name, gui_nodename1[name]) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_replace", { + name = "Replace Nodes", privs = combine_privs(minetest.chatcommands["/replace"].privs, minetest.chatcommands["/replaceinverse"].privs), + get_formspec = function(name) + local search, replace = gui_nodename1[name], gui_nodename2[name] + local search_nodename, replace_nodename = worldedit.normalize_nodename(search), worldedit.normalize_nodename(replace) + return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_replace") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_replace_search;Name;%s]", minetest.formspec_escape(search)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_replace_search_search;Search]" .. + (search_nodename and string.format("item_image[5.5,1.1;1,1;%s]", search_nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_replace_replace;Name;%s]", minetest.formspec_escape(replace)) .. + "button[4,2.18;1.5,0.8;worldedit_gui_replace_replace_search;Search]" .. + (replace_nodename and string.format("item_image[5.5,2.1;1,1;%s]", replace_nodename) + or "image[5.5,2.1;1,1;unknown_node.png]") .. + "button_exit[0,3.5;3,0.8;worldedit_gui_replace_submit;Replace Nodes]" .. + "button_exit[3.5,3.5;3,0.8;worldedit_gui_replace_submit_inverse;Replace Inverse]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_replace", function(name, fields) + if fields.worldedit_gui_replace_search_search or fields.worldedit_gui_replace_replace_search + or fields.worldedit_gui_replace_submit or fields.worldedit_gui_replace_submit_inverse then + gui_nodename1[name] = tostring(fields.worldedit_gui_replace_search) + gui_nodename2[name] = tostring(fields.worldedit_gui_replace_replace) + worldedit.show_page(name, "worldedit_gui_replace") + if fields.worldedit_gui_replace_submit then + minetest.chatcommands["/replace"].func(name, string.format("%s %s", gui_nodename1[name], gui_nodename2[name])) + elseif fields.worldedit_gui_replace_submit_inverse then + minetest.chatcommands["/replaceinverse"].func(name, string.format("%s %s", gui_nodename1[name], gui_nodename2[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_sphere_dome", { + name = "Sphere/Dome", privs = combine_privs(minetest.chatcommands["/hollowsphere"].privs, minetest.chatcommands["/sphere"].privs, minetest.chatcommands["/hollowdome"].privs, minetest.chatcommands["/dome"].privs), + get_formspec = function(name) + local node, radius = gui_nodename1[name], gui_distance2[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_sphere_dome") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_sphere_dome_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_sphere_dome_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_sphere_dome_radius;Radius;%s]", minetest.formspec_escape(radius)) .. + "button_exit[0,3.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow;Hollow Sphere]" .. + "button_exit[3.5,3.5;3,0.8;worldedit_gui_sphere_dome_submit_solid;Solid Sphere]" .. + "button_exit[0,4.5;3,0.8;worldedit_gui_sphere_dome_submit_hollow_dome;Hollow Dome]" .. + "button_exit[3.5,4.5;3,0.8;worldedit_gui_sphere_dome_submit_solid_dome;Solid Dome]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_sphere_dome", function(name, fields) + if fields.worldedit_gui_sphere_dome_search + or fields.worldedit_gui_sphere_dome_submit_hollow or fields.worldedit_gui_sphere_dome_submit_solid + or fields.worldedit_gui_sphere_dome_submit_hollow_dome or fields.worldedit_gui_sphere_dome_submit_solid_dome then + gui_nodename1[name] = tostring(fields.worldedit_gui_sphere_dome_node) + gui_distance2[name] = tostring(fields.worldedit_gui_sphere_dome_radius) + worldedit.show_page(name, "worldedit_gui_sphere_dome") + if fields.worldedit_gui_sphere_dome_submit_hollow then + minetest.chatcommands["/hollowsphere"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) + elseif fields.worldedit_gui_sphere_dome_submit_solid then + minetest.chatcommands["/sphere"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) + elseif fields.worldedit_gui_sphere_dome_submit_hollow_dome then + minetest.chatcommands["/hollowdome"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) + elseif fields.worldedit_gui_sphere_dome_submit_solid_dome then + minetest.chatcommands["/dome"].func(name, string.format("%s %s", gui_distance2[name], gui_nodename1[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_cylinder", { + name = "Cylinder", privs = combine_privs(minetest.chatcommands["/hollowcylinder"].privs, minetest.chatcommands["/cylinder"].privs), + get_formspec = function(name) + local node, axis, length, radius = gui_nodename1[name], gui_axis1[name], gui_distance1[name], gui_distance2[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,5]" .. worldedit.get_formspec_header("worldedit_gui_cylinder") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_cylinder_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_cylinder_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_cylinder_length;Length;%s]", minetest.formspec_escape(length)) .. + string.format("dropdown[4,2.18;2.5;worldedit_gui_cylinder_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + string.format("field[0.5,3.5;4,0.8;worldedit_gui_cylinder_radius;Radius;%s]", minetest.formspec_escape(radius)) .. + "button_exit[0,4.5;3,0.8;worldedit_gui_cylinder_submit_hollow;Hollow Cylinder]" .. + "button_exit[3.5,4.5;3,0.8;worldedit_gui_cylinder_submit_solid;Solid Cylinder]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_cylinder", function(name, fields) + if fields.worldedit_gui_cylinder_search + or fields.worldedit_gui_cylinder_submit_hollow or fields.worldedit_gui_cylinder_submit_solid then + gui_nodename1[name] = tostring(fields.worldedit_gui_cylinder_node) + gui_axis1[name] = axis_indices[fields.worldedit_gui_cylinder_axis] + gui_distance1[name] = tostring(fields.worldedit_gui_cylinder_length) + gui_distance2[name] = tostring(fields.worldedit_gui_cylinder_radius) + worldedit.show_page(name, "worldedit_gui_cylinder") + if fields.worldedit_gui_cylinder_submit_hollow then + minetest.chatcommands["/hollowcylinder"].func(name, string.format("%s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_nodename1[name])) + elseif fields.worldedit_gui_cylinder_submit_solid then + minetest.chatcommands["/cylinder"].func(name, string.format("%s %s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_distance2[name], gui_nodename1[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_pyramid", { + name = "Pyramid", privs = minetest.chatcommands["/pyramid"].privs, + get_formspec = function(name) + local node, axis, length = gui_nodename1[name], gui_axis1[name], gui_distance1[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,4]" .. worldedit.get_formspec_header("worldedit_gui_pyramid") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_pyramid_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_pyramid_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_pyramid_length;Length;%s]", minetest.formspec_escape(length)) .. + string.format("dropdown[4,2.18;2.5;worldedit_gui_pyramid_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + "button_exit[0,3.5;3,0.8;worldedit_gui_pyramid_submit;Pyramid]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_pyramid", function(name, fields) + if fields.worldedit_gui_pyramid_search or fields.worldedit_gui_pyramid_submit then + gui_nodename1[name] = tostring(fields.worldedit_gui_pyramid_node) + gui_axis1[name] = axis_indices[fields.worldedit_gui_pyramid_axis] + gui_distance1[name] = tostring(fields.worldedit_gui_pyramid_length) + worldedit.show_page(name, "worldedit_gui_pyramid") + if fields.worldedit_gui_pyramid_submit then + minetest.chatcommands["/pyramid"].func(name, string.format("%s %s %s", axis_values[gui_axis1[name]], gui_distance1[name], gui_nodename1[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_spiral", { + name = "Spiral", privs = minetest.chatcommands["/spiral"].privs, + get_formspec = function(name) + local node, length, height, space = gui_nodename1[name], gui_distance1[name], gui_distance2[name], gui_distance3[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,6]" .. worldedit.get_formspec_header("worldedit_gui_spiral") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_spiral_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_spiral_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_spiral_length;Side Length;%s]", minetest.formspec_escape(length)) .. + string.format("field[0.5,3.5;4,0.8;worldedit_gui_spiral_height;Height;%s]", minetest.formspec_escape(height)) .. + string.format("field[0.5,4.5;4,0.8;worldedit_gui_spiral_space;Wall Spacing;%s]", minetest.formspec_escape(space)) .. + "button_exit[0,5.5;3,0.8;worldedit_gui_spiral_submit;Spiral]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_spiral", function(name, fields) + if fields.worldedit_gui_spiral_search or fields.worldedit_gui_spiral_submit then + gui_nodename1[name] = fields.worldedit_gui_spiral_node + gui_distance1[name] = tostring(fields.worldedit_gui_spiral_length) + gui_distance2[name] = tostring(fields.worldedit_gui_spiral_height) + gui_distance3[name] = tostring(fields.worldedit_gui_spiral_space) + worldedit.show_page(name, "worldedit_gui_spiral") + if fields.worldedit_gui_spiral_submit then + minetest.chatcommands["/spiral"].func(name, string.format("%s %s %s %s", gui_distance1[name], gui_distance2[name], gui_distance3[name], gui_nodename1[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_copy_move", { + name = "Copy/Move", privs = combine_privs(minetest.chatcommands["/copy"].privs, minetest.chatcommands["/move"].privs), + get_formspec = function(name) + local axis = gui_axis1[name] or 4 + local amount = gui_distance1[name] or "10" + return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_copy_move") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_copy_move_amount;Amount;%s]", minetest.formspec_escape(amount)) .. + string.format("dropdown[4,1.18;2.5;worldedit_gui_copy_move_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_copy_move_copy;Copy Region]" .. + "button_exit[3.5,2.5;3,0.8;worldedit_gui_copy_move_move;Move Region]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_copy_move", function(name, fields) + if fields.worldedit_gui_copy_move_copy or fields.worldedit_gui_copy_move_move then + gui_axis1[name] = axis_indices[fields.worldedit_gui_copy_move_axis] or 4 + gui_distance1[name] = tostring(fields.worldedit_gui_copy_move_amount) + worldedit.show_page(name, "worldedit_gui_copy_move") + if fields.worldedit_gui_copy_move_copy then + minetest.chatcommands["/copy"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name])) + else --fields.worldedit_gui_copy_move_move + minetest.chatcommands["/move"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_distance1[name])) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_stack", { + name = "Stack", privs = minetest.chatcommands["/stack"].privs, + get_formspec = function(name) + local axis, count = gui_axis1[name], gui_count1[name] + return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_stack") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_stack_count;Count;%s]", minetest.formspec_escape(count)) .. + string.format("dropdown[4,1.18;2.5;worldedit_gui_stack_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_stack_submit;Stack]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_stack", function(name, fields) + if fields.worldedit_gui_stack_submit then + gui_axis1[name] = axis_indices[fields.worldedit_gui_stack_axis] + gui_count1[name] = tostring(fields.worldedit_gui_stack_count) + worldedit.show_page(name, "worldedit_gui_stack") + minetest.chatcommands["/stack"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], gui_count1[name])) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_stretch", { + name = "Stretch", privs = minetest.chatcommands["/stretch"].privs, + get_formspec = function(name) + local stretchx, stretchy, stretchz = gui_count1[name], gui_count2[name], gui_count3[name] + return "size[5,5]" .. worldedit.get_formspec_header("worldedit_gui_stretch") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_stretch_x;Stretch X;%s]", minetest.formspec_escape(stretchx)) .. + string.format("field[0.5,2.5;4,0.8;worldedit_gui_stretch_y;Stretch Y;%s]", minetest.formspec_escape(stretchy)) .. + string.format("field[0.5,3.5;4,0.8;worldedit_gui_stretch_z;Stretch Z;%s]", minetest.formspec_escape(stretchz)) .. + "button_exit[0,4.5;3,0.8;worldedit_gui_stretch_submit;Stretch]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_stretch", function(name, fields) + if fields.worldedit_gui_stretch_submit then + gui_count1[name] = tostring(fields.worldedit_gui_stretch_x) + gui_count2[name] = tostring(fields.worldedit_gui_stretch_y) + gui_count3[name] = tostring(fields.worldedit_gui_stretch_z) + worldedit.show_page(name, "worldedit_gui_stretch") + minetest.chatcommands["/stretch"].func(name, string.format("%s %s %s", gui_count1[name], gui_count2[name], gui_count3[name])) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_transpose", { + name = "Transpose", privs = minetest.chatcommands["/transpose"].privs, + get_formspec = function(name) + local axis1, axis2 = gui_axis1[name], gui_axis2[name] + return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_transpose") .. + string.format("dropdown[0,1;2.5;worldedit_gui_transpose_axis1;X axis,Y axis,Z axis,Look direction;%d]", axis1) .. + string.format("dropdown[3,1;2.5;worldedit_gui_transpose_axis2;X axis,Y axis,Z axis,Look direction;%d]", axis2) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_transpose_submit;Transpose]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_transpose", function(name, fields) + if fields.worldedit_gui_transpose_submit then + gui_axis1[name] = axis_indices[fields.worldedit_gui_transpose_axis1] + gui_axis2[name] = axis_indices[fields.worldedit_gui_transpose_axis2] + worldedit.show_page(name, "worldedit_gui_transpose") + minetest.chatcommands["/transpose"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], axis_values[gui_axis2[name]])) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_flip", { + name = "Flip", privs = minetest.chatcommands["/flip"].privs, + get_formspec = function(name) + local axis = gui_axis2[name] + return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_flip") .. + string.format("dropdown[0,1;2.5;worldedit_gui_flip_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_flip_submit;Flip]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_flip", function(name, fields) + if fields.worldedit_gui_flip_submit then + gui_axis2[name] = axis_indices[fields.worldedit_gui_flip_axis] + worldedit.show_page(name, "worldedit_gui_flip") + minetest.chatcommands["/flip"].func(name, axis_values[gui_axis2[name]]) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_rotate", { + name = "Rotate", privs = minetest.chatcommands["/rotate"].privs, + get_formspec = function(name) + local axis, angle = gui_axis1[name], gui_angle[name] + return "size[5.5,3]" .. worldedit.get_formspec_header("worldedit_gui_rotate") .. + string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. + string.format("dropdown[3,1;2.5;worldedit_gui_rotate_axis;X axis,Y axis,Z axis,Look direction;%d]", axis) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_rotate_submit;Rotate]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_rotate", function(name, fields) + if fields.worldedit_gui_rotate_submit then + gui_axis1[name] = axis_indices[fields.worldedit_gui_rotate_axis] + gui_angle[name] = angle_indices[fields.worldedit_gui_rotate_angle] + worldedit.show_page(name, "worldedit_gui_rotate") + minetest.chatcommands["/rotate"].func(name, string.format("%s %s", axis_values[gui_axis1[name]], angle_values[gui_angle[name]])) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_orient", { + name = "Orient", privs = minetest.chatcommands["/orient"].privs, + get_formspec = function(name) + local angle = gui_angle[name] + return "size[5,3]" .. worldedit.get_formspec_header("worldedit_gui_orient") .. + string.format("dropdown[0,1;2.5;worldedit_gui_rotate_angle;90 degrees,180 degrees,270 degrees;%s]", angle) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_orient_submit;Orient]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_orient", function(name, fields) + if fields.worldedit_gui_orient_submit then + gui_angle[name] = angle_indices[fields.worldedit_gui_orient_angle] + worldedit.show_page(name, "worldedit_gui_orient") + minetest.chatcommands["/orient"].func(name, angle_values[gui_angle[name]]) + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_fixlight", { + name = "Fix Lighting", privs = minetest.chatcommands["/fixlight"].privs, + on_select = function(name) + minetest.chatcommands["/fixlight"].func(name, "") + end, +}) + +worldedit.register_gui_function("worldedit_gui_hide", { + name = "Hide Region", privs = minetest.chatcommands["/hide"].privs, + on_select = function(name) + minetest.chatcommands["/hide"].func(name, "") + end, +}) + +worldedit.register_gui_function("worldedit_gui_suppress", { + name = "Suppress Nodes", privs = minetest.chatcommands["/suppress"].privs, + get_formspec = function(name) + local node = gui_nodename1[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_suppress") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_suppress_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_suppress_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + "button_exit[0,2.5;3,0.8;worldedit_gui_suppress_submit;Suppress Nodes]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_suppress", function(name, fields) + if fields.worldedit_gui_suppress_search or fields.worldedit_gui_suppress_submit then + gui_nodename1[name] = tostring(fields.worldedit_gui_suppress_node) + worldedit.show_page(name, "worldedit_gui_suppress") + if fields.worldedit_gui_suppress_submit then + minetest.chatcommands["/suppress"].func(name, gui_nodename1[name]) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_highlight", { + name = "Highlight Nodes", privs = minetest.chatcommands["/highlight"].privs, + get_formspec = function(name) + local node = gui_nodename1[name] + local nodename = worldedit.normalize_nodename(node) + return "size[6.5,3]" .. worldedit.get_formspec_header("worldedit_gui_highlight") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_highlight_node;Name;%s]", minetest.formspec_escape(node)) .. + "button[4,1.18;1.5,0.8;worldedit_gui_highlight_search;Search]" .. + (nodename and string.format("item_image[5.5,1.1;1,1;%s]", nodename) + or "image[5.5,1.1;1,1;unknown_node.png]") .. + "button_exit[0,2.5;3,0.8;worldedit_gui_highlight_submit;Highlight Nodes]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_highlight", function(name, fields) + if fields.worldedit_gui_highlight_search or fields.worldedit_gui_highlight_submit then + gui_nodename1[name] = tostring(fields.worldedit_gui_highlight_node) + worldedit.show_page(name, "worldedit_gui_highlight") + if fields.worldedit_gui_highlight_submit then + minetest.chatcommands["/highlight"].func(name, gui_nodename1[name]) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_restore", { + name = "Restore Region", privs = minetest.chatcommands["/restore"].privs, + on_select = function(name) + minetest.chatcommands["/restore"].func(name, "") + end, +}) + +worldedit.register_gui_function("worldedit_gui_save_load", { + name = "Save/Load", privs = combine_privs(minetest.chatcommands["/save"].privs, minetest.chatcommands["/allocate"].privs, minetest.chatcommands["/load"].privs), + get_formspec = function(name) + local filename = gui_filename[name] + return "size[6,4]" .. worldedit.get_formspec_header("worldedit_gui_save_load") .. + string.format("field[0.5,1.5;4,0.8;worldedit_gui_save_filename;Filename;%s]", minetest.formspec_escape(filename)) .. + "button_exit[0,2.5;3,0.8;worldedit_gui_save_load_submit_save;Save]" .. + "button_exit[3,2.5;3,0.8;worldedit_gui_save_load_submit_allocate;Allocate]" .. + "button_exit[0,3.5;3,0.8;worldedit_gui_save_load_submit_load;Load]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_save", function(name, fields) + if fields.worldedit_gui_save_load_submit_save or worldedit_gui_save_load_submit_allocate or worldedit_gui_save_load_submit_load then + gui_filename[name] = tostring(fields.worldedit_gui_save_axis) + worldedit.show_page(name, "worldedit_gui_save_load") + if fields.worldedit_gui_save_load_submit_save then + minetest.chatcommands["/save"].func(name, gui_filename[name]) + elseif fields.worldedit_gui_save_load_submit_allocate then + minetest.chatcommands["/allocate"].func(name, gui_filename[name]) + else --fields.worldedit_gui_save_load_submit_load + minetest.chatcommands["/load"].func(name, gui_filename[name]) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_lua", { + name = "Run Lua", + get_formspec = function(name) + local code = gui_code[name] + return "size[8,6.5]" .. worldedit.get_formspec_header("worldedit_gui_lua") .. + string.format("textarea[0.5,1;7.5,5.5;worldedit_gui_lua_code;Lua Code;%s]", minetest.formspec_escape(code)) .. + "button_exit[0,6;3,0.8;worldedit_gui_lua_run;Run Lua]" .. + "button_exit[5,6;3,0.8;worldedit_gui_lua_transform;Lua Transform]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_lua", function(name, fields) + if fields.worldedit_gui_lua_run or fields.worldedit_gui_lua_transform then + gui_code[name] = fields.worldedit_gui_lua_value + worldedit.show_page(name, "worldedit_gui_lua") + if fields.worldedit_gui_lua_run then + minetest.chatcommands["/lua"].func(name, gui_code[name]) + else --fields.worldedit_gui_lua_transform + minetest.chatcommands["/luatransform"].func(name, gui_code[name]) + end + return true + end + return false +end) + +worldedit.register_gui_function("worldedit_gui_clearobjects", { + name = "Clear Objects", privs = minetest.chatcommands["/clearobjects"].privs, + on_select = function(name) + minetest.chatcommands["/clearobjects"].func(name, "") + end, +}) + +worldedit.register_gui_function("worldedit_gui_formspec_tester", { + name = "Formspec Tester", + get_formspec = function(name) + local value = gui_formspec[name] + return "size[8,6.5]" .. worldedit.get_formspec_header("worldedit_gui_formspec_tester") .. + string.format("textarea[0.5,1;7.5,5.5;worldedit_gui_formspec_tester_value;Formspec Code;%s]", minetest.formspec_escape(value)) .. + "button_exit[0,6;3,0.8;worldedit_gui_formspec_tester_show;Show Formspec]" + end, +}) + +worldedit.register_gui_handler("worldedit_gui_formspec_tester", function(name, fields) + if fields.worldedit_gui_formspec_tester_show then + gui_formspec[name] = fields.worldedit_gui_formspec_tester_value or "" + worldedit.show_page(name, "worldedit_gui_formspec_tester") + minetest.show_formspec(name, "worldedit:formspec_tester", gui_formspec[name]) + return true + end + return false +end) diff --git a/mods/WorldEdit/worldedit_gui/init.lua b/mods/WorldEdit/worldedit_gui/init.lua index 3088f724..afd8c2c2 100755 --- a/mods/WorldEdit/worldedit_gui/init.lua +++ b/mods/WorldEdit/worldedit_gui/init.lua @@ -102,7 +102,10 @@ if unified_inventory then --unified inventory installed end elseif inventory_plus then --inventory++ installed minetest.register_on_joinplayer(function(player) - inventory_plus.register_button(player, "worldedit_gui", "WorldEdit") + local can_worldedit = minetest.check_player_privs(player:get_player_name(), {worldedit=true}) + if can_worldedit then + inventory_plus.register_button(player, "worldedit_gui", "WorldEdit") + end end) --show the form when the button is pressed and hide it when done diff --git a/mods/WorldEdit/worldedit_infinity/depends.txt b/mods/WorldEdit/worldedit_infinity/depends.txt new file mode 100755 index 00000000..c4f6871d --- /dev/null +++ b/mods/WorldEdit/worldedit_infinity/depends.txt @@ -0,0 +1 @@ +worldedit? diff --git a/mods/WorldEdit/worldedit_infinity/init.lua b/mods/WorldEdit/worldedit_infinity/init.lua index 6001e3e9..8bd3a051 100755 --- a/mods/WorldEdit/worldedit_infinity/init.lua +++ b/mods/WorldEdit/worldedit_infinity/init.lua @@ -1,103 +1,103 @@ -worldedit = {} -local minetest = minetest --local copy of global - -local get_pointed = function(pos, nearest, distance) - if distance > 100 then - return false - end - - --check for collision with node - local nodename = minetest.get_node(pos).name - if nodename ~= "air" - and nodename ~= "default:water_source" - and nodename ~= "default:water_flowing" then - if nodename ~= "ignore" then - return nearest - end - return false - end -end - -local use = function(itemstack, user, pointed_thing) - if pointed_thing.type == "nothing" then --pointing at nothing - local placepos = worldedit.raytrace(user:getpos(), user:get_look_dir(), get_pointed) - if placepos then --extended reach - pointed_thing.type = "node" - pointed_thing.under = nil --wip - pointed_thing.above = nil --wip - end - end - return minetest.item_place_node(itemstack, user, pointed_thing) -end --- - -worldedit.raytrace = function(pos, dir, callback) - local base = {x=math.floor(pos.x), y=math.floor(pos.y), z=math.floor(pos.z)} - local stepx, stepy, stepz = 0, 0, 0 - local componentx, componenty, componentz = 0, 0, 0 - local intersectx, intersecty, intersectz = 0, 0, 0 - - if dir.x == 0 then - intersectx = math.huge - elseif dir.x > 0 then - stepx = 1 - componentx = 1 / dir.x - intersectx = ((base.x - pos.x) + 1) * componentx - else - stepx = -1 - componentx = 1 / -dir.x - intersectx = (pos.x - base.x) * componentx - end - if dir.y == 0 then - intersecty = math.huge - elseif dir.y > 0 then - stepy = 1 - componenty = 1 / dir.y - intersecty = ((base.y - pos.y) + 1) * componenty - else - stepy = -1 - componenty = 1 / -dir.y - intersecty = (pos.y - base.y) * componenty - end - if dir.z == 0 then - intersectz = math.huge - elseif dir.z > 0 then - stepz = 1 - componentz = 1 / dir.z - intersectz = ((base.z - pos.z) + 1) * componentz - else - stepz = -1 - componentz = 1 / -dir.z - intersectz = (pos.z - base.z) * componentz - end - - local distance = 0 - local nearest = {x=base.x, y=base.y, z=base.z} - while true do - local values = {callback(base, nearest, distance)} - if #values > 0 then - return unpack(values) - end - - nearest.x, nearest.y, nearest.z = base.x, base.y, base.z - if intersectx < intersecty then - if intersectx < intersectz then - base.x = base.x + stepx - distance = intersectx - intersectx = intersectx + componentx - else - base.z = base.z + stepz - distance = intersectz - intersectz = intersectz + componentz - end - elseif intersecty < intersectz then - base.y = base.y + stepy - distance = intersecty - intersecty = intersecty + componenty - else - base.z = base.z + stepz - distance = intersectz - intersectz = intersectz + componentz - end - end -end +worldedit = rawget(_G, "worldedit") or {} +local minetest = minetest --local copy of global + +local get_pointed = function(pos, nearest, distance) + if distance > 100 then + return false + end + + --check for collision with node + local nodename = minetest.get_node(pos).name + if nodename ~= "air" + and nodename ~= "default:water_source" + and nodename ~= "default:water_flowing" then + if nodename ~= "ignore" then + return nearest + end + return false + end +end + +local use = function(itemstack, user, pointed_thing) + if pointed_thing.type == "nothing" then --pointing at nothing + local placepos = worldedit.raytrace(user:getpos(), user:get_look_dir(), get_pointed) + if placepos then --extended reach + pointed_thing.type = "node" + pointed_thing.under = nil --wip + pointed_thing.above = nil --wip + end + end + return minetest.item_place_node(itemstack, user, pointed_thing) +end +-- + +worldedit.raytrace = function(pos, dir, callback) + local base = {x=math.floor(pos.x), y=math.floor(pos.y), z=math.floor(pos.z)} + local stepx, stepy, stepz = 0, 0, 0 + local componentx, componenty, componentz = 0, 0, 0 + local intersectx, intersecty, intersectz = 0, 0, 0 + + if dir.x == 0 then + intersectx = math.huge + elseif dir.x > 0 then + stepx = 1 + componentx = 1 / dir.x + intersectx = ((base.x - pos.x) + 1) * componentx + else + stepx = -1 + componentx = 1 / -dir.x + intersectx = (pos.x - base.x) * componentx + end + if dir.y == 0 then + intersecty = math.huge + elseif dir.y > 0 then + stepy = 1 + componenty = 1 / dir.y + intersecty = ((base.y - pos.y) + 1) * componenty + else + stepy = -1 + componenty = 1 / -dir.y + intersecty = (pos.y - base.y) * componenty + end + if dir.z == 0 then + intersectz = math.huge + elseif dir.z > 0 then + stepz = 1 + componentz = 1 / dir.z + intersectz = ((base.z - pos.z) + 1) * componentz + else + stepz = -1 + componentz = 1 / -dir.z + intersectz = (pos.z - base.z) * componentz + end + + local distance = 0 + local nearest = {x=base.x, y=base.y, z=base.z} + while true do + local values = {callback(base, nearest, distance)} + if #values > 0 then + return unpack(values) + end + + nearest.x, nearest.y, nearest.z = base.x, base.y, base.z + if intersectx < intersecty then + if intersectx < intersectz then + base.x = base.x + stepx + distance = intersectx + intersectx = intersectx + componentx + else + base.z = base.z + stepz + distance = intersectz + intersectz = intersectz + componentz + end + elseif intersecty < intersectz then + base.y = base.y + stepy + distance = intersecty + intersecty = intersecty + componenty + else + base.z = base.z + stepz + distance = intersectz + intersectz = intersectz + componentz + end + end +end diff --git a/mods/WorldEdit/worldedit_shortcommands/depends.txt b/mods/WorldEdit/worldedit_shortcommands/depends.txt index a741c93c..de1cb6c7 100755 --- a/mods/WorldEdit/worldedit_shortcommands/depends.txt +++ b/mods/WorldEdit/worldedit_shortcommands/depends.txt @@ -1 +1 @@ -worldedit_commands +worldedit_commands diff --git a/mods/WorldEdit/worldedit_shortcommands/init.lua b/mods/WorldEdit/worldedit_shortcommands/init.lua index 8ef8d586..a3cbb675 100755 --- a/mods/WorldEdit/worldedit_shortcommands/init.lua +++ b/mods/WorldEdit/worldedit_shortcommands/init.lua @@ -1,50 +1,50 @@ ---provides shorter names for the commands in `worldedit_commands` - ---returns true if command could not be aliased, false otherwise -worldedit.alias_chatcommand = function(alias, original_command) - if not minetest.chatcommands[original_command] then - minetest.log("error", "worldedit_shortcommands: original command " .. original_command .. " does not exist") - return true - end - if minetest.chatcommands[alias] then - minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists") - return true - end - minetest.register_chatcommand(alias, minetest.chatcommands[original_command]) - return false -end - -worldedit.alias_chatcommand("/i", "/inspect") -worldedit.alias_chatcommand("/rst", "/reset") -worldedit.alias_chatcommand("/mk", "/mark") -worldedit.alias_chatcommand("/umk", "/unmark") -worldedit.alias_chatcommand("/1", "/pos1") -worldedit.alias_chatcommand("/2", "/pos2") -worldedit.alias_chatcommand("/fp", "/fixedpos") -worldedit.alias_chatcommand("/v", "/volume") -worldedit.alias_chatcommand("/s", "/set") -worldedit.alias_chatcommand("/r", "/replace") -worldedit.alias_chatcommand("/ri", "/replaceinverse") -worldedit.alias_chatcommand("/hspr", "/hollowsphere") -worldedit.alias_chatcommand("/spr", "/sphere") -worldedit.alias_chatcommand("/hdo", "/hollowdome") -worldedit.alias_chatcommand("/do", "/dome") -worldedit.alias_chatcommand("/hcyl", "/hollowcylinder") -worldedit.alias_chatcommand("/cyl", "/cylinder") -worldedit.alias_chatcommand("/pyr", "/pyramid") -worldedit.alias_chatcommand("/spl", "/spiral") -worldedit.alias_chatcommand("/m", "/move") -worldedit.alias_chatcommand("/c", "/copy") -worldedit.alias_chatcommand("/stk", "/stack") -worldedit.alias_chatcommand("/sch", "/stretch") -worldedit.alias_chatcommand("/tps", "/transpose") -worldedit.alias_chatcommand("/fl", "/flip") -worldedit.alias_chatcommand("/rot", "/rotate") -worldedit.alias_chatcommand("/ort", "/orient") -worldedit.alias_chatcommand("/hi", "/hide") -worldedit.alias_chatcommand("/sup", "/suppress") -worldedit.alias_chatcommand("/hlt", "/highlight") -worldedit.alias_chatcommand("/rsr", "/restore") -worldedit.alias_chatcommand("/l", "/lua") -worldedit.alias_chatcommand("/lt", "/luatransform") +--provides shorter names for the commands in `worldedit_commands` + +--returns true if command could not be aliased, false otherwise +worldedit.alias_chatcommand = function(alias, original_command) + if not minetest.chatcommands[original_command] then + minetest.log("error", "worldedit_shortcommands: original command " .. original_command .. " does not exist") + return true + end + if minetest.chatcommands[alias] then + minetest.log("error", "worldedit_shortcommands: alias " .. alias .. " already exists") + return true + end + minetest.register_chatcommand(alias, minetest.chatcommands[original_command]) + return false +end + +worldedit.alias_chatcommand("/i", "/inspect") +worldedit.alias_chatcommand("/rst", "/reset") +worldedit.alias_chatcommand("/mk", "/mark") +worldedit.alias_chatcommand("/umk", "/unmark") +worldedit.alias_chatcommand("/1", "/pos1") +worldedit.alias_chatcommand("/2", "/pos2") +worldedit.alias_chatcommand("/fp", "/fixedpos") +worldedit.alias_chatcommand("/v", "/volume") +worldedit.alias_chatcommand("/s", "/set") +worldedit.alias_chatcommand("/r", "/replace") +worldedit.alias_chatcommand("/ri", "/replaceinverse") +worldedit.alias_chatcommand("/hspr", "/hollowsphere") +worldedit.alias_chatcommand("/spr", "/sphere") +worldedit.alias_chatcommand("/hdo", "/hollowdome") +worldedit.alias_chatcommand("/do", "/dome") +worldedit.alias_chatcommand("/hcyl", "/hollowcylinder") +worldedit.alias_chatcommand("/cyl", "/cylinder") +worldedit.alias_chatcommand("/pyr", "/pyramid") +worldedit.alias_chatcommand("/spl", "/spiral") +worldedit.alias_chatcommand("/m", "/move") +worldedit.alias_chatcommand("/c", "/copy") +worldedit.alias_chatcommand("/stk", "/stack") +worldedit.alias_chatcommand("/sch", "/stretch") +worldedit.alias_chatcommand("/tps", "/transpose") +worldedit.alias_chatcommand("/fl", "/flip") +worldedit.alias_chatcommand("/rot", "/rotate") +worldedit.alias_chatcommand("/ort", "/orient") +worldedit.alias_chatcommand("/hi", "/hide") +worldedit.alias_chatcommand("/sup", "/suppress") +worldedit.alias_chatcommand("/hlt", "/highlight") +worldedit.alias_chatcommand("/rsr", "/restore") +worldedit.alias_chatcommand("/l", "/lua") +worldedit.alias_chatcommand("/lt", "/luatransform") worldedit.alias_chatcommand("/clro", "/clearobjects") \ No newline at end of file