Cleanup and fixup

Non-stylistic changes:
  * Add LuaDoc/LDoc support.
  * Fix `clear_objects` area size calculation.
  * Fix `clear_objects` removing player objects.
  * Fix shadowing of marker entity name with player name.
  * Make visualization functions use `swap_node`.
  * Make hidden nodes unwalkable.
  * Prevent `hide` from hiding air.
  * Make deprecated functions log to deprecated stream when called.
  * Fixed `replaceinverse` not using normalized node names.
  * Added .gitignore.
  * Bump version to 1.1.

Stylistic changes:
  * Change `x = function` to `function x`.
  * Change comment format.
  * Make  missing VoxelManip error less obnoxious.
  * Move `sort_pos` into `common.lua`, which is a required module.
  * Remove local copies of `minetest`.
  * Remove `worldedit = worldedit or {}` from modules.
  * Replace replaceinverse with an inverse argument to `replace`.
  * Added `error()`s on on invalid axes.
  * Change `wip` to `TODO`.
  * Rename `clearobjects` to `clear_objects`.
  * Remove `hollow_{sphere,dome,cylinder}` and replace them with a hollow parameter to each function.
  * Add helpers to reduce code duplication.
  * Renamed `Chat Commands.md` to `ChatCommands.md`.
This commit is contained in:
ShadowNinja 2014-10-29 22:47:08 -04:00
parent 1f277147ca
commit bb8456b711
16 changed files with 788 additions and 983 deletions

2
.gitignore vendored Normal file

@ -0,0 +1,2 @@
*~

@ -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. 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. 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 Compatibility
------------- -------------

@ -107,7 +107,7 @@ Step 4: Other commands
---------------------- ----------------------
### Chat 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 name>` command, without the first forward slash. For example, to see some information about the `//set <node>` command mentioned earlier, simply use `/help /set`. If you're in-game and forgot how a command works, just use the `/help <command name>` command, without the first forward slash. For example, to see some information about the `//set <node>` command mentioned earlier, simply use `/help /set`.
@ -115,6 +115,6 @@ A very useful command to check out is the `//save <schematic>` command, which ca
### WorldEdit GUI ### 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. 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.

@ -21,9 +21,9 @@ Manipulations
------------- -------------
Contained in manipulations.lua, this module allows several node operations to be applied over a region. 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. Returns the number of nodes set.
@ -109,51 +109,33 @@ Primitives
---------- ----------
Contained in primitives.lua, this module allows the creation of several geometric 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. 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. 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. Returns the number of nodes added.
### count = worldedit.dome(pos, radius, nodename) ### count = worldedit.pyramid(pos, axis, height, node_name)
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)
Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`. Adds a pyramid centered at `pos` along the `axis` axis ("x" or "y" or "z") with height `height`.
Returns the number of nodes added. 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. 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. 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. 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. Returns the number of nodes found.

12
config.ld Normal file

@ -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"
}

@ -1,27 +1,9 @@
worldedit = worldedit or {} --- Lua code execution functions.
local minetest = minetest -- local copy of global -- @module worldedit.code
-- Copies and modifies positions `pos1` and `pos2` so that each component of --- Executes `code` as a Lua chunk in the global namespace.
-- `pos1` is less than or equal to the corresponding component of `pos2`. -- @return An error message if the code fails, or nil on success.
-- Returns the new positions. function worldedit.lua(code)
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) local func, err = loadstring(code)
if not func then -- Syntax error if not func then -- Syntax error
return err return err
@ -33,10 +15,12 @@ worldedit.lua = function(code)
return nil return nil
end end
-- Executes `code` as a Lua chunk in the global namespace with the variable
--- 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 -- pos available, for each node in a region defined by positions `pos1` and
-- `pos2`, returning an error if the code fails, or nil otherwise -- `pos2`.
worldedit.luatransform = function(pos1, pos2, code) -- @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) pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local factory, err = loadstring("return function(pos) " .. code .. " end") local factory, err = loadstring("return function(pos) " .. code .. " end")
@ -45,9 +29,7 @@ worldedit.luatransform = function(pos1, pos2, code)
end end
local func = factory() local func = factory()
-- Keep area loaded worldedit.keep_loaded(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0} local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do while pos.x <= pos2.x do

114
worldedit/common.lua Normal file

@ -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

@ -1,11 +1,21 @@
worldedit = worldedit or {} --- Compatibility functions.
local minetest = minetest --local copy of global -- @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.allocate_old = worldedit.allocate
worldedit.deserialize_old = worldedit.deserialize worldedit.deserialize_old = worldedit.deserialize
worldedit.metasave = function(pos1, pos2, filename) function worldedit.metasave(pos1, pos2, filename)
deprecated("save")
local file, err = io.open(filename, "wb") local file, err = io.open(filename, "wb")
if err then return 0 end if err then return 0 end
local data, count = worldedit.serialize(pos1, pos2) local data, count = worldedit.serialize(pos1, pos2)
@ -14,7 +24,8 @@ worldedit.metasave = function(pos1, pos2, filename)
return count return count
end end
worldedit.metaload = function(originpos, filename) function worldedit.metaload(originpos, filename)
deprecated("load")
filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem" filename = minetest.get_worldpath() .. "/schems/" .. file .. ".wem"
local file, err = io.open(filename, "wb") local file, err = io.open(filename, "wb")
if err then return 0 end if err then return 0 end
@ -22,11 +33,13 @@ worldedit.metaload = function(originpos, filename)
return worldedit.deserialize(originpos, data) return worldedit.deserialize(originpos, data)
end end
worldedit.scale = function(pos1, pos2, factor) function worldedit.scale(pos1, pos2, factor)
deprecated("stretch")
return worldedit.stretch(pos1, pos2, factor, factor, factor) return worldedit.stretch(pos1, pos2, factor, factor, factor)
end end
worldedit.valueversion = function(value) function worldedit.valueversion(value)
deprecated("read_header")
local version = worldedit.read_header(value) local version = worldedit.read_header(value)
if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then if not version or version > worldedit.LATEST_SERIALIZATION_VERSION then
return 0 return 0
@ -34,3 +47,28 @@ worldedit.valueversion = function(value)
return version return version
end 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

@ -1,25 +1,44 @@
worldedit = worldedit or {} --- Worldedit.
worldedit.version = {major=1, minor=0} -- @module worldedit
worldedit.version_string = "1.0" -- @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
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)) 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 path = minetest.get_modpath(minetest.get_current_modname())
local loadmodule = function(path) local function load_module(path)
local file = io.open(path) local file = io.open(path)
if not file then if not file then return end
return
end
file:close() file:close()
return dofile(path) return dofile(path)
end end
loadmodule(path .. "/manipulations.lua") dofile(path .. "/common.lua")
loadmodule(path .. "/primitives.lua") load_module(path .. "/manipulations.lua")
loadmodule(path .. "/visualization.lua") load_module(path .. "/primitives.lua")
loadmodule(path .. "/serialization.lua") load_module(path .. "/visualization.lua")
loadmodule(path .. "/code.lua") load_module(path .. "/serialization.lua")
loadmodule(path .. "/compatibility.lua") load_module(path .. "/code.lua")
load_module(path .. "/compatibility.lua")
if minetest.setting_getbool("log_mods") then
print("[WorldEdit] Loaded!")
end
print("[MOD] WorldEdit loaded!")

@ -1,331 +1,138 @@
worldedit = worldedit or {} --- Generic node manipulations.
local minetest = minetest --local copy of global -- @module worldedit.manipulations
-- Copies and modifies positions `pos1` and `pos2` so that each component of local mh = worldedit.manip_helpers
-- `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 --- Sets a region to `node_names`.
worldedit.set = function(pos1, pos2, nodenames) -- @param pos1
if type(nodenames) == "string" then -- @param pos2
nodenames = {nodenames} -- @param node_names Node name or list of node names.
end -- @return The number of nodes set.
function worldedit.set(pos1, pos2, node_names)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) local manip, area = mh.init(pos1, pos2)
local data = mh.get_empty_data(area)
--set up voxel manipulator if type(node_names) == "string" then -- Only one type of node
local manip = minetest.get_voxel_manip() local id = minetest.get_content_id(node_names)
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) -- Fill area with node
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) for i in area:iterp(pos1, pos2) do
data[i] = id
--fill emerged area with ignore end
local nodes = {} else -- Several types of nodes specified
local ignore = minetest.get_content_id("ignore") local node_ids = {}
for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do for i, v in ipairs(node_names) do
nodes[i] = ignore node_ids[i] = minetest.get_content_id(v)
end end
-- Fill area randomly with nodes
--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 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 for i in area:iterp(pos1, pos2) do
data[i] = node_ids[rand(id_count)]
end
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end 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) --- 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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--set up voxel manipulator local manip, area = mh.init(pos1, pos2)
local manip = minetest.get_voxel_manip() local data = manip:get_data()
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2)
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) local search_id = minetest.get_content_id(search_node)
local replace_id = minetest.get_content_id(replace_node)
local nodes = manip:get_data()
local searchnode_id = minetest.get_content_id(searchnode)
local replacenode_id = minetest.get_content_id(replacenode)
local count = 0 local count = 0
for i in area:iterp(pos1, pos2) do --replace searchnode with replacenode
if nodes[i] == searchnode_id then --- TODO: This could be shortened by checking `inverse` in the loop,
nodes[i] = replacenode_id -- but that would have a speed penalty. Is the penalty big enough
count = count + 1 -- 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
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end 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 --- Duplicates a region `amount` times with offset vector `direction`.
local manip = minetest.get_voxel_manip() -- Stacking is spread across server steps, one copy per step.
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) -- @return The number of nodes stacked.
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) function worldedit.stack2(pos1, pos2, direction, amount, finished)
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 i = 0
local translated = {x=0,y=0,z=0} local translated = {x=0, y=0, z=0}
local function nextone() local function next_one()
if i < amount then if i < amount then
i = i + 1 i = i + 1
translated.x = translated.x + direction.x translated.x = translated.x + direction.x
translated.y = translated.y + direction.y translated.y = translated.y + direction.y
translated.z = translated.z + direction.z translated.z = translated.z + direction.z
worldedit.copy2(pos1, pos2, translated, volume) worldedit.copy2(pos1, pos2, translated, volume)
minetest.after(0, nextone) minetest.after(0, next_one)
else else
if finished then if finished then
finished() finished()
end end
end end
end end
nextone() next_one()
return worldedit.volume(pos1, pos2) * amount return worldedit.volume(pos1, pos2) * amount
end 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) --- 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) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--make area stay loaded worldedit.keep_loaded(pos1, pos2)
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 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 if amount < 0 then
local pos = {x=pos1.x, y=0, z=0} local pos = {}
pos.x = pos1.x
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
while pos.y <= pos2.y do while pos.y <= pos2.y do
pos.z = pos1.z pos.z = pos1.z
while pos.z <= pos2.z do while pos.z <= pos2.z do
local node = get_node(pos) --obtain current node local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() --get meta of current node local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] --store current position local value = pos[axis] -- Store current position
pos[axis] = value + amount --move along axis pos[axis] = value + amount -- Move along axis
add_node(pos, node) --copy node to new position set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) --set metadata of new node get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value --restore old position pos[axis] = value -- Restore old position
pos.z = pos.z + 1 pos.z = pos.z + 1
end end
pos.y = pos.y + 1 pos.y = pos.y + 1
@ -333,19 +140,20 @@ worldedit.copy = function(pos1, pos2, axis, amount)
pos.x = pos.x + 1 pos.x = pos.x + 1
end end
else else
local pos = {x=pos2.x, y=0, z=0} local pos = {}
pos.x = pos2.x
while pos.x >= pos1.x do while pos.x >= pos1.x do
pos.y = pos2.y pos.y = pos2.y
while pos.y >= pos1.y do while pos.y >= pos1.y do
pos.z = pos2.z pos.z = pos2.z
while pos.z >= pos1.z do while pos.z >= pos1.z do
local node = get_node(pos) --obtain current node local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() --get meta of current node local meta = get_meta(pos):to_table() -- Get meta of current node
local value = pos[axis] --store current position local value = pos[axis] -- Store current position
pos[axis] = value + amount --move along axis pos[axis] = value + amount -- Move along axis
add_node(pos, node) --copy node to new position set_node(pos, node) -- Copy node to new position
get_meta(pos):from_table(meta) --set metadata of new node get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value --restore old position pos[axis] = value -- Restore old position
pos.z = pos.z - 1 pos.z = pos.z - 1
end end
pos.y = pos.y - 1 pos.y = pos.y - 1
@ -356,31 +164,38 @@ worldedit.copy = function(pos1, pos2, axis, amount)
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end 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) --- 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) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--make area stay loaded worldedit.keep_loaded(pos1, pos2)
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) --- TODO: Move slice by slice using schematic method in the move axis
local get_node, get_meta, add_node, remove_node = minetest.get_node, minetest.get_meta, minetest.add_node, minetest.remove_node -- 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 if amount < 0 then
local pos = {x=pos1.x, y=0, z=0} local pos = {}
pos.x = pos1.x
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
while pos.y <= pos2.y do while pos.y <= pos2.y do
pos.z = pos1.z pos.z = pos1.z
while pos.z <= pos2.z do while pos.z <= pos2.z do
local node = get_node(pos) --obtain current node local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() --get metadata of current node local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) remove_node(pos) -- Remove current node
local value = pos[axis] --store current position local value = pos[axis] -- Store current position
pos[axis] = value + amount --move along axis pos[axis] = value + amount -- Move along axis
add_node(pos, node) --move node to new position set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) --set metadata of new node get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value --restore old position pos[axis] = value -- Restore old position
pos.z = pos.z + 1 pos.z = pos.z + 1
end end
pos.y = pos.y + 1 pos.y = pos.y + 1
@ -388,20 +203,21 @@ worldedit.move = function(pos1, pos2, axis, amount)
pos.x = pos.x + 1 pos.x = pos.x + 1
end end
else else
local pos = {x=pos2.x, y=0, z=0} local pos = {}
pos.x = pos2.x
while pos.x >= pos1.x do while pos.x >= pos1.x do
pos.y = pos2.y pos.y = pos2.y
while pos.y >= pos1.y do while pos.y >= pos1.y do
pos.z = pos2.z pos.z = pos2.z
while pos.z >= pos1.z do while pos.z >= pos1.z do
local node = get_node(pos) --obtain current node local node = get_node(pos) -- Obtain current node
local meta = get_meta(pos):to_table() --get metadata of current node local meta = get_meta(pos):to_table() -- Get metadata of current node
remove_node(pos) remove_node(pos) -- Remove current node
local value = pos[axis] --store current position local value = pos[axis] -- Store current position
pos[axis] = value + amount --move along axis pos[axis] = value + amount -- Move along axis
add_node(pos, node) --move node to new position set_node(pos, node) -- Move node to new position
get_meta(pos):from_table(meta) --set metadata of new node get_meta(pos):from_table(meta) -- Set metadata of new node
pos[axis] = value --restore old position pos[axis] = value -- Restore old position
pos.z = pos.z - 1 pos.z = pos.z - 1
end end
pos.y = pos.y - 1 pos.y = pos.y - 1
@ -412,8 +228,15 @@ worldedit.move = function(pos1, pos2, axis, amount)
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end 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) --- 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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local length = pos2[axis] - pos1[axis] + 1 local length = pos2[axis] - pos1[axis] + 1
if count < 0 then if count < 0 then
@ -423,72 +246,85 @@ worldedit.stack = function(pos1, pos2, axis, count)
local amount = 0 local amount = 0
local copy = worldedit.copy local copy = worldedit.copy
local i = 1 local i = 1
function nextone() function next_one()
if i <= count then if i <= count then
i = i + 1 i = i + 1
amount = amount + length amount = amount + length
copy(pos1, pos2, axis, amount) copy(pos1, pos2, axis, amount)
minetest.after(0, nextone) minetest.after(0, next_one)
end end
end end
nextone() next_one()
return worldedit.volume(pos1, pos2) * count return worldedit.volume(pos1, pos2) * count
end 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 --- 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) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--prepare schematic of large node -- Prepare schematic of large node
local get_node, get_meta, place_schematic = minetest.get_node, minetest.get_meta, minetest.place_schematic 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 placeholder_node = {name="", param1=255, param2=0}
local nodes = {} local nodes = {}
for i = 1, stretchx * stretchy * stretchz do for i = 1, stretch_x * stretch_y * stretch_z do
nodes[i] = placeholder_node nodes[i] = placeholder_node
end end
local schematic = {size={x=stretchx, y=stretchy, z=stretchz}, data=nodes} local schematic = {size={x=stretch_x, y=stretch_y, z=stretch_z}, data=nodes}
local sizex, sizey, sizez = stretchx - 1, stretchy - 1, stretchz - 1 local size_x, size_y, size_z = stretch_x - 1, stretch_y - 1, stretch_z - 1
--make area stay loaded
local manip = minetest.get_voxel_manip()
local new_pos2 = { local new_pos2 = {
x=pos1.x + (pos2.x - pos1.x) * stretchx + sizex, x = pos1.x + (pos2.x - pos1.x) * stretch_x + size_x,
y=pos1.y + (pos2.y - pos1.y) * stretchy + sizey, y = pos1.y + (pos2.y - pos1.y) * stretch_y + size_y,
z=pos1.z + (pos2.z - pos1.z) * stretchz + sizez, z = pos1.z + (pos2.z - pos1.z) * stretch_z + size_z,
} }
manip:read_from_map(pos1, new_pos2) worldedit.keep_loaded(pos1, new_pos2)
local pos = {x=pos2.x, y=0, z=0} local pos = {x=pos2.x, y=0, z=0}
local bigpos = {x=0, y=0, z=0} local big_pos = {x=0, y=0, z=0}
while pos.x >= pos1.x do while pos.x >= pos1.x do
pos.y = pos2.y pos.y = pos2.y
while pos.y >= pos1.y do while pos.y >= pos1.y do
pos.z = pos2.z pos.z = pos2.z
while pos.z >= pos1.z do while pos.z >= pos1.z do
local node = get_node(pos) --obtain current node local node = get_node(pos) -- Get current node
local meta = get_meta(pos):to_table() --get meta of current node local meta = get_meta(pos):to_table() -- Get meta of current node
--calculate far corner of the big node -- Calculate far corner of the big node
local posx = pos1.x + (pos.x - pos1.x) * stretchx local pos_x = pos1.x + (pos.x - pos1.x) * stretch_x
local posy = pos1.y + (pos.y - pos1.y) * stretchy local pos_y = pos1.y + (pos.y - pos1.y) * stretch_y
local posz = pos1.z + (pos.z - pos1.z) * stretchz local pos_z = pos1.z + (pos.z - pos1.z) * stretch_z
--create large node -- Create large node
placeholder_node.name = node.name placeholder_node.name = node.name
placeholder_node.param2 = node.param2 placeholder_node.param2 = node.param2
bigpos.x, bigpos.y, bigpos.z = posx, posy, posz big_pos.x, big_pos.y, big_pos.z = pos_x, pos_y, pos_z
place_schematic(bigpos, schematic) place_schematic(big_pos, schematic)
--fill in large node meta -- Fill in large node meta
if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then --node has meta fields if next(meta.fields) ~= nil or next(meta.inventory) ~= nil then
for x = 0, sizex do -- Node has meta fields
for y = 0, sizey do for x = 0, size_x do
for z = 0, sizez do for y = 0, size_y do
bigpos.x, bigpos.y, bigpos.z = posx + x, posy + y, posz + z for z = 0, size_z do
get_meta(bigpos):from_table(meta) --set metadata of new node big_pos.x = pos_x + x
end big_pos.y = pos_y + y
end big_pos.z = pos_z + z
-- Set metadata of new node
get_meta(big_pos):from_table(meta)
end
end
end end
end end
pos.z = pos.z - 1 pos.z = pos.z - 1
@ -497,11 +333,15 @@ worldedit.stretch = function(pos1, pos2, stretchx, stretchy, stretchz) --wip: te
end end
pos.x = pos.x - 1 pos.x = pos.x - 1
end end
return worldedit.volume(pos1, pos2) * stretchx * stretchy * stretchz, pos1, new_pos2 return worldedit.volume(pos1, pos2) * stretch_x * stretch_y * stretch_z, pos1, new_pos2
end 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) --- 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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local compare local compare
@ -517,37 +357,36 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2)
end end
end end
--calculate the new position 2 after transposition -- Calculate the new position 2 after transposition
local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z} local new_pos2 = {x=pos2.x, y=pos2.y, z=pos2.z}
new_pos2[axis1] = pos1[axis1] + extent2 new_pos2[axis1] = pos1[axis1] + extent2
new_pos2[axis2] = pos1[axis2] + extent1 new_pos2[axis2] = pos1[axis2] + extent1
--make area stay loaded local upper_bound = {x=pos2.x, y=pos2.y, z=pos2.z}
local manip = minetest.get_voxel_manip() if upper_bound[axis1] < new_pos2[axis1] then upper_bound[axis1] = new_pos2[axis1] end
local upperbound = {x=pos2.x, y=pos2.y, z=pos2.z} if upper_bound[axis2] < new_pos2[axis2] then upper_bound[axis2] = new_pos2[axis2] end
if upperbound[axis1] < new_pos2[axis1] then upperbound[axis1] = new_pos2[axis1] end worldedit.keep_loaded(pos1, upper_bound)
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 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 get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
while pos.y <= pos2.y do while pos.y <= pos2.y do
pos.z = pos1.z pos.z = pos1.z
while pos.z <= pos2.z do while pos.z <= pos2.z do
local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2] local extent1, extent2 = pos[axis1] - pos1[axis1], pos[axis2] - pos1[axis2]
if compare(extent1, extent2) then --transpose only if below the diagonal if compare(extent1, extent2) then -- Transpose only if below the diagonal
local node1 = get_node(pos) local node1 = get_node(pos)
local meta1 = get_meta(pos):to_table() local meta1 = get_meta(pos):to_table()
local value1, value2 = pos[axis1], pos[axis2] --save position values local value1, value2 = pos[axis1], pos[axis2] -- Save position values
pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 --swap axis extents pos[axis1], pos[axis2] = pos1[axis1] + extent2, pos1[axis2] + extent1 -- Swap axis extents
local node2 = get_node(pos) local node2 = get_node(pos)
local meta2 = get_meta(pos):to_table() local meta2 = get_meta(pos):to_table()
add_node(pos, node1) set_node(pos, node1)
get_meta(pos):from_table(meta1) get_meta(pos):from_table(meta1)
pos[axis1], pos[axis2] = value1, value2 --restore position values pos[axis1], pos[axis2] = value1, value2 -- Restore position values
add_node(pos, node2) set_node(pos, node2)
get_meta(pos):from_table(meta2) get_meta(pos):from_table(meta2)
end end
pos.z = pos.z + 1 pos.z = pos.z + 1
@ -559,19 +398,20 @@ worldedit.transpose = function(pos1, pos2, axis1, axis2)
return worldedit.volume(pos1, pos2), pos1, new_pos2 return worldedit.volume(pos1, pos2), pos1, new_pos2
end 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) --- 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) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--make area stay loaded worldedit.keep_loaded(pos1, pos2)
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 --- TODO: Flip the region slice by slice along the flip axis using schematic method.
local pos = {x=pos1.x, y=0, z=0} local pos = {x=pos1.x, y=0, z=0}
local start = pos1[axis] + pos2[axis] local start = pos1[axis] + pos2[axis]
pos2[axis] = pos1[axis] + math.floor((pos2[axis] - pos1[axis]) / 2) 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 local get_node, get_meta, set_node = minetest.get_node,
minetest.get_meta, minetest.set_node
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
while pos.y <= pos2.y do while pos.y <= pos2.y do
@ -579,14 +419,14 @@ worldedit.flip = function(pos1, pos2, axis)
while pos.z <= pos2.z do while pos.z <= pos2.z do
local node1 = get_node(pos) local node1 = get_node(pos)
local meta1 = get_meta(pos):to_table() local meta1 = get_meta(pos):to_table()
local value = pos[axis] local value = pos[axis] -- Save position
pos[axis] = start - value pos[axis] = start - value -- Shift position
local node2 = get_node(pos) local node2 = get_node(pos)
local meta2 = get_meta(pos):to_table() local meta2 = get_meta(pos):to_table()
add_node(pos, node1) set_node(pos, node1)
get_meta(pos):from_table(meta1) get_meta(pos):from_table(meta1)
pos[axis] = value pos[axis] = value -- Restore position
add_node(pos, node2) set_node(pos, node2)
get_meta(pos):from_table(meta2) get_meta(pos):from_table(meta2)
pos.z = pos.z + 1 pos.z = pos.z + 1
end end
@ -597,63 +437,74 @@ worldedit.flip = function(pos1, pos2, axis)
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end 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) --- 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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local axis1, axis2 local other1, other2 = worldedit.get_axis_others(axis)
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 angle = angle % 360
local count local count
if angle == 90 then if angle == 90 then
worldedit.flip(pos1, pos2, axis1) worldedit.flip(pos1, pos2, other1)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
elseif angle == 180 then elseif angle == 180 then
worldedit.flip(pos1, pos2, axis1) worldedit.flip(pos1, pos2, other1)
count = worldedit.flip(pos1, pos2, axis2) count = worldedit.flip(pos1, pos2, other2)
elseif angle == 270 then elseif angle == 270 then
worldedit.flip(pos1, pos2, axis2) worldedit.flip(pos1, pos2, other2)
count, pos1, pos2 = worldedit.transpose(pos1, pos2, axis1, axis2) count, pos1, pos2 = worldedit.transpose(pos1, pos2, other1, other2)
else
error("Only 90 degree increments are supported!")
end end
return count, pos1, pos2 return count, pos1, pos2
end 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 --- 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 pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local registered_nodes = minetest.registered_nodes local registered_nodes = minetest.registered_nodes
local wallmounted = { local wallmounted = {
[90]={[0]=0, [1]=1, [2]=5, [3]=4, [4]=2, [5]=3}, [90] = {[0]=0, 1, 5, 4, 2, 3},
[180]={[0]=0, [1]=1, [2]=3, [3]=2, [4]=5, [5]=4}, [180] = {[0]=0, 1, 3, 2, 5, 4},
[270]={[0]=0, [1]=1, [2]=4, [3]=5, [4]=3, [5]=2} [270] = {[0]=0, 1, 4, 5, 3, 2}
} }
local facedir = { local facedir = {
[90]={[0]=1, [1]=2, [2]=3, [3]=0}, [90] = {[0]=1, 2, 3, 0},
[180]={[0]=2, [1]=3, [2]=0, [3]=1}, [180] = {[0]=2, 3, 0, 1},
[270]={[0]=3, [1]=0, [2]=1, [3]=2} [270] = {[0]=3, 0, 1, 2}
} }
angle = angle % 360 angle = angle % 360
if angle == 0 then if angle == 0 then
return 0 return 0
end end
if angle % 90 ~= 0 then
error("Only 90 degree increments are supported!")
end
local wallmounted_substitution = wallmounted[angle] local wallmounted_substitution = wallmounted[angle]
local facedir_substitution = facedir[angle] local facedir_substitution = facedir[angle]
--make area stay loaded worldedit.keep_loaded(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
local count = 0 local count = 0
local get_node, get_meta, add_node = minetest.get_node, minetest.get_meta, minetest.add_node 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} local pos = {x=pos1.x, y=0, z=0}
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
@ -666,13 +517,13 @@ worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotatio
if def.paramtype2 == "wallmounted" then if def.paramtype2 == "wallmounted" then
node.param2 = wallmounted_substitution[node.param2] node.param2 = wallmounted_substitution[node.param2]
local meta = get_meta(pos):to_table() local meta = get_meta(pos):to_table()
add_node(pos, node) set_node(pos, node)
get_meta(pos):from_table(meta) get_meta(pos):from_table(meta)
count = count + 1 count = count + 1
elseif def.paramtype2 == "facedir" then elseif def.paramtype2 == "facedir" then
node.param2 = facedir_substitution[node.param2] node.param2 = facedir_substitution[node.param2]
local meta = get_meta(pos):to_table() local meta = get_meta(pos):to_table()
add_node(pos, node) set_node(pos, node)
get_meta(pos):from_table(meta) get_meta(pos):from_table(meta)
count = count + 1 count = count + 1
end end
@ -686,13 +537,13 @@ worldedit.orient = function(pos1, pos2, angle) --wip: support 6D facedir rotatio
return count return count
end end
--fixes the lighting in a region defined by positions `pos1` and `pos2`, returning the number of nodes updated
worldedit.fixlight = function(pos1, pos2) --- 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) local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
--make area stay loaded worldedit.keep_loaded(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
local nodes = minetest.find_nodes_in_area(pos1, pos2, "air") local nodes = minetest.find_nodes_in_area(pos1, pos2, "air")
local dig_node = minetest.dig_node local dig_node = minetest.dig_node
@ -702,26 +553,40 @@ worldedit.fixlight = function(pos1, pos2)
return #nodes return #nodes
end 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 --- Clears all objects in a region.
local manip = minetest.get_voxel_manip() -- @return The number of objects cleared.
manip:read_from_map(pos1, pos2) function worldedit.clear_objects(pos1, pos2)
pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos1x, pos1y, pos1z = pos1.x, pos1.y, pos1.z worldedit.keep_loaded(pos1, pos2)
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 -- Offset positions to include full nodes (positions are in the center of nodes)
local radius = ((center.x - pos1x + 0.5) + (center.y - pos1y + 0.5) + (center.z - pos1z + 0.5)) ^ 0.5 --bounding sphere radius 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 local count = 0
for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do --all objects in bounding sphere for _, obj in pairs(minetest.get_objects_inside_radius(center, radius)) do
local entity = obj:get_luaentity() local entity = obj:get_luaentity()
if not (entity and entity.name:find("^worldedit:")) then --avoid WorldEdit entities -- Avoid players and WorldEdit entities
if not obj:is_player() and (not entity or
not entity.name:find("^worldedit:")) then
local pos = obj:getpos() local pos = obj:getpos()
if pos.x >= pos1x and pos.x <= pos2x if pos.x >= pos1x and pos.x <= pos2x and
and pos.y >= pos1y and pos.y <= pos2y pos.y >= pos1y and pos.y <= pos2y and
and pos.z >= pos1z and pos.z <= pos2z then --inside region pos.z >= pos1z and pos.z <= pos2z then
-- Inside region
obj:remove() obj:remove()
count = count + 1 count = count + 1
end end
@ -729,3 +594,4 @@ worldedit.clearobjects = function(pos1, pos2)
end end
return count return count
end end

@ -1,470 +1,273 @@
worldedit = worldedit or {} --- Functions for creating primitive shapes.
local minetest = minetest --local copy of global -- @module worldedit.primitives
--adds a hollow sphere centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added local mh = worldedit.manip_helpers
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 --- Adds a sphere of `node_name` centered at `pos`.
local node_id = minetest.get_content_id(nodename) -- @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 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 offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local zstride, ystride = area.zstride, area.ystride local stride_z, stride_y = area.zstride, area.ystride
local count = 0 local count = 0
for z = -radius, radius do for z = -radius, radius do
local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed -- 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 for y = -radius, radius do
local newy = newz + (y + offsety) * ystride local new_y = new_z + (y + offset_y) * stride_y
for x = -radius, radius do for x = -radius, radius do
local squared = x * x + y * y + z * z local squared = x * x + y * y + z * z
if squared >= min_radius and squared <= max_radius then --position is on surface of sphere if squared <= max_radius and (not hollow or squared >= min_radius) then
local i = newy + (x + offsetx) -- Position is on surface of sphere
nodes[i] = node_id local i = new_y + (x + offset_x)
data[i] = node_id
count = count + 1 count = count + 1
end end
end end
end end
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end 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 --- Adds a dome.
local nodes = {} -- @param pos Position to center dome at.
local ignore = minetest.get_content_id("ignore") -- @param radius Dome radius. Negative for concave domes.
for i = 1, worldedit.volume(emerged_pos1, emerged_pos2) do -- @param node_name Name of node to make dome of.
nodes[i] = ignore -- @param hollow Whether the dome should be hollow.
end -- @return The number of nodes added.
-- TODO: Add axis option.
--fill selected area with node function worldedit.dome(pos, radius, node_name, hollow)
local node_id = minetest.get_content_id(nodename) local min_y, max_y = 0, radius
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 if radius < 0 then
radius = -radius radius = -radius
miny, maxy = -radius, 0 min_y, max_y = -radius, 0
end end
--fill selected area with node local manip, area = mh.init_axis_radius(pos, "y", radius)
local node_id = minetest.get_content_id(nodename) 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 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 offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local zstride, ystride = area.zstride, area.ystride local stride_z, stride_y = area.zstride, area.ystride
local count = 0 local count = 0
for z = -radius, radius do for z = -radius, radius do
local newz = (z + offsetz) * zstride + 1 --offset contributed by z plus 1 to make it 1-indexed local new_z = (z + offset_z) * stride_z + 1 --offset contributed by z plus 1 to make it 1-indexed
for y = miny, maxy do for y = min_y, max_y do
local newy = newz + (y + offsety) * ystride local new_y = new_z + (y + offset_y) * stride_y
for x = -radius, radius do for x = -radius, radius do
local squared = x * x + y * y + z * z local squared = x * x + y * y + z * z
if squared >= min_radius and squared <= max_radius then --position is on surface of sphere if squared <= max_radius and (not hollow or squared >= min_radius) then
local i = newy + (x + offsetx) -- Position is in dome
nodes[i] = node_id local i = new_y + (x + offset_x)
data[i] = node_id
count = count + 1 count = count + 1
end end
end end
end end
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end end
--adds a dome centered at `pos` with radius `radius`, composed of `nodename`, returning the number of nodes added --- Adds a cylinder.
worldedit.dome = function(pos, radius, nodename) -- @param pos Position to center base of cylinder at.
--set up voxel manipulator -- @param axis Axis ("x", "y", or "z")
local manip = minetest.get_voxel_manip() -- @param length Cylinder length.
local pos1 = {x=pos.x - radius, y=pos.y, z=pos.z - radius} -- @param radius Cylinder radius.
local pos2 = {x=pos.x + radius, y=pos.y + radius, z=pos.z + radius} -- @param node_name Name of node to make cylinder of.
local emerged_pos1, emerged_pos2 = manip:read_from_map(pos1, pos2) -- @param hollow Whether the cylinder should be hollow.
local area = VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2}) -- @return The number of nodes added.
function worldedit.cylinder(pos, axis, length, radius, node_name, hollow)
local other1, other2 = worldedit.get_axis_others(axis)
--fill emerged area with ignore -- Handle negative lengths
local nodes = {} local current_pos = {x=pos.x, y=pos.y, z=pos.z}
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 if length < 0 then
length = -length length = -length
currentpos[axis] = currentpos[axis] - length current_pos[axis] = current_pos[axis] - length
end end
--set up voxel manipulator -- Set up voxel manipulator
local manip = minetest.get_voxel_manip() local manip, area = mh.init_axis_radius_length(current_pos, axis, radius, length)
local pos1 = { local data = mh.get_empty_data(area)
[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 -- Add cylinder
local nodes = {} local node_id = minetest.get_content_id(node_name)
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 min_radius, max_radius = radius * (radius - 1), radius * (radius + 1)
local stride = {x=1, y=area.ystride, z=area.zstride} 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 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 min_slice, max_slice = offset[axis], offset[axis] + length - 1
local count = 0 local count = 0
for index2 = -radius, radius do 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 -- 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 for index3 = -radius, radius do
local newindex3 = newindex2 + (index3 + offset[other2]) * stride[other2] local new_index3 = new_index2 + (index3 + offset[other2]) * stride[other2]
local squared = index2 * index2 + index3 * index3 local squared = index2 * index2 + index3 * index3
if squared >= min_radius and squared <= max_radius then --position is on surface of cylinder if squared <= max_radius and (not hollow or squared >= min_radius) then
for index1 = min_slice, max_slice do --add column along axis -- Position is in cylinder
local i = newindex3 + index1 * stride[axis] -- Add column along axis
nodes[i] = node_id for index1 = min_slice, max_slice do
local vi = new_index3 + index1 * stride[axis]
data[vi] = node_id
end end
count = count + length count = count + length
end end
end end
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end 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 --- Adds a pyramid.
local currentpos = {x=pos.x, y=pos.y, z=pos.z} -- @param pos Position to center base of pyramid at.
if length < 0 then -- @param axis Axis ("x", "y", or "z")
length = -length -- @param height Pyramid height.
currentpos[axis] = currentpos[axis] - length -- @param node_name Name of node to make pyramid of.
end -- @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 -- Set up voxel manipulator
local manip = minetest.get_voxel_manip() local manip, area = mh.init_axis_radius(pos, axis,
local pos1 = { height >= 0 and height or -height)
[axis]=currentpos[axis], local data = mh.get_empty_data()
[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 -- Handle inverted pyramids
local nodes = {} local start_axis, end_axis, step
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 if height > 0 then
height = height - 1 height = height - 1
step = 1 step = 1
pos1[axis] = pos[axis] --upper half of box
else else
height = height + 1 height = height + 1
step = -1 step = -1
pos2[axis] = pos[axis] --lower half of box
end end
--set up voxel manipulator -- Add pyramid
local manip = minetest.get_voxel_manip() local node_id = minetest.get_content_id(node_name)
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 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 offset = {
x = pos.x - area.MinEdge.x,
y = pos.y - area.MinEdge.y,
z = pos.z - area.MinEdge.z,
}
local size = height * step local size = height * step
local count = 0 local count = 0
for index1 = 0, height, step do --go through each level of the pyramid -- For 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 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 for index2 = -size, size do
local newindex2 = newindex1 + (index2 + offset[other1]) * stride[other1] local new_index2 = new_index1 + (index2 + offset[other1]) * stride[other1]
for index3 = -size, size do for index3 = -size, size do
local i = newindex2 + (index3 + offset[other2]) * stride[other2] local i = new_index2 + (index3 + offset[other2]) * stride[other2]
nodes[i] = node_id data[i] = node_id
end end
end end
count = count + (size * 2 + 1) ^ 2 count = count + (size * 2 + 1) ^ 2
size = size - 1 size = size - 1
end end
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end 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 --- Adds a spiral.
worldedit.spiral = function(pos, length, height, spacer, nodename) -- @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 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, area = mh.init_axis_radius_length(pos, "y", extent, height)
local manip = minetest.get_voxel_manip() local data = mh.get_empty_data(area)
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 -- Set up variables
local nodes = {} local node_id = minetest.get_content_id(node_name)
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 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 offset_x, offset_y, offset_z = pos.x - area.MinEdge.x, pos.y - area.MinEdge.y, pos.z - area.MinEdge.z
local i = offsetz * stride.z + offsety * stride.y + offsetx + 1 local i = offset_z * stride.z + offset_y * stride.y + offset_x + 1
--add first column -- Add first column
local count = height local count = height
local column = i local column = i
for y = 1, height do for y = 1, height do
nodes[column] = node_id data[column] = node_id
column = column + stride.y column = column + stride.y
end end
--add spiral segments -- Add spiral segments
local strideaxis, strideother = stride.x, stride.z local stride_axis, stride_other = stride.x, stride.z
local sign = -1 local sign = -1
local segment_length = 0 local segment_length = 0
spacer = spacer + 1 spacer = spacer + 1
for segment = 1, math.floor(length / spacer) * 2 do --go through each segment except the last -- Go through each segment except the last
if segment % 2 == 1 then --change sign and length every other turn starting with the first 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 sign = -sign
segment_length = segment_length + spacer segment_length = segment_length + spacer
end end
for index = 1, segment_length do --fill segment -- Fill segment
i = i + strideaxis * sign --move along the direction of the segment for index = 1, segment_length do
-- Move along the direction of the segment
i = i + stride_axis * sign
local column = i local column = i
for y = 1, height do --add column -- Add column
nodes[column] = node_id for y = 1, height do
data[column] = node_id
column = column + stride.y column = column + stride.y
end end
end end
count = count + segment_length * height count = count + segment_length * height
strideaxis, strideother = strideother, strideaxis --swap axes stride_axis, stride_other = stride_other, stride_axis -- Swap axes
end end
--add shorter final segment -- Add shorter final segment
sign = -sign sign = -sign
for index = 1, segment_length do for index = 1, segment_length do
i = i + strideaxis * sign i = i + stride_axis * sign
local column = i local column = i
for y = 1, height do --add column -- Add column
nodes[column] = node_id for y = 1, height do
data[column] = node_id
column = column + stride.y column = column + stride.y
end end
end end
count = count + segment_length * height count = count + segment_length * height
--update map nodes mh.finish(manip, data)
manip:set_data(nodes)
manip:write_to_map()
manip:update_map()
return count return count
end end

@ -1,5 +1,5 @@
worldedit = worldedit or {} --- Schematic serialization and deserialiation.
local minetest = minetest -- Local copy of global -- @module worldedit.serialization
worldedit.LATEST_SERIALIZATION_VERSION = 5 worldedit.LATEST_SERIALIZATION_VERSION = 5
local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":" local LATEST_SERIALIZATION_HEADER = worldedit.LATEST_SERIALIZATION_VERSION .. ":"
@ -52,11 +52,10 @@ end
-- @return The serialized data. -- @return The serialized data.
-- @return The number of nodes serialized. -- @return The number of nodes serialized.
function worldedit.serialize(pos1, pos2) function worldedit.serialize(pos1, pos2)
-- Keep area loaded pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2) worldedit.keep_loaded(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0} local pos = {x=pos1.x, y=0, z=0}
local count = 0 local count = 0
local result = {} local result = {}
@ -112,7 +111,7 @@ end
-- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile) -- Contains code based on [table.save/table.load](http://lua-users.org/wiki/SaveTableToFile)
-- by ChillCode, available under the MIT license. -- by ChillCode, available under the MIT license.
-- @return A node list in the latest format, or nil on failure. -- @return A node list in the latest format, or nil on failure.
function worldedit.load_schematic(value) local function load_schematic(value)
local version, header, content = worldedit.read_header(value) local version, header, content = worldedit.read_header(value)
local nodes = {} local nodes = {}
if version == 1 or version == 2 then -- Original flat table format if version == 1 or version == 2 then -- Original flat table format
@ -187,15 +186,15 @@ end
-- @return Low corner position. -- @return Low corner position.
-- @return High corner position. -- @return High corner position.
-- @return The number of nodes. -- @return The number of nodes.
worldedit.allocate = function(origin_pos, value) function worldedit.allocate(origin_pos, value)
local nodes = worldedit.load_schematic(value) local nodes = load_schematic(value)
if not nodes then return nil end if not nodes then return nil end
return worldedit.allocate_with_nodes(origin_pos, nodes) return worldedit.allocate_with_nodes(origin_pos, nodes)
end end
-- Internal -- Internal
worldedit.allocate_with_nodes = function(origin_pos, nodes) function worldedit.allocate_with_nodes(origin_pos, nodes)
local huge = math.huge local huge = math.huge
local pos1x, pos1y, pos1z = huge, huge, huge local pos1x, pos1y, pos1z = huge, huge, huge
local pos2x, pos2y, pos2z = -huge, -huge, -huge local pos2x, pos2y, pos2z = -huge, -huge, -huge
@ -217,14 +216,12 @@ end
--- Loads the nodes represented by string `value` at position `origin_pos`. --- Loads the nodes represented by string `value` at position `origin_pos`.
-- @return The number of nodes deserialized. -- @return The number of nodes deserialized.
worldedit.deserialize = function(origin_pos, value) function worldedit.deserialize(origin_pos, value)
local nodes = worldedit.load_schematic(value) local nodes = load_schematic(value)
if not nodes then return nil end if not nodes then return nil end
-- Make area stay loaded
local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes) local pos1, pos2 = worldedit.allocate_with_nodes(origin_pos, nodes)
local manip = minetest.get_voxel_manip() worldedit.keep_loaded(pos1, pos2)
manip:read_from_map(pos1, pos2)
local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z local origin_x, origin_y, origin_z = origin_pos.x, origin_pos.y, origin_pos.z
local count = 0 local count = 0

@ -1,57 +1,38 @@
worldedit = worldedit or {} --- Functions for visibly hiding nodes
local minetest = minetest --local copy of global -- @module worldedit.visualization
--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", { minetest.register_node("worldedit:placeholder", {
drawtype = "airlike", drawtype = "airlike",
paramtype = "light", paramtype = "light",
sunlight_propagates = true, sunlight_propagates = true,
diggable = false, diggable = false,
walkable = false,
groups = {not_in_creative_inventory=1}, 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 --- Hides all nodes in a region defined by positions `pos1` and `pos2` by
worldedit.hide = function(pos1, pos2) -- non-destructively replacing them with invisible nodes.
--make area stay loaded -- @return The number of nodes hidden.
local manip = minetest.get_voxel_manip() function worldedit.hide(pos1, pos2)
manip:read_from_map(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0} 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 get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
while pos.y <= pos2.y do while pos.y <= pos2.y do
pos.z = pos1.z pos.z = pos1.z
while pos.z <= pos2.z do while pos.z <= pos2.z do
local node = get_node(pos) local node = get_node(pos)
if node.name ~= "worldedit:placeholder" then if node.name ~= "air" and node.name ~= "worldedit:placeholder" then
local data = get_meta(pos):to_table() --obtain metadata of original node -- Save the node's original name
data.fields.worldedit_placeholder = node.name --add the node's name get_meta(pos):set_string("worldedit_placeholder", node.name)
node.name = "worldedit:placeholder" --set node name -- Swap in placeholder node
add_node(pos, node) --add placeholder node node.name = "worldedit:placeholder"
get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata swap_node(pos, node)
end end
pos.z = pos.z + 1 pos.z = pos.z + 1
end end
@ -62,40 +43,44 @@ worldedit.hide = function(pos1, pos2)
return worldedit.volume(pos1, pos2) return worldedit.volume(pos1, pos2)
end 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 --- Suppresses all instances of `node_name` in a region defined by positions
worldedit.suppress = function(pos1, pos2, nodename) -- `pos1` and `pos2` by non-destructively replacing them with invisible nodes.
--ignore placeholder supression -- @return The number of nodes suppressed.
if nodename == "worldedit:placeholder" then function worldedit.suppress(pos1, pos2, node_name)
-- Ignore placeholder supression
if node_name == "worldedit:placeholder" then
return 0 return 0
end end
--make area stay loaded pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local manip = minetest.get_voxel_manip()
manip:read_from_map(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2) worldedit.keep_loaded(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 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 for _, pos in ipairs(nodes) do
local node = get_node(pos) local node = get_node(pos)
local data = get_meta(pos):to_table() --obtain metadata of original node -- Save the node's original name
data.fields.worldedit_placeholder = node.name --add the node's name get_meta(pos):set_string("worldedit_placeholder", node.name)
node.name = "worldedit:placeholder" --set node name -- Swap in placeholder node
add_node(pos, node) --add placeholder node node.name = "worldedit:placeholder"
get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata swap_node(pos, node)
end end
return #nodes return #nodes
end 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 --- Highlights all instances of `node_name` in a region defined by positions
worldedit.highlight = function(pos1, pos2, nodename) -- `pos1` and `pos2` by non-destructively hiding all other nodes.
--make area stay loaded -- @return The number of nodes found.
local manip = minetest.get_voxel_manip() function worldedit.highlight(pos1, pos2, node_name)
manip:read_from_map(pos1, pos2) pos1, pos2 = worldedit.sort_pos(pos1, pos2)
worldedit.keep_loaded(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(pos1, pos2)
local pos = {x=pos1.x, y=0, z=0} 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 get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
local count = 0 local count = 0
while pos.x <= pos2.x do while pos.x <= pos2.x do
pos.y = pos1.y pos.y = pos1.y
@ -103,14 +88,14 @@ worldedit.highlight = function(pos1, pos2, nodename)
pos.z = pos1.z pos.z = pos1.z
while pos.z <= pos2.z do while pos.z <= pos2.z do
local node = get_node(pos) local node = get_node(pos)
if node.name == nodename then --node found if node.name == node_name then -- Node found
count = count + 1 count = count + 1
elseif node.name ~= "worldedit:placeholder" then --hide other nodes elseif node.name ~= "worldedit:placeholder" then -- Hide other nodes
local data = get_meta(pos):to_table() --obtain metadata of original node -- Save the node's original name
data.fields.worldedit_placeholder = node.name --add the node's name get_meta(pos):set_string("worldedit_placeholder", node.name)
node.name = "worldedit:placeholder" --set node name -- Swap in placeholder node
add_node(pos, node) --add placeholder node node.name = "worldedit:placeholder"
get_meta(pos):from_table(data) --set placeholder metadata to the original node's metadata swap_node(pos, node)
end end
pos.z = pos.z + 1 pos.z = pos.z + 1
end end
@ -121,22 +106,26 @@ worldedit.highlight = function(pos1, pos2, nodename)
return count return count
end end
--restores all nodes hidden with WorldEdit functions in a region defined by positions `pos1` and `pos2`, returning the number of nodes restored -- Restores all nodes hidden with WorldEdit functions in a region defined
worldedit.restore = function(pos1, pos2) -- by positions `pos1` and `pos2`.
--make area stay loaded -- @return The number of nodes restored.
local manip = minetest.get_voxel_manip() function worldedit.restore(pos1, pos2)
manip:read_from_map(pos1, pos2)
local pos1, pos2 = worldedit.sort_pos(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 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 local get_node, get_meta, swap_node = minetest.get_node,
minetest.get_meta, minetest.swap_node
for _, pos in ipairs(nodes) do for _, pos in ipairs(nodes) do
local node = get_node(pos) local node = get_node(pos)
local data = get_meta(pos):to_table() --obtain node metadata local meta = get_meta(pos)
node.name = data.fields.worldedit_placeholder --set node name local data = meta:to_table()
data.fields.worldedit_placeholder = nil --delete old nodename node.name = data.fields.worldedit_placeholder
add_node(pos, node) --add original node data.fields.worldedit_placeholder = nil
get_meta(pos):from_table(data) --set original node metadata meta:from_table(data)
swap_node(pos, node)
end end
return #nodes return #nodes
end end

@ -1,7 +1,5 @@
minetest.register_privilege("worldedit", "Can use WorldEdit commands") 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.set_pos = {}
worldedit.inspect = {} worldedit.inspect = {}
@ -340,10 +338,11 @@ minetest.register_chatcommand("/replace", {
description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region", description = "Replace all instances of <search node> with <replace node> in the current WorldEdit region",
privs = {worldedit=true}, privs = {worldedit=true},
func = safe_region(function(name, param) func = safe_region(function(name, param)
local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$")
local newsearchnode = worldedit.normalize_nodename(searchnode) local norm_search_node = worldedit.normalize_nodename(search_node)
local newreplacenode = worldedit.normalize_nodename(replacenode) local norm_replace_node = worldedit.normalize_nodename(replace_node)
local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name], newsearchnode, newreplacenode) local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
norm_search_node, norm_replace_node)
worldedit.player_notify(name, count .. " nodes replaced") worldedit.player_notify(name, count .. " nodes replaced")
end, check_replace), end, check_replace),
}) })
@ -353,10 +352,11 @@ minetest.register_chatcommand("/replaceinverse", {
description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region", description = "Replace all nodes other than <search node> with <replace node> in the current WorldEdit region",
privs = {worldedit=true}, privs = {worldedit=true},
func = safe_region(function(name, param) func = safe_region(function(name, param)
local found, _, searchnode, replacenode = param:find("^([^%s]+)%s+(.+)$") local found, _, search_node, replace_node = param:find("^([^%s]+)%s+(.+)$")
local newsearchnode = worldedit.normalize_nodename(searchnode) local norm_search_node = worldedit.normalize_nodename(search_node)
local newreplacenode = worldedit.normalize_nodename(replacenode) local norm_replace_node = worldedit.normalize_nodename(replace_node)
local count = worldedit.replaceinverse(worldedit.pos1[name], worldedit.pos2[name], searchnode, replacenode) local count = worldedit.replace(worldedit.pos1[name], worldedit.pos2[name],
norm_search_node, norm_replace_node, true)
worldedit.player_notify(name, count .. " nodes replaced") worldedit.player_notify(name, count .. " nodes replaced")
end, check_replace), end, check_replace),
}) })
@ -383,7 +383,7 @@ minetest.register_chatcommand("/hollowsphere", {
func = safe_region(function(name, param) func = safe_region(function(name, param)
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
local node = get_node(name, nodename) local node = get_node(name, nodename)
local count = worldedit.hollow_sphere(worldedit.pos1[name], tonumber(radius), node) local count = worldedit.sphere(worldedit.pos1[name], tonumber(radius), node, true)
worldedit.player_notify(name, count .. " nodes added") worldedit.player_notify(name, count .. " nodes added")
end, check_sphere), end, check_sphere),
}) })
@ -422,7 +422,7 @@ minetest.register_chatcommand("/hollowdome", {
func = safe_region(function(name, param) func = safe_region(function(name, param)
local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$") local found, _, radius, nodename = param:find("^(%d+)%s+(.+)$")
local node = get_node(name, nodename) local node = get_node(name, nodename)
local count = worldedit.hollow_dome(worldedit.pos1[name], tonumber(radius), node) local count = worldedit.dome(worldedit.pos1[name], tonumber(radius), node, true)
worldedit.player_notify(name, count .. " nodes added") worldedit.player_notify(name, count .. " nodes added")
end, check_dome), end, check_dome),
}) })
@ -466,7 +466,7 @@ minetest.register_chatcommand("/hollowcylinder", {
length = length * sign length = length * sign
end end
local node = get_node(name, nodename) local node = get_node(name, nodename)
local count = worldedit.hollow_cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node) local count = worldedit.cylinder(worldedit.pos1[name], axis, length, tonumber(radius), node, true)
worldedit.player_notify(name, count .. " nodes added") worldedit.player_notify(name, count .. " nodes added")
end, check_cylinder), end, check_cylinder),
}) })
@ -1114,7 +1114,7 @@ minetest.register_chatcommand("/clearobjects", {
description = "Clears all objects within the WorldEdit region", description = "Clears all objects within the WorldEdit region",
privs = {worldedit=true}, privs = {worldedit=true},
func = safe_region(function(name, param) func = safe_region(function(name, param)
local count = worldedit.clearobjects(worldedit.pos1[name], worldedit.pos2[name]) local count = worldedit.clear_objects(worldedit.pos1[name], worldedit.pos2[name])
worldedit.player_notify(name, count .. " objects cleared") worldedit.player_notify(name, count .. " objects cleared")
end), end),
}) })

@ -19,7 +19,7 @@ worldedit.mark_pos1 = function(name)
--add marker --add marker
worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1") worldedit.marker1[name] = minetest.add_entity(pos1, "worldedit:pos1")
if worldedit.marker1[name] ~= nil then if worldedit.marker1[name] ~= nil then
worldedit.marker1[name]:get_luaentity().name = name worldedit.marker1[name]:get_luaentity().player_name = name
end end
end end
worldedit.mark_region(name) worldedit.mark_region(name)
@ -42,7 +42,7 @@ worldedit.mark_pos2 = function(name)
--add marker --add marker
worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2") worldedit.marker2[name] = minetest.add_entity(pos2, "worldedit:pos2")
if worldedit.marker2[name] ~= nil then if worldedit.marker2[name] ~= nil then
worldedit.marker2[name]:get_luaentity().name = name worldedit.marker2[name]:get_luaentity().player_name = name
end end
end end
worldedit.mark_region(name) worldedit.mark_region(name)
@ -76,7 +76,7 @@ worldedit.mark_region = function(name)
visual_size={x=sizex * 2, y=sizey * 2}, visual_size={x=sizex * 2, y=sizey * 2},
collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness}, collisionbox = {-sizex, -sizey, -thickness, sizex, sizey, thickness},
}) })
marker:get_luaentity().name = name marker:get_luaentity().player_name = name
table.insert(markers, marker) table.insert(markers, marker)
end end
@ -88,7 +88,7 @@ worldedit.mark_region = function(name)
collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez}, collisionbox = {-thickness, -sizey, -sizez, thickness, sizey, sizez},
}) })
marker:setyaw(math.pi / 2) marker:setyaw(math.pi / 2)
marker:get_luaentity().name = name marker:get_luaentity().player_name = name
table.insert(markers, marker) table.insert(markers, marker)
end end
@ -107,13 +107,13 @@ minetest.register_entity(":worldedit:pos1", {
physical = false, physical = false,
}, },
on_step = function(self, dtime) on_step = function(self, dtime)
if worldedit.marker1[self.name] == nil then if worldedit.marker1[self.player_name] == nil then
self.object:remove() self.object:remove()
end end
end, end,
on_punch = function(self, hitter) on_punch = function(self, hitter)
self.object:remove() self.object:remove()
worldedit.marker1[self.name] = nil worldedit.marker1[self.player_name] = nil
end, end,
}) })
@ -128,13 +128,13 @@ minetest.register_entity(":worldedit:pos2", {
physical = false, physical = false,
}, },
on_step = function(self, dtime) on_step = function(self, dtime)
if worldedit.marker2[self.name] == nil then if worldedit.marker2[self.player_name] == nil then
self.object:remove() self.object:remove()
end end
end, end,
on_punch = function(self, hitter) on_punch = function(self, hitter)
self.object:remove() self.object:remove()
worldedit.marker2[self.name] = nil worldedit.marker2[self.player_name] = nil
end, end,
}) })
@ -147,15 +147,16 @@ minetest.register_entity(":worldedit:region_cube", {
physical = false, physical = false,
}, },
on_step = function(self, dtime) on_step = function(self, dtime)
if worldedit.marker_region[self.name] == nil then if worldedit.marker_region[self.player_name] == nil then
self.object:remove() self.object:remove()
return return
end end
end, end,
on_punch = function(self, hitter) on_punch = function(self, hitter)
for _, entity in ipairs(worldedit.marker_region[self.name]) do for _, entity in ipairs(worldedit.marker_region[self.player_name]) do
entity:remove() entity:remove()
end end
worldedit.marker_region[self.name] = nil worldedit.marker_region[self.player_name] = nil
end, end,
}) })